GraphQL Query Formatting and Best Practices
Learn the anatomy of a GraphQL query, how to format operations, fragments and variables, and the best practices that keep queries fast and readable.
.jpg)
GraphQL queries start small and tidy. A few months later they have nested selections five levels deep, a dozen variables, inline fragments and arguments spread across the screen, and someone pastes the whole thing into a chat window as one long line. Readable queries are easier to review, debug and reuse. This guide explains what GraphQL is, how its query language is structured, how to format queries so they stay readable, and which best practices keep them fast and maintainable.
What is GraphQL?
GraphQL is a query language for APIs and a runtime for executing those queries against a type system you define. It was created at Facebook, open-sourced in 2015, and is now maintained under the GraphQL Foundation. The official GraphQL documentation is an excellent introduction, and the formal rules are in the GraphQL specification.
Compared with a typical REST API, GraphQL changes three things:
- One endpoint. Clients send queries to a single URL, usually over HTTP POST.
- Client-defined responses. The client lists exactly which fields it wants, so the response has the same shape as the query.
- A strongly typed schema. Every field has a type, which allows validation, auto-completion and documentation tools.
That last point matters for formatting. Because the schema is known, tools can parse your query into a syntax tree and print it back consistently, without guessing.
Anatomy of a GraphQL query
A query is made of a few building blocks.
query GetUser($id: ID!, $first: Int = 10) {
user(id: $id) {
id
name
avatarUrl(size: 64)
posts(first: $first, orderBy: { field: CREATED_AT, direction: DESC }) {
edges {
node {
id
title
}
}
}
}
}
- Operation type:
query,mutationorsubscription. A shorthand query with just braces is allowed when it has no name or variables. - Operation name:
GetUser. Optional, but strongly recommended for debugging and logging. - Variables:
$id: ID!declares a typed input.!means required, and= 10sets a default. - Selection set: the fields inside braces. Nested braces select fields on related objects.
- Fields and arguments:
avatarUrl(size: 64)passes an argument to a field. - Aliases:
smallAvatar: avatarUrl(size: 32)renames a field in the response, which lets you request the same field twice with different arguments. - Fragments: reusable groups of fields, covered below.
- Directives: annotations such as
@include(if: $flag)and@skip(if: $flag)that change execution. - Comments: lines starting with
#.
Commas are optional "insignificant" characters in GraphQL, treated like whitespace. That is why { id name } and { id, name } are identical. It also explains why teams usually leave them out.
Why GraphQL formatting matters
The GraphQL parser ignores most whitespace, so the same query can be written in very different layouts:
query GetUser($id:ID!){user(id:$id){id name posts(first:5){edges{node{id title}}}}}
The version above is valid, and nearly impossible to review. Formatting gives you:
- Visible structure. Each indentation level matches one level of nesting in the response, so the shape of the data is visible in the query.
- Easier debugging. Queries copied from network logs, browser developer tools or server traces are typically minified.
- Meaningful diffs. One field per line means adding or removing a field changes one line.
- Fewer syntax mistakes. Missing braces and parentheses are far easier to spot in a regular layout.
- Shared habits. A consistent style removes arguments from code review.
Core formatting conventions
The most common convention is the one used by Prettier and by the reference JavaScript implementation, which print queries like this:
Two-space indentation. Each selection set is indented one level deeper than its parent.
One field per line. Never place several fields on one line, except very short argument lists.
Opening brace on the same line. Write user(id: $id) { and close with } on its own line.
Space after the colon. Write id: $id and $id: ID!.
Wrap long argument lists. When a field's arguments exceed your line width, put one argument per line:
search(
query: $term
first: 20
after: $cursor
filters: { status: ACTIVE, minScore: 4 }
) {
nodes {
id
}
}
Omit commas. They are optional, so most formatters drop them between fields and arguments.
Name every operation. query GetUser is more useful in logs than an anonymous query.
Blank line between operations and fragments in the same document.
Use PascalCase for operation and fragment names, and camelCase for fields and variables. This follows common schema conventions, though your schema's naming comes first.
Fragments, variables and directives
These three features do most of the work in real queries, and each has formatting implications.
Fragments
A fragment is a reusable set of fields on a type. They keep queries short and let UI components declare their own data needs.
query FeedPage($first: Int!) {
feed(first: $first) {
...PostSummary
}
}
fragment PostSummary on Post {
id
title
author {
...AuthorBadge
}
}
fragment AuthorBadge on User {
id
name
avatarUrl(size: 32)
}
Put fragments after the operation that uses them, or next to the component that needs them. Inline fragments, such as ... on Photo { width height }, select fields on one concrete type of an interface or union.
Variables
Never build queries by concatenating user input into the query string. Use variables instead, and send them in a separate JSON object. This protects against injection and lets servers cache and validate queries.
{
"id": "42",
"first": 5
}
Directives
@include and @skip toggle fields based on a variable. Put each directive on the field it modifies, and keep it on the same line when short:
query Profile($id: ID!, $withPosts: Boolean!) {
user(id: $id) {
name
posts @include(if: $withPosts) {
id
}
}
}
Real-world use cases
Reading captured queries. Browser network tabs, proxy logs and server traces show GraphQL as a single minified string. Formatting is the first step to understanding what a client is requesting.
Code review. Queries embedded in application code are part of your contract with the API. Consistent layout lets reviewers see exactly which fields changed.
Documentation and bug reports. Well-formatted queries are easier to paste into an issue, a ticket or a support thread.
Learning and exploration. Tools such as GraphiQL and the Apollo Sandbox let you build queries interactively. Formatting the result helps you understand how nesting maps to the response.
Migrating from REST. Rewriting several REST calls as one GraphQL query is clearer when the selection set mirrors the data you need.
Common mistakes
- Anonymous operations. They are harder to find in logs, monitoring and caches.
- Over-fetching in disguise. Selecting every field "just in case" defeats the main benefit of GraphQL.
- Deeply nested queries. Excessive depth can be slow, and servers often enforce depth and complexity limits.
- Concatenating values into the query string. Use variables.
- Duplicate fragments across files. Share fragments, and keep one definition.
- Ignoring pagination. Always pass
firstorlimitarguments to lists, and follow your schema's pagination pattern. - Treating the formatter as a validator. A formatter makes a query readable. It does not check that fields exist in your schema.
Step-by-step: format a GraphQL query with DevUtilX
For a quick clean-up, with no setup, use the DevUtilX GraphQL Query Formatter. It runs in your browser.
- Open the GraphQL query formatter tool.
- Paste your query, with or without fragments and variables, into the input editor.
- Run the formatter and review the indented output.
- Copy the result into your editor, test console or documentation.
The query is processed locally, so nothing is sent to a server. If the formatter reports a syntax error, check for an unclosed brace or parenthesis, an unterminated string, or a stray character. Keep the DevUtilX GraphQL cheatsheet open as a quick reference for syntax such as aliases, fragments and directives.
Automating formatting in a project
On a team, formatting should happen automatically.
Prettier supports GraphQL out of the box, including .graphql and .gql files and queries inside JavaScript template tags such as gql and graphql. One config covers your JavaScript, CSS and GraphQL together. Our guide on JavaScript code formatting describes the setup.
GraphQL ESLint (@graphql-eslint/eslint-plugin) lints operations against your schema. It can flag unknown fields, unused fragments, missing operation names and deprecated fields, which a formatter alone cannot.
Code generation. Tools like GraphQL Code Generator turn your queries into typed code, which catches mistakes at build time instead of in production.
CI checks. Run the format check and the schema-aware linter on every pull request.
Keep the usual separation of duties: formatters own layout, linters and validators own correctness.
Best practices
- Name every operation and fragment.
- Request only the fields you need. Smaller responses are faster and clearer.
- Use variables for every dynamic value.
- Prefer fragments for repeated field groups, and colocate them with the code that uses them.
- Keep nesting reasonable. If a query needs many levels, consider a dedicated field or query in the schema.
- Paginate lists and respect any server limits.
- Store queries in
.graphqlfiles or tagged templates so tooling can format and lint them. - Use persisted queries in production where supported, to reduce payload size and limit what clients can run.
- Separate formatting from validation. Use both.
Comparison: GraphQL, REST and other approaches
| Aspect | GraphQL | REST | gRPC |
|---|---|---|---|
| Endpoints | Usually one | Many resource URLs | Service methods |
| Response shape | Chosen by the client | Fixed by the server | Defined by a protobuf message |
| Over/under-fetching | Avoided by design | Common | Defined per method |
| Typing | Schema required | Optional (OpenAPI) | Strongly typed |
| Caching | Needs extra work | Works well with HTTP caches | Not HTTP-cache friendly |
| Best for | Flexible clients, complex data graphs | Simple resources, public APIs | Internal service-to-service calls |
GraphQL is not automatically better. It shines when clients have varied data needs, and it adds complexity for caching, security limits and monitoring. The right choice depends on your project.
How a formatter reads your query
Every GraphQL formatter does the same four things: it reads characters, groups them into tokens, builds a tree, and prints the tree back out. Knowing these steps explains why formatting is safe and why a few things, such as comments, need special care.
Lexing: characters into tokens
The lexer scans the text and produces tokens: punctuators such as {, }, (, ), :, !, $, @ and ..., names, numbers and strings. Whitespace, line breaks, commas and the byte order mark are "ignored tokens". The lexer discards them, which is exactly why { id name } and { id, name } are the same query.
Comments are also ignored tokens. A # starts a comment that runs to the end of the line. Because the parser never sees them, comments need extra handling in formatters.
Parsing: tokens into a syntax tree
The parser follows the grammar in the specification and builds an abstract syntax tree. A document contains definitions. An operation definition contains variable definitions, directives and a selection set. A selection set contains fields, fragment spreads and inline fragments. Nothing in this tree records how the original text was laid out.
That is the core safety argument. If the formatter prints a tree that equals the original tree, the query means the same thing, whatever the whitespace looks like.
Printing: the tree back into text
The printer walks the tree and applies layout rules. A simple printer, like the print function in the reference JavaScript implementation, uses fixed rules: two spaces per level, one field per line. A smarter printer, like Prettier's, measures line width and decides whether a group of arguments fits on one line or must break.
Here is a minimal round trip with the graphql npm package:
import { parse, print } from "graphql";
const messy = "query A($id:ID!){user(id:$id){id,name}}";
console.log(print(parse(messy)));
The output is a normalized document with each field on its own line. Parse errors throw a GraphQLError with a line and column, which is how formatting tools report an unclosed brace.
Why comments are the tricky part
The reference print function works from the tree, and the tree has no comment nodes. So a pure parse-and-print round trip drops your # comments. Prettier solves this by attaching comments to nearby nodes and re-printing them. Before you adopt a custom script built on print, test it against a query with comments. If it deletes them, use Prettier instead.
Block strings and descriptions
Triple-quoted strings (""") are block strings. They are mostly used for descriptions in schema definitions, but they are legal as argument values too. The lexer strips common leading indentation from block strings, so re-indenting them does not change their value. A formatter must still leave the text lines inside untouched, apart from that indentation.
Worked example: cleaning up a captured query
Suppose you copy this request body from a browser network tab. Real traffic often looks like this, with the query and variables bundled in one JSON object.
{"operationName":"ProjectBoard","variables":{"slug":"apollo","first":20,"labels":["bug","p1"]},"query":"query ProjectBoard($slug:String!,$first:Int!,$labels:[String!]){project(slug:$slug){id name issues(first:$first,labels:$labels,state:OPEN){totalCount nodes{id title assignee{...Person} labels{name color}}}}} fragment Person on User{id login avatarUrl(size:40)}"}
Two things stand in your way: the query is a JSON string with escaped content, and it has no line breaks. Work through it in three steps.
Step 1: extract the query string. Copy the value of the query field only, without the surrounding quotes. If the string contains \n or \" escapes, unescape them first. Variables belong in a separate pane.
Step 2: format the query. Paste it into the formatter. You get a readable document:
query ProjectBoard($slug: String!, $first: Int!, $labels: [String!]) {
project(slug: $slug) {
id
name
issues(first: $first, labels: $labels, state: OPEN) {
totalCount
nodes {
id
title
assignee {
...Person
}
labels {
name
color
}
}
}
}
}
fragment Person on User {
id
login
avatarUrl(size: 40)
}
Step 3: format the variables. Run the variables object through a JSON formatter. Our JSON formatting guide covers the details.
Now the structure is visible. The query asks for a project, its open issues, and the assignee and labels of each issue. The totalCount field sits beside nodes, so it is a connection-style pagination shape. If you were debugging a slow request, this is the point where you would notice that every issue triggers an assignee lookup and ask whether the server batches those.
Choosing a style and configuring it
Most teams should accept the defaults of their formatter. A few options are still worth knowing.
Prettier settings that affect GraphQL
Prettier reads a single configuration file. These options matter for .graphql files:
{
"printWidth": 80,
"tabWidth": 2,
"bracketSpacing": true,
"overrides": [
{
"files": "*.{graphql,gql}",
"options": { "printWidth": 100 }
}
]
}
printWidthdecides when argument lists wrap. Queries have long argument lists, so some teams raise it for GraphQL files only, as shown in the override.tabWidthsets indentation. Two spaces is the strong convention, and it keeps deep selections from running off the screen.bracketSpacingcontrols the spaces inside object values such as{ field: CREATED_AT }.
Prettier has no option to keep commas. It removes them from lists of fields and arguments, which matches community style.
Queries inside JavaScript and TypeScript
Prettier formats GraphQL inside tagged templates named gql or graphql, and also inside templates preceded by a /* GraphQL */ comment. Interpolations such as ${PostFragment} are supported only at the top level of the document, between definitions. An interpolation in the middle of a selection set stops Prettier from formatting that template. Place fragment interpolations on their own line at the end of the document:
const POST_QUERY = gql`
query PostPage($id: ID!) {
post(id: $id) {
...PostBody
}
}
${POST_BODY_FRAGMENT}
`;
Fragment and field ordering
Prettier never reorders fields, because order can matter for readability and, in mutations, for execution. Teams often agree on informal rules instead: identifiers first (id, then __typename if needed), scalar fields next, then nested objects, then fragment spreads last. A linter such as GraphQL ESLint can enforce ordering rules if you want a machine to do it.
Setting up editors, hooks and CI
A one-time setup stops formatting debates for good.
Editor integration
In VS Code, install the Prettier extension and set it as the default formatter for GraphQL files. The GraphQL language extension adds syntax highlighting, schema-aware auto-completion and inline validation when you point it at a schema file or endpoint with a .graphqlrc configuration. Enable format on save so layout is never a manual step.
Other editors have equivalents. JetBrains IDEs offer a GraphQL plugin, and Neovim and Emacs users can run the GraphQL language server alongside Prettier.
Pre-commit hook
A pre-commit hook formats only the files you changed, so it is fast. With husky and lint-staged, the configuration is short:
{
"lint-staged": {
"*.{graphql,gql}": "prettier --write",
"*.{js,jsx,ts,tsx}": "prettier --write"
}
}
Keep hooks fast. If a hook takes more than a few seconds, people disable it. Leave slow schema validation for CI.
Continuous integration
CI is the backstop, because hooks can be skipped. Run a check, not a rewrite, so the build fails when files are not formatted:
name: lint
on: [pull_request]
jobs:
graphql:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx prettier --check "**/*.{graphql,gql}"
- run: npx eslint . --ext .graphql,.js,.ts
The --check flag exits with a non-zero code when any file differs from Prettier's output. The ESLint step runs the schema-aware rules that Prettier cannot.
Validating against a schema
Formatting accepts any syntactically valid query, including one that asks for a field that does not exist. Schema validation closes that gap. GraphQL ESLint with a schema file catches unknown fields, wrong argument types and unused variables. Code generation tools do the same job as a side effect, because they refuse to generate types for an invalid operation. Whichever you pick, run it in CI against the schema the server actually ships.
Diffing and reviewing GraphQL changes
Format first, then compare. Two queries that differ only in layout produce a noisy diff, and a noisy diff hides real changes. Our guide to comparing files and diffs explains why normalizing text before comparing helps.
When you review a query change, ask these questions:
- Does the new field belong in the response, or is a fragment already fetching it?
- Did the change add a nested list without a
firstorlimitargument? - Did a variable become required (
!)? That breaks callers who do not send it. - Did the operation name change? Dashboards and persisted-query manifests may key on it.
- Is a deprecated field still selected?
A mechanical rule helps here. If a pull request mixes a bulk reformat with real edits, split it into two commits: formatting first, behavior second. Reviewers can then skim the first and study the second.
Performance and payload considerations
Formatting changes nothing about how the server executes a query. It does affect what you send over the wire and what you store, so it helps to know where the costs are.
Pretty for people, compact for the wire
Whitespace adds bytes to every request. In source code you want the readable form. At runtime, many clients strip whitespace from the query string before sending it, and some build tools do the same at compile time. Compression on the HTTP layer also shrinks repeated indentation well, so the difference is usually small. If request size matters, persisted queries remove it entirely, because the client sends an identifier instead of the document. The same trade-off between readable and compact forms appears in our guide to JSON minification and payload size.
Persisted queries
With persisted queries, the client and server agree on a mapping from an identifier to a query document. The client sends the identifier and the variables. There are two common approaches:
- Build-time manifests. A build step extracts every operation, hashes it and publishes the manifest to the server. The server accepts only known identifiers, which also restricts what clients can run.
- Automatic persisted queries. The client first sends only a hash. If the server does not know it, the client retries with the full query, and the server stores it for next time.
Either way, format before you hash. If your tooling hashes the raw text, a harmless re-indent produces a new identifier. Most build tools normalize the document first, and you should confirm yours does.
Depth and breadth
A query's cost depends on how many objects it touches, not on how many lines it has. Deep nesting through bidirectional relationships, such as user { friends { friends { friends } } }, can multiply work at every level. Servers defend with depth limits, complexity scoring or timeouts. A well-formatted query makes depth visible: if you see the indentation marching toward the right edge, that is a cue to review the query.
Fetching lists
Every list field multiplies the size of the response. Page lists with first and a cursor, and ask for totalCount only when you need it, because some servers compute it with an extra database query.
Security considerations
GraphQL's flexibility is also its attack surface. Formatting is not a security control, but readable queries make security review practical.
Use variables, never string concatenation. Variables are passed as typed values and checked against the schema. A concatenated string can change the structure of the query itself.
Limit depth and cost. Without limits, a client can build very expensive queries from a small document. Add depth limits, complexity limits and request timeouts on the server.
Watch for alias and batch abuse. Aliases let a client call the same field many times in one request. A login mutation repeated under hundreds of aliases can bypass a naive per-request rate limit. Rate limit by the work done, not only by the number of HTTP requests.
Think about introspection. Introspection lets clients list the whole schema. It is useful in development. Some teams disable it in production for private APIs. Disabling it hides the schema but does not replace authorization.
Authorize in the resolvers or a data layer. Hiding a field from a client query does not protect it. Every field that returns sensitive data must check permissions on the server.
Keep secrets out of queries and logs. Operation names and variables often end up in logs. Do not put tokens in variables, and scrub sensitive variables before logging them. Never paste real credentials into any online formatter, ours included.
Troubleshooting guide
When a formatter or server rejects a query, the symptom usually points straight to the cause.
| Symptom | Likely cause | Fix |
|---|---|---|
Syntax Error: Expected Name, found "}" |
An empty selection set such as user { }, or a trailing field with arguments missing |
Add at least one field inside the braces |
Syntax Error: Unexpected <EOF> |
A missing closing brace or parenthesis | Count opening and closing braces, or format a smaller piece at a time |
Syntax Error: Unterminated string |
A quote was not closed, or a string spans lines without being a block string | Close the string, or use """ for multi-line text |
Syntax Error: Cannot parse the unexpected character |
A smart quote, a stray backslash or a copied JSON escape | Replace curly quotes with plain quotes and unescape the JSON string |
Unknown argument or Cannot query field |
The query is valid syntax but does not match the schema | Check the schema or update the generated types. A formatter cannot fix this |
Variable "$id" of required type "ID!" was not provided |
The variables object lacks a required value | Add the variable to the JSON, or give it a default in the query |
Variable "$id" is never used |
A declared variable is not referenced | Remove it, or use it in an argument |
Unknown fragment "Person" |
The fragment is defined in another file or not included | Add the definition to the document or import it |
Fragment "X" is never used |
A defined fragment has no spread | Remove it, or spread it where needed |
Must provide query string |
The request body is missing the query field, often from a wrong content type |
Send application/json with a query key |
Prettier leaves a gql template unformatted |
An interpolation sits inside a selection set | Move interpolations to the top level of the document |
| Formatting changes every line in a file | Tabs versus spaces, or CRLF versus LF line endings | Set .editorconfig and Prettier's endOfLine option so everyone agrees |
If a captured query will not format and you cannot see why, copy it into a plain-text editor and look for characters that are not ASCII. Smart quotes and zero-width spaces pasted from chat tools and web pages cause errors that look like nothing.
Migrating an existing codebase to a formatted style
Introducing formatting to a large repository is mostly a social problem. These steps keep it painless.
- Agree on the tool. Choose Prettier unless you have a reason not to, and commit the configuration.
- Run it once, on everything, in a dedicated commit. Do not mix this with other changes, so the commit is easy to review and revert.
- Record the commit in a
.git-blame-ignore-revsfile, and tell your hosting platform to use it. Blame then points at real authors instead of the formatting commit. - Turn on enforcement. Add the pre-commit hook and the CI check in the same pull request, or soon after.
- Add linting gradually. Start with rules that catch bugs, such as unknown fields and unused fragments. Add naming rules after the team has settled.
- Name the anonymous operations. Search for
query {andmutation {and give each operation a name. Check any persisted-query manifest or monitoring dashboards that relied on the old names.
Scattered .graphql strings inside JavaScript can wait. Convert them to tagged templates first, then format them.
Team conventions and a review checklist
Write the conventions down once, in the repository, so new contributors can find them. A short checklist is enough:
- Every operation has a PascalCase name that describes what it does, such as
GetUserorUpdateProfile. - Operation names are unique across the project, since logs and manifests key on them.
- Queries live in
.graphqlfiles orgqltemplates, never in plain strings. - Fragments are colocated with the component that uses them, and named after the type and purpose, such as
PostSummary. - Every dynamic value is a variable.
- Every list has a page size.
- Mutations request the fields the UI needs to update itself, not the whole object.
- Formatting runs in an editor, a hook and CI.
A useful naming pattern pairs a verb with a noun: GetInvoice, ListInvoices, CreateInvoice. The verb tells you whether the operation reads or writes, even from a log line.
Answering common objections
"Formatting hides who wrote the line." It does, once. The ignore-revs file fixes this.
"Our queries are generated." Generated code does not need hand formatting. Format the source documents that the generator reads, and exclude the output directory from checks.
"One line per field makes files long." Length is a feature of the format. A short query is still short, and a long one deserves to look long, because it is expensive to run.
Formatting mutations, subscriptions and input objects
Queries get most of the attention, but mutations and subscriptions follow the same layout rules. A few patterns deserve their own examples.
Mutations with input objects
Modern schemas usually take a single input argument. Declare it as a variable, so the document stays short and the data travels as JSON:
mutation UpdateProfile($input: UpdateProfileInput!) {
updateProfile(input: $input) {
user {
id
displayName
}
errors {
field
message
}
}
}
The matching variables hold the nested values:
{
"input": {
"displayName": "Sam",
"links": [{ "label": "Site", "url": "https://example.com" }]
}
}
Note the payload shape. Returning the changed object lets clients update their caches without a second request. Returning an errors list lets the server report validation problems as data, separate from transport-level errors.
Several mutations in one request
A document may contain several top-level mutation fields. The server runs them one after another, in the order written, so layout is meaningful here. Use aliases so that each result has a distinct key:
mutation ReorderTasks {
first: moveTask(id: "t1", position: 1) {
id
}
second: moveTask(id: "t2", position: 2) {
id
}
}
Literal values like these are fine in a quick test. In application code, move them into variables.
Subscriptions
A subscription has the same syntax as a query, but the server keeps the connection open and pushes results as events happen. Because each event repeats the selection set, keep subscriptions narrow:
subscription OnCommentAdded($postId: ID!) {
commentAdded(postId: $postId) {
id
body
author {
name
}
}
}
A subscription document may select only one top-level field. Request the minimum, since you pay for every field on every event.
Directives with several arguments
When a field carries both arguments and directives, put the directive after the argument list and break the line if it grows long. Formatters apply the same width rule to directives as they do to arguments. Custom directives defined by your server or client library, such as cache hints or client-only flags, follow the same layout.
A short history of the design
GraphQL was built at Facebook to serve its mobile applications, which needed to fetch precisely the data a screen displayed, over slow connections, from a large and interconnected data model. It was open-sourced in 2015, and the specification has been revised since then through an open working group.
Three design choices from that origin explain the syntax you format today.
The query mirrors the response. Nested braces were chosen so a developer can read the query and predict the JSON that comes back. That is why indentation is so valuable: it is the shape of your data.
Names beat positions. Arguments are named, and fields are selected by name, so reordering arguments never changes meaning. This is also why formatters can wrap arguments freely.
Commas stopped mattering. Early drafts made them optional so that editors and pasted JSON-like text would not trip people up. The result is the comma-free style most tools now print.
These decisions favor reading over writing. A format that is easy to scan is the natural companion to a language that was designed to be scanned.
Case study: a team that stopped debating layout
A small product team kept its GraphQL operations in template strings scattered through a React application. Reviews were slow because every pull request included whitespace changes, and nobody could tell whether a field had been added or only moved. Some queries were anonymous, so server logs showed a long list of entries all labeled the same way.
The fix took an afternoon. They added Prettier to the existing configuration, ran it across the repository in one commit and listed that commit in the blame-ignore file. They turned on a lint rule requiring operation names and renamed the anonymous queries. They added the format check to CI.
The lasting benefit was not the pretty layout. It was that diffs shrank to the lines that mattered, and that the logs became searchable by operation name. When a slow endpoint appeared in monitoring, they could point at a named operation and the exact file that sent it.
The details will differ in your organization, but the pattern holds: automate layout once, then spend review time on the contract with the API.
Accessibility and readability
Readable code is also accessible code, in a practical sense. A few small choices help everyone, including people who use screen readers, magnifiers or large fonts.
- Keep lines short. Wrapping at around 80 to 100 characters avoids horizontal scrolling, which is painful when zoomed in.
- Use consistent indentation. Screen readers that announce indentation levels give a reliable picture of nesting only when the indentation is regular.
- Pick meaningful names.
GetUserProfileread aloud conveys more thanQ1. - Do not rely on color alone. Syntax highlighting helps, but formatting should carry the structure for readers who cannot see the colors.
- Format snippets in documentation. Code samples in blog posts and READMEs should be formatted, so readers can follow them by sight or by ear.
FAQ
Are commas required in GraphQL?
No. Commas are treated as whitespace. Most formatters remove them, and leaving them in is harmless.
What is the difference between a query and a mutation?
Queries read data and mutations change it. Both use the same syntax. Servers run top-level mutation fields serially, in order, while query fields may run in parallel.
Do I need to name my queries?
Not for the syntax to be valid, but you should. Names make logs, traces and debugging far easier.
Can a formatter change the meaning of my query?
No. A correct formatter changes only whitespace and optional punctuation. The parsed query stays identical.
Is it safe to paste queries into an online formatter?
Only if it runs locally. Queries can reveal field names, internal types and sometimes sensitive variables. DevUtilX processes your input in the browser, but never paste secrets or tokens into any tool.
Should I use fragments or just repeat fields?
Use fragments when the same group of fields appears in several places, or when a component should declare its own data needs. For one-off queries, plain fields are fine.
Does formatting change how fast my query runs?
No. The server parses the document into the same tree either way. Whitespace affects only the size of the text you send, and compression or persisted queries remove most of that cost.
Why did my formatter delete my comments?
Some tools print from the syntax tree, and the tree has no comment nodes. Prettier attaches comments to nearby nodes and keeps them. If a custom script drops comments, switch to Prettier or add comment handling.
Can I format a query that contains syntax errors?
Usually not. A formatter has to parse the query first, so an unclosed brace stops it. Fix the error shown, usually a line and column, and run it again.
Should variables and queries live in the same file?
Keep the operation in a .graphql file or template, and supply variables from application code at runtime. For bug reports and tests, a separate JSON file for variables is easy to read and diff.
What is the difference between a fragment spread and an inline fragment?
A spread, such as ...PostSummary, refers to a named fragment defined elsewhere. An inline fragment, such as ... on Photo { width }, defines the fields in place and is usually used to select type-specific fields on a union or interface.
Can I use tabs instead of spaces?
The parser does not care. Tabs are ignored whitespace. Pick one style for the repository and let the formatter and .editorconfig enforce it.
Conclusion
Readable GraphQL is mostly discipline: name your operations, indent by nesting, put one field per line, use variables and fragments, and let a tool enforce the layout. Add a schema-aware linter so mistakes are caught before they reach production. For a fast clean-up of any query, try the GraphQL Query Formatter. To keep going, read our guides on JSON formatting and SQL formatting, which cover the same ideas for data and databases.
Further reading
Try the tools
Format and beautify your GraphQL queries to make them more readable and organized. This tool is essential for developers working with GraphQL APIs to ensure clean and maintainable code, improving collaboration and debugging.
A handy cheatsheet for GraphQL queries, mutations, and syntax. This is a must-have for developers who are building or consuming GraphQL APIs and need a quick reference for efficient data fetching and manipulation.
.jpg)
.jpg)
.jpg)