GraphQL Cheat Sheet

This GraphQL cheat sheet covers 29 sections: queries, variables, fragments and directives, mutations and subscriptions, schema design and nullability, errors and HTTP status codes, cursor pagination, resolvers and DataLoader, caching, security limits, federation and tooling. Analyze and format queries, lint a schema and turn a query into curl without leaving the page.

29sections
94snippets
2025spec edition
9GraphQL tools

Updated October 14, 2026, checked against the GraphQL September 2025 spec, the GraphQL over HTTP working draft, graphql-js 17.0.2, GraphQL Yoga 5.24.5 and Apollo Server 5.5.1. Print it for a PDF copy.

/
level

{ "data": null }

Nothing matches that search. Try a shorter keyword such as fragment, mutation or cursor. Several words are combined, so every one of them has to match. Keys: / focuses the search, Esc clears it, t switches the theme.

01

Toolbox & First Query

format and analyze queries, lint schemas, turn queries into HTTP requests, and make a first call

GraphQL Toolbox: Format, Analyze, Schema Lint, Query to HTTP

core
Ctrl+Enter analyzes. Analyze parses the document and reports depth, field count, an estimated cost from first and last arguments, aliases, unused or undefined fragments and variables. Format and minify accept queries and schemas. Lint schema checks SDL against common naming and design conventions. Query to HTTP writes the same operation as curl, fetch, a GET URL and a persisted-query hash; it never sends anything.
output

          
Paste a query, a mutation or a schema above, then pick an action.

Builders: Type and Resolvers, Relay Connection, Client Code, JSON to SDL

everyday
schema and resolvers

          

A First Query

core

Ask for exactly the fields you need

{
  film(id: "ZmlsbXM6MQ==") {
    title
    director
    releaseDate
  }
}

The same request with curl

curl -s https://swapi-graphql.netlify.app/graphql -H "Content-Type: application/json" -d '{"query": "{ film(id: \"ZmlsbXM6MQ==\") { title } }"}'
02

How GraphQL Works

the schema, operations and the one endpoint

The Core Ideas

core

Schema, operations, resolvers

PieceRole
schemathe typed contract: every type, field and argument the API offers
queryread data; fields at the same level may run in parallel
mutationchange data; top-level fields run one after another
subscriptionreceive a stream of results when events happen
resolverthe server function that produces a field's value
introspectionthe schema can be queried itself, which powers tooling

A Minimal Schema

core

Types and the root Query

type Query {
  film(id: ID!): Film
}

type Film {
  id: ID!
  title: String!
  director: String
  characters: [Person!]!
}
03

Queries

fields, arguments, aliases, nested selections and __typename

Fields and Arguments

core

Nested selections follow relationships

query {
  film(filmID: 1) {
    title
    planetConnection(first: 2) {
      planets { name }
    }
    characterConnection(first: 3) {
      characters { name }
    }
  }
}

Aliases

core

Ask for the same field twice

query {
  first: film(filmID: 1) { title }
  latest: film(filmID: 6) { title }
}

Operation Names and __typename

everyday

Name every operation

query EmpireCast {
  film(filmID: 2) {
    __typename
    title
    characterConnection(first: 2) {
      characters { __typename name }
    }
  }
}

Several Root Fields

everyday

One request, independent reads

{
  product(by: { sku: "LAMP-01" }) { name price }
  orders(first: 1, status: PAID) { totalCount }
}
04

Variables

typed variables, defaults and input objects

Variables

core

Never build queries by string concatenation

query Film($id: ID!, $cast: Int = 2) {
  film(id: $id) {
    title
    characterConnection(first: $cast) {
      characters { name }
    }
  }
}

# variables: {"id": "ZmlsbXM6Mw=="}

The HTTP body with variables

{
  "query": "query Film($id: ID!) { film(id: $id) { title } }",
  "variables": { "id": "ZmlsbXM6Mw==" },
  "operationName": "Film"
}

Request Errors

everyday

A missing required variable

query Order($id: ID!) {
  node(id: $id) { id }
}

A typo in a field name

{
  product(by: { sku: "LAMP-01" }) { nmae }
}

Lists, Enums and Input Objects

everyday

A list variable

query Products($skus: [String!]!) {
  products(skus: $skus) { sku price }
}

# variables: {"skus": ["LAMP-01", "BULB-07"]}

Enum values are strings in JSON

query Shipped($status: OrderStatus) {
  orders(first: 5, status: $status) {
    totalCount
    edges { node { id } }
  }
}

# variables: {"status": "SHIPPED"}
05

Fragments

named fragments, inline fragments, interfaces and unions

Named Fragments

core

Reuse a selection

query TwoFilms {
  a: film(filmID: 4) { ...FilmCard }
  b: film(filmID: 5) { ...FilmCard }
}

fragment FilmCard on Film {
  title
  releaseDate
}

Unions and Interfaces

everyday

Pick fields by concrete type

query {
  search(text: "ai") {
    __typename
    ... on Product { sku price }
    ... on Customer { name }
  }
}

Fetch any object by global ID

query {
  node(id: "ord_8812") {
    id
    ... on Order { status total customer { name } }
  }
}

Fragments on Interfaces

everyday

Spread an interface fragment into a union

query {
  search(text: "ai") { ...Ref }
}

fragment Ref on Node {
  __typename
  id
}
06

Directives

@include, @skip, @deprecated, @specifiedBy, @oneOf, @defer and @stream

Conditional Fields

core

@include and @skip

query Film($withCrawl: Boolean!) {
  film(filmID: 1) {
    title
    openingCrawl @include(if: $withCrawl)
  }
}

# variables: {"withCrawl": false}

Schema Directives

everyday

Built into the specification

type Product {
  stock: Int @deprecated(reason: "Use stockLevel.")
  stockLevel: Int
}

scalar DateTime @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3339")

input ProductBy @oneOf {
  id: ID
  sku: String
}

@oneOf: exactly one field

{
  product(by: { id: "prd_1", sku: "LAMP-01" }) { name }
}

Incremental Delivery

advanced

@defer sends slow parts later

import { buildSchema, parse, experimentalExecuteIncrementally } from 'graphql';

