Design
How Pothos carries type information — the SchemaTypes generic and Ref objects — and why that split keeps large schemas and plugins modular.
Pothos gives you a fully typed schema with no code-generation step. To pull that off it has to carry the TypeScript shape of every type and field all the way through to the resolver that uses it. It does this two ways, and knowing which is which explains most of how Pothos behaves.
The SchemaTypes generic
The first mechanism is the SchemaTypes parameter you pass to SchemaBuilder. It's a single object of type-only configuration, shared across the whole schema. It carries your Context shape and any Object, Interface, or Scalar type you want to reference later by name as a string.
const builder = new SchemaBuilder<{
Context: { currentUser: ICharacter };
Objects: { Race: IRace };
}>({});
// 'Race' is now usable by name anywhere a type is expected:
t.field({ type: 'Race', resolve: () => findRace() });Keeping every type in one place is convenient: there's a single object to look at and a single name to spell. On a large schema it turns unwieldy, since every type piles into one generic and each addition widens a type the compiler re-checks everywhere it's referenced.
Ref objects
The second mechanism is the Ref object. Every builder method that creates a type or a field returns a Ref that carries the type information it represents:
const Race = builder.objectRef<IRace>('Race');
// Race is a Ref that already knows its backing model — pass it directly:
t.field({ type: Race, resolve: () => findRace() });Because a Ref holds its own type information, you never register it on SchemaTypes and never spell its name as a string. That's what makes the harder cases work:
- Unions and enums, whose members are values rather than named entries in a generic.
- Large schemas, where each type stays self-contained instead of swelling one central generic.
- Plugins that pull type information from another source, like the Prisma plugin from your Prisma client or the simple-objects plugin from the object you hand it. They can mint a
Reffor a type the builder's generic has never heard of.
Why the split
Separating the type information (the Ref) from the implementation (implement) is what keeps Pothos modular. A Ref exists before its fields do, so two types can reference each other without forward-declaration gymnastics. A plugin can hand you a Ref for a type it derived from somewhere else without ever touching the central generic. And each type carries its own shape, so nothing has to stay in sync with a schema-wide registry.
This is why objectRef is the pattern the guide uses first: it keeps a type and everything the compiler knows about it in one place. The SchemaTypes generic is still there when you specifically want a name to hang behavior on: string-referenced types, the Context shape, and the plugin slots that carry an ORM's generated type information (PrismaTypes, DrizzleRelations). The two interoperate freely; most schemas use objectRef for the bulk of their types and the generic for the handful that need a name.