GraphQL Scalars ā Custom Scalar Types Explained
In this tutorial, you will learn about GraphQL Scalars. We cover key concepts, practical examples, and best practices to help you master this topic.
Custom scalars extend GraphQL's type system beyond the five built-in scalars, adding validation and serialization logic for domain-specific data like dates, emails, and URLs.
What You'll Learn
You will learn how to define custom scalars in SDL, implement the serialize/parseValue/parseLiteral methods, validate input at the API boundary, and use community scalar libraries.
Why Custom Scalars Matter
Without custom scalars, every date, email, and URL is just a String. This forces clients to validate formats themselves and leads to inconsistent data. Custom scalars push validation to the API boundary ā every DateTime value is guaranteed to be ISO 8601, every EmailAddress is a valid email. DodaTech's Durga Antivirus Pro uses custom scalars for DateTime, UUID, HexColor (threat severity indicators), and FileSize ā ensuring consistency across all microservices.
flowchart LR
A["SDL: scalar DateTime"] --> B["GraphQLScalarType\n(name, description)"]
B --> C["serialize(value)\nā output to client"]
B --> D["parseValue(value)\nā input from variables"]
B --> E["parseLiteral(ast)\nā input from query text"]
C --> F["Validation:\nISO 8601 format"]
D --> F
E --> F
style B fill:#dbeafe,stroke:#2563eb
style C fill:#fef3c7,stroke:#d97706
style D fill:#fef3c7,stroke:#d97706
style E fill:#fef3c7,stroke:#d97706
Prerequisites: GraphQL SDL syntax. JavaScript/Node.js for implementation examples.
Built-in Scalars Overview
| Scalar | Description | Example |
|---|---|---|
String |
UTF-8 text | "Hello" |
Int |
32-bit signed integer | 42 |
Float |
Double-precision number | 3.14 |
Boolean |
true or false | true |
ID |
Unique identifier (serialized as string) | "dev-001" |
Defining a Custom Scalar in SDL
scalar DateTime
scalar EmailAddress
scalar URL
scalar JSON
scalar UUID
scalar NonNegativeInt
type Device {
id: UUID!
name: String!
os: String!
lastScan: DateTime
ownerEmail: EmailAddress!
website: URL
metadata: JSON
scanCount: NonNegativeInt!
}
Implementing a DateTime Scalar
const { GraphQLScalarType, Kind } = require('graphql');
const dateTimeScalar = new GraphQLScalarType({
name: 'DateTime',
description: 'ISO 8601 formatted date-time string (e.g., "2026-06-28T12:00:00Z")',
serialize(value) {
// Convert Date object to ISO string for the client
if (value instanceof Date) {
return value.toISOString();
}
// If already a string, validate it
if (typeof value === 'string') {
const parsed = new Date(value);
if (isNaN(parsed.getTime())) {
throw new TypeError('Invalid DateTime string');
}
return parsed.toISOString();
}
return value;
},
parseValue(value) {
// Parse from GraphQL variables
const date = new Date(value);
if (isNaN(date.getTime())) {
throw new TypeError('Invalid DateTime value');
}
return date;
},
parseLiteral(ast) {
// Parse from inline query literals
if (ast.kind === Kind.STRING) {
const date = new Date(ast.value);
if (isNaN(date.getTime())) {
throw new TypeError('Invalid DateTime literal');
}
return date;
}
return null;
},
});
Using Custom Scalars in Apollo Server
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
scalar DateTime
type Threat {
id: ID!
name: String!
detectedAt: DateTime!
resolvedAt: DateTime
}
type Query {
threats(since: DateTime!): [Threat!]!
}
`;
const resolvers = {
DateTime: dateTimeScalar,
Query: {
threats: (_, { since }) => {
// since is already a Date object due to parseValue
return threats.filter(t => t.detectedAt >= since);
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(4000);
# Query passing DateTime variable
query GetRecentThreats($since: DateTime!) {
threats(since: $since) {
id
name
detectedAt
}
}
# Variables
{
"since": "2026-06-27T00:00:00Z"
}
# Expected response
{
"data": {
"threats": [
{ "id": "thr-001", "name": "Emotet", "detectedAt": "2026-06-27T14:30:00Z" }
]
}
}
Using the graphql-scalars Library
const { ApolloServer, gql } = require('apollo-server');
const { DateTimeResolver, EmailAddressResolver, URLResolver, JSONResolver } = require('graphql-scalars');
const typeDefs = gql`
scalar DateTime
scalar EmailAddress
scalar URL
scalar JSON
scalar UUID
type User {
id: UUID!
email: EmailAddress!
website: URL
metadata: JSON
createdAt: DateTime!
}
`;
const resolvers = {
DateTime: DateTimeResolver,
EmailAddress: EmailAddressResolver,
URL: URLResolver,
JSON: JSONResolver,
UUID: DateTimeResolver, // UUIDResolver from graphql-scalars
};
Common Mistakes
1. Not Throwing Errors in parseLiteral/parseValue
If validation fails, throw a TypeError or GraphQLError. Silently returning null changes data without warning.
2. Using String Instead of Custom Scalar for Dates
String accepts any text ā "not-a-date" passes validation. A DateTime scalar rejects invalid dates at the API boundary.
3. Forgetting parseLiteral for Inline Arguments
If you only implement parseValue, inline query arguments (since: "2026-06-27") fail. Always implement all three methods.
4. Not Matching Server and Client Serialization
If your server serializes dates as timestamps but the client expects ISO strings, there is a mismatch. Agree on one format and document it.
5. Ignoring Custom Scalar Performance
Complex validation (like URL Parsing) runs on every request. Benchmark your scalar implementation for production use.
Practice Questions
- What three methods must a custom scalar implement?
- Why use custom scalars instead of String for emails?
- What does the
serializemethod do? - What library provides commonly-used custom scalars?
- How do you register a custom scalar in Apollo Server resolvers?
Answers:
serialize(value to client),parseValue(variable input to server),parseLiteral(inline AST to server).- Custom scalars enforce validation at the API boundary. A String field accepts "not-an-email" silently. An EmailAddress scalar rejects it.
serializeconverts the server's internal representation to the format sent to the client. For dates, this converts a Date object to an ISO string.graphql-scalarsprovides DateTime, EmailAddress, URL, JSON, UUID, PhoneNumber, PostalCode, and many more.- Add the scalar name as a key in the resolvers object, with the
GraphQLScalarTypeinstance as the value.
Challenge: Implement a custom FileSize scalar that accepts human-readable sizes ("10MB", "1.5GB") and validates them, then serialize as bytes (number). Use it in a schema for file upload limits.
FAQ
Mini Project
Create a GraphQL schema for Durga Antivirus Pro that uses at least 5 custom scalars from graphql-scalars. Include types for Scan (UUID, DateTime, FileSize), User (EmailAddress, URL, PhoneNumber), and Threat (JSON for metadata). Implement resolvers and test with validation.
What's Next
| Topic | Description |
|---|---|
| Enums in Depth | Fixed value sets for type safety |
| Schema Definition Language | SDL syntax and type definitions |
| Types & Schema Design | Object types and relationships |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro