---
title: Best Practices for Custom Scalars
---

# Custom Scalars: Best Practices and Testing

Custom scalars must behave predictably and clearly. To maintain a consistent, reliable 
schema, follow these best practices.

### Document expected formats and validation

Provide a clear description of the scalar's accepted input and output formats. For example, a 
`DateTime` scalar should explain that it expects [ISO-8601](https://www.iso.org/iso-8601-date-and-time-format.html) strings ending with `Z`.

Clear descriptions help clients understand valid input and reduce mistakes.

### Validate consistently across `parseValue` and `parseLiteral`

Clients can send values either through variables or inline literals.
Your `parseValue` and `parseLiteral` functions should apply the same validation logic in 
both cases.

Use a shared helper to avoid duplication:

```js
function parseDate(value) {
  const date = new Date(value);
  if (isNaN(date.getTime())) {
    throw new TypeError(`DateTime cannot represent an invalid date: ${value}`);
  }
  return date;
}
```

Both `parseValue` and `parseLiteral` should call this function.

### Return clear errors

When validation fails, throw descriptive errors. Avoid generic messages like "Invalid input."
Instead, use targeted messages that explain the problem, such as:

```text
DateTime cannot represent an invalid date: `abc123`
```

Clear error messages speed up debugging and make mistakes easier to fix.

### Serialize consistently

Always serialize internal values into a predictable format.
For example, a `DateTime` scalar should always produce an ISO string, even if its 
internal value is a `Date` object.

```js
serialize(value) {
  if (!(value instanceof Date)) {
    throw new TypeError('DateTime can only serialize Date instances');
  }
  return value.toISOString();
}
```

Serialization consistency prevents surprises on the client side.

## Testing custom scalars

Testing ensures your custom scalars work reliably with both valid and invalid inputs.
Tests should cover three areas: coercion functions, schema integration, and error handling.

### Unit test serialization and parsing

Write unit tests for each function: `serialize`, `parseValue`, and `parseLiteral`.
Test with both valid and invalid inputs.

```js
describe('DateTime scalar', () => {
  it('serializes Date instances to ISO strings', () => {
    const date = new Date('2024-01-01T00:00:00Z');
    expect(DateTime.serialize(date)).toBe('2024-01-01T00:00:00.000Z');
  });

  it('throws if serializing a non-Date value', () => {
    expect(() => DateTime.serialize('not a date')).toThrow(TypeError);
  });

  it('parses ISO strings into Date instances', () => {
    const result = DateTime.parseValue('2024-01-01T00:00:00Z');
    expect(result).toBeInstanceOf(Date);
    expect(result.toISOString()).toBe('2024-01-01T00:00:00.000Z');
  });

  it('throws if parsing an invalid date string', () => {
    expect(() => DateTime.parseValue('invalid-date')).toThrow(TypeError);
  });
});
```

### Test custom scalars in a schema

Integrate the scalar into a schema and run real GraphQL queries to validate end-to-end behavior.

```js
import { graphql, GraphQLSchema, GraphQLObjectType } from 'graphql';
import { DateTimeResolver as DateTime } from 'graphql-scalars';

const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    now: {
      type: DateTime,
      resolve() {
        return new Date();
      },
    },
  },
});

/*
  scalar DateTime

  type Query {
    now: DateTime
  }
*/
const schema = new GraphQLSchema({
  query: Query,
});

async function testQuery() {
  const response = await graphql({
    schema,
    source: '{ now }',
  });
  console.log(response);
}

testQuery();
```

Schema-level tests verify that the scalar behaves correctly during execution, not just 
in isolation.

## Common use cases for custom scalars

Custom scalars solve real-world needs by handling types that built-in scalars don't cover.

- `DateTime`: Serializes and parses ISO-8601 date-time strings.
- `Email`: Validates syntactically correct email addresses.

```js
function validateEmail(value) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(value)) {
    throw new TypeError(`Email cannot represent invalid email address: ${value}`);
  }
  return value;
}
```

- `URL`: Ensures well-formatted, absolute URLs.

```js
function validateURL(value) {
  try {
    new URL(value);
    return value;
  } catch {
    throw new TypeError(`URL cannot represent an invalid URL: ${value}`);
  }
}
```

- `JSON`: Represents arbitrary JSON structures, but use carefully because it bypasses 
GraphQL's strict type checking.

## When to use existing libraries

Writing scalars is deceptively tricky. Validation edge cases can lead to subtle bugs if 
not handled carefully.

Whenever possible, use trusted libraries like [`graphql-scalars`](https://www.npmjs.com/package/graphql-scalars). They offer production-ready 
scalars for DateTime, EmailAddress, URL, UUID, and many others.

### Example: Handling email validation

Handling email validation correctly requires dealing with Unicode, quoted local parts, and 
domain validation. Rather than writing your own regex, it's better to use a library scalar 
that's already validated against standards.

If you need domain-specific behavior, you can wrap an existing scalar with custom rules:

```js
import { EmailAddressResolver } from 'graphql-scalars';

const StrictEmailAddress = new GraphQLScalarType({
  ...EmailAddressResolver,
  name: 'StrictEmailAddress',
  parseValue(value) {
    const email = EmailAddressResolver.parseValue(value);
    if (!email.endsWith('@example.com')) {
      throw new TypeError('Only example.com emails are allowed.');
    }
    return email;
  },
  parseLiteral(literal, variables) {
    const email = EmailAddressResolver.parseLiteral(literal, variables);
    if (!email.endsWith('@example.com')) {
      throw new TypeError('Only example.com emails are allowed.');
    }
    return email;
  },
});
```

By following these best practices and using trusted tools where needed, you can build custom 
scalars that are reliable, maintainable, and easy for clients to work with.

## Additional resources

- [GraphQL Scalars by The Guild](https://the-guild.dev/graphql/scalars): A production-ready 
library of common custom scalars.
- [GraphQL Scalars Specification](https://github.com/graphql/graphql-scalars): This 
specification is no longer actively maintained, but useful for historical context.