Skip to content
GitHub Discord

PlanetScale

PlanetScale is a cloud database platform built for speed and scalability. This guide covers how to set it up with SST.

Install

Add the PlanetScale provider to your SST app. Learn more about providers.

Terminal window
sst add planetscale

This adds the provider to your sst.config.ts.

sst.config.ts
{
providers: {
planetscale: "1.0.0",
},
}

Then set the PLANETSCALE_SERVICE_TOKEN and PLANETSCALE_SERVICE_TOKEN_ID environment variables. You can create a service token in the PlanetScale dashboard under Settings > Service tokens. The token needs at least the following permissions for the upcoming examples:

  • connect_branch
  • connect_production_branch
  • create_branch
  • delete_branch
  • delete_branch_password
  • read_branch
  • read_database

Reference a database

Reference an existing PlanetScale Vitess database directly from your SST config.

sst.config.ts
const db = planetscale.getDatabaseVitessOutput({
id: "mydb",
organization: "myorg",
});

Branch per stage

PlanetScale supports database branching natively. Combined with SST, every stage and every pull request can get its own isolated database branch automatically.

Conditionally create or reference a branch based on the current stage.

sst.config.ts
const branch =
$app.stage === "production"
? planetscale.getVitessBranchOutput({
id: db.defaultBranch,
organization: db.organization,
database: db.name,
})
: new planetscale.VitessBranch("DatabaseBranch", {
database: db.name,
organization: db.organization,
name: $app.stage,
parentBranch: db.defaultBranch,
});

In production, it references the existing default branch. In any other stage, it creates a new branch from it. So sst deploy --stage pr-42 creates a pr-42 branch in PlanetScale.

This is a pattern used in production by terminal.shop and OpenCode.


Postgres role successors

When you remove a stage whose resources have their removal policy set to "remove", SST deletes its Postgres branch roles. A role that owns database objects, because your app or a migration tool created them, can’t be deleted unless PlanetScale knows which role should take those objects over. Without one, the delete fails with a 422.

Set successor to the role that should receive them. Here db and branch are the Postgres ones, from getDatabasePostgresOutput and a PostgresBranch or getPostgresBranchOutput, in place of the Vitess ones above.

sst.config.ts
const role = new planetscale.PostgresBranchRole("DatabaseRole", {
database: db.name,
organization: db.organization,
branch: branch.name,
name: `${$app.name}-${$app.stage}`,
inheritedRoles: [
"pg_read_all_data",
"pg_write_all_data",
"postgres", // Only needed for pushing schema changes
],
successor: "postgres",
});

Set it on every role you create, including runtime-only roles that own nothing yet, since they may create or own objects later.


Create a password for the branch and wrap it in a sst.Linkable.

sst.config.ts
const password = new planetscale.VitessBranchPassword("DatabasePassword", {
database: db.name,
organization: db.organization,
branch: branch.name,
role: "admin",
name: `${$app.name}-${$app.stage}`,
});
export const database = new sst.Linkable("Database", {
properties: {
host: password.accessHostUrl,
username: password.username,
password: password.plainText,
database: db.name,
port: 3306,
},
});

The Linkable component lets you wrap arbitrary values and make them available to any function or service you link it to. Learn more about linking resources.


Connect to the database

Link the database to any function or service.

sst.config.ts
new sst.aws.Function("Api", {
handler: "src/api.handler",
link: [database],
});

Then access the credentials in a type-safe way through Resource. For example, with Drizzle ORM.

src/drizzle.ts
import { drizzle } from "drizzle-orm/planetscale-serverless";
import { Resource } from "sst";
export const db = drizzle({
connection: {
host: Resource.Database.host,
username: Resource.Database.username,
password: Resource.Database.password,
},
});

You can use any other ORM or database driver the same way.


Examples

Check out the full examples: