Docs
Launch GraphOS Studio

Custom scalars


The specification includes default types Int, Float, String, Boolean, and ID. Although these cover the majority of use cases, some applications need to support other atomic data types (such as Date) or add validation to an existing type. To enable this, you can define custom types.

Defining a custom scalar

To define a custom , add it to your schema like so:

scalar MyCustomScalar

You can now use MyCustomScalar in your schema anywhere you can use a default (e.g., as the type of an object , input type field, or ).

However, still needs to know how to interact with values of this new type.

Defining custom scalar logic

After you define a custom type, you need to define how interacts with it. In particular, you need to define:

  • How the 's value is represented in your backend
    • This is often the representation used by the driver for your backing data store.
  • How the value's back-end representation is serialized to a JSON-compatible type
  • How the JSON-compatible representation is deserialized to the back-end representation

You define these interactions in an instance of the GraphQLScalarType class.

For more information about the graphql library's , see the official documentation.

Example: The Date scalar

NOTE

The code blocks below use TypeScript by default. You can use the dropdown menu above each code block to switch to JavaScript.

If you're using JavaScript, use .js and .jsx file wherever .ts and .tsx appear.

The following GraphQLScalarType object defines interactions for a custom that represents a date (this is one of the most commonly implemented custom scalars). It assumes that our backend represents a date with the Date JavaScript object.

import { GraphQLScalarType, Kind } from 'graphql';
const dateScalar = new GraphQLScalarType({
name: 'Date',
description: 'Date custom scalar type',
serialize(value) {
if (value instanceof Date) {
return value.getTime(); // Convert outgoing Date to integer for JSON
}
throw Error('GraphQL Date Scalar serializer expected a `Date` object');
},
parseValue(value) {
if (typeof value === 'number') {
return new Date(value); // Convert incoming integer to Date
}
throw new Error('GraphQL Date Scalar parser expected a `number`');
},
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
// Convert hard-coded AST string to integer and then to Date
return new Date(parseInt(ast.value, 10));
}
// Invalid hard-coded value (not an integer)
return null;
},
});
import { GraphQLScalarType, Kind } from 'graphql';
const dateScalar = new GraphQLScalarType({
name: 'Date',
description: 'Date custom scalar type',
serialize(value) {
if (value instanceof Date) {
return value.getTime(); // Convert outgoing Date to integer for JSON
}
throw Error('GraphQL Date Scalar serializer expected a `Date` object');
},
parseValue(value) {
if (typeof value === 'number') {
return new Date(value); // Convert incoming integer to Date
}
throw new Error('GraphQL Date Scalar parser expected a `number`');
},
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
// Convert hard-coded AST string to integer and then to Date
return new Date(parseInt(ast.value, 10));
}
// Invalid hard-coded value (not an integer)
return null;
},
});

This initialization defines the following methods:

  • serialize
  • parseValue
  • parseLiteral

Together, these methods describe how interacts with the in every scenario.

serialize

The serialize method converts the 's back-end representation to a JSON-compatible format so can include it in an response.

In the example above, the Date is represented on the backend by the Date JavaScript object. When we send a Date in a response, we serialize it as the integer value returned by the getTime function of a JavaScript Date object.

Note that cannot automatically interpret custom (see issue), so your client must define custom logic to deserialize this value as needed.

parseValue

The parseValue method converts the 's JSON value to its back-end representation before it's added to a 's args.

calls this method when the is provided by a client as a GraphQL variable for an . (When a is provided as a hard-coded argument in the string, parseLiteral is called instead.)

parseLiteral

When an incoming string includes the as a hard-coded value, that value is part of the query 's abstract syntax tree (AST). calls the parseLiteral method to convert the value's AST representation to the 's back-end representation.

In the example above, parseLiteral converts the AST value from a string to an integer, and then converts from integer to Date to match the result of parseValue.

Providing custom scalars to Apollo Server

NOTE

