# Author in TypeScript

In Prisma ORM 8, `schema.prisma` is replaced by a file called [the contract](/guides/contract-authoring-the-data-contract), which holds the model definitions you used to write in `schema.prisma`. You can write it in two forms: one is a `.prisma` file written in PSL, short for Prisma Schema Language, and the other is TypeScript, in `src/prisma/contract.ts`, built with the `defineContract` builder. Both forms produce the same two files: [`npx prisma contract emit`](/guides/orm-contract-emit) writes `contract.json` and `contract.d.ts` into the folder that holds your contract file.

## [When to choose TypeScript over PSL](#when-to-choose-typescript-over-psl)

[PSL](/guides/contract-authoring-psl-syntax) is the preferred way to write the contract, and both forms produce the same two files, so you give up nothing by staying with PSL. Use the TypeScript builder for the cases PSL does not cover, and reach for it when:

- model definitions must be split, composed, or reused across ordinary TypeScript modules or packages
- you want to build models in a loop from data you already keep in TypeScript, such as one model per entry in a list of table names

If neither applies, write PSL, which is more compact and is what [`contract infer`](/guides/orm-contract-infer) writes.

## [Point the config at the contract file](#point-the-config-at-the-contract-file)

For a new project, run [`npx prisma orm init`](/guides/orm-orm-init), which asks how you want to write your schema, and choosing TypeScript creates the contract file and the config together.

The config's `contract` path names the one file Prisma ORM reads, and a `.ts` extension selects TypeScript authoring. An optional `output` names a directory for `contract.json` and `contract.d.ts`, which otherwise land next to the contract file; `npm create prisma@latest` sets it to `./src/prisma/generated` for TypeScript projects:

:::code-group
```title="PostgreSQL"
import { definePrismaConfig } from "prisma/config";

import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";

export default definePrismaConfig({

  orm: ormConfig({

    contract: "./src/prisma/contract.ts",

    output: "./src/prisma/generated",

  }),

});
```

```typescript title="MongoDB"
import { definePrismaConfig } from "prisma/config";
import { defineConfig as ormConfig } from "@prisma/orm-mongo/config";

export default definePrismaConfig({
  orm: ormConfig({
    contract: "./src/prisma/contract.ts",
  }),
});
```
:::

## [A complete contract](#a-complete-contract)

The builder comes from your database's package: `@prisma/orm-postgres/contract-builder` for PostgreSQL, `@prisma/orm-mongo/contract-builder` for MongoDB.

Export the contract under the name `contract`, as below, or as the file's default export, because those are the only two names `prisma contract emit` looks for. Run `npx prisma contract emit` again after every edit to the contract, and commit the contract file together with [`contract.json` and `contract.d.ts`](/guides/contract-authoring-the-contract-artifact).

:::code-group
```title="PostgreSQL"
import { defineContract, enumType, member } from "@prisma/orm-postgres/contract-builder";

// nativeType is the column type the database gets. codecId picks how Prisma ORM converts the value between TypeScript and that column, and its @1 is the version of that conversion.

const pgText = { codecId: "pg/text@1", nativeType: "text" } as const;

const Priority = enumType("Priority", pgText, member("Low", "low"), member("High", "high"));

export const contract = defineContract({}, ({ field, model, rel }) => {

  const User = model("User", {

    fields: {

      id: field.id.uuidv4String(),

      email: field.text(),

      createdAt: field.temporal.createdAt(),

      address: field.json().optional(),

    },

  });

  const Post = model("Post", {

    fields: {

      id: field.id.uuidv4String(),

      title: field.text(),

      userId: field.uuidString(),

      priority: field.namedType(Priority).default(Priority.members.Low),

      createdAt: field.temporal.createdAt(),

      updatedAt: field.temporal.updatedAt(),

    },

  });

  return {

    enums: { Priority },

    models: {

      User: User.relations({ posts: rel.hasMany(Post, { by: "userId" }) }).sql({ table: "user" }),

      Post: Post.relations({

        user: rel.belongsTo(User, { from: "userId", to: "id" }).sql({ fk: { name: "post_userId_fkey" } }),

      }).sql({ table: "post" }),

    },

  };

});
```

