Authoring Contracts
Declare ltree columns in the PSL lane or the TypeScript lane; both produce the same contract
Prisma Next gives you two ways to author your data contract, and prisma-ltree
supports both:
- the PSL lane:
contract.prisma, the surface the official extensions document - the TypeScript lane:
contract.tsviadefineContract
Both produce the same compiled contract. Pick whichever surface fits your project and get exactly the same result.
Composing the extension
Whichever lane you author in, add ltree to extensions in prisma-next.config.ts so
the ltree namespace resolves during emit:
import { defineConfig } from "@prisma-next/postgres/config";
import ltree from "prisma-ltree/control";
export default defineConfig({
contract: "./contract.prisma", // or "./contract.ts"
extensions: [ltree],
});Omitting ltree from extensions while a contract.prisma references ltree.Ltree()
fails emit with PSL_EXTENSION_NAMESPACE_NOT_COMPOSED.
Declaring ltree columns
The extension gives you two named-type constructors: ltree.Ltree() for a single ltree
path (codec pg/ltree@1) and ltree.LtreeArray() for an ltree[] array (codec
pg/ltree-array@1). Author them in either lane:
// use prisma-next
types {
// Single ltree path → codec `pg/ltree@1`, native type `ltree`.
Path = ltree.Ltree()
// ltree[] array → codec `pg/ltree-array@1`, native type `ltree[]`.
Paths = ltree.LtreeArray()
}
/// A page in a hierarchy. `path` is its position in the tree;
/// `breadcrumbs` is the array of ancestor paths.
model Page {
id String @id @default(uuid())
path Path
breadcrumbs Paths
@@map("page")
}The parentheses are required, even though these constructors take no arguments.
Path = ltree.Ltree (no parens) fails with PSL_INVALID_TYPES_MEMBER.
import { defineContract } from "@prisma-next/postgres/contract-builder";
import ltree from "prisma-ltree/pack";
export const contract = defineContract(
{
extensionPacks: { ltree },
},
({ field, model, type }) => {
const types = {
// Single ltree path → codec `pg/ltree@1`, native type `ltree`.
Path: type.ltree.Ltree(),
// ltree[] array → codec `pg/ltree-array@1`, native type `ltree[]`.
Paths: type.ltree.LtreeArray(),
} as const;
const Page = model("Page", {
fields: {
id: field.id.uuidv4String(),
path: field.namedType(types.Path),
breadcrumbs: field.namedType(types.Paths),
},
});
return {
types,
models: { Page: Page.sql({ table: "page" }) },
};
},
);
export default contract;The ltree namespace on type is the same authoring surface the PSL lane exposes as
ltree.Ltree() / ltree.LtreeArray().
After editing either contract, re-emit:
pnpm prisma-next contract emitNext steps
Once your columns are declared, see Getting Started for runtime wiring, the baseline migration, and your first queries.