Custom Scalars
GraphQL's five built-in scalars cover only the most basic value types, so any domain-specific leaf value — a date, an email address, a JSON blob, a URL, geographic coordinates — is typically modeled as a custom scalar, declared in SDL with scalar DateTime. A custom scalar isn't defined by structure like an object type; instead, the server implementation supplies serialize, parseValue, and parseLiteral functions that convert between the internal representation (say, a JavaScript Date object) and the wire format the client actually sends and receives (typically an ISO-8601 string).
Cricket analogy: A match's 'DRS review time remaining' isn't a plain Int or String — it needs its own formatting and parsing rules, just as a custom scalar like Duration needs its own serialize/parse logic beyond the five GraphQL built-ins.
Implementing Serialize, ParseValue, and ParseLiteral
The three functions a custom scalar needs each handle a different data direction: serialize runs when sending a value from server to client (converting the internal representation to something JSON-safe, like turning a Date into an ISO string), parseValue runs when the client sends the scalar as a query variable (JSON input), and parseLiteral runs when the client writes the value directly inline in the query string (an AST literal), which matters because GraphQL's AST literal parsing is stricter and type-specific — a DateTime! literal typed directly in a query arrives as a StringValueNode, not a parsed JSON value, so parseLiteral has to inspect the AST node kind itself.
Cricket analogy: Converting a raw ball-by-ball feed into a scorecard display (serialize) is different work from parsing a manually-entered over count from a scorer's tablet (parseValue), just as GraphQL's serialize and parseValue handle output and input directions separately for a custom scalar.
scalar DateTime
type Match {
id: ID!
scheduledAt: DateTime!
}const { GraphQLScalarType, Kind } = require('graphql');
const DateTimeScalar = new GraphQLScalarType({
name: 'DateTime',
description: 'ISO-8601 encoded UTC date-time string',
serialize(value) {
return value instanceof Date ? value.toISOString() : value;
},
parseValue(value) {
return new Date(value); // value from JSON query variables
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return new Date(ast.value); // value from inline query literal
}
return null;
},
});Popular libraries like graphql-scalars already ship well-tested implementations for common needs — DateTime, EmailAddress, URL, JSON, PositiveInt, and dozens more — so it's usually better to reuse a maintained scalar than hand-roll parsing edge cases like timezone offsets or Unicode email validation.
A custom scalar with no validation logic in parseValue/parseLiteral (e.g. one that just passes the raw value through) defeats the purpose — it becomes indistinguishable from String in practice and gives clients false confidence that the server is enforcing a format it actually isn't.
- Custom scalars model domain-specific leaf values that don't fit the five GraphQL built-ins.
- A custom scalar is defined by behavior (serialize/parseValue/parseLiteral), not by structural fields.
serializehandles server-to-client output;parseValueandparseLiteralhandle client-to-server input.parseLiteralmust inspect the AST node kind because inline query literals aren't parsed as JSON.- Reusing a maintained library (like graphql-scalars) is usually safer than writing scalar logic from scratch.
- A custom scalar without real validation logic provides no more guarantee than a plain String.
Practice what you learned
1. Why would an API define a custom scalar like `DateTime` instead of using the built-in `String`?
2. Which function handles converting a server-side value into the format sent to the client?
3. Why does a custom scalar need a separate `parseLiteral` function in addition to `parseValue`?
4. What is the risk of implementing a custom scalar that just passes values through without validation?
5. What is a practical reason to use a library like graphql-scalars instead of writing custom scalar logic from scratch?
Was this page helpful?
You May Also Like
The Type System In Depth
A deep dive into how GraphQL's schema — scalars, object types, input types, and list/non-null modifiers — forms the typed contract between client and server.
Nullable vs Non-Nullable Types
Why fields are nullable by default in GraphQL, what the `!` modifier guarantees, and how null bubbling propagates a single resolver failure up the response tree.
Enums in GraphQL
How GraphQL enums restrict fields and arguments to a fixed, validated set of named values, and how their SDL names map to internal server representations.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics