Prisma Next is in early access.Read the docs

The emitted artifacts

contract.json and contract.d.ts are the deterministic artifacts every other part of Prisma Next consumes. Here is what is inside them.

Emitting your contract produces two generated files. This page explains what is in each, why they are deterministic, and how their hashes keep your code and your database in agreement.

prisma-next contract emit writes both next to the source by default:

FileContentsConsumed by
contract.jsonThe canonical, machine-readable contract: models, storage, capabilities, and content hashesThe runtime, migration tooling, verification, and anything else that needs to read your schema
contract.d.tsTypeScript declarations derived from the contractThe query APIs and your application code, for typed models and results

Both files are generated. Do not edit them; change the PSL or TypeScript source and re-run contract emit. The files carry a generated-file notice that says exactly this.

Deterministic emission

Emission is deterministic: the same source produces byte-identical artifacts on every machine and every run. Keys are written in canonical order and values in normalized form, which is what makes the artifacts diffable in code review and hashable for verification.

Determinism is also why the contract source must stay pure. A schema that read the clock or the environment would emit differently each time, and every hash-based guarantee below would collapse. The TypeScript authoring page lists the concrete rules.

The content hashes

Emission computes hashes over distinct parts of the contract. Each answers a different question:

HashCoversChanges when
storageHashModels, fields, relations, and the full storage layout: tables, columns, keys, indexesAny schema change
executionHashDefaults Prisma Next applies before writes, such as uuid() generatorsGenerated defaults change
profileHashThe profile the contract is built for: the target database and its familyThe contract targets a different database

The hashes are how Prisma Next connects the contract to a live database. prisma-next db sign checks that the database satisfies the contract and records the hashes in a marker inside the database. From then on, prisma-next db verify and the runtime compare the contract they hold against the marker, so a stale deploy or an unmigrated database is caught by verification rather than by a failing query.

Inside contract.json

The contract separates what your application models from how it is stored. The domain section describes models, fields, and relations; the storage section describes tables, columns, keys, and indexes (or collections and indexes for a MongoDB contract); each model's storage block bridges the two. Everything is grouped by namespace (on PostgreSQL, the schema, typically public).

An abridged emit of a User/Post schema:

prisma/contract.json (abridged)
{
  "schemaVersion": "1",
  "targetFamily": "sql",
  "target": "postgres",
  "profileHash": "sha256:…",
  "roots": {
    "user": { "namespace": "public", "model": "User" },
    "post": { "namespace": "public", "model": "Post" }
  },
  "domain": {
    "namespaces": {
      "public": {
        "models": {
          "User": {
            "fields": {
              "id": { "nullable": false, "type": { "kind": "scalar", "codecId": "pg/uuid@1" } },
              "email": { "nullable": false, "type": { "kind": "scalar", "codecId": "pg/text@1" } }
            },
            "relations": {
              "posts": {
                "cardinality": "1:N",
                "to": { "namespace": "public", "model": "Post" },
                "on": { "localFields": ["id"], "targetFields": ["userId"] }
              }
            },
            "storage": {
              "namespaceId": "public",
              "table": "user",
              "fields": { "id": { "column": "id" }, "email": { "column": "email" } }
            }
          }
        }
      }
    }
  },
  "storage": {
    "storageHash": "sha256:…",
    "namespaces": {
      "public": {
        "entries": {
          "table": {
            "user": {
              "columns": {
                "id": { "nativeType": "uuid", "codecId": "pg/uuid@1", "nullable": false },
                "email": { "nativeType": "text", "codecId": "pg/text@1", "nullable": false }
              },
              "primaryKey": { "columns": ["id"] }
            }
          }
        }
      }
    }
  },
  "execution": { "executionHash": "sha256:…" },
  "capabilities": { "postgres": { "returning": true } },
  "extensionPacks": {}
}

The sections, top to bottom:

  • schemaVersion, targetFamily, target: the contract format version and the database this contract targets.
  • roots: the accessor names your queries start from, each mapping to a model. The SQL builder's db.sql.public.user exists because roots.user points at User, and the model it names is what the ORM accessor db.orm.public.User hangs off.
  • domain: the application's view. Each field carries nullable and a codecId such as pg/text@1, which names the codec that encodes and decodes values of that type. Relations record cardinality and the fields they join on. The model's storage block maps fields to columns.
  • storage: the database's view: tables with columns (native type plus codec), primary keys, uniques, indexes, foreign keys, and value sets backing enums. This is the section migration tooling diffs and db verify checks the live schema against.
  • execution: defaults Prisma Next applies before writes, such as UUID generation, kept out of the database's DDL.
  • capabilities and extensionPacks: the database and extension features available to this contract, merged from the target, adapter, and extension packs at emit time. Query APIs consult these before using gated features; see Capabilities.

Inside contract.d.ts

The declarations file gives the type system the same information. It exports the contract type, branded hash types matching contract.json, and input/output types for every model:

prisma/contract.d.ts (excerpt)
export type StorageHash =
  StorageHashBase<"sha256:9f49f8f9e51a9cc016f1ec2098ebae9406521a3cc2cf00207adc795078333d8b">;
export type ProfileHash =
  ProfileHashBase<"sha256:9c8aa3114e84ed3b7ea2bd57526d9c2e1bf7c5292be694e9d3801f566fda7ccb">;

export type AddressOutput = {
  readonly street: CodecTypes["pg/text@1"]["output"];
  readonly city: CodecTypes["pg/text@1"]["output"];
  readonly zip: CodecTypes["pg/text@1"]["output"] | null;
  readonly country: CodecTypes["pg/text@1"]["output"];
};

Because the hashes are literal types, a client built against one contract version is type-incompatible with another version's artifacts. Type checking catches a mismatched contract before verification has to.

How the application consumes the artifacts

The runtime client is constructed from both files: contract.json as the value, contract.d.ts as the type.

src/prisma/db.ts
import postgres from "@prisma-next/postgres/runtime";
import type { Contract } from "./contract.d";
import contractJson from "./contract.json" with { type: "json" };

export const db = postgres<Contract>({
  contractJson,
});

At startup, the client carries the contract's hashes and verifies them against the database marker before executing queries.

Version control

Commit the artifacts alongside the source. They contain structure only, no data and no credentials, and committed artifacts let teammates, CI, and deploys consume the contract without re-running emission. Re-run contract emit after every source change so the committed artifacts never trail the source; a CI job can enforce this by running the emit and failing if the working tree changes.

Prompt your coding agent

Projects scaffolded with create-prisma@next install Prisma Next skills for your coding agent; the prisma-next-contract skill covers this page. Ask your agent to:

  • "Explain the difference between contract.json and contract.d.ts in this project."
  • "Re-emit the contract and show me what changed in the artifact."

Next steps

On this page