A

PlanetScale

planetscalemysqlvitessserverlessbranchingdatabase

PlanetScale

PlanetScale is a serverless database platform built on Vitess (YouTube's database infrastructure). Created in 2018, PlanetScale brings Git-like workflows to databases with branching, non-blocking schema changes, and horizontal scaling. Originally MySQL-only, PlanetScale now also offers managed Postgres. For JavaScript/TypeScript developers, PlanetScale offers modern serverless features and a workflow designed for continuous deployment.

What is PlanetScale?

PlanetScale is serverless MySQL with database branching:

import { connect } from '@planetscale/database';

const conn = connect({
  url: process.env.DATABASE_URL,
});

const results = await conn.execute('SELECT * FROM users WHERE email = ?', [
  '[email protected]',
]);

console.log(results.rows);

Key Features:

  • MySQL-compatible: Use any MySQL tool/library
  • Database branching: Create database branches like Git
  • Non-blocking schema changes: Deploy without downtime
  • Serverless: Auto-scaling, pay-per-use
  • Horizontal scaling: Powered by Vitess
  • Connection pooling: Built-in (no separate pooler needed)

Why PlanetScale?

1. Database Branching

Create database branches like Git:

# Create development branch from main
pscale branch create my-database dev

# Each PR gets its own database
pscale branch create my-database pr-123

# Delete when done
pscale branch delete my-database pr-123
main branch: production data
   ↓
dev branch: development data (instant copy)
   ↓
pr-123 branch: preview data (instant copy)

Perfect for preview deployments!

2. Non-Blocking Schema Changes

Deploy schema changes without downtime:

# Create schema branch
pscale branch create my-database add-column

# Make changes
pscale shell my-database add-column
> ALTER TABLE users ADD COLUMN phone VARCHAR(20);

# Create deploy request
pscale deploy-request create my-database add-column

# Review and deploy (zero downtime)
pscale deploy-request deploy my-database 1

Schema changes apply without locking tables.

3. Serverless MySQL

Low traffic:
[Small compute] ← Minimal resources

High traffic:
[Large compute] ← Auto-scales

No traffic:
[Connection pooler] ← Maintains connections

Pay only for what you use.

4. Vitess-Powered

Built on Vitess (YouTube's database infrastructure):

  • Horizontal sharding
  • Connection pooling
  • Query rewriting
  • Automatic failover

Using PlanetScale with TypeScript

Setup

# Sign up at https://planetscale.com
# Create database
pscale database create my-database --region us-east

# Get connection string
pscale connect my-database main

# Or use dashboard to get connection string
pnpm add @planetscale/database
import { connect } from '@planetscale/database';

const conn = connect({
  url: process.env.DATABASE_URL,
});

// Query
const results = await conn.execute(
  'SELECT * FROM users WHERE email = ?',
  ['[email protected]']
);

console.log(results.rows);

// Insert
const insertResult = await conn.execute(
  'INSERT INTO users (name, email) VALUES (?, ?)',
  ['Alice', '[email protected]']
);

console.log('Inserted ID:', insertResult.insertId);

// Transaction
const tx = await conn.transaction();

try {
  await tx.execute('UPDATE accounts SET balance = balance - ? WHERE id = ?', [100, 1]);
  await tx.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?', [100, 2]);

  await tx.commit();
} catch (error) {
  await tx.rollback();
  throw error;
}

With Prisma

pnpm add @prisma/client
pnpm add -D prisma
// prisma/schema.prisma
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
  // relationMode = "prisma" // Optional — PlanetScale now supports FK constraints natively
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(uuid())
  name      String
  email     String   @unique
  posts     Post[]
  createdAt DateTime @default(now())

  @@index([email])
}

model Post {
  id        String   @id @default(uuid())
  title     String
  content   String   @db.Text
  authorId  String
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())

  @@index([authorId])
}
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// Create user
const user = await prisma.user.create({
  data: {
    name: 'Alice',
    email: '[email protected]',
    posts: {
      create: [
        { title: 'First Post', content: 'Hello World' },
      ],
    },
  },
  include: {
    posts: true,
  },
});

