# Author in PSL (/docs/orm/contract-authoring/psl-syntax)

> For the complete Prisma documentation index, see [llms.txt](https://www.prisma.io/docs/llms.txt). A markdown version of any docs page is available by appending `.md` to its URL.

Write the Prisma ORM contract in the Prisma schema language you already know, plus the Prisma ORM 8 additions.

Location: ORM > Contract authoring > Author in PSL

PSL, the Prisma Schema Language, is the preferred way to author [your contract](https://www.prisma.io/docs/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`. You write one file, usually `src/prisma/contract.prisma`, and [`npx prisma contract emit`](https://www.prisma.io/docs/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. If you know the Prisma schema language, most of a contract file reads exactly as you expect. Prisma ORM 8 differs in five places:

* named types: give a database column type a name you can reuse on many fields.
* enums: an enum can now say how its values are stored, and what each member stores.
* value objects: a structured value stored inside its parent row, with no table of its own.
* base models and variants: one table can hold more than one kind of record. Put the shared fields in a base model, put the differences in each variant, and Prisma ORM uses one column to tell them apart.
* extension types: field types that come from an npm package, such as vectors.

The `datasource` and `generator` blocks are gone: the connection URL and the file paths are set in `prisma.config.ts` instead. [Coming from Prisma ORM 7](https://www.prisma.io/docs/orm/coming-from-prisma-orm-7#schema) lists every change to the schema file, including what each old `@db.` attribute becomes.

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

Every contract starts with `// use prisma-8`, so keep that line at the top.

  

#### PostgreSQL

```prisma title="src/prisma/contract.prisma" 
// use prisma-8

types {
  ShortName = VarChar(35)
}

type Address {
  street  String
  city    String
  zip     String?
  country String
}

enum Priority {
  @@type("pg/text@1")
  Low    = "low"
  High   = "high"
  Urgent = "urgent"
}

model User {
  id        Uuid     @id @default(uuid())
  email     String
  createdAt DateTime @default(now())
  address   Address?
  posts     Post[]

  @@map("user")
}

model Post {
  id        Uuid      @id @default(uuid())
  title     ShortName
  userId    Uuid
  priority  Priority  @default(Low)
  createdAt DateTime  @default(now())

  user User @relation(fields: [userId], references: [id])

  @@map("post")
}
```

#### MongoDB

```prisma title="src/prisma/contract.prisma" 
// use prisma-8

type Address {
  street  String
  city    String
  zip     String?
  country String
}

enum UserRole {
  @@type("mongo/string@1")
  Admin  = "admin"
  Author = "author"
  Reader = "reader"
}

model User {
  id      ObjectId @id @map("_id")
  email   String
  role    UserRole
  address Address?
  posts   Post[]

  @@map("users")
}

model Post {
  id       ObjectId @id @map("_id")
  title    String
  authorId ObjectId

  author User @relation(fields: [authorId], references: [id])

  @@index([authorId])
  @@map("posts")
}
```

`Uuid` is PostgreSQL's `uuid` type written as a field type. Run `npx prisma contract emit` after any change to refresh `contract.json` and `contract.d.ts`, and then [`npx prisma db init`](https://www.prisma.io/docs/cli/db-init) creates the tables.

## Point the config at the schema [#point-the-config-at-the-schema]

The config's `contract` path names the one file Prisma ORM reads: it takes a single path, so there is no folder of contract files. If the path ends in `.prisma`, Prisma ORM reads it as PSL, and if it ends in `.ts`, as TypeScript. The `db` key holds the connection URL:

```typescript title="prisma.config.ts"
import 'dotenv/config';
import { definePrismaConfig } from "prisma/config";
import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";

export default definePrismaConfig({
  orm: ormConfig({
    contract: "./src/prisma/contract.prisma",
    db: {
      connection: process.env['DATABASE_URL']!,
    },
  }),
});
```

`npx prisma orm init` writes a config like this, `DATABASE_URL` included. The import chooses the database: `@prisma/orm-postgres/config` makes it a PostgreSQL project, and `@prisma/orm-mongo/config` a MongoDB one. [Coming from Prisma ORM 7](https://www.prisma.io/docs/orm/coming-from-prisma-orm-7#schema) lists the other keys.

## Models and fields [#models-and-fields]

Models declare fields with a type, an optional `?` marker, and attributes. [Scalar fields](https://www.prisma.io/docs/orm/data-modeling#scalar-fields) lists the types a field can hold, among them `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, and `Bytes`. You can write a PostgreSQL type wherever you would write `String` or `DateTime`. The PostgreSQL types you can write are `VarChar`, `Char`, `Numeric`, `Timestamp`, `Timestamptz`, `Time`, `Timetz`, `Date`, `Uuid`, `Inet`, `SmallInt`, and `Real`, plus `DateString`, `TimestampString`, `TimestamptzString`, and `TimeString`, which store the same columns but read back as text. On PostgreSQL, plain `String` is a `text` column, `Int` is `int4`, and `DateTime` is `timestamptz`.

* `@id` marks the primary key. `@@id([a, b])` declares a composite key.
* `@unique` adds a unique constraint on one field. `@@unique([userId, title])` adds one across several fields.
* `@@index([...])` declares a secondary index.
* `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` come from Prisma ORM, and the database will not fill them in for you, so a row written by raw SQL or another application gets no value.
* `@map("column_name")` sets a field's column name in the database. `@@map("table_name")` sets the table or collection name when it differs from the model name.

`@updatedAt` is gone, so write `temporal.updatedAt()` where the field's type would go:

```prisma
model Post {
  updatedAt temporal.updatedAt()
}
```

Prisma ORM sets the field to the current time on every create and update. `temporal` is built in, so you do not import it or declare it. On PostgreSQL the column is `timestamptz`, and you can still set the field yourself on a write. `temporal.createdAt()` sets the field once, when the row is created.

How IDs map differs by database:

  

#### PostgreSQL

```prisma
model User {
  id Uuid @id @default(uuid())
}
```

#### MongoDB

```prisma
model User {
  id ObjectId @id @map("_id")
}
```

On PostgreSQL the primary key is an ordinary column, so pick its type and default yourself. On MongoDB the primary key is the document's `_id`, so type it `ObjectId` and map it to `_id`.

## Named types [#named-types]

The `types` block gives a database column type a name you can reuse on many fields:

```prisma
types {
  ShortName = VarChar(35)
}
```

Fields then use `ShortName` like any built-in type. The name keeps the column decision in one place: a `varchar(35)` column rather than `text`. A name is optional, and a field can use the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`, and the `@db.` attributes are gone. The `types` block is PostgreSQL only.

## Enums [#enums]

An enum lists its members. It can also say, with `@@type`, how their values are stored, and what each member stores:

```prisma
enum Priority {
  @@type("pg/text@1")
  Low    = "low"
  High   = "high"
  Urgent = "urgent"
}
```

In `pg/text@1`, `pg` is PostgreSQL, `text` is the column type, and `@1` is the version of how the value is stored and read. Write `@@type("pg/int4@1")` to store the values as integers instead. When a member has no explicit value, the member name itself is stored.

`@@type` is optional, and when you leave it out Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. Give every member the same kind of value, because a mix of string and integer values throws an error whose `code` is `PSL_ENUM_CANNOT_INFER_TYPE`. A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value.

An `enum` block is not a PostgreSQL `enum` type: the column is text or an integer. For a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`:

```prisma
native_enum Role {
  admin  = "admin"
  member = "member"
}

model User {
  role pg.enum(Role)
}
```

Each member needs a value. `pg` comes with `@prisma/orm-postgres`, so there is nothing to import. You do not create the PostgreSQL type yourself: `npx prisma migration plan` includes the `CREATE TYPE`.

## Value objects [#value-objects]

A `type` block declares a value object: a structured value stored inside its parent row, with no table of its own.

```prisma
type Address {
  street  String
  city    String
  zip     String?
  country String
}

model User {
  id        Uuid     @id @default(uuid())
  address   Address?
  addresses Address[]
}
```

A value object field can be optional or a list, and a `type` block can hold a field of another `type`. Watch the two spellings: `types { ... }` declares named types, and `type X { ... }` declares a value object. Storage differs by database: on PostgreSQL a value object field is stored in a single `jsonb` column, while on MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision, and [MongoDB data modeling](https://www.prisma.io/docs/orm/data-modeling/mongodb#embed-or-reference) covers it.

## Relations [#relations]

Relations use the `@relation` syntax you know from Prisma ORM. The side that holds the foreign key declares the scalar field and the mapping, and the other side declares a list:

```prisma
model Post {
  userId Uuid
  user   User @relation(fields: [userId], references: [id])
}

model User {
  posts Post[]
}
```

Add `onDelete` and `onUpdate` to the same `@relation`: they belong on the side that holds the foreign key, not on the list side. For a one-to-one, make the other side singular instead of a list, so `User` declares `profile Profile?`, and put `@unique` on the foreign-key field:

```prisma
model Profile {
  userId Uuid @unique
  user   User @relation(fields: [userId], references: [id])
}
```

Many-to-many relations need a model for the join table, and you write that model yourself: there is no implicit many-to-many. That model must follow two rules: if a side has a composite primary key, that model needs one foreign-key field for each part of it, and its `@@id([...])` must list exactly the foreign-key fields and nothing else.

```prisma
model Post {
  tags Tag[]
}

model Tag {
  posts Post[]
}

model PostTag {
  postId Uuid
  tagId  Uuid

  post Post @relation(fields: [postId], references: [id])
  tag  Tag  @relation(fields: [tagId], references: [id])

  @@id([postId, tagId])
  @@map("post_tag")
}
```

You then read `post.tags` as a list of `Tag`, without mentioning `PostTag` in the query. Break either rule and `npx prisma contract emit` reports which list field it could not match to a model. If two models qualify, `npx prisma contract emit` throws an error whose `code` is `PSL_AMBIGUOUS_BACKRELATION`: put the same `@relation("name")` on both ends of one pair, so on `PostTag.post` for `Post.tags`.

For which shape to choose and which side owns the foreign key, see [relational data modeling](https://www.prisma.io/docs/orm/data-modeling/relational-databases) and [MongoDB data modeling](https://www.prisma.io/docs/orm/data-modeling/mongodb).

## Base models and variants [#base-models-and-variants]

A base model declares a discriminator field, the field whose value says which variant a row is. Each variant names its base and its discriminator value:

```prisma
model Task {
  id     Uuid   @id @default(uuid())
  title  String
  type   String

  @@discriminator(type)
  @@map("task")
}

model Bug {
  severity     String
  stepsToRepro String?

  @@base(Task, "bug")
  @@map("bug")
}
```

A field name is written bare, as `type` is in `@@discriminator(type)`, and a database name is quoted, as in `@@map("task")`. Rows whose `type` column holds `"bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. A variant does not declare an `@id` of its own: it takes the base model's primary key.

You query a variant through the base model: `db.orm.public.Task.variant('Bug')` returns the `Bug` rows only, and `public` here is the PostgreSQL schema. Writing a row of a variant starts with the same `variant('Bug')` call. See [`variant()`](https://www.prisma.io/docs/orm/reference/orm-client#variant).

On a variant, `@@map` does more than rename: on PostgreSQL it chooses between two storage layouts. Give the variant its own `@@map`, as `Bug` has here, and its fields are in a table of their own that shares the base model's primary key. Leave `@@map` out and they are nullable columns in the base table. [Relational data modeling](https://www.prisma.io/docs/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two.

On MongoDB, a variant adds its fields to documents in the base model's collection, so it declares `@@base` but no `@@map` of its own.

## Extension types [#extension-types]

An extension pack is an npm package that adds field types Prisma ORM does not ship, such as vectors. Install the pack, add the import and the `extensions` line to the config shown above, then call its types in the `types` block:

```bash
npm install @prisma/orm-extension-pgvector
```

```typescript title="prisma.config.ts"
import pgvector from "@prisma/orm-extension-pgvector/control";

// inside ormConfig({ ... }), beside contract and db:
    extensions: [pgvector],
```

```prisma title="src/prisma/contract.prisma"
types {
  Embedding1536 = pgvector.Vector(1536)
}

model Post {
  id        Uuid           @id @default(uuid())
  embedding Embedding1536?
}
```

The `pgvector` part of `pgvector.Vector(1536)` is a fixed name the pack declares, not the name you gave the import. List the pack before using its types, and run `npx prisma contract emit` again after changing the extension list. [Using extensions](https://www.prisma.io/docs/orm/extensions/using-extensions) covers installing a pack and names the packs you can add.

## Starting from an existing database [#starting-from-an-existing-database]

If the database already exists, don't write the contract by hand: [`contract infer`](https://www.prisma.io/docs/cli/contract-infer) reads the live schema and writes a starter `contract.prisma` for you to review and edit.

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

Projects created with `npm create prisma@latest -- my-app` include the [Prisma ORM skills](https://www.prisma.io/docs/ai/tools/skills#available-skills-for-prisma-8) for your coding agent: skills are instruction files the agent reads. In an existing project, run `npx prisma skills sync` to add them. The `prisma-8` skill covers PSL authoring, so ask your agent to:

* "Using the prisma-8 skill, add a Status enum stored as text and use it on the Order model."
* "Add a one-to-many between User and Post with the foreign key on Post."
* "Give the Post model a composite unique constraint on userId and title."

## Next steps [#next-steps]

* Run `npx prisma contract emit` and inspect [`contract.json` and `contract.d.ts`](https://www.prisma.io/docs/orm/contract-authoring/the-contract-artifact). You do not import them yourself. `db`, the client that reads both, is in `src/prisma/db.ts`, and `prisma orm init` writes that file. See [transactions and runtime](https://www.prisma.io/docs/orm/reference/transactions-and-runtime).
* If you prefer defining models in code, see [authoring in TypeScript](https://www.prisma.io/docs/orm/contract-authoring/typescript-schema-builder).
* Plan changes to a database you have already created with [`migration plan`](https://www.prisma.io/docs/cli/migration-plan).

## Related pages

- [`Author in TypeScript`](https://www.prisma.io/docs/orm/contract-authoring/typescript-schema-builder): Define the Prisma ORM contract with a typed builder in TypeScript instead of a schema file. Same models, same `contract.json` and `contract.d.ts`, no separate language.
- [`contract.json and contract.d.ts`](https://www.prisma.io/docs/orm/contract-authoring/the-contract-artifact): contract.json and contract.d.ts are the two files every other part of Prisma ORM reads. Here is what is inside them.
- [`Editor support`](https://www.prisma.io/docs/orm/contract-authoring/editor-support): What the Prisma VS Code extension does for a Prisma ORM contract, and what to do when it stops accepting the file.
- [`Supported database features`](https://www.prisma.io/docs/orm/contract-authoring/capabilities): The contract records which database features your packages support, so Prisma ORM can reject an unsupported one early with a clear error.
- [`The data contract`](https://www.prisma.io/docs/orm/contract-authoring/the-data-contract): The data contract is the one description of your data model and how it is stored. Prisma ORM types your queries, plans your migrations, and checks your database against it.