codr.tools
Tools

GraphQL query formatter

Pretty-print and minify GraphQL in your browser, from queries, mutations and fragments to schema definition language, parsed by the reference implementation with syntax errors reported by line and column.

Direction
Direction
In360 bytes
Out0 bytes
Swap
Ratio–
Status–

Loading the GraphQL parser…

Queries, mutations, subscriptions, fragments and schema definitions (SDL) all go through the same parser. Indentation inside a """ block is part of the text, so it is left exactly as it is.

This checks syntax only. It cannot tell you whether a field exists, because it has no schema to check against.

Pretty-print and minify GraphQL in your browser, from queries, mutations and fragments to schema definition language, parsed by the reference implementation with syntax errors reported by line and column.

How to use this tool#

  1. Pick what you want. Format pretty-prints the document; Minify strips every character GraphQL ignores. Both run as you type, so there is no button to press.
  2. Paste your document over the example. Queries, mutations, subscriptions, fragments and schema definition language all go through the same parser, and a file may hold several of them.
  3. Choose the indentation. Two spaces is what every GraphQL tool writes, but four spaces and tabs are there if your project insists.
  4. Read the messages under the output. A syntax error names the line and the column and shows the offending line with a caret under the spot. A note appears when the document held comments, because those are not carried into the output.
  5. Use Swap to go back the other way. It moves the output into the input and flips the direction, so you can minify what you just formatted, or expand a minified query that someone sent you on one line.

The tool doubles as a GraphQL syntax checker: if the document parses, the formatted text appears; if it does not, you get the position of the first problem. Nothing is uploaded, so a query with an internal field name or a token in a header comment is safe to paste.

What formatting actually does here#

The document is parsed into an abstract syntax tree and then printed back out from that tree. Nothing is patched up with regular expressions, which is why the result is the same text no matter how the input was spaced: two documents that mean the same thing come out byte for byte identical, and formatting an already formatted document changes nothing.

Here is a query as it tends to arrive, copied out of a code editor:

graphql
# A comment that will not survive the trip.
query HeroComparison($first:Int=3,$withFriends:Boolean!){
left:hero(episode:EMPIRE){...comparisonFields}
  right: hero(episode: JEDI) { ...comparisonFields }
}
fragment comparisonFields on Character{name,appearsIn,friendsConnection(first:$first)@include(if:$withFriends){totalCount,edges{node{name}}}}

And the same document printed back:

graphql
query HeroComparison($first: Int = 3, $withFriends: Boolean!) {
  left: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  right: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  appearsIn
  friendsConnection(first: $first) @include(if: $withFriends) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

The parser is graphql-js, which calls itself "the JavaScript reference implementation for GraphQL". It runs in your browser, in a chunk the page fetches once, and it makes no network call of its own.

What the printer decides for you#

A printer has to choose a layout, and the choices are not configurable in the library. These are the ones you will notice, all of them checked by running the printer rather than read off a document page.

  • Two spaces per level. That is hard-coded in the printer, and it is what Prettier, graphql-core and the GraphQL specification's own examples use. The four-space and tab options on this page re-indent the printed output afterwards.
  • One field per line, and no commas. Commas are optional separators in GraphQL, so the printer simply does not write them.
  • Arguments on one line while they fit. The printer keeps an argument list inline while the field and its arguments are 80 characters or fewer, and gives every argument its own line from 81 characters up. Directives are never broken that way, however long they get.
  • A blank line between definitions. Always exactly one, no matter how many you wrote.
  • Shorthand for a plain query. A query with no name, no variables and no directives is printed without the query keyword.
  • Descriptions on their own line. A description that fits on one line is printed on one line; a description with line breaks in it stays a three-line""" block.
  • Numbers exactly as you wrote them. 1.50 does not become1.5 and 1E3 does not become 1000. Strings are the opposite: they are re-escaped to the shortest spelling that means the same thing.
  • Nothing is ever reordered. Fields, arguments and definitions come out in the order you wrote them. A formatter that sorted them would change what the document does.
graphql
# 66 characters, so the arguments stay on one line:
search(text: "hello", first: 10, after: "cursor", locale: "en-GB")

# 103 characters, so each argument gets its own line:
search(
  text: "hello"
  first: 10
  after: "opaque-cursor-value"
  locale: "en-GB"
  includeArchived: false
)
graphql
# The same document written three ways.
query { hero { name } }
{ hero { name } }
{hero{name}}

# All three come out like this, because the operation is a query
# with no name, no variables and no directives:
{
  hero {
    name
  }
}

# Give it a name and the keyword comes back:
query Hero {
  hero {
    name
  }
}
graphql
# In:
{ f(a: 1.50, b: 1E3, c: "\u0041\/b") }

# Out: the numbers are copied through exactly as written, while the
# string is re-escaped down to the shortest form that means the same.
{
  f(a: 1.50, b: 1E3, c: "A/b")
}

Comments do not survive#

This is the one thing that surprises people, and it is worth being blunt about: the# comments in your document are not in the output.

