Queries and Fields

This section documents the current GraphQL specification (October 2021), plus the working draft for @defer/@stream/@oneOf, and the GraphQL-over-HTTP specification, as published at graphql.org and spec.graphql.org — which are the references these pages are written and verified against — and, for the integration pages, against Spring for GraphQL (2.0.x), Strawberry, Ariadne, and the client docs for Apollo Client / urql / Relay. No specific patch version is pinned. Some surfaces (Apollo Router/managed federation, graphql-ws internals, the Relay compiler internals, GraalVM native) are linked rather than documented in depth.

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.

This page covers the anatomy of a GraphQL document: the three operation types, naming an operation, fields and their nested selection sets, arguments, aliases, and comments — the vocabulary every later page in this section builds on.

The three operation types

Every GraphQL document sends one of three operation types to the server:

query {
  book(id: "1") {
    title
  }
}

mutation {
  addBook(title: "Dune", author: "Frank Herbert") {
    id
  }
}

subscription {
  bookAdded {
    id
    title
  }
}

query reads data and is side-effect free — a client (and the server) can safely retry it, cache its result, or run several of them in parallel. mutation performs a write and, when a document contains several top-level mutation fields, they execute serially, one after another, rather than in parallel like sibling query fields — because each one may depend on the side effects of the one before it. subscription opens a long-lived connection over which the server pushes an event payload every time something of interest happens, instead of returning a single response. query is by far the most common operation and is also the only one with a shorthand: a document that omits the operation type keyword and the operation name entirely, and starts directly with a selection set, is implicitly a query:

{
  book(id: "1") {
    title
  }
}

This shorthand form is convenient for quick, throwaway requests, but production clients almost always use the full query keyword together with an operation name, for the reasons in the next section. Mutations are covered in depth on Mutations and subscriptions on Subscriptions; this page focuses on queries, since fields, arguments, and aliases work identically across all three operation types.

Naming an operation

An operation name is an optional identifier placed right after the operation type keyword:

query GetBookById {
  book(id: "1") {
    title
    author
  }
}

GetBookById names this specific query the way a function name documents a function. It has no effect on what data comes back, but it pays off in three concrete places:

  • Debugging and logging. Server-side request logs, APM traces, and GraphQL-aware proxies group and report traffic by operation name. An unnamed query shows up in logs as an anonymous blob of selections; a named one shows up as GetBookById, immediately identifying which part of the client sent it.

  • Client codegen. Tools that generate typed request/response code from .graphql documents (Apollo’s codegen, Relay’s compiler, graphql-code-generator) use the operation name to name the generated types and functions — an unnamed operation forces the tool to invent a name.

  • Multi-operation documents. A single .graphql document or request can define several operations; when it does, every operation in that document must be named, because the request also carries a operationName field telling the server which one of them to execute.

Operation names are recommended even for single, ad-hoc queries — there is no downside to naming them, and the convention pays for itself the moment a request needs debugging in production. See graphql.org/learn/queries ("Operation Name") for the full rationale.

Fields and nested selection sets

A field is a single named unit of data a query asks for. Fields are grouped inside curly braces into a selection set, and any field whose value is itself an object (not a scalar like a string, number, or boolean) must carry its own nested selection set naming the sub-fields to fetch from it:

query GetBookWithAuthor {
  book(id: "1") {
    title
    publishedYear
    author {
      name
      country
    }
  }
}

Here book is an object field, so it needs a nested selection (title, publishedYear, author); author is itself an object field, so it needs its own nested selection (name, country); and title, publishedYear, name, and country are scalar fields, which is why they are leaves — a scalar field never carries a selection set of its own. A GraphQL document is invalid if an object field is requested without a selection set, or if a scalar field is given one.

This is the core mechanism that makes GraphQL a client-specified query language: nothing is returned that was not explicitly asked for, and there is no equivalent of a REST endpoint’s fixed response shape or an ORM’s SELECT *. A client that only needs a book’s title fetches only title; a client rendering a full detail page adds author, genre, reviews, and any other fields it needs, in the same round trip, without a new endpoint or a new backend method. See graphql.org/learn/queries ("Fields") for further examples, including selecting fields across a list.

