Setup
Setting up the Prisma plugin
npm install --save @pothos/plugin-prismaSetup
The Prisma plugin uses generated types to describe your models and relations. Add its generator alongside your Prisma client generator, then pass the generated types and datamodel to the builder.
Add the pothos generator to your prisma schema
generator pothos {
provider = "prisma-pothos-types"
}Now the types Pothos uses will be generated whenever you re-generate your prisma client. Run the following command to re-generate the client and create the new types:
npx prisma generateGenerator options:
clientOutput: Where the generated code will import the PrismaClient from. The default is the full path of wherever the client is generated. If you are checking in the generated file, you should specify a relative path for this importoutput: Where to write the generated types
Example with more options:
generator client {
provider = "prisma-client"
output = "../lib/prisma"
}
generator pothos {
provider = "prisma-pothos-types"
clientOutput = "./prisma" // relative path from pothos output to prisma client
output = "../lib/pothos-prisma-types.ts"
}If model or relation completions are missing, check the client import in the generated file.
Set up the builder
This example uses the generated client above and a SQLite database. Install
@prisma/adapter-better-sqlite3 for this adapter; use the adapter for your database if it differs.
exposeDescriptions also accepts { models: true, fields: true } to configure descriptions separately.
import SchemaBuilder from '@pothos/core';
import { PrismaClient } from '../lib/prisma/client';
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
import PrismaPlugin from '@pothos/plugin-prisma';
import type PrismaTypes from '../lib/pothos-prisma-types'; // path to generated types, specified in your prisma.schema
import { getDatamodel } from '../lib/pothos-prisma-types';
const prisma = new PrismaClient({
adapter: new PrismaBetterSqlite3({ url: 'file:./dev.db' }),
});
const builder = new SchemaBuilder<{
PrismaTypes: PrismaTypes;
Context: { userId: number };
}>({
plugins: [PrismaPlugin],
prisma: {
client: prisma,
// This give pothos information about your tables, relations, and indexes to help it generate optimal queries at runtime.
// This used to be attached to the prisma client, but has been removed in most runtimes/modes to reduce bundle size.
dmmf: getDatamodel(),
// defaults to false, uses /// comments from prisma schema as descriptions
// for object types, relations and exposed fields.
// descriptions can be omitted by setting description to false
exposeDescriptions: false,
// use where clause from prismaRelatedConnection for totalCount (defaults to true)
filterConnectionTotalCount: true,
// warn when not using a query parameter correctly
onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn',
// leave selections inside @defer fragments out of the planned query (defaults to true)
skipDeferredFragments: true,
},
});The examples use an authenticated request context with userId: number. Supply it through your
GraphQL server; see Context.
Pass the Prisma client through the plugin options. Including its full type in Context can slow
TypeScript checking; see this TypeScript issue.
You can also load or create the prisma client dynamically for each request. This can be used to periodically re-create clients or create read-only clients for certain types of users.
Replace the builder above with the following to select between prisma and a second client.
This SQLite example uses READ_ONLY_REPLICA_URL for a replica database maintained by your application;
the adapter does not configure replication or enforce read-only access. Configure database access
permissions separately. Both clients must use the same generated Prisma client
and schema. For another database, use its driver adapter and replica connection options.
const replicaUrl = process.env.READ_ONLY_REPLICA_URL;
if (!replicaUrl) {
throw new Error('READ_ONLY_REPLICA_URL is required');
}
const readOnlyPrisma = new PrismaClient({
adapter: new PrismaBetterSqlite3({ url: replicaUrl }),
});
const builder = new SchemaBuilder<{
Context: { user: { isAdmin: boolean } };
PrismaTypes: PrismaTypes;
}>({
plugins: [PrismaPlugin],
prisma: {
client: (ctx) => (ctx.user.isAdmin ? prisma : readOnlyPrisma),
dmmf: getDatamodel(),
},
});Detecting unused query arguments
Forgetting to spread the query argument from t.prismaField or t.prismaConnection into your
prisma query can result in inefficient queries, or even missing data. To help catch these issues,
the plugin can warn you when you are not using the query argument correctly.
The onUnusedQuery option can be set to warn or error to enable this feature. When set to
warn it will log a warning to the console if Pothos detects that you have not properly used the
query in your resolver. Similarly if you set the option to error it will throw an error instead.
You can also pass a function which will receive the info object which can be used to log or throw
your own error.
The check tracks access to properties on the query object. If no properties are accessed on the query object before the
resolver returns, it will trigger the onUnusedQuery condition.
It's recommended to enable this check in development to more quickly find potential issues.
Deferred fragments
skipDeferredFragments controls query planning; it does not enable incremental execution. The application
must register the defer directive and use an executor, server transport, and client that support
the same incremental delivery protocol. With GraphQL.js 17, ordinary execute rejects schemas
containing @defer or @stream; incremental execution uses experimentalExecuteIncrementally.
For a server integration, see GraphQL Yoga's defer and stream setup,
which uses @graphql-yoga/plugin-defer-stream. Check the integration's supported versions when
choosing an executor and client; adding directives or changing skipDeferredFragments alone is
not sufficient.
Selections inside a @defer fragment are left out of the planned query by default, so the initial
payload is not delayed by data the client has agreed to wait for. When the deferred fragment
resolves, its fields are loaded through fallback queries, batched as
usual.
Set skipDeferredFragments: false in the plugin options to plan deferred selections with the rest
of the query. queryFromInfo accepts the same option per call.
Run the publishing API
The publishing example
uses this SQLite adapter and generated client with one schema for author pages, private drafts,
media attachments, and pagination. Prisma Utils adds filters and draft inputs to that same schema.
The example's schema.prisma defines User, Profile, Post, Comment, Media, and PostMedia, including
the foreign keys and unique constraints needed by their relations.
From a checkout of the Pothos repository, with Node.js 22 or newer:
pnpm install --frozen-lockfile
pnpm --dir website check:local
npm --prefix website/local-examples run prisma -- authorThe setup generates both clients and SQLite DDL from the Prisma schema. Each run creates a fresh temporary database and removes it afterward. The output contains the GraphQL response and emitted SQL. The example guide lists the operations and suggested edits. Applications should use Prisma migrations for their persistent databases.
The request's userId comes from authentication in an application. The local runner supplies it as
a fixture. Public author fields return published posts; private viewer fields return only the
current author's drafts. Ownership and publication checks also apply to draft updates and node
refetches. Selecting a GraphQL type does not by itself enforce those restrictions.