console.log(user);

Note: PlanetScale now supports foreign key constraints. Older projects may still use relationMode = "prisma" to enforce referential integrity at the application level, but this is no longer required.

With mysql2

pnpm add mysql2
import mysql from 'mysql2/promise';

const pool = mysql.createPool(process.env.DATABASE_URL!);

// Query
const [rows] = await pool.query('SELECT * FROM users WHERE email = ?', [
  '[email protected]',
]);

console.log(rows);

// Insert
const [result] = await pool.query(
  'INSERT INTO users (name, email) VALUES (?, ?)',
  ['Bob', '[email protected]']
);

console.log('Inserted ID:', result.insertId);

Database Branching Workflow

Development Workflow

# 1. Create dev branch
pscale branch create my-database dev

# 2. Get connection string for dev branch
pscale connect my-database dev

# 3. Make schema changes
pscale shell my-database dev
> CREATE TABLE posts (
>   id VARCHAR(36) PRIMARY KEY,
>   title VARCHAR(255),
>   content TEXT
> );

# 4. Test in development

# 5. Create deploy request
pscale deploy-request create my-database dev

# 6. Deploy to main (zero downtime)
pscale deploy-request deploy my-database 1

Preview Deployment Workflow

# In CI/CD (GitHub Actions, etc.)
- name: Create preview branch
  run: |
    BRANCH_NAME="pr-${{ github.event.pull_request.number }}"
    pscale branch create my-database $BRANCH_NAME --from main
    DATABASE_URL=$(pscale connect my-database $BRANCH_NAME --format url)
    echo "DATABASE_URL=$DATABASE_URL" >> $GITHUB_ENV

# Deploy preview with its own database
# Delete branch when PR is merged/closed

PlanetScale + Framework Integration

With Next.js

// lib/db.ts
import { PrismaClient } from '@prisma/client';

let prisma: PrismaClient;

if (process.env.NODE_ENV === 'production') {
  prisma = new PrismaClient();
} else {
  if (!global.prisma) {
    global.prisma = new PrismaClient();
  }
  prisma = global.prisma;
}

export default prisma;
// app/api/users/route.ts
import prisma from '@/lib/db';
import { NextResponse } from 'next/server';

export async function GET() {
  const users = await prisma.user.findMany();
  return NextResponse.json(users);
}

export async function POST(request: Request) {
  const body = await request.json();
  const user = await prisma.user.create({
    data: body,
  });
  return NextResponse.json(user);
}

With Node.js/Express

import express from 'express';
import { PrismaClient } from '@prisma/client';

const app = express();
const prisma = new PrismaClient();

app.use(express.json());

app.get('/users', async (req, res) => {
  const users = await prisma.user.findMany();
  res.json(users);
});

app.post('/users', async (req, res) => {
  const user = await prisma.user.create({
    data: req.body,
  });
  res.json(user);
});

app.listen(3000);

Schema Changes (Deploy Requests)

Create Deploy Request

# 1. Create schema branch
pscale branch create my-database add-phone-column

# 2. Connect to branch
pscale shell my-database add-phone-column

# 3. Make changes
> ALTER TABLE users ADD COLUMN phone VARCHAR(20);

# 4. Create deploy request
pscale deploy-request create my-database add-phone-column

# 5. Review diff in dashboard

# 6. Deploy (zero downtime)
pscale deploy-request deploy my-database 1

Safe Schema Changes

PlanetScale enables non-blocking schema changes:

-- Add column (safe)
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- Add index (safe)
CREATE INDEX idx_users_email ON users(email);

-- Remove column (requires multiple deploys)
-- 1. Stop using the column in code
-- 2. Deploy code changes
-- 3. Remove column via deploy request
ALTER TABLE users DROP COLUMN old_column;

Pricing

PlanetScale no longer offers a free tier. Pricing is based on provisioned compute cluster sizes for predictable monthly costs.

