Migrating from the Prisma plugin
Updating an existing Pothos schema for Prisma ORM 8
Start with your Prisma ORM 8 client and contract, then update Pothos to use
@pothos/plugin-prisma-next. You can keep the structure of your GraphQL schema: prismaObject,
t.expose*, t.relation, nodes and connections still define the fields your clients use.
Connect the new client
Follow Setup to replace the plugin import, pass your contract as PrismaContract, and
configure prisma: { client: db }. The new client replaces the Prisma ORM 7 client and the
Pothos generator output used by @pothos/plugin-prisma. Use one plugin per program.
What changed
takeandskipare nowlimitandoffset.whereandorderByuse Prisma ORM 8's shapes, not Prisma Client's.- The
dmmf,exposeDescriptionsandonUnusedQueryplugin options are gone. - A
clientfunction also needs the base client'scontext; see Plugin options. prismaObjectandprismaNodetake noincludeorfindUnique; useselect.- A connection helper's
getQuerytakes(args, ctx), withoutnestedSelection. @pothos/plugin-prisma-utilshas no counterpart.
Declare data for computed fields
Exposed fields and relations load their own data. If a custom resolver reads additional columns,
declare those with select so they are loaded whenever the field is requested:
builder.prismaObjectField('User', 'fullName', (t) =>
t.string({
select: ['firstName', 'lastName'],
resolve: (user) => `${user.firstName} ${user.lastName}`,
}),
);Use a type-level select for data shared by every field or authorization check on that type.
Selections covers both forms.
Update queries and mutations
For a read, query describes which rows to return. A field that previously spread the plugin's
query into findUnique can become:
builder.queryField('me', (t) =>
t.prismaField({
type: 'User',
nullable: true,
query: (_args, ctx) => ({ where: { id: ctx.userId } }),
}),
);If you already compose a native ORM query, return it from collection, for example
collection: () => db.orm.Post.where({ published: 1 }) for published posts.
For writes, resolve receives the selected ORM query and lets you call create, update, or
delete on it. See Mutations for complete examples and
Transactions for multi-step operations.
Custom t.field resolvers that return database rows should use
collectionFromInfo when loading those rows.
That ensures the result includes the relations and other data requested by the GraphQL query.
Keep your connection queries
GraphQL clients can keep using first, after, last and before. In the schema, replace a
connection's cursor option with the order you want users to see:
builder.prismaObjectField('User', 'postsConnection', (t) =>
t.relatedConnection('posts', {
query: { orderBy: { createdAt: 'desc' } },
}),
);The plugin adds a unique key to break ties. See Ordering and cursors
for nullable columns and supported sort fields. Root connections no longer take a resolve callback.
Use collection for checks before loading, and
connection helpers when a custom resolver needs the loaded rows.
Adapt custom relation fields
A filtered count now takes query: { where }, for example
t.relationCount('posts', { query: { where: { published: 1 } } }).
For a relation with custom resolver logic, use t.field with a select.
nestedSelection loads the relations requested beneath that field, and
takes its options in one object, such as nestedSelection({ path: ['post'] }).