Fix: Pothos Not Working — Types Not Resolving, Plugin Errors, or Prisma Integration Failing
Quick Answer
How to fix Pothos GraphQL schema builder issues — type-safe schema definition, object and input types, Prisma plugin, relay connections, auth scope plugin, and schema printing.
The Problem
A Pothos type reference doesn’t resolve:
const builder = new SchemaBuilder({});
builder.queryType({
fields: (t) => ({
user: t.field({
type: 'User', // Error: Type "User" has not been implemented
resolve: () => getUser(),
}),
}),
});Or the Prisma plugin doesn’t generate the expected types:
builder.prismaObject('User', {
fields: (t) => ({
id: t.exposeID('id'),
name: t.exposeString('name'),
}),
});
// Error: Unknown model "User"Or the schema builds but queries return type errors at runtime:
Cannot return null for non-nullable field Query.usersWhy This Happens
Pothos is a code-first GraphQL schema builder for TypeScript. It generates schemas from TypeScript code instead of SDL (Schema Definition Language):
- Types must be defined before referencing them — Pothos uses a builder pattern. If you reference
'User'in a query before defining the User type withbuilder.objectType, the schema fails to build. Types can be defined in any file, but all must be imported beforebuilder.toSchema(). - The Prisma plugin needs the generated Prisma client —
builder.prismaObject('User', ...)maps to Prisma models. The plugin reads the Prisma schema at build time. If the Prisma client isn’t generated (npx prisma generate), model types are unknown. - Nullable vs non-nullable is strict — Pothos enforces GraphQL nullability at the type level. If a resolver returns
nullfor a non-nullable field (t.fieldwithoutnullable: true), GraphQL throws at runtime. - Plugins add methods to the builder —
t.prismaField,t.authField,t.relayConnectiononly exist after enabling the corresponding plugin. Using them without the plugin causes “not a function” errors.
Fix 1: Basic Schema Building
npm install @pothos/core graphql// lib/schema/builder.ts — create the builder
import SchemaBuilder from '@pothos/core';
interface Context {
currentUser: { id: string; role: string } | null;
db: typeof db;
}
export const builder = new SchemaBuilder<{
Context: Context;
Scalars: {
DateTime: { Input: Date; Output: Date };
ID: { Input: string; Output: string };
};
}>({});
// Register custom scalars
builder.scalarType('DateTime', {
serialize: (date) => date.toISOString(),
parseValue: (value) => new Date(value as string),
});// lib/schema/types/user.ts — define User type
import { builder } from '../builder';
// Define the User object type
builder.objectType('User', {
description: 'A registered user',
fields: (t) => ({
id: t.exposeID('id'),
name: t.exposeString('name'),
email: t.exposeString('email'),
role: t.exposeString('role'),
createdAt: t.expose('createdAt', { type: 'DateTime' }),
// Computed field
displayName: t.string({
resolve: (user) => `${user.name} (${user.role})`,
}),
// Relationship
posts: t.field({
type: ['Post'],
resolve: async (user, _, ctx) => {
return ctx.db.query.posts.findMany({
where: eq(posts.authorId, user.id),
});
},
}),
}),
});
// Define the Post type
builder.objectType('Post', {
fields: (t) => ({
id: t.exposeID('id'),
title: t.exposeString('title'),
body: t.exposeString('body'),
published: t.exposeBoolean('published'),
author: t.field({
type: 'User',
resolve: async (post, _, ctx) => {
return ctx.db.query.users.findFirst({
where: eq(users.id, post.authorId),
});
},
}),
}),
});// lib/schema/queries.ts — define queries
import { builder } from './builder';
builder.queryType({
fields: (t) => ({
users: t.field({
type: ['User'],
resolve: async (_, __, ctx) => {
return ctx.db.query.users.findMany();
},
}),
user: t.field({
type: 'User',
nullable: true, // Can return null if not found
args: {
id: t.arg.id({ required: true }),
},
resolve: async (_, args, ctx) => {
return ctx.db.query.users.findFirst({
where: eq(users.id, args.id),
});
},
}),
posts: t.field({
type: ['Post'],
args: {
limit: t.arg.int({ defaultValue: 20 }),
offset: t.arg.int({ defaultValue: 0 }),
},
resolve: async (_, args, ctx) => {
return ctx.db.query.posts.findMany({
limit: args.limit!,
offset: args.offset!,
});
},
}),
}),
});// lib/schema/mutations.ts
import { builder } from './builder';
// Input type
const CreateUserInput = builder.inputType('CreateUserInput', {
fields: (t) => ({
name: t.string({ required: true }),
email: t.string({ required: true }),
role: t.string({ defaultValue: 'user' }),
}),
});
builder.mutationType({
fields: (t) => ({
createUser: t.field({
type: 'User',
args: {
input: t.arg({ type: CreateUserInput, required: true }),
},
resolve: async (_, { input }, ctx) => {
const [user] = await ctx.db.insert(users).values(input).returning();
return user;
},
}),
deleteUser: t.field({
type: 'Boolean',
args: { id: t.arg.id({ required: true }) },
resolve: async (_, { id }, ctx) => {
await ctx.db.delete(users).where(eq(users.id, id));
return true;
},
}),
}),
});// lib/schema/index.ts — build the schema
import { builder } from './builder';
// Import all type/query/mutation files to register them
import './types/user';
import './queries';
import './mutations';
// Build the schema
export const schema = builder.toSchema();Fix 2: Prisma Plugin
npm install @pothos/plugin-prisma
npx prisma generate # Required — generates PrismaClient types// lib/schema/builder.ts — with Prisma plugin
import SchemaBuilder from '@pothos/core';
import PrismaPlugin from '@pothos/plugin-prisma';
import type PrismaTypes from '@pothos/plugin-prisma/generated';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export const builder = new SchemaBuilder<{
PrismaTypes: PrismaTypes;
Context: { prisma: typeof prisma; currentUser: User | null };
}>({
plugins: [PrismaPlugin],
prisma: { client: prisma },
});// Types automatically mapped from Prisma schema
builder.prismaObject('User', {
fields: (t) => ({
id: t.exposeID('id'),
name: t.exposeString('name'),
email: t.exposeString('email'),
// Prisma relation — auto-resolved
posts: t.relation('posts', {
args: { published: t.arg.boolean() },
query: (args) => ({
where: args.published != null ? { published: args.published } : {},
orderBy: { createdAt: 'desc' },
}),
}),
// Count
postCount: t.relationCount('posts'),
}),
});
builder.prismaObject('Post', {
fields: (t) => ({
id: t.exposeID('id'),
title: t.exposeString('title'),
body: t.exposeString('body'),
published: t.exposeBoolean('published'),
author: t.relation('author'),
createdAt: t.expose('createdAt', { type: 'DateTime' }),
}),
});
// Queries with Prisma
builder.queryField('users', (t) =>
t.prismaField({
type: ['User'],
resolve: async (query, _, __, ctx) => {
// `query` includes select/include from nested field selections
return ctx.prisma.user.findMany({ ...query });
},
}),
);
builder.queryField('user', (t) =>
t.prismaField({
type: 'User',
nullable: true,
args: { id: t.arg.id({ required: true }) },
resolve: async (query, _, args, ctx) => {
return ctx.prisma.user.findUnique({
...query,
where: { id: args.id },
});
},
}),
);Fix 3: Auth Scope Plugin
npm install @pothos/plugin-scope-authimport SchemaBuilder from '@pothos/core';
import ScopeAuthPlugin from '@pothos/plugin-scope-auth';
export const builder = new SchemaBuilder<{
Context: { currentUser: User | null };
AuthScopes: {
isLoggedIn: boolean;
isAdmin: boolean;
};
}>({
plugins: [ScopeAuthPlugin],
authScopes: async (context) => ({
isLoggedIn: !!context.currentUser,
isAdmin: context.currentUser?.role === 'admin',
}),
});
// Protected query
builder.queryField('me', (t) =>
t.field({
type: 'User',
authScopes: { isLoggedIn: true },
resolve: (_, __, ctx) => ctx.currentUser!,
}),
);
// Admin-only mutation
builder.mutationField('deleteUser', (t) =>
t.field({
type: 'Boolean',
authScopes: { isAdmin: true },
args: { id: t.arg.id({ required: true }) },
resolve: async (_, { id }, ctx) => {
await ctx.db.delete(users).where(eq(users.id, id));
return true;
},
}),
);
// Protected type — all fields require auth
builder.objectType('SecretData', {
authScopes: { isAdmin: true },
fields: (t) => ({
key: t.exposeString('key'),
value: t.exposeString('value'),
}),
});Fix 4: Relay Plugin (Cursor Pagination)
npm install @pothos/plugin-relayimport RelayPlugin from '@pothos/plugin-relay';
const builder = new SchemaBuilder<{ /* ... */ }>({
plugins: [RelayPlugin],
relay: {},
});
// Node interface — enables `node(id: "...")` query
builder.prismaNode('User', {
id: { field: 'id' },
fields: (t) => ({
name: t.exposeString('name'),
email: t.exposeString('email'),
posts: t.relatedConnection('posts', { cursor: 'id' }),
}),
});
// Connection query — cursor-based pagination
builder.queryField('users', (t) =>
t.prismaConnection({
type: 'User',
cursor: 'id',
resolve: (query, _, __, ctx) => {
return ctx.prisma.user.findMany({ ...query });
},
}),
);
// Query:
// query { users(first: 10, after: "cursor") { edges { node { name } } pageInfo { hasNextPage endCursor } } }Fix 5: Print Schema to SDL
import { printSchema } from 'graphql';
import { schema } from './lib/schema';
import fs from 'fs';
// Generate schema.graphql for client codegen
const sdl = printSchema(schema);
fs.writeFileSync('schema.graphql', sdl);
console.log('Schema written to schema.graphql');// package.json
{
"scripts": {
"schema:generate": "tsx scripts/print-schema.ts"
}
}Fix 6: Use with GraphQL Yoga
// app/api/graphql/route.ts — Pothos + Yoga + Next.js
import { createYoga } from 'graphql-yoga';
import { schema } from '@/lib/schema';
import { auth } from '@/auth';
const yoga = createYoga({
schema,
context: async ({ request }) => {
const session = await auth();
return {
currentUser: session?.user || null,
prisma,
db,
};
},
graphqlEndpoint: '/api/graphql',
fetchAPI: { Response },
});
export { yoga as GET, yoga as POST, yoga as OPTIONS };Still Not Working?
“Type X has not been implemented” — the type definition file isn’t imported. Pothos registers types when their definition code executes. Import all type files in your schema/index.ts before calling builder.toSchema(). Order doesn’t matter — just import them.
Prisma plugin shows “Unknown model” — run npx prisma generate to create the PrismaClient types. The Pothos Prisma plugin reads from the generated types at @pothos/plugin-prisma/generated. If you renamed or added models, regenerate.
Non-nullable field returns null — the resolver returned null or undefined for a field without nullable: true. Either make the field nullable or ensure the resolver always returns a value. For relationships, this often means the related record doesn’t exist.
Circular type references cause TypeScript errors — Pothos handles circular references (User → Posts → User) through lazy evaluation with () => ... wrappers. If TypeScript complains, use builder.objectRef('User') to create a forward reference.
For related GraphQL issues, see Fix: GraphQL Yoga Not Working and Fix: tRPC Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: GraphQL Yoga Not Working — Schema Errors, Resolvers Not Executing, or Subscriptions Failing
How to fix GraphQL Yoga issues — schema definition, resolver patterns, context and authentication, file uploads, subscriptions with SSE, error handling, and Next.js integration.
Fix: Fastify Not Working — 404, Plugin Encapsulation, and Schema Validation Errors
How to fix Fastify issues — route 404 from plugin encapsulation, reply already sent, FST_ERR_VALIDATION, request.body undefined, @fastify/cors, hooks not running, and TypeScript type inference.
Fix: Better Auth Not Working — Login Failing, Session Null, or OAuth Callback Error
How to fix Better Auth issues — server and client setup, email/password and OAuth providers, session management, middleware protection, database adapters, and plugin configuration.
Fix: BullMQ Not Working — Jobs Not Processing, Workers Not Starting, or Redis Connection Failing
How to fix BullMQ issues — queue and worker setup, Redis connection, job scheduling, retry strategies, concurrency, rate limiting, event listeners, and dashboard monitoring.