npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

prisma-custom-models-generator

v1.0.0

Published

Prisma generator that scaffolds custom models from your schema, without overwriting your code

Readme

npm version npm npm

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

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('[email protected]');

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:

| 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:

  • 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

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

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

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

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 repo roster for @omar-dulaimi/prisma-custom-models-generator