Prisma utils
Build Prisma-compatible filter, order-by, create, and update input types from your schema.
Writing input types for filtering, ordering, and mutating Prisma models by hand is repetitive and easy to get out of sync with your schema. The prisma-utils plugin adds builder helpers that produce input types shaped to match Prisma's own where, orderBy, create, and update arguments, so a filter you define type-checks against the query you'll pass it to. It layers on top of the Prisma plugin but is otherwise independent; you can adopt it a few input types at a time.
This package is highly experimental and not recommended for production use. The helpers are building blocks that may change with breaking releases as they mature.
Setup
Enable the prismaUtils feature on the Pothos generator in your schema.prisma:
generator client {
provider = "prisma-client"
output = "../lib/prisma"
}
generator pothos {
provider = "prisma-pothos-types"
clientOutput = "./prisma" // relative path from the pothos output to the prisma client
output = "../lib/pothos-prisma-types.ts"
prismaUtils = true // enable the prisma-utils feature
}Then add the plugin alongside the Prisma plugin when you build. The utils lean on your scalar mappings, so register any custom scalars (like DateTime) the input types will reference:
import SchemaBuilder from '@pothos/core';
import { PrismaClient } from '@prisma/client';
import PrismaPlugin from '@pothos/plugin-prisma';
import PrismaUtils from '@pothos/plugin-prisma-utils';
import type PrismaTypes from '../lib/pothos-prisma-types';
import { getDatamodel } from '../lib/pothos-prisma-types';
export const prisma = new PrismaClient({});
export default new SchemaBuilder<{
Scalars: {
DateTime: { Input: Date; Output: Date };
};
PrismaTypes: PrismaTypes;
}>({
plugins: [PrismaPlugin, PrismaUtils],
prisma: {
client: prisma,
dmmf: getDatamodel(),
},
});What the plugin is for
The goal is not to generate every input type automatically; there are too many design trade-offs in filtering and mutation inputs for one scheme to fit every schema. Instead the plugin gives you composable building blocks, so writing your own helpers or a code generator becomes far easier. Each helper below produces one Prisma-compatible input type you assemble into where, orderBy, create, and update arguments.
Filters
Scalar and enum filters
builder.prismaFilter builds a filter input for a scalar or enum, exposing the operators you list in ops:
const StringFilter = builder.prismaFilter('String', {
ops: ['contains', 'equals', 'startsWith', 'not'],
});
export const IntFilter = builder.prismaFilter('Int', {
ops: ['equals', 'not'],
});
builder.enumType(Position, { name: 'Position' });
const PositionFilter = builder.prismaFilter(Position, {
ops: ['not', 'equals'],
});Object (where) filters
builder.prismaWhere builds a filter matching a model's where clause. Its fields can be a static object or a function; each field takes a filter, a scalar type name (for equality-only), or a t.field for extra options. Relations are filtered by referencing another where filter:
const PlayerWhere = builder.prismaWhere('Player', {
fields: {
id: IntFilter,
},
});
const GameWhere = builder.prismaWhere('Game', {
fields: (t) => ({
// Use a filter for rich operators...
id: IntFilter,
// ...or a scalar type name for equality only.
playedAt: 'DateTime',
// Relations reference another where filter.
homeTeam: TeamWhere,
// t.field adds options like a description.
homeTeamId: t.field({ type: IntFilter, description: 'filter by home team id' }),
}),
});Scalar list filters
builder.prismaScalarListFilter builds a filter for a scalar-array column:
export const StringListFilter = builder.prismaScalarListFilter('String', {
name: 'StringListFilter',
ops: ['has', 'hasSome', 'hasEvery', 'isEmpty', 'equals'],
});Object list filters
builder.prismaListFilter wraps a where filter with list operators, for filtering a to-many relation:
const PlayerListFilter = builder.prismaListFilter(PlayerWhere, {
ops: ['every', 'some', 'none'],
});Order-by inputs
builder.prismaOrderBy builds an orderBy input. Set a scalar field to true to make it sortable; reference another order-by input to sort by a relation:
const TeamOrderBy = builder.prismaOrderBy('Team', {
fields: {
name: true,
},
});
export const PlayerOrderBy = builder.prismaOrderBy('Player', {
fields: () => ({
id: true,
name: true,
number: true,
team: TeamOrderBy,
}),
});Create inputs
builder.prismaCreate builds an input for a create mutation. For types with circular references, add an explicit InputObjectRef<Types, Prisma.…CreateInput> annotation so the types resolve; simple types without cycles can omit it. The first type argument is the builder's SchemaTypes, which you recover once with a helper alias.
import { InputObjectRef } from '@pothos/core';
import { Prisma } from '@prisma/client';
// Recover the builder's SchemaTypes for the input-ref annotations below.
type Types = typeof builder extends PothosSchemaTypes.SchemaBuilder<infer T> ? T : never;
export const PlayerCreate: InputObjectRef<Types, Prisma.PlayerCreateInput> = builder.prismaCreate('Player', {
name: 'PlayerCreate',
fields: () => ({
// scalars
id: 'Int',
name: 'String',
number: 'Int',
// relation inputs are defined separately, below
team: PlayerCreateTeam,
// list relations are declared the same way — Pothos makes the input a list
stats: PlayerCreateStats,
}),
});builder.prismaCreateRelation defines the nested input for one relation. create points at a prismaCreate input; connect points at a prismaWhere/prismaWhereUnique filter:
export const PlayerCreateTeam = builder.prismaCreateRelation('Player', 'team', {
fields: () => ({
// built with builder.prismaCreate
create: TeamCreateWithoutPlayers,
// built with builder.prismaWhereUnique
connect: TeamUniqueFilter,
}),
});
export const PlayerCreateStats = builder.prismaCreateRelation('Player', 'stats', {
fields: () => ({
create: PlayerStatCreateWithoutPlayer,
connect: PlayerStatUniqueFilter,
}),
});Update inputs
builder.prismaUpdate mirrors prismaCreate for update mutations, with the same annotation guidance for circular references:
export const PlayerUpdate: InputObjectRef<Types, Prisma.PlayerUpdateInput> = builder.prismaUpdate('Player', {
name: 'PlayerUpdate',
fields: () => ({
id: 'Int',
name: 'String',
number: 'Int',
team: PlayerUpdateTeam,
stats: PlayerUpdateStats,
}),
});builder.prismaUpdateRelation exposes the full set of Prisma nested-write operations. Define only the ones a given relation needs:
export const PlayerUpdateTeam = builder.prismaUpdateRelation('Player', 'team', {
fields: () => ({
create: TeamCreateWithoutPlayers, // builder.prismaCreate
update: TeamUpdateWithoutPlayers, // builder.prismaUpdate
connect: TeamUniqueFilter, // builder.prismaWhereUnique
}),
});
export const PlayerUpdateStats = builder.prismaUpdateRelation('Player', 'stats', {
fields: () => ({
create: PlayerStatCreateWithoutPlayer, // builder.prismaCreate
createMany: {
// builder.prismaCreateMany
skipDuplicates: 'Boolean',
data: PlayerStatCreateManyWithoutPlayer,
},
set: PlayerStatUniqueFilter, // builder.prismaWhereUnique
disconnect: PlayerStatUniqueFilter,
delete: PlayerStatUniqueFilter,
connect: PlayerStatUniqueFilter,
update: {
where: PlayerStatUniqueFilter, // builder.prismaWhereUnique
data: PlayerStatUpdateWithoutPlayer, // builder.prismaUpdate
},
updateMany: {
where: PlayerStatWithoutPlayerFilter, // builder.prismaWhere
data: PlayerStatUpdateWithoutPlayer, // builder.prismaUpdate
},
deleteMany: PlayerStatWithoutPlayerFilter, // builder.prismaWhere
}),
});Atomic number updates
builder.prismaIntAtomicUpdate builds an input for Prisma's atomic integer operations, so a mutation can increment or decrement a column instead of overwriting it:
const IntUpdate = builder.prismaIntAtomicUpdate();
// or with options
const IntUpdateWithOps = builder.prismaIntAtomicUpdate({
name: 'IntUpdate',
ops: ['increment', 'decrement'],
});
export const PlayerStatUpdate = builder.prismaUpdate('PlayerStat', {
name: 'PlayerStatUpdate',
fields: () => ({
assists: 'Int',
goals: IntUpdate,
}),
});Generators
Hand-writing every input type for a large schema is exactly the repetition these helpers exist to remove. Pothos does not ship an official generator, but two example generators show how to wire the building blocks into one. They're deliberately limited and not built for reuse, and they'll change with breaking updates, so copy and adapt them rather than importing them.
- Static generation writes the input types to a TypeScript file you import into your schema. See the example static generator, the file it produces, and how it's consumed.
- Dynamic generation creates the input types at runtime through helpers imported into your app. See the example dynamic generator and how it's used.