It is not an oversight in the library. The specification lists comments underIgnored Tokens, alongside whitespace, line terminators, commas and the byte order mark, and says they "are used to improve readability and provide separation between lexical tokens, but are otherwise insignificant and not referenced in syntactical grammar productions". A parser that builds a syntax tree from the grammar therefore has nowhere to put them, and a printer that walks that tree has nothing to write. graphql-js, graphql-core andgraphql.web all behave the same way.

Rather than let them disappear quietly, this tool counts the comment tokens in your input and tells you how many went. If you need them kept, use Prettier instead: it attaches each comment to the node it sits beside and prints them back. It costs roughly three times the JavaScript of a parser and printer on their own, which is why this page does not ship it.

Minifying, and what gets removed#

Minify does not rewrite your document. It removes the characters the specification calls ignored: the byte order mark, whitespace, line terminators, comments and commas. What is left is the same document with nothing between the tokens but the single spaces the grammar still needs, such as the one between two adjacent names.

Commas go because GraphQL never needed them. The specification is explicit: "Similar to white space and line terminators, commas (,) are used to improve the legibility of source text and separate lexical tokens but are otherwise syntactically and semantically insignificant within GraphQL Documents."

graphql
query HeroComparison($first:Int=3$withFriends:Boolean!){left:hero(episode:EMPIRE){...comparisonFields}right:hero(episode:JEDI){...comparisonFields}}fragment comparisonFields on Character{name appearsIn friendsConnection(first:$first)@include(if:$withFriends){totalCount edges{node{name}}}}

That is the query from the top of this page: 382 bytes formatted, 289 minified, about 24 per cent off. String and block string contents are never touched, so a minified document sends exactly the same values as the one you wrote.

The document is parsed before anything is stripped. A minifier that only lexes would happily shrink a broken query and hand you something a server rejects for a different reason; here a document that does not parse gets the error instead of an output.

Schema definition language works too#

Schema definition language is part of the same grammar, so a .graphql schema file goes through the same parser as a query. Types, interfaces, unions, enums, inputs, scalars, directive declarations, schema blocks and extend definitions are all handled, including directives from federation and other toolchains: the parser does not need to know what @key means to lay it out correctly.

graphql
"""
A character in the Star Wars trilogy.
"""
interface Character {
  id: ID!
  name: String!
  friends: [Character!]
}

type Droid implements Character @key(fields: "id") {
  id: ID!
  name: String!
  primaryFunction: String
}

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

input ReviewInput {
  stars: Int! = 5
  commentary: String
}

union SearchResult = Droid | Human

scalar DateTime

directive @key(fields: String!) on OBJECT | INTERFACE

Descriptions are the one place where the printer is opinionated. A description you wrote as a multi-line """ block keeps that shape; a one-line description collapses onto a single line, which is the form graphql-js prints. Prettier expands the same description back into three lines, so the two tools disagree here and will fight over a file if you run both.

Syntax is not validity#

This page checks one thing: whether your text is a well-formed GraphQL document. That is a real check, and it catches the mistakes people actually make, but it is not the same as the document being valid. Validity is defined against a schema, and there is no schema here.

graphql
# Every one of these parses. A server would reject all four.

# 1. Two operations, neither of them named.
query { a }
query { b }

# 2. Two operations with the same name.
query A { a }
query A { b }

# 3. A field that exists in no schema anywhere.
{ thisFieldIsMadeUp }

# 4. A directive nobody declared.
{ user @invented(level: 3) { id } }

Concretely, a syntax check cannot see:

  • a field, argument, type or enum value that does not exist in your schema;
  • an argument of the wrong type, or a required argument left out;
  • a variable that is declared and never used, or used and never declared;
  • a fragment spread on a type the fragment cannot apply to;
  • a directive used somewhere its declaration does not allow;
  • two operations sharing a name, or an anonymous operation in a document with others.

For those you need the schema in hand. graphql-eslint checks operations and schema files in your repository, graphql-schema-linter checks naming and documentation conventions in an SDL file, and your own server does the full job on every request it receives. Treat this page as the step before those: get the document well-formed and readable, then hand it to something that knows your API.

Reading the error messages#

A syntax error names the first problem the parser met, with the line and the column, and prints the offending line with a caret under the spot. The parser stops at the first error, so fixing one can reveal the next.

graphql
# In:
query Hero {
  hero(episode: ) {
    name
  }
}

# Out:
Unexpected ")" (line 2, column 17)

2 |   hero(episode: ) {
  |                 ^

The messages you will see most:

  • Expected Name, found end of input. The document stops in the middle of something. Usually a missing closing brace, and the reported line is the end of the file rather than the place the brace should have been.
  • Unexpected Name "quary". A word where a keyword belonged. A typo inquery, mutation, fragment or on lands here.
  • Unexpected ")". An argument with no value, as inhero(episode: ).
  • Unterminated string. A " with no closing partner on the same line. GraphQL strings cannot span lines; use a """ block for that.
  • Unexpected character: "%". Something that is not part of the language at all. Template placeholders left in by a client library, such as a stray${variable} from a JavaScript template string, usually show up this way.

