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:
Inputis the value a resolver receives after GraphQL parses an argument or input field.Outputis 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:
| Hook | Purpose |
|---|---|
coerceOutputValue | Serialize a resolver result. |
coerceInputValue | Parse an external input value. |
coerceInputLiteral | Parse a constant AST value, with variables already substituted. |
valueToLiteral | Convert 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.