Cypher Query Formatter (Neo4j)

Paste a Cypher query from application code, a log or the Neo4j Browser history and get a layout where each clause reads as a step. The query is formatted on your device; your graph model is not sent anywhere.

Input

Settings

History

Load from URL

How Cypher is laid out

Cypher reads top to bottom: find a pattern, filter it, pass results on with WITH, and return. Written on one line — which is how queries often end up in Java, Python or JavaScript string literals — that structure disappears. PasteKit restores it with a built-in Cypher tokenizer and printer.

Each clause starts a new line: MATCH, OPTIONAL MATCH, WHERE, WITH, UNWIND, CREATE, MERGE with its ON CREATE SET and ON MATCH SET parts, SET, REMOVE, DELETE, CALL, RETURN, ORDER BY, SKIP and LIMIT. Within a clause, spacing is normalised around commas, comparison operators and map literals such as {vip: true}.

Graph patterns are treated as a unit. (c:Customer)-[:PLACED]->(o:Order) stays together exactly as written, because inserting spaces or line breaks inside the arrows makes a pattern harder, not easier, to read. String literals, $parameters, backtick-quoted names and // comments are preserved.

Options and shortcuts

Cypher has no extra settings on this page. The toolbar indent controls how far continuation lines and nested subqueries such as CALL { ... } are indented, and the line width governs when a long RETURN list or WHERE condition is wrapped.

Ctrl/Cmd+Enter formats, Ctrl/Cmd+Shift+C copies the result for Neo4j Browser, Bloom or your code, and Ctrl/Cmd+K opens the command palette. Ctrl/Cmd+Shift+M minifies data formats elsewhere on the site but has no Cypher equivalent.

If the text cannot be tokenised, for example because a string or a backtick-quoted name is never closed, the position is reported instead of output.

Style conventions and pitfalls

The Neo4j Cypher style guide recommends a few naming conventions that formatting makes easy to check: node labels in PascalCase (:Customer), relationship types in upper snake case (:LIVES_IN), and properties and variables in camelCase. Clause keywords are conventionally written in upper case.

A formatter cannot check the graph itself. A label that does not exist, a misspelled relationship type or a property on the wrong node will all format perfectly and then return no rows. Use EXPLAIN in Neo4j to see the plan without running the query, and watch for warnings about unknown labels.

Two runtime traps are worth checking while you have the query open: MERGE on a full pattern creates the whole pattern when any part is missing (merge the nodes first, then the relationship), and an unbounded variable-length pattern such as -[:KNOWS*]- can explode on large graphs, so give it an upper bound.

When results come back as JSON from the HTTP API or a driver, the JSON formatter makes them readable.

Examples

MATCH, WITH and OPTIONAL MATCH

Every clause gets a line, while each path pattern stays on one line as written.

Input
match (c:Customer {vip: true})-[:PLACED]->(o:Order)-[:CONTAINS]->(p:Product) where o.created >= date('2026-01-01') and p.price > 100 with c, count(distinct o) as orders, collect(p.name)[..5] as products optional match (c)-[:LIVES_IN]->(city:City) return c.name, city.name as city, orders, products order by orders desc limit 10
Output
MATCH (c:Customer {vip: true})-[:PLACED]->(o:Order)-[:CONTAINS]->(p:Product)
WHERE o.created >= date('2026-01-01') AND p.price > 100
WITH c, count(DISTINCT o) AS orders, collect(p.name)[..5] AS products
OPTIONAL MATCH (c)-[:LIVES_IN]->(city:City)
RETURN c.name, city.name AS city, orders, products
ORDER BY orders DESC
LIMIT 10
Open this example in the tool

Upsert with MERGE and UNWIND

ON CREATE SET and ON MATCH SET are placed under the MERGE they belong to.

Input
merge (u:User {id: $id}) on create set u.created = datetime(), u.name = $name on match set u.lastSeen = datetime() with u unwind $tags as tag merge (t:Tag {name: tag}) merge (u)-[:INTERESTED_IN]->(t) return u
Output
MERGE (u:User {id: $id})
ON CREATE SET u.created = datetime(), u.name = $name
ON MATCH SET u.lastSeen = datetime()
WITH u
UNWIND $tags AS tag
MERGE (t:Tag {name: tag})
MERGE (u)-[:INTERESTED_IN]->(t)
RETURN u
Open this example in the tool

Shortest path with a list comprehension

The bounded variable-length pattern and the list comprehension are kept intact.

Input
match p=shortestPath((a:Person {name:'Aisha'})-[:KNOWS*..6]-(b:Person {name:'Ben'})) return [n in nodes(p) | n.name] as chain, length(p) as hops
Output
MATCH p = shortestPath(
  (a:Person {name: 'Aisha'})-[:KNOWS*..6]-(b:Person {name: 'Ben'})
)
RETURN [n IN nodes(p) | n.name] AS chain, length(p) AS hops
Open this example in the tool

Common errors and how to fix them

ErrorCauseFix
Unterminated string literalA string opened with ’ or " is not closed, often because an apostrophe inside it was not escaped.Escape the inner quote (') or switch to the other quote character. Better still, pass the value as a $parameter.
Unclosed backtick-quoted nameA label, property or variable name in backticks, such as Order Line, is missing its closing backtick.Close the backtick, or rename the label to avoid spaces.
Unbalanced brackets in a patternA node ( ), relationship [ ] or map { } is not closed.Check each pattern on the reported line; relationship brackets inside long paths are the usual culprit.

Frequently asked questions

Does it work with Memgraph, Amazon Neptune or other openCypher databases?

Yes, as long as the query uses openCypher syntax. Vendor-specific procedures in CALL clauses are kept as written.

Will it check my labels and relationship types?

No. It has no access to your database. Run EXPLAIN in Neo4j to catch unknown labels and types.

Can I format queries with parameters?

Yes. $parameters are recognised and left untouched.

Does it support GQL?

Cypher is the main ancestor of the ISO GQL standard and most simple GQL queries look like Cypher, but GQL-only syntax is not specifically supported.

Related tools