Guide

Inferring Types

Extract backing types and builder types for TypeScript helpers

In some cases you may want to use the types from your input or object refs to build helpers, or provide accurate types for other functions.

To get types from any Pothos ref object, you can use the $inferType and $inferInput properties on the corresponding output or input ref. Use these properties in TypeScript type expressions, not to read runtime data. An object ref carries its backing model, not a GraphQL query result: clients select fields, and resolvers can expose fields that are not properties of the backing model.

import SchemaBuilder from '@pothos/core';

const builder = new SchemaBuilder({});

const MyInput = builder.inputType('MyInput', {
  fields: (t) => ({
    id: t.id({ required: true }),
    name: t.string({ required: true }),
  }),
});

// { id: string; name: string; }
type MyInputShape = typeof MyInput.$inferInput;

const UserRef = builder.objectRef<{ id: string; name: string }>('User').implement({
  fields: (t) => ({
    id: t.exposeID('id'),
    name: t.exposeString('name'),
  }),
});
type UserType = typeof UserRef.$inferType;

When building helpers, most Pothos types have a generic called Types that extends SchemaTypes. This combines all the defaults and settings passed in when creating the SchemaBuilder. To make your own type helpers and utility functions, you often need access to the Types used by your builder.

This can be inferred from the builder using typeof builder.$inferSchemaTypes.

The following is a simple helper for creating objects that have an id field. The helper itself isn't that useful, but shows how inferring SchemaTypes from a builder can work.

import type { FieldMap } from '@pothos/core';

type BuilderTypes = typeof builder.$inferSchemaTypes;

function createObjectWithId<T extends { id: string }>(
  name: string,
  fields: (t: PothosSchemaTypes.ObjectFieldBuilder<BuilderTypes, T>) => FieldMap,
) {
  const ref = builder.objectRef<T>(name);

  ref.implement({
    fields: (t) => ({
      ...fields(t),
      id: t.id({
        resolve: (parent) => parent.id,
        nullable: false,
      }),
    }),
  });

  return ref;
}

const UserWithId = createObjectWithId<{
  id: string;
  name: string;
}>('UserWithId', (t) => ({
  name: t.exposeString('name'),
}));

Rather than explicitly using the inferred type, you can also infer SchemaTypes from the builder in an argument. In the following example, we pass in the builder to the createPaginationArgs, and infer the Types from the provided builder. This is useful when building helpers that might be used with multiple builder instances.

import type { SchemaTypes } from '@pothos/core';

function createPaginationArgs<Types extends SchemaTypes>(
  builder: PothosSchemaTypes.SchemaBuilder<Types>,
) {
  return builder.args((t) => ({
    limit: t.int(),
    offset: t.int(),
  }));
}

const users: UserType[] = [
  { id: '1', name: 'Ada' },
  { id: '2', name: 'Grace' },
];

function toUser(input: MyInputShape): UserType {
  return { id: input.id, name: input.name };
}

builder.queryType({
  fields: (t) => ({
    user: t.field({
      type: UserRef,
      args: { input: t.arg({ type: MyInput, required: true }) },
      resolve: (_parent, { input }) => toUser(input),
    }),
  }),
});

builder.queryField('getUsers', (t) =>
  t.field({
    type: [UserWithId],
    args: {
      ...createPaginationArgs(builder),
    },
    resolve: (_parent, { limit, offset }) =>
      users.slice(offset ?? 0, (offset ?? 0) + (limit ?? users.length)),
  }),
);

The example uses the inferred input shape in toUser and returns real users from getUsers. A limit of 1 returns one user; 2 returns both. If MyInput.name is defined with t.int({ required: true }) instead of t.string({ required: true }), the inferred input has a numeric name, so returning it from toUser produces a TypeScript error: UserType still requires a string. The GraphQL input field also changes from String! to Int!.

On this page

No Headings
Edit on GitHub