Prisma 2+ generator to emit custom models from your schema"
22
stars
10
commits
TypeScript
primary language
Aug 23, 2026
updated
A Prisma generator that scaffolds custom models from your Prisma schema, and never overwrites the code you write into them.
Explore the options »
Report Bug
·
Request Feature
Prisma has no way to attach your own methods to a model. This generator writes the boilerplate for you: one file per model, wired to the right Prisma Client type, ready for you to add methods to.
Because those files are meant to be edited, the generator treats them as yours. Once a file exists it is never rewritten, and files it did not create are never touched. See Regeneration is safe.
Prisma documents three ways to add custom methods to a model, and this generator emits all three. Prisma recommends the first:
| Prisma's approach | behavior |
|---|---|
Client extensions ($extends), recommended by Prisma | EXTEND_CLIENT |
| Wrapping a model in a class | WRAP |
Extending a model delegate with Object.assign | EXTEND |
^20.19 || ^22.12 || >=24.07.9.1 and 6.19.3)prisma-client and the legacy prisma-client-js
generator providersThis generator does not bundle its own copy of Prisma. It reads the already parsed datamodel out of the options Prisma hands it, so it uses whatever Prisma version you installed.
npm install --save-dev prisma-custom-models-generator
yarn add --dev prisma-custom-models-generator
pnpm add -D prisma-custom-models-generator
Add the generator to your schema. output is where your custom model files go,
and it should be a directory you are happy to commit, because you will be
writing code in it.
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
generator custom_models {
provider = "prisma-custom-models-generator"
output = "../src/models"
behavior = "EXTEND_CLIENT"
}
datasource db {
provider = "sqlite"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
On Prisma 7 the connection URL lives in prisma.config.ts, not in the schema:
// prisma.config.ts
import { defineConfig } from 'prisma/config';
export default defineConfig({
schema: 'prisma/schema.prisma',
datasource: { url: process.env.DATABASE_URL ?? 'file:./dev.db' },
});
Then generate:
npx prisma generate
That writes src/models/Users.ts:
// Custom model scaffold generated by prisma-custom-models-generator.
//
// This file is yours to edit. Later `prisma generate` runs detect your
// changes and leave the file alone; the generator only rewrites scaffolds it
// created that are still untouched. Delete the file to get a fresh scaffold.
import { Prisma } from "../generated/prisma/client";
/**
* Custom methods for the `User` model, as a Prisma Client extension.
* ...
*/
export const UsersExtension = Prisma.defineExtension({
name: 'Users',
model: {
user: {
// define methods here, comma-separated
},
},
});
Fill in your methods:
export const UsersExtension = Prisma.defineExtension({
name: 'Users',
model: {
user: {
async findByEmail(email: string) {
const ctx = Prisma.getExtensionContext(this);
return (ctx as any).findFirst({ where: { email } });
},
},
},
});
And apply the extension. On Prisma 7 the PrismaClient constructor requires a
driver adapter:
import { PrismaClient } from './generated/prisma/client';
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
import { UsersExtension } from './models/Users';
const adapter = new PrismaBetterSqlite3({ url: 'file:./dev.db' });
export const prisma = new PrismaClient({ adapter }).$extends(UsersExtension);
// now available on the model, fully typed
await prisma.user.findByEmail('someone@example.com');
Run npx prisma generate again as often as you like. Your findByEmail stays.
Earlier versions deleted the entire output directory on every run. For a tool
whose output exists to hold hand-written code, that made it unusable: your
methods were destroyed each time you ran prisma generate, along with any
unrelated file that happened to live in that directory.
The generator now keeps a small manifest, .prisma-custom-models.json, in the
output directory. It records a hash of every file the generator wrote. That is
enough to tell your work apart from its own:
| Situation | What happens |
|---|---|
| File does not exist | Created |
| File exists, generator wrote it, you have not edited it | Rewritten if the scaffold changed |
| File exists, generator wrote it, you edited it | Left alone, and the run says so |
| File exists, generator never wrote it | Left alone |
| Model deleted from your schema, scaffold untouched | Removed |
| Model deleted from your schema, scaffold edited | Kept, and the run says so |
Two rules follow from that, and they hold by default:
Commit .prisma-custom-models.json along with your models. Without it the
generator cannot recognise its own past output, so it falls back to the safest
possible reading: everything already on disk belongs to you, and nothing gets
overwritten.
To get a fresh scaffold for one model, delete the file and regenerate. To
overwrite everything, set force = "true".
EXTEND_CLIENT (recommended)A Prisma Client extension. This is the approach Prisma recommends, and the only
one where your methods appear directly on prisma.user.
import { Prisma } from "../generated/prisma/client";
export const UsersExtension = Prisma.defineExtension({
name: 'Users',
model: {
user: {
// define methods here, comma-separated
},
},
});
Prisma.defineExtension type-checks the extension on its own, without needing a
client instance, which is why it is what gets emitted.
WRAP (default)A class that wraps a single model delegate.
import type { PrismaClient } from "../generated/prisma/client";
export class Users {
constructor(private readonly prismaUser: PrismaClient['user']) {
}
}
const users = new Users(prisma.user);
EXTENDObject.assign onto a model delegate. Your methods sit next to the built-in
ones on the returned object.
import type { PrismaClient } from "../generated/prisma/client";
export function Users(prismaUser: PrismaClient['user']) {
return Object.assign(prismaUser, {
// define methods here, comma-separated
});
}
const users = Users(prisma.user);
await users.findMany();
WRAP remains the default so that existing schemas keep generating what they
generated before.
| Option | Description | Type | Default |
|---|---|---|---|
output | Directory the model files are written to | string | ./generated |
behavior | Which of Prisma's three approaches to emit | WRAP, EXTEND or EXTEND_CLIENT | WRAP |
force | Overwrite files even if you have edited them | "true" or "false" | "false" |
generator custom_models {
provider = "prisma-custom-models-generator"
output = "../src/models"
behavior = "EXTEND_CLIENT"
force = "false"
}
File names come from pluralising the model name, so a schema containing both
Setting and Settings maps two models onto one Settings.ts. Other pairs
that collide the same way: User/Users, Person/People, Datum/Data.
Generation fails with an error naming the models involved. Rename one of them to proceed. Earlier versions wrote both models to the one path and kept whichever came last, so one of your models silently had no scaffold at all.
npm install
npm run build # tsc -> ./lib
npm run typecheck # tsc --noEmit
npm run lint # prettier --check
npm test # vitest run
npm run gen # build, then run prisma generate on the fixture schema
scripts/verify-package.sh is the check that matters before publishing. It
builds the package, npm packs it, installs the tarball into an empty project,
runs npx prisma generate, hand-edits a scaffold, regenerates, and asserts the
edit survived and the result still type-checks:
./scripts/verify-package.sh
9 commits
1 commits
TypeScript
72.3%
Shell
27.6%
Prisma 2+ generator to emit custom models from your schema"
22
stars
10
commits
TypeScript
primary language
Aug 23, 2026
updated
A Prisma generator that scaffolds custom models from your Prisma schema, and never overwrites the code you write into them.
Explore the options »
Report Bug
·
Request Feature
Prisma has no way to attach your own methods to a model. This generator writes the boilerplate for you: one file per model, wired to the right Prisma Client type, ready for you to add methods to.
Because those files are meant to be edited, the generator treats them as yours. Once a file exists it is never rewritten, and files it did not create are never touched. See Regeneration is safe.
Prisma documents three ways to add custom methods to a model, and this generator emits all three. Prisma recommends the first:
| Prisma's approach | behavior |
|---|---|
Client extensions ($extends), recommended by Prisma | EXTEND_CLIENT |
| Wrapping a model in a class | WRAP |
Extending a model delegate with Object.assign | EXTEND |
^20.19 || ^22.12 || >=24.07.9.1 and 6.19.3)prisma-client and the legacy prisma-client-js
generator providersThis generator does not bundle its own copy of Prisma. It reads the already parsed datamodel out of the options Prisma hands it, so it uses whatever Prisma version you installed.
npm install --save-dev prisma-custom-models-generator
yarn add --dev prisma-custom-models-generator
pnpm add -D prisma-custom-models-generator
Add the generator to your schema. output is where your custom model files go,
and it should be a directory you are happy to commit, because you will be
writing code in it.
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
generator custom_models {
provider = "prisma-custom-models-generator"
output = "../src/models"
behavior = "EXTEND_CLIENT"
}
datasource db {
provider = "sqlite"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
On Prisma 7 the connection URL lives in prisma.config.ts, not in the schema:
// prisma.config.ts
import { defineConfig } from 'prisma/config';
export default defineConfig({
schema: 'prisma/schema.prisma',
datasource: { url: process.env.DATABASE_URL ?? 'file:./dev.db' },
});
Then generate:
npx prisma generate
That writes src/models/Users.ts:
// Custom model scaffold generated by prisma-custom-models-generator.
//
// This file is yours to edit. Later `prisma generate` runs detect your
// changes and leave the file alone; the generator only rewrites scaffolds it
// created that are still untouched. Delete the file to get a fresh scaffold.
import { Prisma } from "../generated/prisma/client";
/**
* Custom methods for the `User` model, as a Prisma Client extension.
* ...
*/
export const UsersExtension = Prisma.defineExtension({
name: 'Users',
model: {
user: {
// define methods here, comma-separated
},
},
});
Fill in your methods:
export const UsersExtension = Prisma.defineExtension({
name: 'Users',
model: {
user: {
async findByEmail(email: string) {
const ctx = Prisma.getExtensionContext(this);
return (ctx as any).findFirst({ where: { email } });
},
},
},
});
And apply the extension. On Prisma 7 the PrismaClient constructor requires a
driver adapter:
import { PrismaClient } from './generated/prisma/client';
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
import { UsersExtension } from './models/Users';
const adapter = new PrismaBetterSqlite3({ url: 'file:./dev.db' });
export const prisma = new PrismaClient({ adapter }).$extends(UsersExtension);
// now available on the model, fully typed
await prisma.user.findByEmail('someone@example.com');
Run npx prisma generate again as often as you like. Your findByEmail stays.
Earlier versions deleted the entire output directory on every run. For a tool
whose output exists to hold hand-written code, that made it unusable: your
methods were destroyed each time you ran prisma generate, along with any
unrelated file that happened to live in that directory.
The generator now keeps a small manifest, .prisma-custom-models.json, in the
output directory. It records a hash of every file the generator wrote. That is
enough to tell your work apart from its own:
| Situation | What happens |
|---|---|
| File does not exist | Created |
| File exists, generator wrote it, you have not edited it | Rewritten if the scaffold changed |
| File exists, generator wrote it, you edited it | Left alone, and the run says so |
| File exists, generator never wrote it | Left alone |
| Model deleted from your schema, scaffold untouched | Removed |
| Model deleted from your schema, scaffold edited | Kept, and the run says so |
Two rules follow from that, and they hold by default:
Commit .prisma-custom-models.json along with your models. Without it the
generator cannot recognise its own past output, so it falls back to the safest
possible reading: everything already on disk belongs to you, and nothing gets
overwritten.
To get a fresh scaffold for one model, delete the file and regenerate. To
overwrite everything, set force = "true".
EXTEND_CLIENT (recommended)A Prisma Client extension. This is the approach Prisma recommends, and the only
one where your methods appear directly on prisma.user.
import { Prisma } from "../generated/prisma/client";
export const UsersExtension = Prisma.defineExtension({
name: 'Users',
model: {
user: {
// define methods here, comma-separated
},
},
});
Prisma.defineExtension type-checks the extension on its own, without needing a
client instance, which is why it is what gets emitted.
WRAP (default)A class that wraps a single model delegate.
import type { PrismaClient } from "../generated/prisma/client";
export class Users {
constructor(private readonly prismaUser: PrismaClient['user']) {
}
}
const users = new Users(prisma.user);
EXTENDObject.assign onto a model delegate. Your methods sit next to the built-in
ones on the returned object.
import type { PrismaClient } from "../generated/prisma/client";
export function Users(prismaUser: PrismaClient['user']) {
return Object.assign(prismaUser, {
// define methods here, comma-separated
});
}
const users = Users(prisma.user);
await users.findMany();
WRAP remains the default so that existing schemas keep generating what they
generated before.
| Option | Description | Type | Default |
|---|---|---|---|
output | Directory the model files are written to | string | ./generated |
behavior | Which of Prisma's three approaches to emit | WRAP, EXTEND or EXTEND_CLIENT | WRAP |
force | Overwrite files even if you have edited them | "true" or "false" | "false" |
generator custom_models {
provider = "prisma-custom-models-generator"
output = "../src/models"
behavior = "EXTEND_CLIENT"
force = "false"
}
File names come from pluralising the model name, so a schema containing both
Setting and Settings maps two models onto one Settings.ts. Other pairs
that collide the same way: User/Users, Person/People, Datum/Data.
Generation fails with an error naming the models involved. Rename one of them to proceed. Earlier versions wrote both models to the one path and kept whichever came last, so one of your models silently had no scaffold at all.
npm install
npm run build # tsc -> ./lib
npm run typecheck # tsc --noEmit
npm run lint # prettier --check
npm test # vitest run
npm run gen # build, then run prisma generate on the fixture schema
scripts/verify-package.sh is the check that matters before publishing. It
builds the package, npm packs it, installs the tarball into an empty project,
runs npx prisma generate, hand-edits a scaffold, regenerates, and asserts the
edit survived and the result still type-checks:
./scripts/verify-package.sh
9 commits
1 commits
TypeScript
72.3%
Shell
27.6%