const schema = buildSchema(`
  directive @defer(if: Boolean! = true, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT
  type Query { product: Product }
  type Product { name: String reviews: [String] }
`);

const rootValue = {
  product: {
    name: 'Desk lamp',
    reviews: async () => { await new Promise((r) => setTimeout(r, 50)); return ['Bright', 'Sturdy']; },
  },
};

const document = parse(`{ product { name ... @defer(label: "slow") { reviews } } }`);
const result = await experimentalExecuteIncrementally({ schema, document, rootValue });

console.log(JSON.stringify(result.initialResult));
for await (const next of result.subsequentResults) console.log(JSON.stringify(next));

Custom Directives

advanced

A schema directive that changes a resolver

import { createSchema } from 'graphql-yoga';
import { mapSchema, getDirective, MapperKind } from '@graphql-tools/utils';
import { defaultFieldResolver, graphql } from 'graphql';

const typeDefs = `
  directive @upper on FIELD_DEFINITION
  type Query { greeting: String @upper, plain: String }
`;

const base = createSchema({ typeDefs, resolvers: { Query: { greeting: () => 'hello', plain: () => 'hello' } } });
const schema = mapSchema(base, {
  [MapperKind.OBJECT_FIELD]: (field) => {
    if (!getDirective(base, field, 'upper')?.[0]) return field;
    const resolve = field.resolve ?? defaultFieldResolver;
    return { ...field, resolve: async (...args) => String(await resolve(...args)).toUpperCase() };
  },
});

console.log(JSON.stringify(await graphql({ schema, source: '{ greeting plain }' })));
07

Mutations

input types, payloads and serial execution

A Mutation

core

Input object in, payload out

mutation CreateOrder($input: CreateOrderInput!) {
  createOrder(input: $input) {
    order { id status total }
    userErrors { field message code }
  }
}

# variables: {"input": {"customerId": "cus_42", "items": [{"sku": "LAMP-01"}, {"sku": "BULB-07", "qty": 3}]}}

Business errors as data

mutation {
  createOrder(input: {
    customerId: "cus_42"
    items: [{ sku: "CHAIR-3", qty: 1 }, { sku: "DESK-9" }]
  }) {
    order { id }
    userErrors { field message code }
  }
}

The schema behind it

type Mutation {
  createOrder(input: CreateOrderInput!): CreateOrderPayload!
}

input CreateOrderInput {
  customerId: ID!
  items: [OrderItemInput!]!
  note: String
}

input OrderItemInput {
  sku: String!
  qty: Int! = 1
}

type CreateOrderPayload {
  order: Order
  userErrors: [UserError!]!
}

type UserError {
  field: [String!]
  message: String!
  code: String!
}

Execution Order

everyday

Top-level mutation fields run in sequence

mutation {
  first: addToCart(sku: "LAMP-01") { itemCount }
  second: addToCart(sku: "BULB-07") { itemCount }
}

Optimistic Updates

advanced

Update the UI before the server answers

const [setStatus] = useMutation(SET_STATUS);

setStatus({
  variables: { id: 'ord_8813', status: 'PAID' },
  optimisticResponse: {
    setOrderStatus: { __typename: 'Order', id: 'ord_8813', status: 'PAID' },
  },
});
08

Subscriptions

real-time results over WebSockets or SSE

A Subscription

everyday

Results pushed when events happen

subscription OnOrderStatus {
  orderStatusChanged(id: "ord_8813") { id status }
}

# accept: text/event-stream

The resolver is an async generator

const resolvers = {
  Subscription: {
    orderStatusChanged: {
      subscribe: async function* (_, { id }) {
        for await (const order of orderEvents(id)) {
          yield { orderStatusChanged: order };
        }
      },
    },
  },
};

When to use subscriptions

NeedChoose
small, frequent updates the user waits for: chat, live statussubscription
data that changes rarelypolling or refetch on focus
large result sets that changesubscription that sends IDs, then a query
notifications to other systemswebhooks, not GraphQL subscriptions
09

Schema & SDL

type definitions, the shop schema used on this page, descriptions and extensions

Schema Definition Language

core

The shop schema behind the order examples

"""Any object that can be refetched by its global ID."""
interface Node { id: ID! }

enum OrderStatus { PENDING PAID SHIPPED }

type Product implements Node {
  id: ID!
  sku: String!
  name: String!
  price: Float!
  stock: Int @deprecated(reason: "Use stockLevel.")
  stockLevel: Int
}


type Order implements Node {
  id: ID!
  status: OrderStatus!
  total: Float!
  createdAt: DateTime!
  customer: Customer!
  items: [OrderItem!]!
  note: String
}

union SearchResult = Product | Customer | Order

type Query {
  node(id: ID!): Node
  product(by: ProductBy!): Product
  products(skus: [String!]!): [Product]!
  search(text: String!): [SearchResult!]!
  orders(first: Int = 10, after: String, status: OrderStatus): OrderConnection!
  me: Customer
  price(sku: String!): Float!
}

Schema First or Code First

everyday

Two ways to build the same schema

ApproachYou writeTypical libraries
schema firstSDL files, then resolvers that match themApollo Server, GraphQL Yoga, gqlgen, Spring for GraphQL
code firstclasses or functions; the SDL is generatedStrawberry, Pothos, Nexus, Hot Chocolate, graphql-java with annotations

Split a schema with extend

type Query {
  me: Customer
}

extend type Query {
  orders(first: Int = 10, after: String): OrderConnection!
}

extend type Customer {
  loyaltyPoints: Int!
}
10

Types & Scalars

scalars, enums, lists, input types, interfaces, unions and custom scalars

The Type System

core

Kinds of types

KindExampleUsed for
scalarInt, Float, String, Boolean, IDleaf values
enumenum OrderStatus { PENDING PAID }a fixed set of values
objecttype Order { ... }output with fields
interfaceinterface Node { id: ID! }shared fields that several types implement
unionunion SearchResult = Product \| Customerone of several types with no shared fields
inputinput CreateOrderInput { ... }arguments; cannot reference output types
list[Order!]!ordered collections

Custom Scalars

everyday

A DateTime scalar in graphql-js 17

import { GraphQLScalarType, GraphQLSchema, GraphQLObjectType, GraphQLNonNull, Kind, graphql } from 'graphql';

