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.
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 10MATCH (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
Upsert with MERGE and UNWIND
ON CREATE SET and ON MATCH SET are placed under the MERGE they belong to.
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 uMERGE (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
Shortest path with a list comprehension
The bounded variable-length pattern and the list comprehension are kept intact.
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 hopsMATCH 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
Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
Unterminated string literal | A 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 name | A 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 pattern | A 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.