neo4j-cypher-skill
Generates, optimizes, and validates Cypher 25 queries for Neo4j 2025.x and 2026.x. Use when writing new Cypher queries, optimizing slow queries, graph pattern matching, vector or fulltext search, subqueries, or batch writes. Covers MATCH, MERGE, CREATE, WITH, RETURN, CALL, UNWIND, FOREACH, LOAD CSV,
By neo4j-contrib · 1,132 installs
npx skills add neo4j-contrib/neo4j-skills --skill neo4j-cypher-skill
Source repository · Upstream listing
When to Use
Writing, optimizing, or debugging Cypher queries
Graph pattern matching, QPEs, variable length paths
Vector/fulltext search, subqueries, batch writes, LOAD CSV
When NOT to Use
Driver migration/API changes → neo4j migration skill
DB admin (users, config, backups) → neo4j cli tools skill
Hybrid search that combines vector with fulltext or other ranked sources → neo4j vector index skill
GQL conformance note: LET , FINISH , FILTER , and INSERT are valid Cypher 25 clauses (introduced via GQL conformance, mostly in Neo4j 2025.06). On older versions, fall back to WITH / (omit RETURN) / WHERE / CREATE . INSERT requires & separated multi labels and does not support dynamic labels/types.
Pre flight
? Known Unknown
<db name schema.json found in project Use it directly — skip live inspection —
Schema (from context or live DB) Use directly Run Schema First Protocol
Neo4j version Use version features Default to 2025.01 safe set
Executing (not generating)? Use EXPLAIN + write gate State query is unvalidated
Schema unknown + no tool → produce non executable sketch outside a code block:
Never fill guessed names — realistic guesses get copied blindly.
Defaults — apply every query
1. CYPHER 25 — first token; never repeat after UNION or inside subqueries
2. Schema first — inspect before writing; if schema in prompt, use it directly
3. MERGE on constrained key only; rel MERGE on already bound endpoints only
4. Label free MATCH (n) forbidden unless bound or followed by WHERE n:$($label)
5. LIMIT 25 default on all exploratory reads; push WITH n LIMIT before high cardinality operations (variable length traversals, fan out MATCH, Cartesian products)
6. Comments: // only — is SQL, invalid
7. REPEATABLE ELEMENTS / DIFFERENT RELATIONSHIPS go after MATCH , not end of pattern
8. SHOW commands: YIELD before WHERE ; combinable with general Cypher clauses incl. UNION / RETURN [2026.05] — SHOW DATABASES still requires system db (use USE system ). CALL on system db: YIELD then WHERE [2026.07]
9. Inline node predicates (:Label WHERE p=x) — valid in MATCH only
10. WHERE cannot follow bare UNWIND — use WITH x WHERE
11. (a) [:R] (b) — undirected matches both directions, double counts; use directed unless unknown
12. DETACH DELETE — plain DELETE throws if node has relationships
Style
Element Convention
Node labels PascalCase :Person
Rel types SCREAMING SNAKE CASE :KNOWS
Properties/vars camelCase firstName
Clauses UPPERCASE MATCH
Booleans/null lowercase true false null
Strings single quoted; double only if contains '
Schema is truth. :Person , :KNOWS , name in examples are illustrative — substitute real names from schema.
Schema First Protocol
Priority order:
1. <db name schema.json anywhere in project → read directly, state file name + schema retrieved at , skip live inspection. If significantly outdated and DB reachable, offer re fetch. Full rules: [references/schema guardrail.md](references/schema guardrail.md).
Existence — labels/rel types/properties must be in schema; try synonym resolution before asking
Property type — reason about intent first (e.g. string vs INTEGER may be null check); ask only if unclear
Relationship direction — wrong direction → correct silently and note
Synonym mapping — unambiguous → resolve silently; ambiguous → pick most likely, note; ask if unresolvable
Scripts: generate schema.py (live DB + APOC), define schema.py (no DB), import neo4j schema.py (converts neo4j graphrag python , graph schema introspector , graph schema json js utils , mcp neo4j data modeling ).
2. Schema in context → use it, skip inspection.
3. Schema missing → run:
Property types per label — check APOC first:
Validate before returning any query: label exists · rel type+direction correct · property on that label · index ONLINE.
Key Patterns
MERGE
SET n = {} replaces all props. SET n += {} merges (safe partial update). Use += for updates.
WITH scope
Every var not listed in WITH is dropped. WITH carries all forward.
Subqueries — cheat sheet
CALL { WITH x ... } deprecated → CALL (x) { ... } . COLLECT {} returns exactly one column.
CALL IN TRANSACTIONS (bulk writes)
Input stream must be outside subquery. Auto commit only — never wrap in beginTransaction() . PERIODIC COMMIT deprecated.
DISJOINT BY [2026.06, Cypher 25] on IN CONCURRENT TRANSACTIONS prevents deadlocks by scheduling batches that share lock prone resources sequentially — use when importing relationships:
DISJOINT BY (expr,...) declares lock keys (outer query variables only); DISJOINT BY AUTO infers them via static analysis; DISJOINT BY NONE disables. Overrides dbms.cypher.transactions.default subquery batch strategy . EXPLAIN / PROFILE shows keys in DISJOINT BY (...) on TransactionForeach .
QPE basics
Quantifier outside group: (pattern){N,M} . Groups start+end with node. REPEATABLE ELEMENTS needs bounded {m,n} . ACYCLIC implies nodes cannot repeat within a path (stronger than default DIFFERENT RELATIONSHIPS ).
Match mode — add after MATCH :
DIFFERENT RELATIONSHIPS (default) — each rel traversed once per path
REPEATABLE ELEMENTS [2025.x] — nodes/rels revisitable; use for circular routes, weight optimized paths, constrained backtracking; requires bounded {m,n}
Conditional CALL subqueries [2025.06]
Use WHEN…THEN…ELSE for if else if write logic; mutually exclusive (first match wins). Not available pre 2025.06.
Dynamic relationship types [2025.x]
Spatial / Point
Create POINT index: CREATE POINT INDEX name IF NOT EXISTS FOR (n:Place) ON (n.coords)
Aggregation grouping keys
Non aggregating expressions in RETURN / WITH are implicit grouping keys — GROUP BY optional:
Explicit GROUP BY subclause on WITH / RETURN [2026.07, Cypher 25] states grouping keys explicitly — GQL aligned alternative to implicit grouping; implicit grouping stays valid:
GROUP BY () = no grouping keys (one row); GROUP BY ALL = every non aggregating return item is a key. Grouping keys absent from the projection are not returned. Rules → [references/cypher syntax.md](references/cypher syntax.md).
count(n) counts non null; count( ) counts rows including nulls. collect(DISTINCT expr) deduplicates.
count() is faster than size(collect()) — count() reads the internal store; collect() builds a list first.
ORDER BY / WHERE subclause expressions referencing a projection item more complex than a variable or var.prop are deprecated [2026.07] — alias the expression in the projection and order by the alias. Same for names that shadow an incoming variable. ORDER BY / WHERE may now call aggregation functions absent from the projection list when the projection clause already aggregates.
Common Syntax Traps (top causes of broken queries)
Wrong Right
ORDER BY n.prop AS x DESC ORDER BY n.prop DESC
ORDER BY preAggVar after agg RETURN Use RETURN alias
count(r WHERE r.x=5) sum(CASE WHEN r.x=5 THEN 1 ELSE 0 END)
UNWIND list AS x WHERE x 5 UNWIND list AS x WITH x WHERE x 5
least(a,b) / greatest(a,b) CASE WHEN a<b THEN a ELSE b END
comment // comment
shortestPath((a) [ ] (b)) SHORTEST 1 (a)(() [] ()){1,}(b)
id(n) elementId(n)
[:REL 1..5] (() [:REL] ()){1,5}
CALL { WITH x ... } CALL (x) { ... }
COLLECT { (a) [:R] (b) } COLLECT { MATCH ... RETURN b }
SET n = {k:v} partial update SET n += {k:v}
DELETE n with relationships DETACH DELETE n
WHERE n.x = null WHERE n.x IS NULL
toInteger(null) throws toIntegerOrNull(null)
n.$key dynamic property n[$key]
SET n:$label SET n:$($label)
ZONED DATETIME = date(...) → 0 rows Use datetime(...) or .year accessor
ISO string with Z suffix stored/compared as UTC Z ≠ UTC in Neo4j — Z is parsed as an offset, not the UTC timezone; planner and range indexes treat them differently. Explicitly coerce: datetime({datetime: datetime('2025 09 10T03:43:00Z'), timezone: 'UTC'}) ([neo4j 13519](https://github.com/neo4j/neo4j/issues/13519))
FOREACH ... RETURN UNWIND ... RETURN
Full trap table → [references/syntax traps.md](references/syntax traps.md)
Output Mode and Write Gate
Default: parameterized queries. Return named properties, not full nodes or RETURN .
Exception: schema/diagnostic queries ( CALL db.schema.visualization() , SHOW INDEXES YIELD , EXPLAIN ) where the object is the point.
Validation workflow:
1. EXPLAIN before any write — catches syntax errors, missing indexes
2. New read: test with LIMIT 1 first
3. Write: verify read half as RETURN before replacing with SET / CREATE / DELETE
4. PROFILE to measure db hits; check for AllNodesScan , CartesianProduct , Eager
Query API v2 (no driver needed — works for schema inspection, EXPLAIN, reads, writes):
Write execution gate — only when agent executes (MCP/cypher shell/HTTP), NOT when generating for code/scripts/user to run:
1. Run EXPLAIN → report estimated rows affected
2. Wait for user confirmation before executing
Version Gates
Default to 2025.01 safe features when version unknown.
Feature Min version Fallback
CYPHER 25 , QPEs, CALL (x) {} 2025.01 require 2025+
Match modes ( DIFFERENT RELATIONSHIPS , REPEATABLE ELEMENTS ) 2025.01 require 2025+
Dynamic labels $($expr) , coll.sort() 2025.01 APOC or app side
CONCURRENT TRANSACTIONS , REPORT STATUS 2025.01 drop / omit
SEARCH clause (vector/fulltext) 2026.01 CALL db.index.vector.queryNodes(...) (deprecated 2026.04)
ACYCLIC path mode (no repeated nodes in path) 2026.03 post filter with size(nodes(p)) = size(apoc.coll.toSet(nodes(p)))
string.indexOf() , string.join() , string.regexReplace() 2026.05 apoc.text. or app side
GROUP BY subclause on WITH / RETURN , cardinality() 2026.07 implicit grouping keys; size() / size(keys(map))
WHERE on procedure calls run against the system database 2026.07 filter rows client side
GQL aliases: FOR = UNWIND , PROPERTY EXISTS = IS NOT NULL , IS [NOT] LABELED = n:Label ; function aliases ( local time , zoned datetime , duration between , collect list , etc.) 2026.02–04 GQL compliance only — use Cypher equivalents; full list → [references/cypher syntax.md](references/cypher syntax.md)
GRAPH TYPE schema DDL ( ALTER CURRENT GRAPH TYPE SET/ADD/ALTER/DROP , SHOW CURRENT GRAPH TYPE ) 2026.02 (preview), GA 2026.06 Use individual CREATE CONSTRAINT / CREATE INDEX
GROUP BY subclause on WITH / RETURN (explicit grouping keys, GQL alignment) 2026.07 Implicit grouping — list non aggregating expressions in the projection
cardinality() — keys in a MAP, elements in a LIST, nodes+rels in a PATH 2026.07 size() for LIST/MAP keys, length() for PATH
Aggregation functions in ORDER BY / WHERE that are not projection items (aggregating projection only) 2026.07 Project the aggregate as an alias, then order/filter on the alias
WHERE after YIELD in procedure calls on the system database 2026.07 YIELD + RETURN , filter client side
Performance
EXPLAIN/PROFILE red flags: AllNodesScan CartesianProduct NodeByLabelScan Eager
Fix Eager — three approaches (choose simplest that works):
1. Add specific labels to MATCH nodes to eliminate read/write ambiguity:
MATCH (x:CallingPoint) instead of bare MATCH (x) when writing :City nodes
2. Collect first, then write : WITH collect(u) AS users UNWIND users AS u ...
3. CALL IN TRANSACTIONS : isolates each batch in its own tr