| 1 | --- |
| 2 | title: Contracts |
| 3 | description: Author Prisma ORM v8 contracts in Prisma Schema Language or TypeScript, generate optional Effect bindings and row schemas, and connect them to migrations. |
| 4 | --- |
| 5 | |
| 6 | A Prisma ORM v8 **contract** defines your models, fields, relations, and |
| 7 | storage mappings. It supplies the types for application queries and the |
| 8 | schema that Prisma uses to plan migrations. |
| 9 | |
| 10 | Choose one authoritative source: **Prisma Schema Language (PSL)** or |
| 11 | **TypeScript**. Both use the same Effect-native Postgres runtime and |
| 12 | [Alchemy migration resources](/sql/prisma/migrations). |
| 13 | |
| 14 | :::caution |
| 15 | This guide targets Prisma ORM v8 release candidates and PostgreSQL 17+. |
| 16 | The contract toolchain is different from earlier Prisma versions; expect |
| 17 | breaking changes between release candidates. See [Postgres](/sql/prisma/postgres) |
| 18 | for dependencies and the client API, or the [Cloudflare integration](/cloudflare/data/prisma) |
| 19 | for a complete deployment example. |
| 20 | ::: |
| 21 | |
| 22 | ## Choose an authoring form |
| 23 | |
| 24 | | | PSL-first | TypeScript-first | |
| 25 | | --- | --- | --- | |
| 26 | | Source of truth | `contract.psl` (or `.prisma`) | `contract.ts` | |
| 27 | | Model definitions | Prisma Schema Language | Prisma's native builders | |
| 28 | | Application import | Generated `makeDatabase` | The authored `contract` | |
| 29 | | Generate before compiling queries | Required | Not required | |
| 30 | | Optional Effect row schemas | Generated standalone module | `makeSchemas(contract)` or generation | |
| 31 | |
| 32 | The generated contract files are outputs, not a second schema to edit. |
| 33 | TypeScript-first applications can infer query types directly from their |
| 34 | source contract; migration tooling still emits contract artifacts. |
| 35 | |
| 36 | ## Define a PSL contract |
| 37 | |
| 38 | Create `src/prisma/contract.psl`: |
| 39 | |
| 40 | ```prisma |
| 41 | model User { |
| 42 | id id.uuidv7Native() |
| 43 | email String @unique |
| 44 | name String |
| 45 | createdAt temporal.createdAtString() |
| 46 | posts Post[] |
| 47 | } |
| 48 | |
| 49 | model Post { |
| 50 | id id.uuidv7Native() |
| 51 | title String |
| 52 | authorId Uuid |
| 53 | author User @relation(fields: [authorId], references: [id]) |
| 54 | } |
| 55 | ``` |
| 56 | |
| 57 | `User` has many posts, and each `Post` belongs to a user through `authorId`. |
| 58 | Prisma's built-in field presets provide the ID and creation-time defaults. |
| 59 | |
| 60 | ### Configure PSL generation |
| 61 | |
| 62 | Point Prisma at the source and register Alchemy's Effect outputs in |
| 63 | `prisma.config.ts`: |
| 64 | |
| 65 | ```typescript |
| 66 | import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; |
| 67 | import { definePrismaConfig } from "prisma/config"; |
| 68 | import { withEffect } from "alchemy/Prisma/ORM/generator"; |
| 69 | |
| 70 | export default definePrismaConfig({ |
| 71 | orm: withEffect( |
| 72 | ormConfig({ |
| 73 | contract: "./src/prisma/contract.psl", |
| 74 | output: "./src/prisma/generated", |
| 75 | }), |
| 76 | { client: true, schemas: true }, |
| 77 | ), |
| 78 | }); |
| 79 | ``` |
| 80 | |
| 81 | Paths resolve relative to the configuration file. `withEffect` selects the |
| 82 | additional outputs; it does not generate files when the config is imported. |
| 83 | Run: |
| 84 | |
| 85 | ```sh |
| 86 | pnpm exec alchemy prisma generate |
| 87 | ``` |
| 88 | |
| 89 | This runs offline and emits Prisma's contract artifacts plus the selected |
| 90 | Effect bindings. Registration belongs in `prisma.config.ts`, rather than a |
| 91 | legacy `generator` block in PSL. Native `prisma contract emit` does not invoke |
| 92 | Alchemy's Effect generator. |
| 93 | |
| 94 | ### Query a PSL contract |
| 95 | |
| 96 | Import the generated factory in application code: |
| 97 | |
| 98 | ```typescript |
| 99 | import { makeDatabase } from "./prisma/generated/client.ts"; |
| 100 | |
| 101 | const db = yield* makeDatabase(connectionString); |
| 102 | const users = yield* db.orm.public.User.select("id", "email").all(); |
| 103 | ``` |
| 104 | |
| 105 | These calls run inside an Effect. `connectionString` is an Effect producing a |
| 106 | redacted connection string, as returned by Alchemy's database connection bindings. |
| 107 | The [Cloudflare integration](/cloudflare/data/prisma) shows Hyperdrive wiring. |
| 108 | The generated factory binds your contract to the |
| 109 | shared Effect runtime, rather than generating another ORM implementation. |
| 110 | |
| 111 | ## Define a TypeScript contract |
| 112 | |
| 113 | Create `src/prisma/contract.ts` with the equivalent native Prisma builders: |
| 114 | |
| 115 | ```typescript |
| 116 | import { defineContract } from "alchemy/Prisma/ORM"; |
| 117 | |
| 118 | export const contract = defineContract({}, ({ field, model, rel }) => ({ |
| 119 | models: { |
| 120 | User: model("User", { |
| 121 | fields: { |
| 122 | id: field.id.uuidv7Native(), |
| 123 | email: field.text().unique(), |
| 124 | name: field.text(), |
| 125 | createdAt: field.temporal.createdAtString(), |
| 126 | }, |
| 127 | relations: { |
| 128 | posts: rel.hasMany("Post", { by: "authorId" }), |
| 129 | }, |
| 130 | }), |
| 131 | Post: model("Post", { |
| 132 | fields: { |
| 133 | id: field.id.uuidv7Native(), |
| 134 | title: field.text(), |
| 135 | authorId: field.uuidNative(), |
| 136 | }, |
| 137 | relations: { |
| 138 | author: rel.belongsTo("User", { from: "authorId", to: "id" }), |
| 139 | }, |
| 140 | }), |
| 141 | }, |
| 142 | })); |
| 143 | ``` |
| 144 | |
| 145 | Alchemy's `defineContract` export uses Prisma's original runtime function and |
| 146 | builders. It adds compatibility types for metadata lost in Prisma rc.11's |
| 147 | published declarations; it is not a separate model language. The adapter |
| 148 | supports explicit storage names through `.sql({ table: "users" })` and |
| 149 | `.column("created_at")`, but rejects global `snake_case` naming. Defaults on |
| 150 | `field.namedType(...)` retain upstream inference limitations. The declaration |
| 151 | gaps are tracked in [Prisma #30341](https://github.com/prisma/orm/issues/30341). |
| 152 | |
| 153 | :::note[Cloudflare Workers] |
| 154 | Before importing the native contract builder, configure Arktype with |
| 155 | `jitless: true` in a separate, first-imported module and preserve that module |
| 156 | from tree-shaking. Follow [Configure validation for Workers](/cloudflare/data/prisma#configure-validation-for-workers). |
| 157 | Ordinary Node and Bun applications do not need this setup. PSL-first Workers |
| 158 | use the emitted contract and do not import the authoring builder. |
| 159 | ::: |
| 160 | |
| 161 | ### Configure TypeScript emission |
| 162 | |
| 163 | The Prisma CLI and Alchemy's migration resources read the same |
| 164 | `prisma.config.ts`: |
| 165 | |
| 166 | ```typescript |
| 167 | import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; |
| 168 | import { definePrismaConfig } from "prisma/config"; |
| 169 | |
| 170 | export default definePrismaConfig({ |
| 171 | orm: ormConfig({ |
| 172 | contract: "./src/prisma/contract.ts", |
| 173 | output: "./src/prisma/generated", |
| 174 | }), |
| 175 | }); |
| 176 | ``` |
| 177 | |
| 178 | This config is enough for native contract emission and migration planning. |
| 179 | Wrap `ormConfig(...)` with `withEffect` only when you also want generated |
| 180 | Effect clients or standalone schemas. |
| 181 | |
| 182 | ### Query the authored contract |
| 183 | |
| 184 | Pass the source contract directly to `Postgres` inside an Effect: |
| 185 | |
| 186 | ```typescript |
| 187 | import { Postgres } from "alchemy/Prisma/ORM/Postgres"; |
| 188 | import { contract } from "./prisma/contract.ts"; |
| 189 | |
| 190 | const db = yield* Postgres(connectionString, { contract }); |
| 191 | const users = yield* db.orm.public.User.select("id", "email").all(); |
| 192 | ``` |
| 193 | |
| 194 | No generated application import or explicit contract type parameter is |
| 195 | required. Queries, transactions, streaming, errors, and connection cleanup |
| 196 | behave the same as the PSL-first factory. |
| 197 | |
| 198 | ## Choose generated outputs |
| 199 | |
| 200 | `alchemy prisma generate` supports both authoring forms. The second argument |
| 201 | to `withEffect` controls which Effect modules it emits: |
| 202 | |
| 203 | | Options | Effect outputs | |
| 204 | | --- | --- | |
| 205 | | `{ client: true, schemas: true }` | Client factory and standalone row schemas | |
| 206 | | `{ client: true, schemas: false }` | Client factory only | |
| 207 | | `{ client: false, schemas: true }` | Standalone row schemas only | |
| 208 | |
| 209 | Both options default to `true`. Prisma's canonical `contract.json` and |
| 210 | `contract.d.ts` are emitted in every mode. With both options enabled, the |
| 211 | output directory contains: |
| 212 | |
| 213 | ```text |
| 214 | src/prisma/generated/ |
| 215 | ├── contract.json # Runtime contract data |
| 216 | ├── contract.d.ts # Contract type declaration |
| 217 | ├── runtime.ts # Runtime contract data export |
| 218 | ├── client.ts # makeDatabase factory |
| 219 | ├── schemas.ts # Standalone Effect row schemas |
| 220 | └── index.ts # Selected Effect exports |
| 221 | ``` |
| 222 | |
| 223 | Schema-only generation also works with a TypeScript source when a frontend |
| 224 | needs validators without importing the contract builder. |
| 225 | |
| 226 | ## Validate rows without a database |
| 227 | |
| 228 | TypeScript-first applications can derive schemas directly: |
| 229 | |
| 230 | ```typescript |
| 231 | import { makeSchemas } from "alchemy/Prisma/ORM/Schema"; |
| 232 | import * as Schema from "effect/Schema"; |
| 233 | import { contract } from "./prisma/contract.ts"; |
| 234 | |
| 235 | const schemas = makeSchemas(contract); |
| 236 | const user = yield* Schema.decodeUnknownEffect(schemas.public.User)(input); |
| 237 | ``` |
| 238 | |
| 239 | PSL-first applications import the standalone generated module: |
| 240 | |
| 241 | ```typescript |
| 242 | import * as Schema from "effect/Schema"; |
| 243 | import { schemas } from "./prisma/generated/schemas.ts"; |
| 244 | |
| 245 | const user = yield* Schema.decodeUnknownEffect(schemas.public.User)(input); |
| 246 | ``` |
| 247 | |
| 248 | The generated schema module imports Effect, not Alchemy, Prisma, a connection |
| 249 | URL, or the generated client. |
| 250 | |
| 251 | These validate **scalar database rows**, not create/update inputs, relation |
| 252 | includes, or public API responses. Nullable fields accept `null` but remain |
| 253 | required. Defaults do not make row fields optional. Define separate public |
| 254 | response schemas when database rows contain private fields. |
| 255 | |
| 256 | Built-in mappings preserve enums, scalar lists, nullable fields, numbers, |
| 257 | lossless `bigint`, decimal strings, bytes, and recursive JSON. Prisma's |
| 258 | `*-string` temporal codecs remain strings; generation does not convert them |
| 259 | to JavaScript `Date`. Unsupported codecs, including Temporal-object codecs, |
| 260 | fail with the field path and codec ID rather than accepting unknown values. |
| 261 | Value objects, unions, and inherited or variant model schemas are outside this |
| 262 | scalar-row generator; use client-only generation and define application schemas |
| 263 | for those models. |
| 264 | |
| 265 | ### Map custom scalar codecs |
| 266 | |
| 267 | Provide an explicit codec mapping for additional scalar types: |
| 268 | |
| 269 | ```typescript |
| 270 | const options = { |
| 271 | codecs: { |
| 272 | "custom/date@1": { |
| 273 | schema: Schema.Date, |
| 274 | expression: "Schema.Date", |
| 275 | }, |
| 276 | }, |
| 277 | }; |
| 278 | |
| 279 | const schemas = makeSchemas(contract, options); |
| 280 | ``` |
| 281 | |
| 282 | The same mapping belongs in the second argument to `withEffect` for standalone |
| 283 | generation. An expression can reference `Schema` or names introduced through |
| 284 | its `imports` array. Custom import paths resolve from the generated module. |
| 285 | Mappings must describe the codec's actual row representation. |
| 286 | |
| 287 | ## Regenerate during development |
| 288 | |
| 289 | Watch your source while developing: |
| 290 | |
| 291 | ```sh |
| 292 | pnpm exec alchemy prisma generate --watch |
| 293 | ``` |
| 294 | |
| 295 | Use `--config ./path/to/prisma.config.ts` to choose another configuration. |
| 296 | The watcher observes the configuration directory, excluding generated output |
| 297 | and dependencies. Each generation uses a fresh process so imported TypeScript |
| 298 | changes are picked up. Keep shared contract modules under that directory. |
| 299 | Invalid source reports an error; the watcher remains available for the next edit. |
| 300 | |
| 301 | In a PSL-first project, run generation before type checking or bundling: |
| 302 | |
| 303 | ```sh |
| 304 | pnpm exec alchemy prisma generate |
| 305 | pnpm exec tsc --noEmit |
| 306 | ``` |
| 307 | |
| 308 | Generation preserves unchanged file contents, removes Effect outputs disabled |
| 309 | by configuration, and refuses to overwrite handwritten Effect modules. Commit |
| 310 | the generated files used by your application, including `contract.d.ts` if your |
| 311 | repository normally ignores declaration files. |
| 312 | |
| 313 | ## Connect the contract to migrations |
| 314 | |
| 315 | The source contract, emitted artifacts, and database each have a separate role: |
| 316 | |
| 317 | | Step | What it does | |
| 318 | | --- | --- | |
| 319 | | Author | Edit `contract.psl` or `contract.ts` | |
| 320 | | Generate | Emit contract artifacts and optional Effect bindings, without a database | |
| 321 | | Plan | Compare contracts and write reviewable migration packages | |
| 322 | | Apply | Run pending migration packages against the database | |
| 323 | |
| 324 | `Prisma.Contract` uses `prisma.config.ts` to emit the contract and plan missing |
| 325 | migration packages during deployment. `Prisma.Migrate` applies those packages. |
| 326 | Neither resource runs the optional Effect generator: PSL-first applications |
| 327 | must generate their client before compiling or bundling it. |
| 328 | |
| 329 | Changing a source file or running `alchemy prisma generate` does not change |
| 330 | the database. Follow [Migrations](/sql/prisma/migrations) to review and apply |
| 331 | schema changes, including changes that need an explicit data backfill. |
| 332 | |
| 333 | ## Where next |
| 334 | |
| 335 | - [Postgres](/sql/prisma/postgres) — queries, prepared statements, and transactions. |
| 336 | - [Cloudflare integration](/cloudflare/data/prisma) — provision a database and query it from a Worker. |
| 337 | - [SQL databases](/sql/databases) — database choices and canonical deployment guides. |
| 338 | - [Migrations](/sql/prisma/migrations) — plan and apply contract changes on deployment. |
| 339 | - [TypeScript-first example](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-neon-prisma) — authored contract with optional standalone schema generation. |
| 340 | - [PSL-first example](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-neon-prisma-psl) — PSL source with generated client and schemas. |