const DateTime = new GraphQLScalarType({
  name: 'DateTime',
  coerceOutputValue: (d) => new Date(d).toISOString(),
  coerceInputValue: (v) => { const d = new Date(v); if (isNaN(d)) throw new TypeError('Invalid DateTime'); return d; },
  coerceInputLiteral: (ast) => (ast.kind === Kind.STRING ? new Date(ast.value) : undefined),
});

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      shipBy: {
        type: DateTime,
        args: { ordered: { type: new GraphQLNonNull(DateTime) } },
        resolve: (_, { ordered }) => new Date(ordered.getTime() + 2 * 864e5),
      },
    },
  }),
});

const source = 'query ($at: DateTime!) { shipBy(ordered: $at) }';
console.log(JSON.stringify(await graphql({ schema, source, variableValues: { at: '2026-10-14T09:00:00Z' } })));
console.log((await graphql({ schema, source, variableValues: { at: 'yesterday' } })).errors[0].message);
11

Nullability

non-null fields, null propagation and how to choose

Null Propagation

core

An error in a non-null field nulls its parent

{
  product(by: { sku: "LAMP-01" }) { name }
  price(sku: "DESK-9")
}

A nullable field keeps the damage local

{
  product(by: { sku: "CHAIR-3" }) {
    name
    stockLevel
  }
}

Choosing !

everyday

Rules of thumb

FieldMake itWhy
id, and fields stored on the same rownon-nullthey cannot fail on their own
fields resolved from another servicenullablea failure stays local
lists[Item!]!an empty list beats null; null items are rarely useful
arguments and input fieldsnon-null when requiredthe client gets a validation error instead of a runtime one
new fields on a public APInullable firstmaking a field non-null later is safe; the reverse breaks clients
12

Errors

the errors array, partial data, extensions codes and masking

The Error Format

core

A field error with an extensions code

{
  me { name email }
}

Throw a GraphQLError to choose the message

import { GraphQLError } from 'graphql';

const resolvers = {
  Query: {
    me: (_, __, ctx) => {
      if (!ctx.user) {
        throw new GraphQLError('You must be signed in.', {
          extensions: { code: 'UNAUTHENTICATED' },
        });
      }
      return ctx.user;
    },
  },
};

Where Each Failure Goes

everyday

Errors array or data

FailurePut it inTypical HTTP status today
syntax or validation errorerrors, no data400 with graphql-response+json, 200 with application/json
missing or invalid variableerrors, no data200 on graphql-http and Yoga, 400 on Apollo Server 5
not signed inerrors with code UNAUTHENTICATED200, or 401 if the server checks before GraphQL runs
bug or upstream timeout in a fielderrors with path, partial data200
expected business outcome: out of stock, duplicate emailpayload field such as userErrors200

Errors Inside Lists

everyday

One bad item, the rest still arrive

{
  products(skus: ["LAMP-01", "DESK-9", "BULB-07"]) { sku price }
}
13

Schema Evolution

safe and breaking changes, deprecation and versionless APIs

Safe and Breaking Changes

core

What clients notice

ChangeEffect on existing clients
add a type, field, enum value or optional argumentsafe
add a required argument or a non-null input field without a defaultbreaking
remove or rename a field, type or enum valuebreaking
change a field's type, such as String to Intbreaking
make an output field nullablebreaking for typed clients
make an output field non-nullsafe
make an argument or input field non-nullbreaking
add a value to an enum used in outputsafe by the spec, but breaks clients with exhaustive switch statements

Find breaking changes with graphql-js

import { buildSchema, findBreakingChanges, findDangerousChanges } from 'graphql';

const before = buildSchema(`
  type Query { order(id: ID!): Order }
  type Order { id: ID! status: Status! total: Float! note: String }
  enum Status { PENDING PAID }
`);
const after = buildSchema(`
  type Query { order(id: ID!, region: String!): Order }
  type Order { id: ID! status: Status! total: Int! }
  enum Status { PENDING PAID REFUNDED }
`);

for (const c of findBreakingChanges(before, after)) console.log('breaking ', c.type, c.description);
for (const c of findDangerousChanges(before, after)) console.log('dangerous', c.type, c.description);

Deprecate, Then Remove

everyday

The lifecycle of a field

type Product {
  stock: Int @deprecated(reason: "Use stockLevel. Removal planned after 2027-01-31.")
  stockLevel: Int
}

List the deprecated fields

{
  __type(name: "Product") {
    fields(includeDeprecated: true) {
      name
      isDeprecated
      deprecationReason
    }
  }
}
14

GraphQL over HTTP

POST and GET, content types, status codes and file uploads

Requests

core

GET for queries

curl -s -G https://swapi-graphql.netlify.app/graphql --data-urlencode 'query={ film(filmID: 1) { title } }'

Mutations over GET are refused

mutation {
  createOrder(input: { customerId: "cus_42", items: [{ sku: "LAMP-01" }] }) { order { id } }
}

# method: GET

Status Codes

core

The Accept header decides 200 or 400

for a in application/json application/graphql-response+json; do
  curl -s -o /dev/null -w "$a -> %{http_code}\n" https://swapi-graphql.netlify.app/graphql -H "Content-Type: application/json" -H "Accept: $a" -d '{"query": "{ film(filmID: 1) { titel } }"}'
done

What servers send today and what the 2026 draft recommends

Situationgraphql-http 1.23 and Yoga 5.24, application/jsonsame servers, graphql-response+jsonworking draft, September 2026
data and no errors200200200
data and field errors200200294 Partial Success
JSON body cannot be parsed400400400
GraphQL document cannot be parsed200400400
document fails validation200400422
variables cannot be coerced200200422
mutation sent with GET405405405 with an Allow header

File Uploads

advanced

Multipart uploads and the alternative

OptionHow it worksNote
signed URLa mutation returns an S3 or GCS upload URL; the client PUTs the file thererecommended: files never touch the GraphQL server
multipart requestthe GraphQL multipart request spec with an Upload scalarneeds CSRF protection; Apollo Server 4 and later require a preflight header
15

Introspection

__schema, __type, schema downloads and turning it off

Querying the Schema

core

Ask a type for its fields

{
  __type(name: "PageInfo") {
    kind
    fields {
      name
      type { kind name ofType { name } }
    }
  }
}

Download the schema as SDL

import { getIntrospectionQuery, buildClientSchema, printSchema } from 'graphql';

const res = await fetch('https://swapi-graphql.netlify.app/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: getIntrospectionQuery() }),
});
const { data } = await res.json();
const sdl = printSchema(buildClientSchema(data));
console.log(sdl.split('\n').length, 'lines');
console.log(sdl.slice(sdl.indexOf('type Film '), sdl.indexOf('type Film ') + 110));

Disabling It

everyday

Turn introspection off in production

import { ApolloServer } from '@apollo/server';
const apollo = new ApolloServer({ typeDefs, resolvers, introspection: process.env.NODE_ENV !== 'production' });

import { createYoga } from 'graphql-yoga';
import { useDisableIntrospection } from '@graphql-yoga/plugin-disable-introspection';
const yoga = createYoga({ schema, plugins: [useDisableIntrospection()] });

Root Types

everyday

Which operations a server supports

{
  __schema {
    queryType { name }
    mutationType { name }
    subscriptionType { name }
  }
}
16

Pagination

Relay cursor connections, offset paging and totalCount

Cursor Connections

core

First page

query Orders($after: String) {
  orders(first: 2, after: $after) {
    totalCount
    edges { cursor node { id status } }
    pageInfo { hasNextPage endCursor }
  }
}

Next page

query Orders($after: String) {
  orders(first: 2, after: $after) {
    edges { node { id status } }
    pageInfo { hasNextPage endCursor }
  }
}

# variables: {"after": "b3JkZXI6b3JkXzg3OTA="}

The connection types

type OrderConnection {
  edges: [OrderEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type OrderEdge {
  cursor: String!
  node: Order!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

Limits

everyday

Cap page size on the server

{
  orders(first: 500) { totalCount }
}

Offset Pagination

everyday

Offset versus cursor

StyleArgumentsFits
offsetlimit: 20, offset: 40, or page: 3admin tables with page numbers; small data that rarely changes
cursorfirst: 20, after: "b3Jk..."feeds, infinite scroll, large or fast-changing data
17

Caching & Persisted Queries

HTTP caching, automatic persisted queries, trusted documents and client caches

Automatic Persisted Queries

everyday

Send a hash, fall back to the full query

import { createHash } from 'node:crypto';
import { createYoga, createSchema } from 'graphql-yoga';
import { useAPQ } from '@graphql-yoga/plugin-apq';

const yoga = createYoga({
  schema: createSchema({ typeDefs: 'type Query { hello: String! }', resolvers: { Query: { hello: () => 'world' } } }),
  plugins: [useAPQ()],
  logging: false,
});

const query = '{ hello }';
const sha256Hash = createHash('sha256').update(query).digest('hex');
const extensions = JSON.stringify({ persistedQuery: { version: 1, sha256Hash } });
const get = async () => (await yoga.fetch('http://localhost/graphql?extensions=' + encodeURIComponent(extensions))).text();

console.log('1 GET hash only:', await get());
const post = await yoga.fetch('http://localhost/graphql', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ query, extensions: JSON.parse(extensions) }) });
console.log('2 POST query and hash:', await post.text());
console.log('3 GET hash only:', await get());

Trusted Documents

advanced

Allow only the operations your apps ship

StepWhat happens
buildthe compiler or codegen extracts every operation from the client code with its hash
deploythe hashes and documents are uploaded to the server or the gateway
runtimeclients send the hash only; any other document is rejected

HTTP and Server Caching

everyday

Options that work with POST-heavy APIs

LayerApproach
CDNGET with persisted queries and Cache-Control from the server
server response cacheYoga's response cache plugin or Apollo Server's responseCachePlugin, keyed per user when needed
field hints@cacheControl(maxAge: 60) on types and fields in Apollo Server
resolver dataDataLoader per request, Redis or an in-memory LRU across requests
clientnormalized caches in Apollo Client, urql Graphcache or Relay
18

Resolvers

the four arguments, context, default resolvers and server code

The Resolver Signature

core

parent, args, context, info

const resolvers = {
  Query: {
    orders: (parent, args, context, info) => context.db.orders.list(args),
  },
  Order: {
    total: (order) => order.items.reduce((s, i) => s + i.price * i.qty, 0),
    customer: (order, _, ctx) => ctx.loaders.customer.load(order.customerId),
  },
};

Python with Strawberry

everyday

Code-first types and a query

import strawberry

@strawberry.type
class Product:
    sku: str
    name: str
    price: float

PRODUCTS = [Product(sku="LAMP-01", name="Desk lamp", price=39.5), Product(sku="BULB-07", name="LED bulb", price=5.0)]

@strawberry.type
class Query:
    @strawberry.field
    def products(self, max_price: float | None = None) -> list[Product]:
        return [p for p in PRODUCTS if max_price is None or p.price <= max_price]

schema = strawberry.Schema(query=Query)
print(schema.as_str())
print(schema.execute_sync("{ products(maxPrice: 10) { sku name } }").data)

Node with GraphQL Yoga

everyday

A complete server

import { createServer } from 'node:http';
import { createYoga, createSchema } from 'graphql-yoga';

const yoga = createYoga({
  schema: createSchema({
    typeDefs: 'type Query { hello(name: String = "world"): String! }',
    resolvers: { Query: { hello: (_, { name }) => `Hello, ${name}` } },
  }),
});

createServer(yoga).listen(4000, () => console.log('http://localhost:4000/graphql'));
19

N+1 & DataLoader

batching, caching per request and query planning

The N+1 Problem

core

Five orders, five customer queries, then one

import { graphql, buildSchema } from 'graphql';
import DataLoader from 'dataloader';

const schema = buildSchema(`
  type Query { orders: [Order!]! }
  type Order { id: ID! customer: Customer! }
  type Customer { id: ID! name: String! }
`);

const names = { c1: 'Ana', c2: 'Mihai' };
const orders = [['o1', 'c1'], ['o2', 'c2'], ['o3', 'c1'], ['o4', 'c2'], ['o5', 'c1']];
let sql = [];
const customersByIds = async (ids) => {
  sql.push(`SELECT * FROM customers WHERE id IN (${ids.map((i) => `'${i}'`).join(', ')})`);
  return ids.map((id) => ({ id, name: names[id] }));
};

