Fundamentals

SchemaBuilder

The central builder every Pothos type is defined on, configured through its SchemaTypes generic and constructor options.

Every Pothos schema starts with a SchemaBuilder, and every type in the schema is defined through it. The builder holds two kinds of configuration: a SchemaTypes generic that shapes what TypeScript infers, and a constructor options object that carries runtime settings like the list of plugins.

Creating a builder

import SchemaBuilder from '@pothos/core';

interface Context {
  user?: { id: number; name: string };
}

const builder = new SchemaBuilder<{
  Context: Context;
  Scalars: {
    DateTime: { Input: Date; Output: Date };
  };
}>({});

The generic parameter is an object Pothos calls SchemaTypes. This one fills in two of its entries: Context declares the type of the context object resolvers receive as their third argument (the Context guide covers where that object comes from), and Scalars declares the TypeScript types for a custom DateTime scalar (the Scalars guide covers implementing it). The constructor takes an options object; with no plugins here it stays empty.

You typically create one builder per schema and export it from a shared module, so every file that defines part of the schema imports the same builder. The Project layout pattern shows one way to organize this.

The SchemaTypes generic

The generic is type-level configuration: it changes what TypeScript accepts and infers, and nothing you write in it is passed to the constructor. Every entry is optional; you supply the ones you care about, and Pothos fills in defaults for the rest. Each entry is covered in depth on its own page:

  • Context: the shape of the per-request context object. Covered in Context.
  • Scalars: the Input/Output TypeScript types for each named scalar. The runtime implementation is registered later with builder.scalarType, covered in Scalars.
  • DefaultFieldNullability and DefaultInputFieldRequiredness: schema-wide defaults for whether output fields are nullable and input fields are required. Covered in Default nullability.
  • Objects, Interfaces, and Inputs: maps from type name to backing model (the TypeScript shape behind a type), which let you reference types by name as strings and keep your type definitions in one place. Object types covers this style alongside the others.
  • Plugin entries: each installed plugin can add its own entries (AuthScopes from scope-auth, PrismaTypes from prisma, and so on), documented on that plugin's page.

Three entries also have a constructor counterpart: Defaults (covered in the v4 migration guide), DefaultFieldNullability, and DefaultInputFieldRequiredness. Setting one of these to a non-default value makes the matching constructor option (defaults, defaultFieldNullability, defaultInputFieldRequiredness) required, and its value has to match. None of the other core entries corresponds to a constructor option. The context value is created by your server for each request, and scalar implementations and named types are registered through builder methods.

Constructor options

The options object holds the runtime configuration. In core, the main option is plugins, the list of plugins the builder should use:

import SchemaBuilder from '@pothos/core';
import RelayPlugin from '@pothos/plugin-relay';

const builder = new SchemaBuilder<{
  Context: Context;
}>({
  plugins: [RelayPlugin],
});

Each installed plugin can also add its own keys to the options object. Some are optional (relay), and some become required as soon as the plugin package is imported (prisma from @pothos/plugin-prisma). The Using plugins guide covers installing and configuring plugins.

Building the schema

export const schema = builder.toSchema();

toSchema resolves every type registered on the builder and returns a standard graphql-js GraphQLSchema, so anything that accepts a GraphQLSchema can serve it. Call it after all of your types have been defined, usually at the bottom of the module that imports every type definition.

By default the returned schema is sorted lexicographically; pass toSchema({ sortSchema: false }) to keep definition order instead. toSchema accepts a few other options (directives, extensions), and some plugins add their own build-time options here.