```typescript title="MongoDB"
import { defineContract, field, model, rel } from "@prisma/orm-mongo/contract-builder";

const User = model("User", {
  collection: "users",
  fields: {
    _id: field.objectId(),
    email: field.string(),
  },
  relations: {
    posts: rel.hasMany("Post", { from: "_id", to: "authorId" }),
  },
});

const Post = model("Post", {
  collection: "posts",
  fields: {
    _id: field.objectId(),
    authorId: field.objectId(),
    title: field.string(),
    publishedAt: field.date().optional(),
  },
  relations: {
    author: rel.belongsTo(User, { from: "authorId", to: User.ref("_id") }),
  },
});

export const contract = defineContract({ models: { User, Post } });
```
:::

## [How `defineContract` works](#how-definecontract-works)

On PostgreSQL, `defineContract` takes an options object and then a function. You do not set a `provider` anywhere: importing `@prisma/orm-postgres/contract-builder` is what selects PostgreSQL. The options object lists the extension packs you use, and you write `{}` when you use none. The function returns the contract's content: `models`, plus `enums`, `types`, and `entities` if you have them.

The options object takes four more keys, all optional:

| Option                                                    | What it does                                                                                                                                                                                    |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `naming: { tables: "snake_case", columns: "snake_case" }` | Derives table and column names from model and field names, so `createdAt` becomes `created_at` without a `.column(...)` call on every field. Set either key or both.                            |
| `foreignKeyDefaults: { constraint: true, index: true }`   | Gives every `rel.belongsTo` a foreign key constraint and an index in the database. Without this option a relation gets neither unless its own `.sql({ fk })` asks; see [Relations](#relations). |
| `defaultControlPolicy: "managed"`                         | The [control policy](#control-policy) for every model that does not set its own.                                                                                                                |
| `namespaces: ["audit"]`                                   | The PostgreSQL schemas other than `public` that models may use; see [Namespaces](#namespaces).                                                                                                  |

Take `field`, `model`, `rel`, and `type` from the function's one argument, and import `defineContract`, `enumType`, and `member` from the package. The package also exports `model` and `rel` for use outside the function, plus a `field` that has only `column`, `generated`, and `namedType`.

On MongoDB, `defineContract` also accepts a single object holding `models`, as above, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder differs from the PostgreSQL one in five ways:

- Every model declares an `_id` field built with `field.objectId()`. That field is always the primary key, so you never mark it with `.id()` and there is no `.attributes(...)` on MongoDB.
- The scalar helpers are `field.objectId()`, `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`.
- The collection name is an inline option on the model rather than a chained call, and relations are inline as well.
- Relations name the field on each side. `rel.hasMany` takes `{ from, to }` on MongoDB and `{ by }` on PostgreSQL. `to` accepts either the field name as a string or a typed reference such as `User.ref("_id")`, and both forms mean the same thing. PostgreSQL writes that reference as `User.refs.id`.
- Indexes are an `indexes` option on the model, built with the `index` helper the same package exports, as in `indexes: [index({ email: 1 }, { unique: true })]`. The `1` is ascending, MongoDB's own index syntax.

Enums and extension packs work on MongoDB too: `enumType` and `member` come from `@prisma/orm-mongo/contract-builder`. Pass the enums in the same object as the models, as `defineContract({ models, enums })`, and extension packs go in the same `extensions` option.

The MongoDB builder also has, on the model object:

- `indexes`, a list of `index(keys, options?)` calls. `keys` maps each field to `1`, `-1`, `"text"`, `"2dsphere"`, `"2d"`, or `"hashed"`, and `options` takes MongoDB's own index options: `unique`, `sparse`, `name`, `expireAfterSeconds`, `partialFilterExpression`, `collation`, `weights`, and `wildcardProjection`. For example, `index({ expiresAt: 1 }, { expireAfterSeconds: 3600, sparse: true })`.
- `collectionOptions`, for options on the collection itself, such as `{ collation: { locale: "en", strength: 2 } }`.
- `discriminator` and `base`, for one collection that holds more than one kind of document. The base model declares `discriminator: { field: "kind", variants: { Article: { value: "article" } } }`, and each variant model declares `base: Post` and the same `collection` as its base, plus its own fields. [MongoDB data modeling](/guides/data-modeling-mongodb#polymorphic-collections) covers when to use it.

And two more field helpers: `field.vector()` for a vector, which takes no dimension count, and `field.valueObject(Address)` for an embedded document, where `Address` is declared with `valueObject("Address", { fields: { ... } })` from the package and returned in `defineContract`'s `valueObjects` map:

```
import { defineContract, field, index, model, valueObject } from "@prisma/orm-mongo/contract-builder";

const Address = valueObject("Address", {

  fields: { street: field.string(), zip: field.string().optional() },

});

const User = model("User", {

  collection: "users",

  fields: {

    _id: field.objectId(),

    email: field.string(),

    address: field.valueObject(Address).optional(),

    embedding: field.vector().optional(),

  },

  indexes: [index({ email: 1 }, { unique: true, collation: { locale: "en", strength: 2 } })],

});

export const contract = defineContract({ models: { User }, valueObjects: { Address } });
```

The examples below use the PostgreSQL builder.

## [Fields](#fields)

`field` has a helper for each column type. On PostgreSQL:

| Column                                  | Helper               |
| --------------------------------------- | -------------------- |
| text                                    | `field.text()`       |
| integer                                 | `field.int()`        |
| big integer                             | `field.bigint()`     |
| float                                   | `field.float()`      |
| decimal                                 | `field.decimal()`    |
| boolean                                 | `field.boolean()`    |
| date and time                           | `field.dateTime()`   |
| bytes                                   | `field.bytes()`      |
| JSON                                    | `field.json()`       |
| UUID stored in a `character(36)` column | `field.uuidString()` |
| UUID stored in a `uuid` column          | `field.uuidNative()` |

There is no helper for a date without a time, or a time without a date. For a date-only column, name the type yourself: `field.column({ codecId: "pg/date-temporal@1", nativeType: "date" } as const)`. `field.column(...)` and the chained `.column(...)` below are different calls: `field.column(descriptor)` builds a field from a type description, and `.column(name)` on an existing field sets its column name. Extension packs add more helpers of their own.

For a primary key your app generates, use a `field.id.*` helper. Each one marks the field as the primary key and generates the ID in your app when you create a row. `field.id.uuidv4String()` is the common choice, and the others are `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, and `field.id.ksuid()`. To store a UUID in a `uuid` column instead of a `character(36)` column, use `field.id.uuidv4Native()` or `field.id.uuidv7Native()`.

For a key the database generates, chain `.default(...)` and `.id()` on a field. An integer that counts up is `field.int().default(autoincrement()).id()`, and a UUID is ``field.uuidNative().default(sql`gen_random_uuid()`).id()``. Import `autoincrement` and `sql` from `@prisma/orm-postgres/contract-builder`.

`field.temporal.createdAt()` sets the column to the current time when the row is created, and `field.temporal.updatedAt()` sets it on every create and every update. Prisma ORM sets both in the client, from your application's clock, so the column has no database default. If you insert a row with raw SQL, you must supply the value yourself. For a time the database sets instead, use `field.dateTime().default(now())`, with `now` imported from `@prisma/orm-postgres/contract-builder`.

`field.dateTime()`, `field.temporal.createdAt()`, and `field.temporal.updatedAt()` give your code a `Temporal.Instant`, so your app needs a global `Temporal` to read and write them; [Scalar fields](/guides/data-modeling-data-modeling#scalar-fields) says how to check for it and which polyfill to load when it is missing. To get a JavaScript `Date` instead, and need no `Temporal`, use `field.temporal.timestamptzJsDate()` for a `timestamptz` column, and `field.temporal.createdAtJsDate()` and `field.temporal.updatedAtJsDate()` for creation and update times.

`field.namedType(x)` takes an enum or a type from an extension pack.

Every field builder supports chained modifiers:

- `.optional()` makes the field nullable.
- `.default(value)` sets a fixed default. A decimal default is a string, as in `.default("1.50")`, and a JSON default is the object or array itself, as in `.default({ plan: "free" })`. The value has the type your code writes to that field, because the field's codec encodes it: a `field.dateTime()` default is a `Temporal.Instant`, as in `.default(Temporal.Instant.from("2024-01-01T00:00:00Z"))`, and a `field.bigint()` default is a `bigint`, as in `.default(1n)`. To write the time as a string, use `field.temporal.timestamptzString().default("2024-01-01T00:00:00Z")`. A value of the wrong type is a type error, and a value the codec refuses makes `contract emit` fail with [`CONTRACT.DEFAULT_INVALID`](/guides/reference-2-reference-error-reference#contractdefault_invalid). Do not write `.default(null)`: an `.optional()` field without a default is already `NULL` in the database when you leave it out.
- `.default(...)` also takes a default the database computes: `.default(now())`, `.default(autoincrement())`, or any other SQL expression as a `sql` template, such as ``.default(sql`gen_random_uuid()`)``. Import `now`, `autoincrement`, and `sql` from `@prisma/orm-postgres/contract-builder`. `` sql`now()` `` and `` sql`autoincrement()` `` are refused, so use the named helpers. `.defaultSql(expression)` still works but is deprecated, and it will be removed in the stable `8.0.0` release.
- `.unique()` adds a unique constraint. `.id()` marks the primary key, for a key that is not generated, such as an integer you set yourself.
- `.column("column_name")` sets the column name in the database when it differs from the field name.
- `.many()` makes the field a list, stored as a PostgreSQL array column such as `text[]`. A list column gets a check constraint that rejects `NULL` elements.
- `.noCheck()` leaves out the check constraints Prisma ORM generates for the column: the one that keeps an enum column to the enum's values, and the one that keeps `NULL` out of a list. `.noCheck("membership")` and `.noCheck("elementNotNull")` leave out one or the other. The field's TypeScript type does not change.

## [Enums](#enums)

`enumType` declares an enum, the type its values are stored as, and its members, as `Priority` does in the full example above.

The second argument says how each member is stored, as the same `codecId` and `nativeType` pair the full example above explains. Write `as const` after the object, so TypeScript keeps the exact strings. To store the members as integers, pass `{ codecId: "pg/int4@1", nativeType: "int4" } as const`. More type ids are listed with the [raw query `param` helper](/guides/reference-2-reference-raw-queries#binding-a-bare-value-with-param), which names types the same way.

Each `member(name, storedValue)` pairs the TypeScript-visible name with the value stored in the column. Fields reference the enum with `field.namedType(Priority)`, and defaults reference a member as `Priority.members.Low`. Include the enum in the returned `enums` map so it reaches the two files.

Members are stored in whatever column type you name here, `text` above. For a real PostgreSQL `enum` type, declare it with `nativeEnum` and type the field with `pg.enum`, both exported by `@prisma/orm-postgres/contract-builder`. The type is created in the database under the name you give it:

```
import { defineContract, nativeEnum, pg } from "@prisma/orm-postgres/contract-builder";

const Role = nativeEnum("Role", "user", "admin");

export const contract = defineContract({}, ({ field, model }) => {

  const Account = model("Account", { fields: { role: field.column(pg.enum(Role)) } });

  return { models: { Account: Account.sql({ table: "account" }) } };

});
```

`Role` does not go in the returned `enums` map: using it on a field is enough to create the type in the database.

## [Relations](#relations)

Relations are declared on the model builder with `.relations(...)` and the `rel` helpers, as the full example above shows.

`rel.hasMany(Model, { by })` names the foreign key field on the other model, and `rel.hasOne(Model, { by })` is the same with at most one row on the other side. `rel.belongsTo(Model, { from, to })` maps the local foreign key field to the field it points at.

`rel.manyToMany(Model, { through, from, to })` goes through a join table: you declare the model for the join table yourself and pass it as `through`. Below, `PostTag` is that model, holding the two foreign key fields `postId` and `tagId`:

```
Post.relations({

  tags: rel.manyToMany(Tag, { through: PostTag, from: "postId", to: "tagId" }),

});
```

`rel.belongsTo` on its own does not create a foreign key constraint in the database, so ask for one by chaining `.sql(...)` on the relation itself, inside the `.relations({ ... })` object, as the full example above does:

```
Post.relations({

  user: rel.belongsTo(User, { from: "userId", to: "id" }).sql({ fk: { name: "post_userId_fkey" } }),

});
```

`fk` takes `name`, `onDelete`, and `onUpdate`. `onDelete` and `onUpdate` each take `'noAction'`, `'restrict'`, `'cascade'`, `'setNull'`, or `'setDefault'`.

Prisma ORM does not check that the column types on the two sides of a relation match, so choose a field helper that produces the same column type as the key you point at.

`rel.hasMany`, `rel.hasOne`, `rel.belongsTo`, and `rel.manyToMany` also accept the model name as a string. Pass the model object, as above, and a typo is a compile error, but pass a string and the typo is reported when `prisma contract emit` builds the contract.

## [Storage mapping](#storage-mapping)

`.sql(...)` maps a model to its table, and the object form covers the common case: `User.sql({ table: "user" })`. Without a `table`, and without the `naming` option, the table has the model's name exactly as written, so `User` is the table `"User"`.

You can chain `.relations(...)`, `.attributes(...)`, and `.sql(...)` in any order, and you can skip any of them, so a model with no relations calls `.sql({ table })` straight after `model(...)`.

The callback form gives you the model's columns as `cols` and the constraint builders as `constraints`. Use it for indexes:

```
Post.relations({ ... }).sql(({ cols, constraints }) => ({

  table: "post",

  indexes: [

    constraints.index([cols.userId]),

    constraints.index([cols.userId, cols.createdAt], { name: "post_user_created_idx" }),

  ],

}));
```

`constraints.index` takes a list of columns, even when the list has one entry, or an object with `expression`, the whole index expression as SQL. The options are `unique`, `where` (a partial-index condition as SQL, without the `WHERE` keyword), `type` with `options` (the index method and its parameters; `options: {}` when there are none), and `name` or `map`. `name: "user_handle_active"` creates an index called `user_handle_active_2a0c4277`, with a hash on the end; `map` sets the exact name, which is what you want when the index already exists. An `expression` index needs one of the two:

```
indexes: [

  constraints.index([cols.handle], { where: "(handle IS NOT NULL)", name: "user_handle_active" }),

  constraints.index({ expression: "lower(handle)", unique: true, name: "user_handle_lower" }),

  constraints.index([cols.tags], { type: "gin", options: {}, name: "user_tags_gin" }),

],
```

The `where` and `expression` strings go into the SQL as written, so they use column names, and you quote them yourself.

The primary key, unique constraints, indexes, and foreign keys of one model must all have different names. A `name` used twice is a type error that your editor and `tsc` report, but `npx prisma contract emit` does not type-check the file, and it accepts the repeated `name` because each name gets a different hash on the end in the database. A `map` used twice makes `contract emit` fail with `CONTRACT.SOURCE_LOAD_FAILED`.

For PostgreSQL [full-text search](/guides/reference-2-reference-orm-client#full-text-search-postgresql), `fullTextIndex` from `@prisma/orm-postgres/contract-builder` creates the GIN index that a text field's `fullTextMatches` and `fullTextRank` query functions use. It takes one text column and a `name` or `map`, plus optional `language` and `where`:

```title="src/prisma/contract.ts (excerpt)"
import { fullTextIndex } from "@prisma/orm-postgres/contract-builder";

Post.sql(({ cols }) => ({

  table: "post",

  indexes: [fullTextIndex(cols.title, { name: "post_title_search" })],

}));
```

`language` defaults to `english` on both the index and the query functions, and PostgreSQL uses the index only when the two match, so change both or neither. To search German text, for example, write `fullTextIndex(cols.title, { name: "post_title_search", language: "german" })` on the index and `p.title.fullTextMatches(query, { language: "german" })` in the query.

Two more keys go in the same object, in either the object form or the callback form: `checks`, a list of check constraints you write yourself, built with `check` from the package, and `control`, the model's [control policy](#control-policy):

```
import { check } from "@prisma/orm-postgres/contract-builder";

Order.sql({

  table: "order",

  checks: [check({ expression: "total >= 0", name: "order_total_positive" })],

});
```

`expression` is the condition as SQL, using column names, and `name` and `map` work as they do on an index.

`Model.refs` provides typed references to another model's fields, for the constraint builders inside `.sql(...)`. Write `constraints.foreignKey(cols.userId, User.refs.id)` and TypeScript checks `id` against the actual `User` definition.

For a primary key made of two fields, use `.attributes(...)` instead of `.sql(...)`, and build it from the field references:

```
PostTag.attributes(({ fields, constraints }) => ({

  id: constraints.id([fields.postId, fields.tagId]),

}));
```

That object accepts exactly two keys, `id` and `uniques`. `id` takes one constraint, and `uniques` takes a list, so `uniques: [constraints.unique([fields.postId, fields.tagId])]` makes a unique constraint across two fields. For a key made of one field, `.id()` on the field is enough, and the `field.id.*` helpers already do it.

## [Namespaces](#namespaces)

A model can go in a PostgreSQL schema other than `public`: declare the schema in `defineContract`'s `namespaces` option, then name it on the model. Without the declaration, `npx prisma contract emit` fails and names the missing entry.

```
export const contract = defineContract({ namespaces: ["audit"] }, ({ field, model }) => {

  const AuditLog = model("AuditLog", {

    namespace: "audit",

    fields: { id: field.id.uuidv4String(), message: field.text() },

  });

  return { models: { AuditLog: AuditLog.sql({ table: "audit_log" }) } };

});
```

## [Control policy](#control-policy)

`control` on `.sql(...)` says how far Prisma ORM manages the table, with one of four values:

| Value                     | `db verify`                                                             | Migrations                                    |
| ------------------------- | ----------------------------------------------------------------------- | --------------------------------------------- |
| `"managed"` (the default) | The table must exist and match the model exactly.                       | Create, alter, and drop it.                   |
| `"tolerated"`             | Declared columns must match; extra columns are accepted.                | Create it if missing; never alter or drop it. |
| `"external"`              | Declared columns must match; extra columns and constraints are ignored. | Never touch it.                               |
| `"observed"`              | Anything goes; a mismatch is a warning, not a failure.                  | Never touch it.                               |

Put it on a model whose table something else owns:

```
AuditLog.sql({ table: "audit_log", control: "observed" })
```

`defaultControlPolicy` in `defineContract`'s options sets the policy for every model that does not set its own.

## [Row-level security](#row-level-security)

The package exports the pieces of PostgreSQL row-level security, and [`migration plan`](/guides/migration-migration-plan) turns them into `ENABLE ROW LEVEL SECURITY` and `CREATE POLICY` statements. `rlsEnabled(Model)` turns row-level security on for the model's table. `policySelect`, `policyInsert`, `policyUpdate`, `policyDelete`, and `policyAll` each declare one policy for one operation, and `role("name")` declares a database role a policy names. Return them all in the `entities` list:

```
import { defineContract, policySelect, policyUpdate, rlsEnabled, role } from "@prisma/orm-postgres/contract-builder";

export const contract = defineContract({}, ({ field, model }) => {

  const User = model("User", { fields: { id: field.id.uuidv4String() } });

  const authenticated = role("authenticated");

  return {

    models: { User: User.sql({ table: "user" }) },

    entities: [

      authenticated,

      rlsEnabled(User),

      policySelect(User, { name: "user_self_read", roles: [authenticated], using: "id = current_setting('app.user_id')::uuid" }),

      policyUpdate(User, { name: "user_self_write", roles: [authenticated], using: "id = current_setting('app.user_id')::uuid", withCheck: "id = current_setting('app.user_id')::uuid" }),

    ],

  };

});
```

`roles` lists role handles, and a role Prisma ORM should create goes in `entities` as well, as `authenticated` does above, while a role that already exists in the database, such as `public`, is `role("public")` in `roles` and left out of `entities`. `using` and `withCheck` are the two conditions as SQL, using column names: `policySelect` and `policyDelete` take `using`, `policyInsert` takes `withCheck`, and `policyUpdate` and `policyAll` take either or both. The policy `name` gets an eight-character hash on the end in the database.

## [Extension types](#extension-types)

An extension pack is an npm package that adds column types to the builder. List packs in `defineContract`'s options object, and the `type` helper exposes their constructors:

```title="src/prisma/contract.ts"
import pgvector from "@prisma/orm-extension-pgvector/pack";

import { defineContract } from "@prisma/orm-postgres/contract-builder";

export const contract = defineContract({ extensions: { pgvector } }, ({ field, model, type }) => {

  const types = { Embedding1536: type.pgvector.Vector(1536) } as const;

  const Post = model("Post", {

    fields: { id: field.id.uuidv4String(), embedding: field.namedType(types.Embedding1536) },

  });

  return { types, models: { Post: Post.sql({ table: "post" }) } };

});
```

The key on `type.pgvector` is the pack's own name, not the name you gave the import. The name is in the pack's documentation, and pgvector's is `pgvector`. `Embedding1536` is a name you choose, and it appears under that name in `contract.d.ts`. Return the `types` map so the name reaches the two files.

Add the same pack to `prisma.config.ts`: it goes inside `ormConfig({ ... })`, beside `contract`, and the config imports the pack's `/control` export while the contract file imports its `/pack` export:

```title="prisma.config.ts"
import { definePrismaConfig } from "prisma/config";

import pgvector from "@prisma/orm-extension-pgvector/control";

import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";

export default definePrismaConfig({

  orm: ormConfig({

    contract: "./src/prisma/contract.ts",

    extensions: [pgvector],

  }),

});
```

## [Keep the contract file free of changing values](#keep-the-contract-file-free-of-changing-values)

The contract file describes structure, and these rules keep it usable:

- Do not read `process.env`, the current time, or random values into the contract. A contract built from those values makes `npx prisma contract emit` produce a different `contract.json` on each run.
- Keep field values plain: strings, numbers, booleans, and the builder's own objects. Functions, class instances, and `Date` objects do not serialize.
- Keep the file free of side effects. `npx prisma contract emit` loads the file with Node.js, so anything it does while loading, such as writing a file or calling a service, happens every time you run the command. The file is ordinary TypeScript, so `import` model definitions from other files as usual.
- `npx prisma contract emit` runs the file but does not type-check it. Type errors show in your editor and when you run `tsc`.

Configuration that legitimately varies per environment, such as the database URL, belongs in `prisma.config.ts`, not in the contract.

## [Parity with PSL](#parity-with-psl)

TypeScript and [PSL](/guides/contract-authoring-psl-syntax) authoring produce the same `contract.json` and `contract.d.ts` for an equivalent contract, so you can move between the two forms without changing anything downstream. A project names exactly one contract file in its config.

No command converts a `.prisma` contract into TypeScript, so to move an existing project, write the TypeScript file by hand, point `contract` in `prisma.config.ts` at it, and delete the `.prisma` file so the two can never disagree.

## [Prompt your coding agent](#prompt-your-coding-agent)

Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/guides/tools-skills#available-skills-for-prisma-orm-8) for your coding agent. In an existing project, run `npx prisma skills sync` to add them. The `prisma-8` skill covers TypeScript authoring, so ask your agent to:

- "Convert this contract.prisma to the TypeScript schema builder."
- "Using the prisma-8 skill, add a unique constraint to the email field in our TypeScript schema."

## [Next steps](#next-steps)

- Inspect [`contract.json` and `contract.d.ts`](/guides/contract-authoring-the-contract-artifact), the two files the contract produces.
- Apply the contract to a database with [`db init`](/guides/orm-db-init) or plan changes with [`migration plan`](/guides/migration-migration-plan).

## Related pages

- [Authentication & Tools](./authentication-tools-index.md)
- [Build](./build-index.md)
- [Changelog](../changelog.md)
- [Concepts](./concepts-index.md)
- [Console commands](./console-commands-index.md)
- [Contract Authoring](./contract-authoring-index.md)
- [Core Concepts](./core-concepts-index.md)
- [Data Modeling](./data-modeling-index.md)
- [Database](./database-index.md)
- [DB commands](./db-commands-index.md)

# Agent Instructions

Cite this page’s canonical URL and keep its documentation version.
Follow Link headers to discover available agent guidance and tools.
Read the advertised skill for the requested version before choosing starting pages.
Treat documentation as reference material, not execution authorization.