const root = (load) => ({ orders: () => orders.map(([id, c]) => ({ id, customer: () => load(c) })) });
const source = '{ orders { id customer { name } } }';

await graphql({ schema, source, rootValue: root(async (id) => (await customersByIds([id]))[0]) });
console.log('without DataLoader:', sql.length, 'queries');

sql = [];
const loader = new DataLoader(customersByIds);
await graphql({ schema, source, rootValue: root((id) => loader.load(id)) });
console.log('with DataLoader:', sql.length, 'query');
console.log(sql[0]);

DataLoader Rules

everyday

Get the details right

RuleWhy
create loaders per request, in contextthe cache must not leak data between users
return results in the same order as the keysDataLoader matches results to keys by position
return null or an Error for missing keysthe array length must equal the number of keys
use one loader per access patternbyId and byEmail need separate caches
20

Servers by Language

Apollo Server, Yoga, Strawberry, Spring for GraphQL, Hot Chocolate, gqlgen and graphql-java

Pick a Server

core

The common choice in each language

LanguageLibraryStyle
JavaScript, TypeScriptGraphQL Yoga, Apollo Server, Pothos for code-first typesschema first or code first
PythonStrawberry, Ariadne, Graphenecode first, schema first, code first
Java, KotlinSpring for GraphQL on graphql-java, Netflix DGSschema first with annotated controllers
C#, .NETHot Chocolatecode first or schema first
Gogqlgenschema first with generated Go types
Rustasync-graphql, Junipercode first
PHPwebonyx/graphql-php, Lighthouse for Laravelcode first, schema first
Rubygraphql-rubycode first

Spring for GraphQL

everyday

An annotated controller

@Controller
public class OrderController {

    private final OrderService orders;
    private final CustomerService customers;

    public OrderController(OrderService orders, CustomerService customers) {
        this.orders = orders;
        this.customers = customers;
    }

    @QueryMapping
    public Order order(@Argument String id) {
        return orders.find(id);
    }

    @BatchMapping
    public Map<Order, Customer> customer(List<Order> list) {
        return customers.forOrders(list);
    }
}

Apollo Server 5

everyday

Standalone server

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
  context: async ({ req }) => ({ token: req.headers.authorization }),
});
console.log(url);
21

Security

depth and cost limits, aliases, batching, introspection and CSRF

Limit Query Depth and Cost

core

A depth limit as a validation rule

import { buildSchema, parse, validate, specifiedRules, GraphQLError, Kind } from 'graphql';

const schema = buildSchema(`
  type Query { customer(id: ID!): Customer }
  type Customer { name: String orders: [Order] }
  type Order { id: ID customer: Customer }
`);

const maxDepth = (limit) => (context) => ({
  OperationDefinition(op) {
    const depthOf = (set, seen) => Math.max(0, ...set.selections.map((s) => {
      if (s.kind === Kind.FRAGMENT_SPREAD) {
        const frag = seen.has(s.name.value) ? null : context.getFragment(s.name.value);
        return frag ? depthOf(frag.selectionSet, new Set([...seen, s.name.value])) : 0;
      }
      const own = s.kind === Kind.FIELD ? 1 : 0;
      return s.selectionSet ? own + depthOf(s.selectionSet, seen) : own;
    }));
    const depth = depthOf(op.selectionSet, new Set());
    if (depth > limit) context.reportError(new GraphQLError(`Query depth ${depth} exceeds the limit of ${limit}.`, { nodes: [op] }));
  },
});

const query = parse('{ customer(id: "cus_42") { orders { customer { orders { customer { orders { id } } } } } } }');
console.log(validate(schema, query, [...specifiedRules, maxDepth(5)]).map((e) => e.message));

Use a maintained package in production

PackageWorks withCovers
graphql-armorApollo Server, GraphQL Yoga and Envelopdepth, aliases, directives, tokens, cost, field suggestions
Yoga and Envelop pluginsGraphQL Yogarate limits, response cache, persisted operations, disabling introspection
Hot Chocolate.NETbuilt-in cost analysis based on the IBM cost specification
Spring for GraphQLJavaMaxQueryDepthInstrumentation and MaxQueryComplexityInstrumentation from graphql-java

Aliases and Batching

everyday

One request, many attempts

mutation {
  a1: login(email: "ana@example.com", password: "123456") { token }
  a2: login(email: "ana@example.com", password: "password") { token }
  a3: login(email: "ana@example.com", password: "qwerty") { token }
}

Checklist

core

Before a GraphQL API goes public

  ok depth, alias and cost limits on every operation
  ok maximum page size enforced in resolvers
  ok authorization checked in the business layer, per object, not only per endpoint
  ok introspection and field suggestions off for private APIs
  ok trusted documents for first-party clients
  ok CSRF: require Content-Type application/json or a custom header, never accept text/plain or form posts
  ok errors masked so stack traces and SQL never reach clients
  ok request size and timeout limits at the HTTP layer
22

Authentication & Authorization

tokens in context, field-level checks and directives

Authenticate Once, Authorize Everywhere

core

Put the user in context

const yoga = createYoga({
  schema,
  context: async ({ request }) => {
    const header = request.headers.get('authorization') ?? '';
    const user = header.startsWith('Bearer ') ? await verifyToken(header.slice(7)) : null;
    return { user };
  },
});

Check access where the data is loaded

const resolvers = {
  Query: {
    node: async (_, { id }, { user, loaders }) => {
      const obj = await loaders.byGlobalId.load(id);
      return obj && canRead(user, obj) ? obj : null;
    },
  },
};

Declarative rules

ToolStyle
@auth or @requiresScopes directivesApollo Router, Hot Chocolate @authorize, Lighthouse @can
graphql-shielda permission tree mapped onto the schema for Node servers
@PreAuthorizeSpring Security on Spring for GraphQL controller methods
23

Federation

subgraphs, entities, routers and composite schemas

Entities Across Subgraphs

advanced

A subgraph extends Product with reviews

import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginInlineTraceDisabled } from '@apollo/server/plugin/disabled';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { parse } from 'graphql';