In the examples below, we use top-level await calls to start our server asynchronously. If you'd like to see how we set this up, check out the Getting Started guide for details.

After you define your GraphQLScalarType instance, you include it in the same resolver map that contains for your schema's other types and :

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { GraphQLScalarType, Kind } from 'graphql';
const typeDefs = `#graphql
/* highlight-line */ scalar Date
type Event {
id: ID!
date: Date!
}
type Query {
events: [Event!]
}
`;
const dateScalar = new GraphQLScalarType({
// See definition above
});
const resolvers = {
Date: dateScalar,
// ...other resolver definitions...
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server listening at: ${url}`);
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { GraphQLScalarType } from 'graphql';
const typeDefs = `#graphql
/* highlight-line */ scalar Date
type Event {
id: ID!
date: Date!
}
type Query {
events: [Event!]
}
`;
const dateScalar = new GraphQLScalarType({
// See definition above
});
const resolvers = {
Date: dateScalar,
// ...other resolver definitions...
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server listening at: ${url}`);

Example: Restricting integers to odd values

In this example, we create a custom called Odd that can only contain odd integers:

index.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { GraphQLScalarType, Kind, GraphQLError } from 'graphql';
// Basic schema
const typeDefs = `#graphql
scalar Odd
type Query {
# Echoes the provided odd integer
echoOdd(odd: Odd!): Odd!
}
`;
// Validation function for checking "oddness"
function oddValue(value: number) {
if (typeof value === 'number' && Number.isInteger(value) && value % 2 !== 0) {
return value;
}
throw new GraphQLError('Provided value is not an odd integer', {
extensions: { code: 'BAD_USER_INPUT' },
});
}
const resolvers = {
Odd: new GraphQLScalarType({
name: 'Odd',
description: 'Odd custom scalar type',
parseValue: oddValue,
serialize: oddValue,
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
return oddValue(parseInt(ast.value, 10));
}
throw new GraphQLError('Provided value is not an odd integer', {
extensions: { code: 'BAD_USER_INPUT' },
});
},
}),
Query: {
echoOdd(_, { odd }) {
return odd;
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server listening at: ${url}`);
index.js
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { GraphQLScalarType, Kind, GraphQLError } from 'graphql';
// Basic schema
const typeDefs = `#graphql
scalar Odd
type Query {
# Echoes the provided odd integer
echoOdd(odd: Odd!): Odd!
}
`;
// Validation function for checking "oddness"
function oddValue(value) {
if (typeof value === 'number' && Number.isInteger(value) && value % 2 !== 0) {
return value;
}
throw new GraphQLError('Provided value is not an odd integer', {
extensions: { code: 'BAD_USER_INPUT' },
});
}
const resolvers = {
Odd: new GraphQLScalarType({
name: 'Odd',
description: 'Odd custom scalar type',
parseValue: oddValue,
serialize: oddValue,
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
return oddValue(parseInt(ast.value, 10));
}
throw new GraphQLError('Provided value is not an odd integer', {
extensions: { code: 'BAD_USER_INPUT' },
});
},
}),
Query: {
echoOdd(_, { odd }) {
return odd;
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server listening at: ${url}`);

Importing a third-party custom scalar

If another library defines a custom , you can import it and use it just like any other symbol.

For example, the graphql-type-json package defines the GraphQLJSON object, which is an instance of GraphQLScalarType. You can use this object to define a JSON that accepts any value that is valid JSON.

First, install the library:

$ npm install graphql-type-json

Then import the GraphQLJSON object and add it to the map as usual:

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import GraphQLJSON from 'graphql-type-json';
const typeDefs = `#graphql
scalar JSON
type MyObject {
myField: JSON
}
type Query {
objects: [MyObject]
}
`;
const resolvers = {
JSON: GraphQLJSON,
// ...other resolvers...
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server listening at: ${url}`);
Previous
Unions and interfaces
Next
Directives
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company