Field arguments

Any field, at any depth in a selection set, can take arguments that parameterize what it returns — not just the top-level fields:

query GetBookAndReviews {
  book(id: "1") {
    title
    reviews(first: 3, minRating: 4) {
      rating
      comment
    }
  }
}

book takes an id argument selecting which book to fetch, and the nested reviews field takes its own first and minRating arguments, independently narrowing which reviews come back for that book. Every field declares its own arguments in the schema — with their own names, types, and optional default values — so what a given field accepts (a lookup key, a pagination window, a filter, a sort order) is whatever that field’s definition says, not a convention imposed from outside. Because arguments are per-field rather than global to the whole request the way a REST query string is, a single query can filter and paginate several unrelated parts of the response independently. See graphql.org/learn/queries ("Arguments") for how argument types and default values are declared.

Aliases

Two sibling fields in the same selection set cannot share a result key by default — the field name is what becomes the JSON key in the response, so requesting the same field twice with different arguments would produce two entries competing for the same key. An alias solves this by giving one of those selections a different result key:

query CompareEditions {
  firstEdition: book(id: "1") {
    title
    publishedYear
  }
  latestEdition: book(id: "42") {
    title
    publishedYear
  }
}

firstEdition and latestEdition are both selections of the book field, each with its own id argument; without the aliases, both would try to occupy the book key in the response and the second would simply overwrite the first. The alias goes in front of the field name, separated by a colon, and it is the alias — not the underlying field name — that appears as the key in the returned JSON, as the next section shows concretely.

The shape of the query mirrors the shape of the response

A GraphQL response is a JSON object whose shape is structurally identical to the shape of the query that produced it: every selected field becomes a key at the corresponding nesting level, in the same order, and nothing else is added. There is no separate response schema to consult — the query is the response’s shape, just with data filled in instead of field names. Reading the query above top-to-bottom already tells you exactly what the response object will look like.

Running the aliased query from the previous section illustrates this concretely, since the aliases become the actual keys in the output:

query CompareEditions {
  firstEdition: book(id: "1") {
    title
    publishedYear
  }
  latestEdition: book(id: "42") {
    title
    publishedYear
  }
}
{
  "data": {
    "firstEdition": {
      "title": "Dune",
      "publishedYear": 1965
    },
    "latestEdition": {
      "title": "Dune: Deluxe Edition",
      "publishedYear": 2019
    }
  }
}

Every top-level response field is nested one level under data (a top-level errors array sits alongside data when something went wrong); below that, firstEdition and latestEdition appear exactly where firstEdition: book(…​) and latestEdition: book(…​) appeared in the query, and each nested object carries exactly title and publishedYear — no more, no less — because that is exactly what the corresponding selection set asked for. This predictability is a large part of why typed client tooling (generated response types, normalized caches keyed by field/argument combinations) works as well as it does with GraphQL: the response’s shape can be derived from the query text alone, before the request is ever sent. See graphql.org/learn/queries ("Aliases") for the canonical version of this example.

Comments in GraphQL documents

A # starts a comment that runs to the end of the line, anywhere in a GraphQL document — inside a selection set, next to a field, or on its own line:

# Fetches a single book by id, including up to three of its most recent reviews.
query GetBookWithReviews {
  book(id: "1") {
    title
    reviews(first: 3) {  # newest reviews first, per the schema's default ordering
      comment
    }
  }
}

Comments are stripped before the document is executed and have no effect on the request or response; they exist purely to document intent for whoever reads the .graphql file next — a role similar to comments in any other source file. Some tools additionally read specially-formatted comments (description strings in schema definition language use """, not ) for documentation purposes, but plain comments in an operation document are never surfaced to the server or the client beyond the text of the request itself.

Next steps

With operations, fields, arguments, aliases, and the response-shape principle in place, Variables, Directives \& Fragments covers how to parameterize a query from outside its text, conditionally include or skip fields, and reuse selection sets across operations. For the two operation types only touched on here, see Mutations and Subscriptions; for the concepts this page assumes — the schema, types, and the overall request/response cycle — see Getting Started.