Postgres Single Node (Starting at $5/month):

  • Postgres only — not available for MySQL/Vitess
  • PS-5 node: 1/16 vCPU, 512 MB RAM, 10 GB storage
  • No high availability (single instance, expect downtime if node fails)
  • Best for hobby projects, dev environments, and testing
  • Upgrade to HA (3 nodes) for $15/month at the same node size

Scaler Pro (Starting at $39/month):

  • Available for both MySQL/Vitess and Postgres
  • Highly available clusters (1 primary + 2 replicas across 3 AZs)
  • Unmetered row reads/writes
  • 10 GB storage included
  • 1 production branch, ~1,440 hours of dev branching
  • Scales by compute size: PS-10 ($39/mo) → PS-20 ($59/mo) → PS-40 ($99/mo) and up
  • This is the cheapest option for MySQL/Vitess databases

PlanetScale Metal (Starting at $50/month):

  • Local NVMe storage for low-latency workloads
  • M-10 cluster with 10 GB NVMe at $50/mo
  • Scales based on disk size and compute

Enterprise (Custom Pricing):

  • Custom cluster sizes, SSO included
  • Unlimited development branches
  • 99.999% SLA for multi-region deployments

Additional Costs:

  • Storage overages: $0.50/GB per instance ($1.50/GB effective for HA clusters)
  • Egress: 100 GB included, then $0.06/GB
  • SSO add-on: $199/month (standard plans)
  • Extra dev branches: ~$0.014/hour or $5/month per Postgres dev branch

PlanetScale vs. Other Databases

Feature PlanetScale Neon Traditional MySQL
Database MySQL + Postgres PostgreSQL MySQL
Branching Yes Yes Manual
Serverless Yes Yes No
Schema Changes Non-blocking Standard Blocking
Horizontal Scale Yes (Vitess) Limited Manual sharding
Starting Price $5/month (Postgres), $39/month (MySQL) Free tier Self-hosted
Best For High-scale apps Postgres apps Traditional hosting

Limitations

  • Foreign key constraints: Supported since 2024 (previously a Vitess limitation)
  • No triggers: Not supported by Vitess
  • No stored procedures: Not supported
  • No full-text search: Use external search service
  • No free tier: Postgres from $5/month, MySQL from $39/month
  • Read-heavy: Write scaling more limited than reads

Best Practices

1. Use Indexes

-- Add indexes for common query patterns
CREATE INDEX idx_posts_author_id ON posts(author_id);
CREATE INDEX idx_posts_created_at ON posts(created_at);

-- Composite indexes for common queries
CREATE INDEX idx_posts_author_created ON posts(author_id, created_at);

2. Use Foreign Key Constraints

PlanetScale now supports foreign key constraints natively. For older projects, you can still use relationMode = "prisma" in your Prisma schema if preferred.

3. Use Branches for Testing

# Test migrations on branch first
pscale branch create my-database test-migration
pscale shell my-database test-migration
> -- Test your migration here

# If successful, deploy to main
pscale deploy-request create my-database test-migration

4. Use Connection Pooling

// @planetscale/database has built-in pooling
// No additional pooler needed

import { connect } from '@planetscale/database';
const conn = connect({ url: process.env.DATABASE_URL });

Key Takeaways

  • Serverless MySQL and Postgres with database branching
  • Non-blocking schema changes (zero downtime deploys)
  • Powered by Vitess (YouTube's database infrastructure)
  • No free tier: Postgres from $5/month (single node), MySQL/Vitess from $39/month
  • Foreign key constraints: Now supported natively
  • MySQL and Postgres: Both database engines available
  • Perfect for: High-scale apps with preview deployments
  • Best workflow: Branch for every PR
  • MySQL - Database technology behind PlanetScale
  • Neon - Alternative for PostgreSQL
  • Databases Overview - Compare all databases
  • Next.js - Popular framework for PlanetScale apps
  • Prisma - Recommended ORM for PlanetScale

PlanetScale is an excellent choice for applications needing modern serverless features and database branching. With support for both MySQL (via Vitess) and Postgres, its workflow is designed for continuous deployment, making it perfect for teams using preview deployments. Use it when you need Git-like database workflows at scale.

Last updated: March 23, 2026