Skip to content

Scalars and Enums

Every query, no matter how deep it traverses, eventually arrives at a leaf — a single concrete value. Those leaves are scalars and enums. They are the smallest pieces of your schema, but choosing them carefully is what makes a field self-documenting and hard to misuse. This lesson covers the built-in scalars, custom scalars, and the enum — your sharpest tool for a fixed set of choices.

GraphQL ships with five scalar types. Every schema can use them without declaring anything:

  • Int — a signed 32-bit integer. Good for counts, quantities, and small whole numbers.
  • Float — a signed double-precision floating-point number. Good for prices, ratings, measurements.
  • String — UTF-8 text.
  • Booleantrue or false.
  • ID — a unique identifier. It serializes as a string but is semantically opaque: it is meant to be used as a key, not parsed or computed on.

The distinction between ID and String matters even though both travel as text. Declaring a field as ID tells every reader “this is an identifier, treat it as a token,” which is a different promise from “this is human-readable text.”

type Product {
id: ID!
name: String!
priceUSD: Float!
stock: Int!
inStock: Boolean!
}

The built-in five do not cover everything. There is no built-in date type, no email type, no URL type. When a value has its own format and validation rules, you can declare a custom scalar:

scalar DateTime
type Product {
id: ID!
name: String!
releasedAt: DateTime!
}

Declaring scalar DateTime adds the name to your schema; the server then attaches logic that serializes and validates the value (commonly an ISO-8601 string such as 2026-06-25T10:00:00Z). Custom scalars centralize a format in one place: every DateTime field is parsed and validated the same way, so clients always know exactly what shape to expect. Popular custom scalars include DateTime, EmailAddress, URL, and JSON — reach for one whenever a String would force every client to reinvent the same parsing rules.

An enum is a type whose value must be one of a fixed, named set. When a field can only ever be one of a handful of known options, an enum captures that in the type system:

enum OrderStatus {
PENDING
PAID
SHIPPED
DELIVERED
CANCELLED
}
type Order {
id: ID!
status: OrderStatus!
}

The value of status is guaranteed to be one of those five names — the server will reject anything else before a resolver runs. Enum values are conventionally written in SCREAMING_SNAKE_CASE, and they are not strings: PAID is a symbol the schema knows about, not the text "PAID".

flowchart TD
  Field["Order.status : OrderStatus"]
  Enum["enum OrderStatus"]
  V1["PENDING"]
  V2["PAID"]
  V3["SHIPPED"]
  V4["DELIVERED"]
  V5["CANCELLED"]
  Field --> Enum
  Enum --> V1
  Enum --> V2
  Enum --> V3
  Enum --> V4
  Enum --> V5
An enum constrains a field to one of a fixed set of named values.

This is one of the most common design decisions you will face. Use an enum when the set of valid values is known, small, and meaningful to your domain — statuses, roles, sizes, sort directions. The payoff is real:

  • Validation is free — invalid values are rejected by the type system, not your resolver.
  • The contract documents itself — a client sees every legal value in the schema.
  • Tooling autocompletes the options, and code generators produce a real enum type.

Use a plain String when the set is open-ended or user-supplied — names, search terms, free-form notes, anything you do not control. A useful rule of thumb: if you could write down every valid value today and the list rarely changes, it is an enum; if the values come from users or grow unpredictably, it is a string.

The example below defines the OrderStatus enum and an Order type, then queries an order and reads its status back as an enum value. Press Run.

JavaScript

The status field returned SHIPPED. Had the resolver produced a value outside the enum — say RETURNED — GraphQL would have raised an error instead of silently passing bad data to the client. That is the guarantee an enum buys you.

Which built-in scalar is meant for opaque identifiers rather than human-readable text?
Why might you define a custom scalar such as DateTime?
When is an enum a better choice than a String field?
What happens if a resolver returns a value that is not one of an enum’s declared members?