Empty braces are worth calling out because the message is confusing: a selection set must hold at least one field, so { user { } } is reported asExpected Name, found "}" rather than as an empty block.

Doing this on the command line#

Prettier

shell
# Prettier formats .graphql and .gql files with no plugin to install.
npx prettier --write schema.graphql 'src/**/*.gql'

# Checked with prettier 3.9.8: it keeps # comments, keeps the blank
# lines you wrote between definitions, and writes every description
# as a three-line """ block.

Prettier is the right answer for files in a repository, mostly because it is the one that keeps your comments. Its layout matches graphql-js closely, including the two-space indentation and where it breaks a long argument list, and differs on descriptions and on blank lines between definitions.

Node

javascript
// npm install graphql
import { parse, print, stripIgnoredCharacters } from 'graphql';
import { readFileSync } from 'node:fs';

const text = readFileSync('query.graphql', 'utf8');

// Pretty-print
console.log(print(parse(text)));

// Minify
console.log(stripIgnoredCharacters(text));

// parse() throws a GraphQLError carrying .locations and .positions
try {
  parse('query { foo');
} catch (error) {
  console.log(error.message);        // Syntax Error: Expected Name, found <EOF>.
  console.log(error.locations);      // [ { line: 1, column: 12 } ]
}

Python

python
# pip install graphql-core
from graphql import parse, print_ast, GraphQLSyntaxError

try:
    print(print_ast(parse(open("query.graphql").read())))
except GraphQLSyntaxError as error:
    print(error.message, error.locations)

# graphql-core is a port of graphql-js, so the output is identical,
# down to the two-space indentation. Checked with graphql-core 3.2.12.

Questions people ask#

Is my query sent anywhere?

No. The parser is JavaScript running in your browser, fetched once with the page. There is nowhere for the text to go: the site is static files with no backend, and this tool has no schema endpoint to introspect. Disconnect from the network and it keeps working, which is the quickest way to prove it.

Why did my comments disappear?

Because the GraphQL grammar treats them as ignored tokens, so the parser does not keep them and the printer has nothing to print. Every parser that builds a syntax tree behaves this way. Use Prettier if the comments matter, and keep the source file as the thing you edit.

Can it check my query against my schema?

No. Validity is defined against a schema and this page has none, by design: fetching your schema would mean sending your query somewhere. It tells you the document is well-formed, not that the fields exist.

Does it handle schema files, not just queries?

Yes. Schema definition language is part of the same grammar, so type, interface, union, enum, input, scalar, directive, schema and extend definitions all format and minify here. A file mixing operations and type definitions parses as well; only a server would object.

Why did the word query vanish from my query?

Because the shorthand form applies. The specification says that "if a document contains only one operation and that operation is a query which defines no variables and contains no directives then that operation may be represented in a short-hand form which omits the query keyword and operation name", and the printer writes that shorthand whenever the operation qualifies. Naming the operation brings the keyword back, and naming it is a good idea anyway: the name is what shows up in server logs and traces.

Can I get four spaces, or tabs?

Yes, from the indentation control. Be aware that it re-indents the printed output, and it deliberately leaves the inside of a """ block alone, because whitespace in there is part of the text rather than layout. Two spaces is what every other GraphQL tool produces, so it is the safer choice for anything that gets committed.

Is minified GraphQL still valid?

Yes. Removing ignored tokens cannot change what a document means, which is exactly why the specification calls them ignored. Minifying is worth doing for a query you are about to embed in a URL or ship in a bundle; it is not worth doing to a file people have to read.

What happens with a very large document?

It is parsed in your browser like anything else, so the limit is your machine rather than an upload size. A schema of several hundred kilobytes formats in well under a second. Documents nested thousands of levels deep are the one real limit: the parser walks them recursively, and the tool reports that it ran out of stack instead of crashing the tab.

Which version of GraphQL is this?

There is one GraphQL language, not a set of versions. The parser here is graphql-js 17, which tracks the current specification, and the grammar it accepts has been stable for years. If you are hunting a difference between your server and this page, it will be in validation or in a server-specific extension, not in the syntax.

  • Format, minify and validate JSON in your browser, with syntax errors explained by line and column, repeated keys listed, and number literals kept exactly as written.

  • Convert YAML to JSON and JSON back to YAML in your browser, following YAML 1.2, with anchors and merge keys resolved, multi-document streams, and syntax errors reported by line and column.

  • Format and beautify SQL in your browser across 20 dialects, from PostgreSQL and MySQL to SQL Server, BigQuery and Snowflake, with your own indent width and keyword case, plus a minifier that puts a query back on one line.

  • Pretty-print, minify and check XML in your browser, with well-formedness errors reported by line and column, and comments, CDATA sections, processing instructions and mixed content all kept intact.