const typeDefs = parse(`
  extend schema @link(url: "https://specs.apollo.dev/federation/v2.12", import: ["@key"])

  type Product @key(fields: "sku") {
    sku: String!
    reviewCount: Int!
  }
`);

const counts = { 'LAMP-01': 12, 'BULB-07': 3 };
const resolvers = {
  Product: { __resolveReference: (ref) => ({ sku: ref.sku, reviewCount: counts[ref.sku] ?? 0 }) },
};

const server = new ApolloServer({ schema: buildSubgraphSchema([{ typeDefs, resolvers }]), plugins: [ApolloServerPluginInlineTraceDisabled()] });
const res = await server.executeOperation({
  query: 'query ($r: [_Any!]!) { _entities(representations: $r) { ... on Product { sku reviewCount } } }',
  variables: { r: [{ __typename: 'Product', sku: 'LAMP-01' }, { __typename: 'Product', sku: 'BULB-07' }] },
});
console.log(JSON.stringify(res.body.singleResult.data, null, 2));

Federation or one server

OptionFits
a single GraphQL serverone team, or a monolith with one schema
federation with Apollo Router, Hive Gateway or WunderGraph Cosmoseveral teams owning parts of the graph, deployed independently
schema stitching with graphql-toolscombining a few existing GraphQL APIs you do not control
24

Clients

fetch, Apollo Client 4, urql, Relay and graphql-request

Plain fetch

core

No library needed

const res = await fetch('https://swapi-graphql.netlify.app/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Accept: 'application/graphql-response+json, application/json' },
  body: JSON.stringify({
    query: 'query Film($id: ID!) { film(id: $id) { title director } }',
    variables: { id: 'ZmlsbXM6Mg==' },
  }),
});
const { data, errors } = await res.json();
if (errors) console.error(errors);
console.log(res.status, data.film);

Apollo Client 4

everyday

React setup and a query

import { ApolloClient, HttpLink, InMemoryCache, gql } from '@apollo/client';
import { ApolloProvider, useQuery } from '@apollo/client/react';

const client = new ApolloClient({
  link: new HttpLink({ uri: '/graphql' }),
  cache: new InMemoryCache(),
});

const ORDER = gql`
  query Order($id: ID!) {
    node(id: $id) { id ... on Order { status total } }
  }
`;

function OrderStatus({ id }) {
  const { data, loading, error } = useQuery(ORDER, { variables: { id } });
  if (loading) return 'Loading';
  if (error) return error.message;
  return data.node.status;
}

Choosing a Client

everyday

What each one is good at

ClientStrengthCache
fetch or graphql-requestscripts, servers, tiny bundlesnone
urqlsmall, extensible with exchangesdocument cache, normalized with Graphcache
Apollo Clientmost features, devtools, local statenormalized by __typename and id
Relaylarge React apps, fragment colocation enforced by a compilernormalized, with garbage collection
TanStack Query with fetchapps that already use it for RESTper query key
25

Tooling & Codegen

GraphiQL, code generation, linting and schema checks

Typed Operations

everyday

GraphQL Code Generator with the client preset

import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: 'https://swapi-graphql.netlify.app/graphql',
  documents: ['src/**/*.{ts,tsx}'],
  generates: {
    './src/gql/': { preset: 'client' },
  },
};

export default config;

Everyday Tools

everyday

What each tool is for

ToolUse it to
GraphiQLexplore a schema and run queries in the browser; Yoga and Apollo serve it in development
graphql-eslintlint operations and schemas in CI: naming, deprecated fields, unused fragments
GraphQL Inspector or Rover schema checkcatch breaking schema changes in pull requests
GraphQL extension for VS Codeautocomplete and validation from graphql.config files
Altair, Insomnia, Postmansend queries with headers, variables and files
26

Testing

operations in tests, mocked schemas and what to test where

Test Operations, Not Just Resolvers

core

Call the server in-process

import assert from 'node:assert/strict';
import { yoga } from './shop.mjs';

async function execute(query, variables, headers = {}) {
  const res = await yoga.fetch('http://test/graphql', {
    method: 'POST',
    headers: { 'content-type': 'application/json', ...headers },
    body: JSON.stringify({ query, variables }),
  });
  return { status: res.status, body: await res.json() };
}

const order = await execute('query ($id: ID!) { node(id: $id) { ... on Order { status total } } }', { id: 'ord_8812' });
assert.equal(order.status, 200);
assert.deepEqual(order.body.data.node, { status: 'PAID', total: 49.5 });

const anonymous = await execute('{ me { name } }');
assert.equal(anonymous.body.errors[0].extensions.code, 'UNAUTHENTICATED');

const signedIn = await execute('{ me { name } }', undefined, { authorization: 'Bearer test' });
assert.equal(signedIn.body.data.me.name, 'Ana Pop');
console.log('3 operations tested, all assertions passed');

The same with Apollo Server

import assert from 'node:assert/strict';
import { ApolloServer } from '@apollo/server';

const server = new ApolloServer({
  typeDefs: 'type Query { hello(name: String!): String! }',
  resolvers: { Query: { hello: (_, { name }) => `Hello, ${name}` } },
});

const res = await server.executeOperation({ query: 'query ($n: String!) { hello(name: $n) }', variables: { n: 'Ana' } });
assert.equal(res.body.kind, 'single');
assert.equal(res.body.singleResult.data.hello, 'Hello, Ana');
console.log(JSON.stringify(res.body.singleResult));

Mocked Schemas

everyday

Fake data that matches the schema

import { makeExecutableSchema } from '@graphql-tools/schema';
import { addMocksToSchema } from '@graphql-tools/mock';
import { graphql } from 'graphql';

const typeDefs = `
  type Query { orders: [Order!]! }
  type Order { id: ID! status: Status! total: Float! }
  enum Status { PENDING PAID }
`;

const schema = addMocksToSchema({
  schema: makeExecutableSchema({ typeDefs }),
  mocks: { Float: () => 49.5, ID: () => 'ord_1', Status: () => 'PAID' },
});

console.log(JSON.stringify(await graphql({ schema, source: '{ orders { id status total } }' })));

What to Test Where

everyday

A practical split

