Skip to content

GraphQL Scalars — Custom Scalar Types Explained

DodaTech Updated 2026-06-28 6 min read

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
ā„¹ļø Info

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

  1. What three methods must a custom scalar implement?
  2. Why use custom scalars instead of String for emails?
  3. What does the serialize method do?
  4. What library provides commonly-used custom scalars?
  5. How do you register a custom scalar in Apollo Server resolvers?

Answers:

  1. serialize (value to client), parseValue (variable input to server), parseLiteral (inline AST to server).
  2. Custom scalars enforce validation at the API boundary. A String field accepts "not-an-email" silently. An EmailAddress scalar rejects it.
  3. serialize converts the server's internal representation to the format sent to the client. For dates, this converts a Date object to an ISO string.
  4. graphql-scalars provides DateTime, EmailAddress, URL, JSON, UUID, PhoneNumber, PostalCode, and many more.
  5. Add the scalar name as a key in the resolvers object, with the GraphQLScalarType instance 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

Can custom scalars return objects?

Yes — serialize can return any JSON-serializable value (objects, arrays, numbers). However, clients typically expect simple values. For complex data, use object types instead of scalars.

What happens if serialize throws an error?

The error is caught and added to the response errors array. The field returns null. If the field is non-null, null propagates to the parent.

Are custom scalars supported in all GraphQL clients?

Yes — clients see custom scalars as the serialized value (string, number, etc). They don't need special support beyond handling whatever type you serialize to.

Should I use graphql-scalars or write my own?

Start with graphql-scalars for common types (DateTime, Email, URL). Write custom scalars only for domain-specific types (FileSize, HexColor, IPAddress).

How do custom scalars work with TypeScript?

Define the scalar type in your codegen config. GraphQL Code Generator can produce TypeScript types that map custom scalars to specific TS types (Date, URL, etc).

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
⬅ GraphQL SDL Guide
āž” Enums in Depth

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro