GraphQL Custom Scalar Types — Extending Primitives for Domain-Specific Values
In this tutorial, you will learn about Graphql Custom Scalar Types. We cover key concepts, practical examples, and best practices to help you master this topic.
GraphQL custom scalars extend the built-in types (String, Int, Float, Boolean, ID) to represent domain-specific values like dates, URLs, email addresses, and JSON objects with custom validation.
What You'll Learn
- Defining custom scalar types
- Implementing parseValue, serialize, and parseLiteral
- Using community scalar libraries
Why It Matters
Built-in scalars lack validation and semantic meaning. Custom scalars enforce data integrity and make schemas self-documenting.
Code Examples
# Custom scalar definitions
scalar DateTime
scalar URL
scalar Email
scalar JSON
scalar PositiveInt
scalar PhoneNumber
type User {
id: ID!
email: Email!
website: URL
createdAt: DateTime!
metadata: JSON
age: PositiveInt
phone: PhoneNumber
}
// Custom scalar implementation
const { GraphQLScalarType, Kind } = require('graphql');
const DateTimeScalar = new GraphQLScalarType({
name: 'DateTime',
description: 'ISO 8601 date-time string',
serialize(value) {
return value instanceof Date ? value.toISOString() : value;
},
parseValue(value) {
const date = new Date(value);
if (isNaN(date.getTime())) {
throw new TypeError('Invalid date');
}
return date;
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
const date = new Date(ast.value);
if (isNaN(date.getTime())) {
throw new TypeError('Invalid date');
}
return date;
}
throw new TypeError('DateTime must be a string');
}
});
const EmailScalar = new GraphQLScalarType({
name: 'Email',
description: 'Valid email address',
serialize(value) { return value; },
parseValue(value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
throw new TypeError('Invalid email address');
}
return value;
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(ast.value)) {
throw new TypeError('Invalid email address');
}
return ast.value;
}
throw new TypeError('Email must be a string');
}
});
// Using graphql-scalars library
const { DateTimeResolver, URLResolver, JSONResolver } = require('graphql-scalars');
const typeDefs = `
scalar DateTime
scalar URL
scalar JSON
`;
const resolvers = {
DateTime: DateTimeResolver,
URL: URLResolver,
JSON: JSONResolver
};
Common Mistakes
1. Reimplementing Standard Scalars
Use community libraries for common scalars like DateTime and URL.
2. Not Validating Input in parseValue
Custom scalars should validate both input and output to catch errors early.
3. Using String Instead of Custom Scalars
Semantic types improve API documentation and client code clarity.
4. Ignoring parseLiteral for AST Values
Variables use parseValue, but inline literals use parseLiteral. Implement both.
5. Creating Too Many Custom Scalars
Only create custom scalars for values with specific validation or formatting needs.
Practice Questions
- How do you define a custom scalar in SDL?
- What three methods must a custom scalar implement?
- Why use custom scalars instead of String?
- What is the difference between parseValue and parseLiteral?
- What library provides common custom scalars?
Answers:
- Use the scalar keyword: scalar DateTime.
- serialize, parseValue, and parseLiteral.
- For validation, semantic meaning, and self-documenting schemas.
- parseValue handles variable input; parseLiteral handles inline literal values in queries.
- graphql-scalars provides DateTime, URL, JSON, Email, and many more.
Challenge: Create custom scalars for a financial application: Currency (validated format), Percentage (0-100 range), UUID, and UnixTimestamp. Implement all three methods and add comprehensive validation.
FAQ
Mini Project
Build a library of custom scalars for a healthcare application: DateOfBirth (validates age > 0), BloodPressure (formatted string), SSN (masked), and ZipCode (validated format). Implement all three methods and write tests for validation.
What's Next
Explore enum types for fixed value sets, then learn about interface types for shared field definitions.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro