Skip to content

The Type System

We have called the schema “the contract” several times. Now we open it up. The schema is built from a small set of type kinds, written in the Schema Definition Language (SDL). Learn these building blocks and you can read — and design — any GraphQL API.

The most common kind of type is an object type: a named collection of fields. Each field has a name and a type, and may take arguments. Here is an object type describing a track in a music library:

type Track {
id: ID!
title: String!
durationSeconds: Int!
explicit: Boolean
artist: Artist!
}

Track has five fields. Each field’s type tells the client what it will get back: an ID, some String and Int values, a Boolean, and a link to another object type, Artist. That last field is what turns a flat record into a graph — fields can point at other types, and clients can traverse them.

Every selection eventually bottoms out at a scalar: a single, indivisible value. GraphQL ships with five built-in scalars:

  • Int — a signed 32-bit integer.
  • Float — a signed double-precision floating-point value.
  • String — UTF-8 text.
  • Booleantrue or false.
  • ID — a unique identifier, serialized as a string but semantically opaque (you are not meant to do math on it).

You can also define custom scalars (for example DateTime or EmailAddress) to attach validation and serialization rules, but the five above are the foundation.

Three object types are special because they are the entry points into the graph. A query has to start somewhere, and these names are where it starts:

  • Query — the read entry point. Every read begins by selecting a field here.
  • Mutation — the write entry point. Fields here create, update, or delete data.
  • Subscription — the real-time entry point. Fields here stream updates over time.

Only Query is required. A schema with reads but no writes simply omits Mutation and Subscription.

flowchart TD
  Query["Query (read entry)"]
  Mutation["Mutation (write entry)"]
  Subscription["Subscription (stream entry)"]
  Track["type Track"]
  Artist["type Artist"]
  Scalars["Scalars: ID, String, Int, Boolean, Float"]
  Query --> Track
  Mutation --> Track
  Track -->|"artist field"| Artist
  Track --> Scalars
  Artist --> Scalars
Root types are the doorways; object types and scalars form the rest of the graph.

By default every field in GraphQL is nullable: it may return a value or null. Adding a trailing ! makes the field non-null — the server guarantees a real value, never null. Read the ! as “guaranteed”:

type Artist {
id: ID! # always present
name: String! # always present
bio: String # may be null — biography is optional
tracks: [Track!]! # a non-null list of non-null Tracks
}

That last line is worth decoding. [Track!]! is a list type with two exclamation marks doing two different jobs:

  • The inner Track! means every element in the list is a non-null Track.
  • The outer ! means the list itself is never null (it may still be empty, []).

So tracks always returns a list, and that list never contains a null hole. Nullability is part of the contract: it tells clients exactly which fields they must guard against and which they can trust.

The example below defines a two-type schema — Artist and Track — wires up resolvers, and runs a query that crosses from one type into the other. Press Run to execute it with the real GraphQL engine.

JavaScript

Look closely at the output. title and durationSeconds come back as guaranteed values because they are non-null in the schema. artist is a nested object — the query stepped from Track into Artist — and inside it bio comes back as null, which is allowed because bio was declared without a !. The type system predicted every one of these shapes before the resolver ran.

What does a trailing exclamation mark, as in String!, declare about a field?
Which of these is NOT one of GraphQL’s five built-in scalar types?
Which root type is the read entry point and is the only one a schema must define?
What does the type [Track!]! mean?