Neo4j Cypher Skill
Generates, optimizes, and validates Cypher 25 queries for Neo4j 2025.x and 2026.x.
- Skill ID
- neo4j-contrib/neo4j-skills/neo4j-cypher-skill
- Publisher
- neo4j-contrib
- Repository
- neo4j-skills
- Installs
- 0
- Files
- 13
- Requires
- Neo4j >= 2025.01 (safe baseline); Cypher 25
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
neo4j-contrib/neo4j-skills/neo4j-cypher-skillInstalls these files- README.md
- SKILL.md
- references/advanced-patterns.md
- references/apoc.md
- references/cypher-syntax.md
- references/graph-type.md
- references/indexes.md
- references/performance.md
- references/schema-guardrail.md
- references/syntax-traps.md
- scripts/define_schema.py
- scripts/generate_schema.py
- scripts/import_neo4j_schema.py
What this skill tells the agent
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:
(<SOURCE_LABEL> {<KEY>: $value})-[:<REL_TYPE>]->(<TARGET_LABEL>)Never fill guessed names — realistic guesses get copied blindly.
Defaults — apply every query
CYPHER 25— first token; never repeat afterUNIONor inside subqueries- Schema first — inspect before writing; if schema in prompt, use it directly
MERGEon constrained key only; relMERGEon already-bound endpoints only- Label-free
MATCH (n)forbidden unless bound or followed byWHERE n:$($label) LIMIT 25default on all exploratory reads; pushWITH n LIMITbefore high-cardinality operations (variable-length traversals, fan-out MATCH, Cartesian products)- Comments:
//only —--is SQL, invalid REPEATABLE ELEMENTS/DIFFERENT RELATIONSHIPSgo afterMATCH, not end of patternSHOWcommands:YIELDbeforeWHERE; combinable with general Cypher clauses incl.UNION/RETURN[2026.05] —SHOW DATABASESstill requires system db (useUSE system).CALLonsystemdb:YIELDthenWHERE[2026.07]- Inline node predicates
(:Label WHERE p=x)— valid inMATCHonly WHEREcannot follow bareUNWIND— useWITH x WHERE(a)-[:R]-(b)— undirected matches both directions, double-counts; use directed unless unknownDETACH DELETE— plainDELETEthrows 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,namein examples are illustrative — substitute real names from schema.
Schema-First Protocol
Priority order:
<db-name>-schema.jsonanywhere 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.
- 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).
- Schema in context → use it, skip inspection.
- Schema missing → run:
CALL db.schema.visualization() YIELD nodes, relationships RETURN nodes, relationships;
SHOW INDEXES YIELD name, type, labelsOrTypes, properties, state WHERE state = 'ONLINE';
SHOW CONSTRAINTS YIELD name, type, labelsOrTypes, properties;
SHOW PROCEDURES YIELD name RETURN split(name,'.')[0] AS namespace, count(*) AS procedures;Property types per label — check APOC first:
// If APOC available (preferred — use this):
CALL apoc.meta.schema() YIELD value RETURN value;
// No APOC AND database ≤ 100k nodes/rels only (expensive on large graphs):
CALL db.schema.nodeTypeProperties() YIELD nodeType, propertyName, propertyTypes, mandatory;
CALL db.schema.relTypeProperties() YIELD relType, propertyName, propertyTypes, mandatory;Validate before returning any query: label exists · rel type+direction correct · property on that label · index ONLINE.
Key Patterns
MERGE
// MERGE on constrained key; set extras in ON CREATE/ON MATCH
CYPHER 25
MATCH (a:Person {id: $a}) MATCH (b:Person {id: $b})
MERGE (a)-[r:KNOWS]->(b)
ON CREATE SET r.since = date()
ON MATCH SET r.lastSeen = date()SET n = {} replaces all props. SET n += {} merges (safe partial update). Use += for updates.
WITH scope
CYPHER 25
MATCH (a:Person)-[:KNOWS]->(b:Person)
WITH a, count(*) AS friends // b dropped here
WHERE friends > 5
RETURN a.name, friends ORDER BY friends DESCEvery var not listed in WITH is dropped. WITH * carries all forward.
Subqueries — cheat sheet
EXISTS { (a)-[:R]->(b) } // boolean check
COUNT { (a)-[:R]->(b) WHERE a.x > 0 } // count
COLLECT { MATCH (a)-[:R]->(b) RETURN b.name } // collect list (full MATCH+RETURN required)
CALL (p) { MATCH (p)-[:ACTED_IN]->(m) RETURN m } // correlated subquery (explicit import)
OPTIONAL CALL (p) { ... } // nullable subqueryCALL { WITH x ... } deprecated → CALL (x) { ... }. COLLECT {} returns exactly one column.
CALL IN TRANSACTIONS (bulk writes)
CYPHER 25
LOAD CSV WITH HEADERS FROM 'file:///data.csv' AS row
CALL (row) {
MERGE (p:Person {id: row.id}) SET p += row
} IN TRANSACTIONS OF 1000 ROWS ON ERROR CONTINUE REPORT STATUS AS sInput 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:
CYPHER 25
LOAD CSV WITH HEADERS FROM 'file:///rels.csv' AS line
CALL (line) {
MATCH (a:Movie {id: line.movieId}), (b:Person {id: line.personId})
MERGE (b)-[:ACTED_IN]->(a)
} IN CONCURRENT TRANSACTIONS OF 1000 ROWS DISJOINT BY (line.movieId, line.personId)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
CYPHER 25
MATCH SHORTEST 1 (a:Person {name:'Alice'})(()-[:KNOWS]->()){1,}(b:Person {name:'Bob'})
RETURN b.name
// ACYCLIC [2026.03] — no repeated nodes within a path (prevents cycles)
CYPHER 25
MATCH p = ACYCLIC (start:Router {name: $from})-[:LINK]-+(end:Router {name: $to})
RETURN [n IN nodes(p) | n.name] AS route
ORDER BY length(p) LIMIT 5Quantifier 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
More from neo4j-contrib
Use this skill in a project
Open any RiverX project, search the Skills panel for neo4j-contrib/neo4j-skills/neo4j-cypher-skill, and install it in one click.
