Resolvers
How resolver functions produce a field's value, and what they can return.
A resolver is the function that produces a field's value. When a field appears in a query, the graphql-js executor calls that field's resolver and expects back a value matching the field's type. The t.expose* helpers from the Fields guide write a resolver for you that reads a property off the backing object; this guide is about writing the resolve function yourself.
characterCount: t.int({
resolve: () => characters.length,
}),characterCount is an Int field whose resolver returns a number. The executor runs it each time the field is selected in a query, and the returned value becomes the field's value in the response.
The four arguments
The executor calls a resolver with four positional arguments, (parent, args, context, info):
character: t.field({
type: Character,
nullable: true,
args: { id: t.arg.id({ required: true }) },
resolve: (parent, args) =>
characters.find((character) => character.id === args.id) ?? null,
}),parentis the value the field's parent resolver returned, which is the backing model for that type (covered in Object types). On a field of the rootQuery,Mutation, orSubscriptiontype there is no parent object, soparentis therootValuethe server passed to the executor, usuallyundefined.argsis the field's arguments, already coerced and type-checked against the field'sargsdefinition. The Arguments guide covers declaring them.contextis the per-request context: the object your server builds for each request, where values like the authenticatedctx.userand database handles live.infois information about the current query and field selection, which plugins like Prisma and Drizzle read to plan a single database query for the fields a request selected.
Most resolvers only need one or two of these, and since the parameters are positional you can leave off any trailing ones you don't use.
What a resolver returns
A resolver returns a value matching the field's type, or a promise for one; the executor awaits the promise before continuing. A synchronous resolver returns its value directly, and an async resolver returns a promise for it.
featuredCharacter: t.field({
type: Character,
nullable: true,
resolve: async () => {
const character = await loadCharacter('1');
return character ?? null;
},
}),The field's type decides what counts as a valid value, and Pothos checks it in TypeScript. A scalar field returns that scalar. A field returning an object type returns the backing model for that type rather than a GraphQL-shaped object, so you can hand back a database row or a plain object without reshaping it first. A list field accepts any iterable of those values, and a nullable field may also return null or undefined; character above returns null when no character matches the requested id.