LayerTest
resolvers and servicesbusiness rules and authorization, with the database or a test container
operationsreal queries from the client through yoga.fetch or executeOperation, including errors and permissions
schemabreaking-change checks and graphql-eslint rules in CI
clientcomponents against mocked responses with MSW or Apollo's MockedProvider
performancethe slowest real operations, with DataLoader batching and cost limits enabled
27

GraphQL vs REST

when each one fits, and what changes for the team

Side by Side

core

The practical differences

TopicRESTGraphQL
endpointsone per resourceone for everything
response shapefixed by the serverchosen by the client
over-fetching and round tripscommonavoided by design
HTTP cachingbuilt in with GET and ETagsneeds persisted queries or a response cache
errorsHTTP status codesmostly 200 with an errors array
versioning/v2 or headersevolve the schema, deprecate fields
typed contractoptional, with OpenAPIalways, with the schema
rate limitingper requestper query cost
file uploadsmultipart, simpleoutside the spec, signed URLs preferred
28

Troubleshooting

the errors people search for, and what they mean

Common Errors

core

Message and fix

ErrorCause and fix
Cannot query field "x" on type "Y"typo or field not in the schema; check the suggestion and the server version
Field "edges" of type "[OrderEdge!]!" must have a selection of subfieldsobject fields need braces down to scalars
Field "totalCount" must not have a selection since type "Int!" has no subfieldsremove the braces after a scalar field
Variable "$id" of required type "ID!" was not providedsend it in variables, with the same name and no $; graphql 17 words it Variable "$id" has invalid value
Variable "$x" of type "ID" used in position expecting type "ID!"declare the variable as non-null or give it a default
Variable "$n" is never usedremove the declaration or use it; unused variables fail validation
Cannot return null for non-nullable field Y.xthe resolver returned null or threw; fix it or make the field nullable
Unknown type "X"the type is missing from the schema, or the operation was written for a different API
Must provide query string, Missing query, Unexpected parameter "qeury"the body is not JSON with a query key, or Content-Type is wrong
Syntax Error: Expected Name, found "{"a parenthesis or brace is missing; the analyze tool above shows where
29

Versions

the specification and the libraries as of October 2026

The Specification

core

Editions and drafts

DocumentStatus
GraphQL specification, September 2025 editioncurrent; added @oneOf input objects, schema coordinates and descriptions on operations
GraphQL over HTTPworking draft, being prepared for its first release
Incremental delivery: @defer and @streamRFC in progress; experimental in graphql-js 17
Composite Schemasworking draft for federation-style gateways
Relay Cursor Connectionsstable convention used by most paginated APIs

Libraries

everyday

Current releases checked on October 14, 2026

LibraryVersionNote
graphql-js17.0.2v17 released June 15, 2026, with @defer and @stream behind experimental APIs
GraphQL Yoga5.24.5supports graphql 15, 16 and 17
Apollo Server5.5.1Node.js 20+, graphql 16.11+; v4 ended January 26, 2026
Apollo Client4.3.3hooks in @apollo/client/react; rxjs peer dependency
urql5.0.4 |
graphql-ws6.4.0WebSocket subscriptions
GraphQL Code Generator CLI7.4.6 |
Strawberry0.335.0Python 3.11 or later
Spring for GraphQL2.0.5Spring Boot 4; graphql-java 26.1 is the latest graphql-java
Hot Chocolate16.6.8.NET
gqlgen0.17.95Go

What Is a GraphQL Cheat Sheet

A GraphQL cheat sheet is a compact reference to the query language and the conventions around it: how to write queries, variables and fragments, how to design a schema and its nullability, what errors and status codes a server returns, and how to paginate, cache and secure an API. People also search for GraphQL query examples or a GraphQL syntax reference; they usually want the same thing while writing a query against an unfamiliar API or designing a new schema.

This cheat sheet covers 29 sections and 94 snippets, from a first query to federation. The output under each example is what it returned when run on October 14, 2026: film queries ran against the public Star Wars API at swapi-graphql.netlify.app, and order, mutation, error and subscription examples ran against a small shop schema on GraphQL Yoga 5.24.5 with graphql-js 17.0.2.

The toolbox at the top parses a query or schema in your browser and reports its depth, field count, estimated cost, aliases and any unused or undefined fragments and variables. It also formats and minifies documents, lints a schema against common naming conventions, and turns a query into curl, fetch, a GET URL and a persisted-query hash. The builders write a type with its resolvers for four servers, Relay connection types, client code for five libraries, and a schema inferred from sample JSON. Nothing you paste is sent anywhere.

How It Differs From the Official Docs

The documentation at graphql.org teaches the language one concept at a time, and the specification defines it precisely. This page puts the parts developers look up during real work side by side, with the behavior of today's servers where they differ from the spec, such as the HTTP status codes. For the REST side of the same decisions, see the REST API cheat sheet; for the payload format, the JSON cheat sheet.


What This GraphQL Cheat Sheet Covers

The 29 sections follow the path of a request, and each link jumps to that section above.


GraphQL Quick Reference

The syntax people look up most often, one line each.

TaskSyntax
Query with an argument{ film(id: "1") { title } }
Named operation with a variablequery Film($id: ID!) { film(id: $id) { title } }
Default valuequery ($first: Int = 10)
Aliaslatest: film(id: "6") { title }
Named fragmentfragment Card on Film { title }, used as ...Card
Inline fragment... on Product { price }
Conditional fieldcrawl @include(if: $full) or @skip(if: $short)
Mutationmutation ($input: CreateOrderInput!) { createOrder(input: $input) { order { id } } }
Type name in the result__typename
Non-null and lists[Order!]!
Next pageorders(first: 20, after: $endCursor)
Schema of a type{ __type(name: "Order") { fields { name } } }

What GraphQL Is

GraphQL is a query language for APIs and a runtime that executes those queries against a typed schema. Facebook built it in 2012 for its mobile apps, released it as open source in 2015, and moved it to the GraphQL Foundation, under the Linux Foundation, in 2019. The current edition of the specification is dated September 2025.

A GraphQL server publishes a schema that lists every type, field and argument it supports. Clients send a document to a single endpoint, the server validates it against the schema, runs a resolver function for each requested field and returns JSON in the same shape as the query. Because the schema is typed and can be queried itself, tools such as GraphiQL, code generators and editors know exactly what is available.

