Re-indenting a GraphQL document is mostly brace counting. Every { raises the indent level, every } lowers it, and each field goes on its own line. That's enough to turn a query pasted as one long line into something readable, with no knowledge of the schema it's written against.
Comments don't survive formatting
Know this before you paste in anything you care about. The tokenizer treats # as "skip to end of line" and never emits a token for what follows, so a formatted query comes back correctly indented with every comment removed. They aren't moved to another line. A query annotated with # TODO: paginate this or # deprecated, remove after Q3 loses those lines without warning.
The tool pretty-prints query structure and doesn't try to preserve source. Keep the commented version in your editor. Use the output for pasting into a GraphQL client, and don't commit it back to your codebase.
What it checks, and what it takes on trust
The formatter validates two things: every { has a matching }, and every ( has a matching ). Anything else passes through unvalidated, including field names that don't exist on the type, a missing argument, or a variable that's referenced but never declared. A query your GraphQL server would reject instantly can still come out perfectly indented. That's the price of not needing your schema as input. Schema-aware validation, the kind a GraphQL IDE plugin does, needs the schema. Structural pretty-printing doesn't.
Directives and block strings get special handling
Two GraphQL-specific forms are handled on purpose rather than falling out of generic tokenizing. A directive (@include(if: $showDetails)) stays attached to the field it follows instead of starting a new line. Triple-quoted block strings, used for multi-line description arguments, are parsed as a single token, so a newline inside the quotes doesn't throw off the brace-depth tracker:
query GetUser($id: ID!, $showDetails: Boolean!) {
user(id: $id) {
name
email @include(if: $showDetails)
bio(format: MARKDOWN)
}
}Spacing inside arguments vs. inside the selection set
Commas are treated differently depending on where they appear. Inside parentheses, in an argument list, a comma gets a trailing space: (first: 10, after: $cursor). At the top level of a selection set, GraphQL doesn't require commas between fields at all, since newlines do that job. The formatter leaves spacing there alone and puts each field on its own indented line, however the input separated them.