PlanetScale is a serverless MySQL database built on Vitess with Git-like branching for schemas — new Client({ url: DATABASE_URL }) from @planetscale/database initializes the HTTP-based edge driver (no TCP connection pool needed). await client.execute("SELECT * FROM users WHERE id = ?", [id]) runs parameterized SQL. connection.transaction(async (tx) => { await tx.execute(...); await tx.execute(...) }) wraps statements in a transaction. Prisma: provider = "mysql" with relationMode = "prisma" (no foreign key DDL). Schema branching: pscale branch create myapp preview forks the schema — make DDL changes, open a deploy request, and PlanetScale applies them non-blocking with no table locking. pscale deploy-request create myapp preview submits the migration. Deploy requests are reviewed like PRs and can be applied with zero downtime. @planetscale/database SDK works in Cloudflare Workers and Vercel Edge Functions — no connection pool config needed. Drizzle ORM: drizzle(connection, { schema }) with the PlanetScale HTTP driver. pscale data dump and pscale data load for import/export. Claude Code generates PlanetScale schema, Prisma and Drizzle setup, and edge-compatible query patterns.
CLAUDE.md for PlanetScale
## PlanetScale Stack
- Edge driver: @planetscale/database >= 1.x — HTTP-based, works in Cloudflare Workers and Edge runtimes
- Init: import { connect } from "@planetscale/database"; const conn = connect({ url: process.env.DATABASE_URL! })
- Query: const { rows } = await conn.execute("SELECT * FROM users WHERE id = ?", [id])
- Transaction: await conn.transaction(async (tx) => { await tx.execute(...) })
- Prisma: provider = "mysql", add relationMode = "prisma" in datasource block (Vitess has no FK DDL)
- Drizzle: import { drizzle } from "drizzle-orm/planetscale-serverless"; const db = drizzle(conn, { schema })
- Branching: pscale branch create APP BRANCH → develop → pscale deploy-request create APP BRANCH
PlanetScale Connection
// lib/planetscale/client.ts — PlanetScale edge-compatible client
import { connect, type Connection } from "@planetscale/database"
function createConnection(): Connection {
return connect({
url: process.env.DATABASE_URL!,
fetch: globalThis.fetch, // use native fetch (edge-compatible)
})
}
// Singleton for Node.js; always create new for edge workers
const globalConn = globalThis as unknown as { psConn?: Connection }
export function getConnection(): Connection {
if (process.env.NEXT_RUNTIME === "edge") {
// Edge: create a new connection per request (no persistent TCP)
return createConnection()
}
// Node.js: reuse connection
if (!globalConn.psConn) globalConn.psConn = createConnection()
return globalConn.psConn
}
// ── Type-safe query helpers ────────────────────────────────────────────────
export type QueryResult<T> = {
rows: T[]
rowsAffected: number
insertId: string
}
export async function query<T>(
sql: string,
args: unknown[] = [],
): Promise<T[]> {
const conn = getConnection()
const result = await conn.execute(sql, args, { as: "object" })
return result.rows as T[]
}
export async function execute(
sql: string,
args: unknown[] = [],
): Promise<{ rowsAffected: number; insertId: string }> {
const conn = getConnection()
const result = await conn.execute(sql, args)
return { rowsAffected: result.rowsAffected, insertId: result.insertId }
}
export async function transact<T>(
fn: (tx: Connection) => Promise<T>,
): Promise<T> {
const conn = getConnection()
return conn.transaction(fn)
}
Prisma Schema for PlanetScale
// prisma/schema.prisma — PlanetScale / Vitess compatible
generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma" // Required: Vitess doesn't support FK constraints
}
model User {
id String @id @default(cuid())
email String @unique
name String?
plan String @default("free")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
orders Order[]
@@index([email])
}
model Order {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id])
amount Decimal @db.Decimal(10, 2)
status String @default("pending")
createdAt DateTime @default(now())
@@index([userId]) // Required: all FK-like columns need explicit indexes in Vitess
@@index([status])
}
model Product {
id String @id @default(cuid())
sku String @unique
name String
description String? @db.Text
price Decimal @db.Decimal(10, 2)
stock Int @default(0)
createdAt DateTime @default(now())
@@index([sku])
@@fulltext([name, description]) // MySQL FULLTEXT for search
}
Drizzle ORM with PlanetScale
// lib/planetscale/drizzle.ts — Drizzle ORM with PlanetScale serverless driver
import { drizzle } from "drizzle-orm/planetscale-serverless"
import { connect } from "@planetscale/database"
import { mysqlTable, varchar, decimal, int, boolean, timestamp, text } from "drizzle-orm/mysql-core"
import { eq, and, like, gte, lte, desc } from "drizzle-orm"
const connection = connect({ url: process.env.DATABASE_URL! })
// ── Schema ─────────────────────────────────────────────────────────────────
export const users = mysqlTable("users", {
id: varchar("id", { length: 128 }).primaryKey(),
email: varchar("email", { length: 255 }).notNull().unique(),
name: varchar("name", { length: 255 }),
plan: varchar("plan", { length: 50 }).notNull().default("free"),
createdAt: timestamp("created_at").defaultNow().notNull(),
})
export const products = mysqlTable("products", {
id: varchar("id", { length: 128 }).primaryKey(),
sku: varchar("sku", { length: 100 }).notNull().unique(),
name: varchar("name", { length: 255 }).notNull(),
description: text("description"),
price: decimal("price", { precision: 10, scale: 2 }).notNull(),
stock: int("stock").notNull().default(0),
inStock: boolean("in_stock").notNull().default(true),
createdAt: timestamp("created_at").defaultNow().notNull(),
})
export const db = drizzle(connection, { schema: { users, products } })
// ── Queries ────────────────────────────────────────────────────────────────
export async function getUserByEmail(email: string) {
return db.select().from(users).where(eq(users.email, email)).limit(1)
.then((rows) => rows[0] ?? null)
}
export async function searchProducts(
query: string,
minPrice?: number,
maxPrice?: number,
): Promise<typeof products.$inferSelect[]> {
const conditions = [like(products.name, `%${query}%`)]
if (minPrice !== undefined) conditions.push(gte(products.price, String(minPrice)))
if (maxPrice !== undefined) conditions.push(lte(products.price, String(maxPrice)))
return db.select().from(products)
.where(and(...conditions))
.orderBy(desc(products.createdAt))
.limit(20)
}
Next.js Edge API Route
// app/api/users/[id]/route.ts — edge-compatible PlanetScale query
export const runtime = "edge"
import { NextResponse } from "next/server"
import { query } from "@/lib/planetscale/client"
type User = { id: string; email: string; name: string; plan: string }
export async function GET(
_req: Request,
{ params }: { params: { id: string } },
) {
const users = await query<User>(
"SELECT id, email, name, plan FROM users WHERE id = ? LIMIT 1",
[params.id],
)
if (users.length === 0) {
return NextResponse.json({ error: "Not found" }, { status: 404 })
}
return NextResponse.json(users[0])
}
For the Neon alternative when needing a PostgreSQL-compatible serverless database with database branching, scale to zero, and a standard pg/Prisma PostgreSQL provider — Neon uses PostgreSQL wire protocol with branching for preview environments, while PlanetScale uses MySQL wire protocol with Vitess-powered branching and non-blocking schema changes via deploy requests, see the Neon guide. For the Supabase alternative when needing a full Postgres-based BaaS with Auth, Realtime subscriptions, Storage, Edge Functions, and a rich PostgreSQL extension ecosystem beyond just the database — Supabase is the full open-source Firebase alternative while PlanetScale focuses exclusively on the best-in-class serverless MySQL database experience, see the Supabase guide. The Claude Skills 360 bundle includes PlanetScale skill sets covering Prisma, Drizzle, and edge-compatible queries. Start with the free tier to try serverless MySQL generation.