omar-dulaimi/prisma-custom-models-generator

Prisma 2+ generator to emit custom models from your schema"

22

stars

10

commits

TypeScript

primary language

Aug 23, 2026

updated

custom-models
prisma
prisma-generator
Browse cluster: Prisma code generation and GraphQL

README

npm version npm npm

Prisma Custom Models Generator

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

Table of Contents

About

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 approachbehavior
Client extensions ($extends), recommended by PrismaEXTEND_CLIENT
Wrapping a model in a classWRAP
Extending a model delegate with Object.assignEXTEND

Requirements

  • Node ^20.19 || ^22.12 || >=24.0
  • Prisma 6 or 7 (verified against 7.9.1 and 6.19.3)
  • Works with both the prisma-client and the legacy prisma-client-js generator providers

This 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.

Installation

npm install --save-dev prisma-custom-models-generator
yarn add --dev prisma-custom-models-generator
pnpm add -D prisma-custom-models-generator

Usage

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.

Regeneration is safe

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:

SituationWhat happens
File does not existCreated
File exists, generator wrote it, you have not edited itRewritten if the scaffold changed
File exists, generator wrote it, you edited itLeft alone, and the run says so
File exists, generator never wrote itLeft alone
Model deleted from your schema, scaffold untouchedRemoved
Model deleted from your schema, scaffold editedKept, and the run says so

Two rules follow from that, and they hold by default:

  • The generator only writes a file it created and you have not edited.
  • The generator only deletes a file it created and you have not edited.

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".

Behaviors

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);

EXTEND

Object.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.

Options

OptionDescriptionTypeDefault
outputDirectory the model files are written tostring./generated
behaviorWhich of Prisma's three approaches to emitWRAP, EXTEND or EXTEND_CLIENTWRAP
forceOverwrite 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"
}

Model names that collide

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.

Development

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

Community

Stargazers

Contributors

omar-dulaimi/prisma-custom-models-generator

Prisma 2+ generator to emit custom models from your schema"

22

stars

10

commits

TypeScript

primary language

Aug 23, 2026

updated

custom-models
prisma
prisma-generator
Browse cluster: Prisma code generation and GraphQL

README

npm version npm npm

Prisma Custom Models Generator

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

Table of Contents

About

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 approachbehavior
Client extensions ($extends), recommended by PrismaEXTEND_CLIENT
Wrapping a model in a classWRAP
Extending a model delegate with Object.assignEXTEND

Requirements

  • Node ^20.19 || ^22.12 || >=24.0
  • Prisma 6 or 7 (verified against 7.9.1 and 6.19.3)
  • Works with both the prisma-client and the legacy prisma-client-js generator providers

This 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.

Installation

npm install --save-dev prisma-custom-models-generator
yarn add --dev prisma-custom-models-generator
pnpm add -D prisma-custom-models-generator

Usage

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.

Regeneration is safe

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:

SituationWhat happens
File does not existCreated
File exists, generator wrote it, you have not edited itRewritten if the scaffold changed
File exists, generator wrote it, you edited itLeft alone, and the run says so
File exists, generator never wrote itLeft alone
Model deleted from your schema, scaffold untouchedRemoved
Model deleted from your schema, scaffold editedKept, and the run says so

Two rules follow from that, and they hold by default:

  • The generator only writes a file it created and you have not edited.
  • The generator only deletes a file it created and you have not edited.

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".

Behaviors

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);

EXTEND

Object.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.

Options

OptionDescriptionTypeDefault
outputDirectory the model files are written tostring./generated
behaviorWhich of Prisma's three approaches to emitWRAP, EXTEND or EXTEND_CLIENTWRAP
forceOverwrite 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"
}

Model names that collide

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.

Development

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

Community

Stargazers

Contributors

Languages

TypeScript

72.3%

Shell

27.6%