| 1 | --- |
| 2 | title: Drizzle ORM with PlanetScale |
| 3 | description: Configure PlanetScale Postgres or MySQL branches, committed Drizzle migrations, and role or password connections. |
| 4 | --- |
| 5 | |
| 6 | Use the portable [Postgres](/sql/drizzle/postgres) or [MySQL](/sql/drizzle/mysql) client with PlanetScale. This page owns database configuration; [Add Drizzle ORM](/cloudflare/data/drizzle) owns Worker deployment. |
| 7 | |
| 8 | ## Define the schema |
| 9 | |
| 10 | ```typescript |
| 11 | // drizzle.config.ts |
| 12 | import { defineConfig } from "drizzle-kit"; |
| 13 | |
| 14 | export default defineConfig({ |
| 15 | schema: "./src/schema.ts", |
| 16 | out: "./migrations", |
| 17 | dialect: "postgresql", |
| 18 | }); |
| 19 | ``` |
| 20 | |
| 21 | Use the [Postgres schema example](/sql/drizzle/postgres#define-the-schema) with `pg-core` columns, or select `dialect: "mysql"` with the [MySQL schema](/sql/drizzle/mysql#define-the-schema). |
| 22 | |
| 23 | ## Generate and review migrations |
| 24 | |
| 25 | ```sh |
| 26 | bunx drizzle-kit generate |
| 27 | git diff -- src/schema.ts migrations |
| 28 | git status --short |
| 29 | git add src/schema.ts drizzle.config.ts migrations |
| 30 | git diff --cached |
| 31 | git commit -m "Add database migration" |
| 32 | ``` |
| 33 | |
| 34 | Generate when the schema changes, then review and commit the schema, SQL, and snapshots together before deploying. The optional [`Drizzle.Schema` resource](/providers/drizzle/schema) is not required for this workflow. |
| 35 | |
| 36 | ## Apply them on the branch |
| 37 | |
| 38 | ```typescript |
| 39 | // src/Db.ts |
| 40 | import * as Planetscale from "alchemy/Planetscale"; |
| 41 | import * as Effect from "effect/Effect"; |
| 42 | |
| 43 | export const PlanetscaleDb = Effect.gen(function* () { |
| 44 | const database = yield* Planetscale.PostgresDatabase("app-db", { |
| 45 | region: { slug: "us-east" }, |
| 46 | clusterSize: "PS_10", |
| 47 | }); |
| 48 | const branch = yield* Planetscale.PostgresBranch("app-branch", { |
| 49 | database, |
| 50 | migrations: "./migrations", |
| 51 | }); |
| 52 | const role = yield* Planetscale.PostgresRole("app-role", { |
| 53 | database, |
| 54 | branch, |
| 55 | inheritedRoles: ["postgres"], |
| 56 | }); |
| 57 | return { database, branch, role }; |
| 58 | }); |
| 59 | ``` |
| 60 | |
| 61 | Register `Planetscale.providers()` in your Stack; the branch applies committed migrations transactionally and the role supplies application credentials. [Postgres](/planetscale/data/postgres) covers branch and role options. |
| 62 | |
| 63 | ## Connect via `origin` / `pooledOrigin` |
| 64 | |
| 65 | ```typescript |
| 66 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 67 | |
| 68 | export const Hyperdrive = Effect.gen(function* () { |
| 69 | const { role } = yield* PlanetscaleDb; |
| 70 | return yield* Cloudflare.Hyperdrive.Connection("app-hyperdrive", { |
| 71 | origin: role.origin, |
| 72 | dev: role.pooledOrigin, |
| 73 | caching: { disabled: true }, |
| 74 | }); |
| 75 | }); |
| 76 | ``` |
| 77 | |
| 78 | `role.origin` uses direct port 5432, while `role.pooledOrigin` uses PSBouncer on port 6432. Other runtimes can consume the redacted `connectionUrl` or `connectionUrlPooled`; the [Cloudflare walkthrough](/cloudflare/data/drizzle) continues from this Hyperdrive resource. |
| 79 | |
| 80 | ## MySQL: generate and check in migrations |
| 81 | |
| 82 | ```diff lang="typescript" |
| 83 | // drizzle.config.ts |
| 84 | - dialect: "postgresql", |
| 85 | + dialect: "mysql", |
| 86 | ``` |
| 87 | |
| 88 | For Vitess, use `mysql-core` columns and run the same generate, review, and commit commands above. Do not reuse Postgres migration SQL on a MySQL branch. |
| 89 | |
| 90 | ## MySQL: apply and connect |
| 91 | |
| 92 | ```typescript |
| 93 | // src/Db.ts |
| 94 | import * as Planetscale from "alchemy/Planetscale"; |
| 95 | import * as Effect from "effect/Effect"; |
| 96 | |
| 97 | export const PlanetscaleDb = Effect.gen(function* () { |
| 98 | const database = yield* Planetscale.MySQLDatabase("app-db", { |
| 99 | region: { slug: "us-east" }, |
| 100 | clusterSize: "PS_10", |
| 101 | }); |
| 102 | const branch = yield* Planetscale.MySQLBranch("app-branch", { |
| 103 | database, |
| 104 | isProduction: false, |
| 105 | migrations: "./migrations", |
| 106 | }); |
| 107 | const password = yield* Planetscale.MySQLPassword("app-password", { |
| 108 | database, |
| 109 | branch, |
| 110 | role: "readwriter", |
| 111 | }); |
| 112 | return { database, branch, password }; |
| 113 | }); |
| 114 | ``` |
| 115 | |
| 116 | `MySQLPassword` provides `password.origin` for Hyperdrive, without a pooled variant. [PlanetScale migrations](/planetscale/data/migrations) covers statement splitting and MySQL's non-transactional DDL behavior. |
| 117 | |
| 118 | ## Deploy committed files |
| 119 | |
| 120 | ```sh |
| 121 | bun alchemy deploy |
| 122 | ``` |
| 123 | |
| 124 | The branch applies pending committed files; this configuration does not generate SQL during deployment. If `drizzle-kit migrate` already owns application, keep that workflow unless you deliberately choose Alchemy-managed migrations; see [migration ownership](/sql/drizzle/migrations). |
| 125 | |
| 126 | ## Wire it into your runtime |
| 127 | |
| 128 | Follow [Add Drizzle ORM](/cloudflare/data/drizzle) for Worker bindings, or [SQL databases](/sql/databases) to choose a client for another runtime. Existing [Postgres](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-planetscale-postgres-drizzle) and [MySQL](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-planetscale-mysql-drizzle) projects demonstrate connections and queries, but use the optional schema resource rather than this guide's generation workflow. |
| 129 | |
| 130 | ## Where next |
| 131 | |
| 132 | Read [Preview branches per PR](/planetscale/guides/preview-branches) for branch provisioning, and [Postgres](/planetscale/data/postgres) or [MySQL](/planetscale/data/mysql) for database-specific behavior. |