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

Pothos

Selections

Includes on types

In some cases, you may want to always pre-load certain relations. This can be helpful for defining fields directly on type where the underlying data may come from a related table.

builder.prismaObject('User', {
  // This will always include the profile when a user object is loaded.  Deeply nested relations can
  // also be included this way.
  include: {
    profile: true,
  },
  fields: (t) => ({
    id: t.exposeID('id'),
    email: t.exposeString('email'),
    bio: t.string({
      // The profile relation will always be loaded, and user will now be typed to include the
      // profile field so you can return the bio from the nested profile relation.
      resolve: (user) => user.profile.bio,
    }),
  }),
});

Select mode for types

By default, the prisma plugin will use include when including relations, or generating fallback queries. This means we are always loading all columns of a table when loading it in a t.prismaField or a t.relation. This is usually what we want, but in some cases, you may want to select specific columns instead. This can be useful if you have tables with either a very large number of columns, or specific columns with large payloads you want to avoid loading.

To do this, you can add a select instead of an include to your prismaObject:

builder.prismaObject('User', {
  select: {
    id: true,
  },
  fields: (t) => ({
    id: t.exposeID('id'),
    email: t.exposeString('email'),
  }),
});

The t.expose* and t.relation methods will all automatically add selections for the exposed fields WHEN THEY ARE QUERIED, ensuring that only the requested columns will be loaded from the database.

In addition to the t.expose and t.relation, you can also add custom selections to other fields:

builder.prismaObject('User', {
  select: {
    id: true,
  },
  fields: (t) => ({
    id: t.exposeID('id'),
    email: t.exposeString('email'),
    bio: t.string({
      // This will select user.profile.bio when the the `bio` field is queried
      select: {
        profile: {
          select: {
            bio: true,
          },
        },
      },
      resolve: (user) => user.profile.bio,
    }),
  }),
});

Using arguments or context in your selections

The following is a slightly contrived example, but shows how arguments can be used when creating a selection for a field:

const PostDraft = builder.prismaObject('Post', {
  fields: (t) => ({
    title: t.exposeString('title'),
    commentFromDate: t.string({
      args: {
        date: t.arg({ type: 'Date', required: true }),
      },
      select: (args) => ({
        comments: {
          take: 1,
          where: {
            createdAt: {
              gt: args.date,
            },
          },
        },
      }),
      resolve: (post) => post.comments[0]?.content,
    }),
  }),
});

Optimized queries without t.prismaField

In some cases, it may be useful to get an optimized query for fields where you can't use t.prismaField.

This may be required for combining with other plugins, or because your query does not directly return a PrismaObject. In these cases, you can use the queryFromInfo helper. An example of this might be a mutation that wraps the prisma object in a result type.

const Post = builder.prismaObject('Post', {...});
 
builder.objectRef<{
  success: boolean;
  post?: Post
  }>('CreatePostResult').implement({
  fields: (t) => ({
    success: t.boolean(),
    post: t.field({
      type: Post,
      nullable:
      resolve: (result) => result.post,
    }),
  }),
});
 
builder.mutationField(
  'createPost',
  {
    args: (t) => ({
      title: t.string({ required: true }),
      ...
    }),
  },
  {
    resolve: async (parent, args, context, info) => {
      if (!validateCreatePostArgs(args)) {
        return {
          success: false,
        }
      }
 
      const post = prisma.city.create({
        ...queryFromInfo({
          context,
          info,
          // nested path where the selections for this type can be found
          path: ['post']
          // optionally you can pass a custom initial selection, generally you wouldn't need this
          // but if the field at `path` is not selected, the initial selection set may be empty
          select: {
            comments: true,
          },
        }),
        data: {
          title: args.input.title,
          ...
        },
      });
 
      return {
        success: true,
        post,
      }
    },
  },
);

On this page