1---
2title: Contracts
3description: 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
6A Prisma ORM v8 **contract** defines your models, fields, relations, and
7storage mappings. It supplies the types for application queries and the
8schema that Prisma uses to plan migrations.
9
10Choose 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
15This guide targets Prisma ORM v8 release candidates and PostgreSQL 17+.
16The contract toolchain is different from earlier Prisma versions; expect
17breaking changes between release candidates. See [Postgres](/sql/prisma/postgres)
18for dependencies and the client API, or the [Cloudflare integration](/cloudflare/data/prisma)
19for 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
32The generated contract files are outputs, not a second schema to edit.
33TypeScript-first applications can infer query types directly from their
34source contract; migration tooling still emits contract artifacts.
35
36## Define a PSL contract
37
38Create `src/prisma/contract.psl`:
39
40```prisma
41model User {
42 id id.uuidv7Native()
43 email String @unique
44 name String
45 createdAt temporal.createdAtString()
46 posts Post[]
47}
48
49model 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`.
58Prisma's built-in field presets provide the ID and creation-time defaults.
59
60### Configure PSL generation
61
62Point Prisma at the source and register Alchemy's Effect outputs in
63`prisma.config.ts`:
64
65```typescript
66import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";
67import { definePrismaConfig } from "prisma/config";
68import { withEffect } from "alchemy/Prisma/ORM/generator";
69
70export 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
81Paths resolve relative to the configuration file. `withEffect` selects the
82additional outputs; it does not generate files when the config is imported.
83Run:
84
85```sh
86pnpm exec alchemy prisma generate
87```
88
89This runs offline and emits Prisma's contract artifacts plus the selected
90Effect bindings. Registration belongs in `prisma.config.ts`, rather than a
91legacy `generator` block in PSL. Native `prisma contract emit` does not invoke
92Alchemy's Effect generator.
93
94### Query a PSL contract
95
96Import the generated factory in application code:
97
98```typescript
99import { makeDatabase } from "./prisma/generated/client.ts";
100
101const db = yield* makeDatabase(connectionString);
102const users = yield* db.orm.public.User.select("id", "email").all();
103```
104
105These calls run inside an Effect. `connectionString` is an Effect producing a
106redacted connection string, as returned by Alchemy's database connection bindings.
107The [Cloudflare integration](/cloudflare/data/prisma) shows Hyperdrive wiring.
108The generated factory binds your contract to the
109shared Effect runtime, rather than generating another ORM implementation.
110
111## Define a TypeScript contract
112
113Create `src/prisma/contract.ts` with the equivalent native Prisma builders:
114
115```typescript
116import { defineContract } from "alchemy/Prisma/ORM";
117
118export 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
145Alchemy's `defineContract` export uses Prisma's original runtime function and
146builders. It adds compatibility types for metadata lost in Prisma rc.11's
147published declarations; it is not a separate model language. The adapter
148supports 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
151gaps are tracked in [Prisma #30341](https://github.com/prisma/orm/issues/30341).
152
153:::note[Cloudflare Workers]
154Before importing the native contract builder, configure Arktype with
155`jitless: true` in a separate, first-imported module and preserve that module
156from tree-shaking. Follow [Configure validation for Workers](/cloudflare/data/prisma#configure-validation-for-workers).
157Ordinary Node and Bun applications do not need this setup. PSL-first Workers
158use the emitted contract and do not import the authoring builder.
159:::
160
161### Configure TypeScript emission
162
163The Prisma CLI and Alchemy's migration resources read the same
164`prisma.config.ts`:
165
166```typescript
167import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";
168import { definePrismaConfig } from "prisma/config";
169
170export default definePrismaConfig({
171 orm: ormConfig({
172 contract: "./src/prisma/contract.ts",
173 output: "./src/prisma/generated",
174 }),
175});
176```
177
178This config is enough for native contract emission and migration planning.
179Wrap `ormConfig(...)` with `withEffect` only when you also want generated
180Effect clients or standalone schemas.
181
182### Query the authored contract
183
184Pass the source contract directly to `Postgres` inside an Effect:
185
186```typescript
187import { Postgres } from "alchemy/Prisma/ORM/Postgres";
188import { contract } from "./prisma/contract.ts";
189
190const db = yield* Postgres(connectionString, { contract });
191const users = yield* db.orm.public.User.select("id", "email").all();
192```
193
194No generated application import or explicit contract type parameter is
195required. Queries, transactions, streaming, errors, and connection cleanup
196behave the same as the PSL-first factory.
197
198## Choose generated outputs
199
200`alchemy prisma generate` supports both authoring forms. The second argument
201to `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
209Both options default to `true`. Prisma's canonical `contract.json` and
210`contract.d.ts` are emitted in every mode. With both options enabled, the
211output directory contains:
212
213```text
214src/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
223Schema-only generation also works with a TypeScript source when a frontend
224needs validators without importing the contract builder.
225
226## Validate rows without a database
227
228TypeScript-first applications can derive schemas directly:
229
230```typescript
231import { makeSchemas } from "alchemy/Prisma/ORM/Schema";
232import * as Schema from "effect/Schema";
233import { contract } from "./prisma/contract.ts";
234
235const schemas = makeSchemas(contract);
236const user = yield* Schema.decodeUnknownEffect(schemas.public.User)(input);
237```
238
239PSL-first applications import the standalone generated module:
240
241```typescript
242import * as Schema from "effect/Schema";
243import { schemas } from "./prisma/generated/schemas.ts";
244
245const user = yield* Schema.decodeUnknownEffect(schemas.public.User)(input);
246```
247
248The generated schema module imports Effect, not Alchemy, Prisma, a connection
249URL, or the generated client.
250
251These validate **scalar database rows**, not create/update inputs, relation
252includes, or public API responses. Nullable fields accept `null` but remain
253required. Defaults do not make row fields optional. Define separate public
254response schemas when database rows contain private fields.
255
256Built-in mappings preserve enums, scalar lists, nullable fields, numbers,
257lossless `bigint`, decimal strings, bytes, and recursive JSON. Prisma's
258`*-string` temporal codecs remain strings; generation does not convert them
259to JavaScript `Date`. Unsupported codecs, including Temporal-object codecs,
260fail with the field path and codec ID rather than accepting unknown values.
261Value objects, unions, and inherited or variant model schemas are outside this
262scalar-row generator; use client-only generation and define application schemas
263for those models.
264
265### Map custom scalar codecs
266
267Provide an explicit codec mapping for additional scalar types:
268
269```typescript
270const options = {
271 codecs: {
272 "custom/date@1": {
273 schema: Schema.Date,
274 expression: "Schema.Date",
275 },
276 },
277};
278
279const schemas = makeSchemas(contract, options);
280```
281
282The same mapping belongs in the second argument to `withEffect` for standalone
283generation. An expression can reference `Schema` or names introduced through
284its `imports` array. Custom import paths resolve from the generated module.
285Mappings must describe the codec's actual row representation.
286
287## Regenerate during development
288
289Watch your source while developing:
290
291```sh
292pnpm exec alchemy prisma generate --watch
293```
294
295Use `--config ./path/to/prisma.config.ts` to choose another configuration.
296The watcher observes the configuration directory, excluding generated output
297and dependencies. Each generation uses a fresh process so imported TypeScript
298changes are picked up. Keep shared contract modules under that directory.
299Invalid source reports an error; the watcher remains available for the next edit.
300
301In a PSL-first project, run generation before type checking or bundling:
302
303```sh
304pnpm exec alchemy prisma generate
305pnpm exec tsc --noEmit
306```
307
308Generation preserves unchanged file contents, removes Effect outputs disabled
309by configuration, and refuses to overwrite handwritten Effect modules. Commit
310the generated files used by your application, including `contract.d.ts` if your
311repository normally ignores declaration files.
312
313## Connect the contract to migrations
314
315The 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
325migration packages during deployment. `Prisma.Migrate` applies those packages.
326Neither resource runs the optional Effect generator: PSL-first applications
327must generate their client before compiling or bundling it.
328
329Changing a source file or running `alchemy prisma generate` does not change
330the database. Follow [Migrations](/sql/prisma/migrations) to review and apply
331schema 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.