Pothos v4 is now available! 🎉Check out the full migration guide here

Pothos

Fields

Fields for Object and Interface types are defined using a shape function. This is a function that takes a FieldBuilder as an argument, and returns an object whose keys are field names, and whose values are fields created by the FieldBuilder. These examples will mostly add fields to the Query type, but the topics covered in this guide should apply to any object or interface type.

Scalars

Scalar fields can be defined a couple of different ways:

Field method

builder.queryType({
  fields: (t) => ({
    name: t.field({
      description: 'Name field',
      type: 'String',
      resolve: () => 'Gina',
    }),
  }),
});

Convenience methods

Convenience methods are just wrappers around the field method that omit the type option.

builder.queryType({
  fields: (t) => ({
    id: t.id({ resolve: () => '123' }),
    int: t.int({ resolve: () => 123 }),
    float: t.float({ resolve: () => 1.23 }),
    boolean: t.boolean({ resolve: () => false }),
    string: t.string({ resolve: () => 'abc' }),
    idList: t.idList({ resolve: () => ['123'] }),
    intList: t.intList({ resolve: () => [123] }),
    floatList: t.floatList({ resolve: () => [1.23] }),
    booleanList: t.booleanList({ resolve: () => [false] }),
    stringList: t.stringList({ resolve: () => ['abc'] }),
  }),
});

Other types

Fields for non-scalar fields can also be created with the field method.

Some types like Objects and Interfaces can be referenced by name if they have a backing model defined in the schema builder.

const builder = new SchemaBuilder<{
  Objects: { Giraffe: { name: string } };
}>({});
 
builder.queryType({
  fields: t => ({
    giraffe: t.field({
      description: 'A giraffe'
      type: 'Giraffe',
      resolve: () => ({ name: 'Gina' }),
    }),:
  }),
});

For types not described in the SchemaTypes type provided to the builder, including types that can not be added there like Unions and Enums, you can use a Ref returned by the builder method that created them in the type parameter. For types created using a class (Objects or Interfaces) or Enums created using a typescript enum, you can also use the class or enum that was used to define them.

const LengthUnit = builder.enumType('LengthUnit', {
  values: { Feet: {}, Meters: {} },
});
 
builder.objectType('Giraffe', {
  fields: (t) => ({
    preferredNeckLengthUnit: t.field({
      type: LengthUnit,
      resolve: () => 'Feet',
    }),
  }),
});
 
builder.queryType({
  fields: (t) => ({
    giraffe: t.field({
      type: 'Giraffe',
      resolve: () => ({ name: 'Gina' }),
    }),
  }),
});

Lists

To create a list field, you can wrap the the type in an array

builder.queryType({
  fields: t => ({
    giraffes: t.field({
      description: 'multiple giraffes'
      type: ['Giraffe'],
      resolve: () => [{ name: 'Gina' }, { name: 'James' }],
    }),
    giraffeNames: t.field({
      type: ['String'],
      resolve: () => ['Gina', 'James'],
    })
  }),
});

Nullable fields

Unlike some other GraphQL implementations, fields in Pothos are non-nullable by default. It is still often desirable to make fields in your schema nullable. This default can be changed in the SchemaBuilder constructor, see Changing Default Nullability.

builder.queryType({
  fields: (t) => ({
    nullableField: t.field({
      type: 'String',
      nullable: true,
      resolve: () => null,
    }),
    nullableString: t.string({
      nullable: true,
      resolve: () => null,
    }),
    nullableList: t.field({
      type: ['String'],
      nullable: true,
      resolve: () => null,
    }),
    spareseList: t.field({
      type: ['String'],
      nullable: {
        list: false,
        items: true,
      },
      resolve: () => [null],
    }),
  }),
});

Note that by default even if a list field is nullable, the items in that list are not. The last example above shows how you can make list items nullable.

Exposing fields from the underlying data

Some GraphQL implementations have a concept of "default resolvers" that can automatically resolve fields that have a property of the same name in the underlying data. In Pothos, these relationships need to be explicitly defined, but there are helper methods that make exposing fields easier.

These helpers are not available for root types (Query, Mutation and Subscription), but will work on any other object type or interface.

const builder = new SchemaBuilder<{
  Objects: { Giraffe: { name: string } };
}>({});
 
builder.objectType('Giraffe', {
  fields: (t) => ({
    name: t.exposeString('name', {}),
  }),
});

The available expose helpers are:

  • exposeString
  • exposeInt
  • exposeFloat
  • exposeBoolean
  • exposeID
  • exposeStringList
  • exposeIntList
  • exposeFloatList
  • exposeBooleanList
  • exposeIDList

Arguments

Arguments for a field can be defined in the options for a field:

builder.queryType({
  fields: (t) => ({
    giraffeByName: t.field({
      type: 'Giraffe',
      args: {
        name: t.arg.string({ required: true }),
      },
      resolve: (root, args) => {
        if (args.name !== 'Gina') {
          throw new NotFoundError(`Unknown Giraffe ${name}`);
        }
 
        return { name: 'Gina' };
      },
    }),
  }),
});

For more information see the Arguments Guide.

Adding fields to existing type

In addition to being able to define fields when defining types, you can also add additional fields independently. This is useful for breaking up types with a lot of fields into multiple files, or co-locating fields with their type (e.g., add all query/mutation fields for a type in the same file where the type is defined).

builder.queryFields((t) => ({
  giraffe: t.field({
    type: Giraffe,
    resolve: () => new Giraffe('James', new Date(Date.UTC(2012, 11, 12)), 5.2),
  }),
}));
 
builder.objectField(Giraffe, 'ageInDogYears', (t) =>
  t.int({
    resolve: (parent) => parent.age * 7,
  }),
);

To see all the methods available for defining fields see the SchemaBuilder API

Nested Lists

You can use t.listRef to create a list of lists

const Query = builder.queryType({
  fields: (t) => ({
    example: t.field({
      type: t.listRef(
        t.listRef('String'),
        // items are non-nullable by default, this can be overridden
        // by passing `nullable: true`
        { nullable: true },
      ),
      resolve: (parent, args) => {
        return [['a', 'b'], ['c', 'd'], null];
      },
    }),
  }),
});

On this page