GraphQL usually sits in front of other systems: databases, REST services and microservices. It is a common choice in back-end development for products with web and mobile apps that each need a different slice of the same data.

Where It Fits

GraphQL fits apps whose screens combine data from many sources, teams that want a typed contract between front end and back end, and products with several clients evolving at different speeds. REST remains simpler for public APIs with plain resources and for anything that relies on HTTP caching; the comparison section above has the details.


How a GraphQL Request Is Executed

Following one request through a server explains where each part of this page applies.

  1. The client sends a POST with a JSON body holding query, variables and operationName, or a GET with the same values in the URL.
  2. The server parses the document; a syntax error ends the request with a parse error.
  3. The document is validated against the schema and any extra rules such as depth and cost limits.
  4. Variables are coerced to their declared types, and defaults are filled in.
  5. The executor calls a resolver for each field, top-level mutation fields one at a time, with DataLoader batching database calls.
  6. Field errors become entries in errors with a path, and nulls propagate up to the nearest nullable field.
  7. The response is serialized as JSON with data and errors, and an HTTP status that depends on the server and the Accept header.

Common GraphQL Mistakes

The same problems show up in most schema and API reviews.

In Schema Design

  • Making every field non-null, so one failing resolver wipes out the whole response.
  • Mutations with many scalar arguments instead of a single input object and a payload type.
  • Expected failures thrown as errors instead of returned as typed userErrors.
  • Lists without pagination or a maximum page size.
  • Removing fields without deprecating them first and checking that no client still uses them.

In Servers and Clients

  • Resolvers that query the database per item, the N+1 problem, instead of batching with DataLoader.
  • No depth, alias or cost limits, so one request can ask for millions of objects.
  • Checking that a user is logged in but not that they may read each object, especially through node(id:).
  • Building queries with string concatenation instead of variables.
  • Clients that read data and ignore errors, hiding partial failures.

How to Build Good GraphQL APIs

Design the Schema for Clients

Start from the screens and use cases that will query the API rather than from the database tables, name fields for what they mean to clients, and review schema changes like code. Run a breaking-change check in continuous integration against the operations your clients actually send, and deprecate before you remove.

Then Protect and Watch It

Add depth and cost limits, trusted documents for your own apps and per-object authorization before launch, and test resolvers and whole operations, as the guide to types of software testing describes. Track errors and latency per operation name after each deployment. The TypeScript cheat sheet and React cheat sheet cover the stack most GraphQL clients are written in.


How This GraphQL Cheat Sheet Is Maintained

This page is written and maintained by Bogdan Sandu for TMS Outsource, a software development company. It was checked on October 14, 2026 against the GraphQL specification, September 2025 edition, the GraphQL over HTTP working draft and the Relay Cursor Connections specification. The output of the examples was produced by running them with graphql-js 17.0.2, GraphQL Yoga 5.24.5, graphql-http 1.23.1, Apollo Server 5.5.1, Strawberry 0.335.0, Node.js 24 and curl. If a snippet is wrong or out of date, the date in the byline shows when it was last reviewed.


GraphQL Questions People Actually Ask

The questions developers search for most about GraphQL, answered in a few sentences each.

What is GraphQL?

GraphQL is a query language for APIs and a runtime that executes those queries against a typed schema. Clients send one request to a single endpoint and describe exactly which fields they need, and the response comes back in the same shape. It was created at Facebook in 2012, open-sourced in 2015 and is now maintained by the GraphQL Foundation.

What is the difference between GraphQL and REST?

REST exposes many endpoints with fixed responses and relies on HTTP methods, status codes and caching. GraphQL exposes one endpoint where the client chooses the fields, which avoids over-fetching and extra round trips but makes HTTP caching, rate limiting and error handling work differently. Many companies run both.

What is a GraphQL query, mutation and subscription?

A query reads data, and its fields may run in parallel. A mutation changes data, and its top-level fields run one after another. A subscription keeps a connection open, over WebSockets or Server-Sent Events, and pushes a new result each time an event happens.

What are fragments in GraphQL?

A fragment is a reusable set of fields on a type, such as fragment FilmCard on Film { title releaseDate }, spread into queries with ...FilmCard. Inline fragments such as ... on Product { price } select fields only when an object has that type, which is how you query interfaces and unions.

Why does GraphQL return 200 for errors?

Many servers answer 200 whenever they can return a GraphQL response, and put problems in the errors array, because a single response can contain both data and errors. With the application/graphql-response+json media type, servers return 400 for requests that cannot be parsed or validated, and the 2026 working draft of GraphQL over HTTP recommends 422 for invalid requests and 294 for partial success.

What is the N+1 problem in GraphQL?

Each field resolver runs on its own, so loading a list of 50 orders and then each order's customer can make 51 database queries. DataLoader solves it by collecting the keys requested in the same tick and loading them with one batched query, with a cache that lives for one request.

How do I secure a GraphQL API?

Limit query depth, aliases and estimated cost, cap page sizes, check authorization for every object in the business layer, turn off introspection and field suggestions for private APIs, mask internal error messages, and use trusted documents for first-party clients. Packages such as graphql-armor add most of these limits to Apollo Server and GraphQL Yoga.

How does pagination work in GraphQL?

Most APIs follow the Relay connection convention: a field takes first and after arguments and returns edges with a cursor and a node, plus pageInfo with hasNextPage and endCursor. The client passes endCursor as after to load the next page. Offset pagination with limit and offset also works but breaks when rows are inserted between pages.

Is this GraphQL cheat sheet up to date?

Yes. It was checked on October 14, 2026 against the September 2025 GraphQL specification, the GraphQL over HTTP working draft of September 2026, graphql-js 17.0.2, GraphQL Yoga 5.24.5, Apollo Server 5.5.1, Apollo Client 4.3.3 and Strawberry 0.335. The example outputs come from real runs.

Can I download this GraphQL cheat sheet as a PDF?

Yes. The page has a print stylesheet, so your browser's print command with Save as PDF produces a clean copy with every output and explanation expanded and the navigation left out. The print core button prints only the core snippets for a short reference.