Schema and Type System
|
This section documents the current GraphQL specification (October 2021), plus the working draft for
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as GraphQL tooling iterates quickly. |
Every GraphQL API is described by a schema: a single, strongly typed contract naming every type, field, and root operation the server exposes. This page covers how that schema is written down — the Schema Definition Language — and the building blocks it’s assembled from: object types, scalars, enums, the List and Non-Null wrapping types, and the three root operation types.
The Schema Definition Language (SDL)
The Schema Definition Language, or SDL, is the language-agnostic syntax used to write a GraphQL schema. It is
not tied to any particular server implementation or programming language — a .graphql schema file written
against the SDL means the same thing whether the server behind it is written in Java, JavaScript, Python, or
anything else. Every example on this page and the rest of this section is SDL.
An SDL document is a flat list of type definitions — object types, scalars, enums, and the other kinds covered across this section — with no notion of files, imports, or modules built into the language itself; how a project splits a large schema across several files is a tooling and build-pipeline concern, not something SDL specifies. Introspection (mentioned on Getting Started) is really just the server answering, in SDL terms, "what does your schema currently say" — a live schema and its written-down SDL describe the exact same contract.
See graphql.org/learn/schema for the canonical introduction to the type system this page walks through.
Object types and fields
An object type is the schema’s basic building block: a named type with a set of fields, where each field has its own name and its own type. Object types are how a schema describes the shape of the data a client can select in a query’s selection set:
type Book {
id: ID!
title: String!
publishedYear: Int
author: Author!
}
type Author {
id: ID!
name: String!
}
Book and Author are both object types. Each field declaration is fieldName: FieldType, and a field’s type
can be a scalar (String, Int) or another object type (Author) — object types can reference each other
freely, which is exactly what lets a query traverse relationships in a single round trip, as
Getting Started and
Queries and Fields describe. A field can also declare its own
arguments, in parentheses after the field name, the same way an operation-level field does — schema-side, an
argument is just part of that field’s type signature. See
graphql.org/learn/schema ("Object types and fields") for further examples.
The five built-in scalar types
A scalar type resolves to a concrete leaf value — it has no sub-fields of its own, which is why a scalar field is never followed by a selection set in a query. GraphQL ships five built-in scalars:
| Scalar | Represents |
|---|---|
|
A signed 32-bit integer. |
|
A signed double-precision floating-point value. |
|
A UTF-8 character sequence. |
|
|
|
A unique identifier, serialized as a |
Beyond these five, a schema can define its own custom scalars (Date, DateTime, URL, and similar are
common examples), each backed by server-side serialization and parsing logic; custom scalars are outside the
scope of this page. See graphql.org/learn/schema ("Scalar types") for the
full built-in scalar reference.
Enums
An enum restricts a field’s value to one of a fixed, named set of options — useful anywhere a field’s legitimate values are a small, closed list rather than an arbitrary string:
enum BookGenre {
FICTION
NON_FICTION
POETRY
BIOGRAPHY
}
A field typed BookGenre can only ever resolve to one of those four values, and a client argument typed
BookGenre can only ever be set to one of them — both directions are validated against the schema before
execution, so an invalid enum value never reaches a resolver. Enum values are written in SCREAMING_SNAKE_CASE
by convention, matching how most other languages' own enum constants look once generated from the schema. See
graphql.org/learn/schema ("Enumeration types") for further detail.
List and Non-Null: the wrapping types
String, BookGenre, and every other type named so far is nullable by default and describes a single value.
Two wrapping types modify that default, and they combine to express four distinct, commonly confused
contracts:
| Syntax | Meaning |
|---|---|
|
A nullable single value: either a string or |
|
A non-null single value: always a string, never |
|
A nullable list of nullable items: the list itself can be |
|
A nullable list of non-null items: the list can be |
|
A non-null list of nullable items: the list is always present (never |
|
A non-null list of non-null items: the list is always present, and every element in it is always a non-null string — the strictest, and most common, combination for a field returning a collection. |
The practical difference matters most to client code: a client consuming [String!]! can safely iterate the
result and read every element without a null check anywhere; a client consuming [String] has to guard against
the whole list being absent and against individual null entries inside it. Choosing the loosest wrapping
that still matches reality (a nullable list only where "no data yet" is a real, distinct case from "an empty
list") keeps client code simpler without hiding genuine absence. ! can wrap any type, not just scalars — Author!, [Book!]!, and even a Non-Null list of Non-Null objects all follow exactly the same rules shown
above. See graphql.org/learn/schema ("Lists and Non-Null") for the formal
treatment.
The root operation types
Every schema declares up to three special root operation types — the entry points every request starts
from — named, by convention, Query, Mutation, and Subscription:
type Query {
book(id: ID!): Book
books(genre: BookGenre): [Book!]!
}
type Mutation {
addBook(title: String!, author: String!): Book!
}
type Subscription {
bookAdded: Book!
}
These three are ordinary object types in every respect except one: the schema designates them as roots, so
their fields are the only fields a client can select at the top level of a query, mutation, or
subscription operation, respectively (the three operation types introduced on
Queries and Fields). Query is mandatory — a schema with no
readable data isn’t a useful schema — while Mutation and Subscription are optional, present only when the
API supports writes or long-lived event streams. See
graphql.org/learn/schema ("The Query and Mutation types") for how the root
types relate to the rest of the type system.
Descriptions for self-documenting schemas
A triple-quoted string placed immediately above a type or field definition is a description: documentation that becomes part of the schema itself and is returned by introspection, so tools like GraphiQL or Apollo Sandbox (from Getting Started) can display it as inline help without any separate documentation source to keep in sync:
"""
A published book available for lookup and lending.
"""
type Book {
id: ID!
"""
The book's title as printed on its cover.
"""
title: String!
publishedYear: Int
}
A single-line description can also use a plain double-quoted string ("…") instead of the triple-quoted
form; the triple-quoted form exists mainly so a description can span multiple lines, or contain a literal ",
without escaping. Because descriptions live in the schema and travel with introspection, they are the primary
way a GraphQL API documents itself to the clients consuming it — there is no separate, parallel API reference
to fall out of date the way a hand-maintained REST document can. See
graphql.org/learn/schema ("Documentation") for further conventions.
Putting it together
A single, more complete example ties the pieces above into one schema: an object type using several scalars, an
enum, a Non-Null-wrapped list, a description, and a Query root type that exposes it:
"""
A published book available for lookup and lending.
"""
type Book {
id: ID!
title: String!
publishedYear: Int
"""
The book's genre, drawn from a fixed set of categories.
"""
genre: BookGenre!
"""
Reader reviews for this book, newest first. Always present, though it may be empty.
"""
reviews: [Review!]!
author: Author!
}
type Author {
id: ID!
name: String!
}
type Review {
rating: Int!
comment: String
}
enum BookGenre {
FICTION
NON_FICTION
POETRY
BIOGRAPHY
}
"""
Entry point for every read in this API.
"""
type Query {
book(id: ID!): Book
books(genre: BookGenre): [Book!]!
}
Reading this schema top to bottom already answers most of the questions a client needs answered before writing
a single query: which fields are guaranteed to be present (id, title, genre, reviews, author on
Book), which are optional (publishedYear, comment on Review), which values genre can actually take,
and what the two entry points under Query accept and return.
Next steps
This page covers the core type system SDL uses to describe shapes of data. Two extensions build directly on it: interfaces, unions, and input types cover polymorphism and structured arguments, and directives attach metadata (such as deprecation) to schema elements —
Once these building blocks are familiar, Schema Design covers the best practices for combining them into a schema that stays maintainable as an API grows.