1---
2title: MySQL
3description: PlanetScale MySQL (Vitess) as Stack resources — databases, branches, and passwords, with SQL migrations per branch and a straight line into Cloudflare Hyperdrive.
4---
5
6PlanetScale MySQL is Vitess-backed serverless MySQL with cheap,
7instant database branches. In alchemy the database, its branches, and
8the passwords that grant access are all resources in your Stack —
9three types: `MySQLDatabase`, `MySQLBranch`, and `MySQLPassword`.
10
11Using Postgres instead? See [Postgres](/planetscale/data/postgres). New
12here? [Set up credentials](/planetscale/setup) first.
13
14## Create a database
15
16```typescript
17// alchemy.run.ts
18import * as Alchemy from "alchemy";
19import * as Planetscale from "alchemy/Planetscale";
20import * as Effect from "effect/Effect";
21
22export default Alchemy.Stack(
23 "MyApp",
24 {
25 providers: Planetscale.providers(),
26 state: Alchemy.localState(),
27 },
28 Effect.gen(function* () {
29 const database = yield* Planetscale.MySQLDatabase("app-db", {
30 region: { slug: "us-east" },
31 clusterSize: "PS_10",
32 });
33
34 return { databaseId: database.id, name: database.name };
35 }),
36);
37```
38
39`clusterSize` is required — `"PS_10"`, `"PS_20"`, up through
40`"PS_2800"`, or `"PS_DEV"` for development workloads. No `name`
41needed: alchemy generates a unique lowercase name from the app,
42stage, and logical ID. `region` cannot be changed after creation —
43changing it replaces the database.
44
45## Branch the database
46
47A `MySQLBranch` is a fork of the database's schema (branched from
48`parentBranch`, default `"main"`):
49
50```typescript
51const branch = yield* Planetscale.MySQLBranch("app-branch", {
52 database,
53 isProduction: false,
54});
55```
56
57Non-production branches run on the `PS_DEV` size. `parentBranch`
58also accepts another `MySQLBranch` resource, so you can stack
59branches off each other.
60
61Production branches take a `clusterSize` and can enable safe
62migrations:
63
64```typescript
65const prod = yield* Planetscale.MySQLBranch("prod-branch", {
66 database,
67 isProduction: true,
68 clusterSize: "PS_10",
69 safeMigrations: true,
70});
71```
72
73Changing `clusterSize` on a deployed production branch resizes it in
74place; toggling `safeMigrations` syncs on the next deploy.
75
76## Grant access with a password
77
78A `MySQLPassword` mints credentials scoped to one branch and one
79role:
80
81```typescript
82const password = yield* Planetscale.MySQLPassword("app-password", {
83 database,
84 branch,
85 role: "readwriter",
86});
87```
88
89`role` is `"reader"`, `"writer"`, `"admin"`, or `"readwriter"`.
90Optional `ttl` (seconds) expires the password automatically, and
91`cidrs` restricts which IP ranges may use it:
92
93```typescript
94const admin = yield* Planetscale.MySQLPassword("admin-password", {
95 database,
96 role: "admin", // branch defaults to "main"
97 ttl: 86400,
98 cidrs: ["203.0.113.0/24"],
99});
100```
101
102The attributes give you `host`, `username`, and `password` — the
103plaintext is `Redacted` so it never leaks into logs or state dumps.
104Changing `role`, `ttl`, `replica`, or the target database/branch
105replaces the password with a freshly minted one.
106
107## Connect from Cloudflare
108
109The password's `origin` attribute is the exact shape
110[Hyperdrive](/cloudflare/data/hyperdrive) accepts, so wiring a Worker to
111the branch is one resource:
112
113```typescript
114import * as Cloudflare from "alchemy/Cloudflare";
115
116const hyperdrive = yield* Cloudflare.Hyperdrive.Connection("app-hyperdrive", {
117 origin: password.origin,
118});
119```
120
121At runtime, query through
122[`Drizzle.MySQL`](/sql/drizzle/mysql) or the raw
123[`SQL.MySQL`](/sql/effect-sql/mysql) client — both take Hyperdrive's
124`connectionString` and default to the text protocol the proxy
125requires. See the full example —
126[cloudflare-planetscale-mysql-drizzle](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-planetscale-mysql-drizzle)
127— for a Worker querying the branch through Hyperdrive with Drizzle.
128
129## Apply migrations
130
131Point `migrations` at a directory of `.sql` files — on either the
132database (applied to the default branch) or a branch:
133
134```typescript
135const branch = yield* Planetscale.MySQLBranch("app-branch", {
136 database,
137 isProduction: false,
138 migrations: "./migrations",
139});
140```
141
142Files are sorted by numeric prefix (`0001_init.sql`, `0002_...`) and
143applied in order; applied migrations are tracked in a
144`__alchemy_migrations` table so each file runs once. `importFiles`
145lists extra `.sql` files (seed data) applied after migrations and
146re-applied only when their contents change. Full details in
147[Migrations](/planetscale/data/migrations).
148
149## Branch per preview stage
150
151Long-lived stages own the database; ephemeral PR stages reference it
152with `MySQLDatabase.ref` and only create their own branch, password,
153and compute:
154
155```typescript
156const { stage } = yield* Alchemy.Stack;
157
158const database = stage.startsWith("pr-")
159 ? yield* Planetscale.MySQLDatabase.ref("app-db", {
160 stage: `staging-${stage}`,
161 })
162 : yield* Planetscale.MySQLDatabase("app-db", {
163 region: { slug: "us-east" },
164 clusterSize: "PS_10",
165 });
166```
167
168Tearing down a PR stage deletes its branch and password but leaves
169the shared database untouched. The
170[branch from a shared database](/cloudflare/data/branch-from-shared-database)
171guide walks through the whole pattern.
172
173## Where next
174
175- [Migrations](/planetscale/data/migrations) — SQL migrations and seed
176 files in depth.
177- [Hyperdrive](/cloudflare/data/hyperdrive) — pooled edge connections
178 from Workers.
179- [Drizzle: MySQL](/sql/drizzle/mysql) /
180 [Effect SQL: MySQL](/sql/effect-sql/mysql) — the runtime clients.
181- [Postgres](/planetscale/data/postgres) — the PostgreSQL side of
182 PlanetScale.
183
184Reference:
185
186- [MySQLDatabase](/providers/planetscale/mysql/mysqldatabase) ·
187 [MySQLBranch](/providers/planetscale/mysql/mysqlbranch) ·
188 [MySQLPassword](/providers/planetscale/mysql/mysqlpassword)