Pothos
Guide

Scalars

Pothos includes GraphQL's String, Boolean, Int, Float, and ID scalars. Use their names as field and argument types, or the shorthand builders shown in Fields.

A custom scalar needs both a TypeScript declaration in SchemaTypes and a runtime implementation. The declaration describes the values your resolvers work with:

  • Input is the value a resolver receives after GraphQL parses an argument or input field.
  • Output is the value a resolver returns, before GraphQL serializes it for the response.

These are application types, not necessarily the JSON types sent by clients. A date scalar can receive a string from a client, parse it into a Date, and serialize a resolver's Date back to a string. In that case both Input and Output are Date.

Defining a scalar

This scalar accepts positive integers and checks both input values and resolver results:

import SchemaBuilder from '@pothos/core';
import { GraphQLError, Kind } from 'graphql';

const builder = new SchemaBuilder<{
  Scalars: {
    PositiveInt: { Input: number; Output: number };
  };
}>({});

function positiveInt(value: unknown): number {
  if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) {
    throw new GraphQLError('PositiveInt must be a positive safe integer');
  }

  return value;
}

builder.scalarType('PositiveInt', {
  serialize: positiveInt,
  parseValue: positiveInt,
  parseLiteral: (node) => {
    if (node.kind !== Kind.INT) {
      throw new GraphQLError('PositiveInt must be an integer literal');
    }

    return positiveInt(Number(node.value));
  },
});

builder.queryType({
  fields: (t) => ({
    double: t.field({
      type: 'PositiveInt',
      args: { value: t.arg({ type: 'PositiveInt', required: true }) },
      resolve: (_parent, { value }) => value * 2,
    }),
  }),
});

export const schema = builder.toSchema();

parseValue handles variables, parseLiteral handles inline GraphQL literals, and serialize handles output. Validate each path: TypeScript declarations alone do not validate values at runtime. This example explicitly rejects string and float literals, even when they represent whole numbers.

{
  double(value: 3)
}
{
  "data": {
    "double": 6
  }
}

Passing 0, -1, 1.5, or "3" is an error. An output outside the scalar's accepted range is also an error; GraphQL applies the field's nullability rules when reporting it.

Adding an existing scalar

Use builder.addScalarType to register an existing graphql-js GraphQLScalarType. Declare its application types in the builder first. For example, with graphql-scalars installed:

import SchemaBuilder from '@pothos/core';
import { DateResolver, JSONResolver } from 'graphql-scalars';

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

builder.addScalarType('JSON', JSONResolver);
builder.addScalarType('Date', DateResolver);

builder.queryType({
  fields: (t) => ({
    date: t.field({ type: 'Date', resolve: () => new Date('2026-01-01T00:00:00Z') }),
  }),
});

export const schema = builder.toSchema();

The same method accepts your own GraphQLScalarType instance. Check an implementation's parsing and serialization contracts when choosing its Input and Output types; Pothos does not infer those types from the scalar instance. The two types can differ, and an implementation may accept more output types than your application needs to return.

For a general JSON scalar, unknown lets resolvers check an input's structure before using it. The global TypeScript JSON type describes the JSON.parse and JSON.stringify API, not JSON data.

Customizing built-in scalar types

You can override the TypeScript types of built-in scalars through Scalars too. This changes resolver checking; it does not replace GraphQL's parsing or serialization behavior. For example, to restrict your application to string IDs:

const builder = new SchemaBuilder<{
  Scalars: {
    ID: { Input: string; Output: string };
  };
}>({});

GraphQL still accepts integer ID inputs and parses them to strings. Use scalarType or addScalarType if you also need to replace a scalar's runtime implementation.

GraphQL 17 coercion hooks

Starting with Pothos 4.13, scalarType also accepts GraphQL 17's coercion hooks:

HookPurpose
coerceOutputValueSerialize a resolver result.
coerceInputValueParse an external input value.
coerceInputLiteralParse a constant AST value, with variables already substituted.
valueToLiteralConvert an external value to a constant AST value.

These hooks are used on GraphQL 17. GraphQL 16 ignores them, so keep serialize, parseValue, and parseLiteral for implementations that also run on GraphQL 16. The earlier example works on both versions.

On GraphQL 17, you can provide coerceOutputValue instead of serialize. If both are present, GraphQL uses serialize. A coerceInputLiteral implementation must be paired with coerceInputValue; otherwise schema construction throws. addScalarType preserves these hooks when registering an existing scalar that provides them.

On this page