CREATE — Writing Nodes and Relationships
CREATE writes new nodes and
relationships into the graph. Every
pattern element on the create side becomes a new entity — CREATE
never deduplicates. For upsert semantics use
MERGE.
A quick guided walkthrough lives in the Ten-Minute Tour → Create some data.
Overview
| You want to… | Clause |
|---|---|
| Add a node | CREATE (n:Label {k: v}) |
| Add multiple nodes | CREATE (a:L), (b:L) |
| Add an edge between existing nodes | MATCH (a), (b) CREATE (a)-[:R]->(b) |
| Add nodes + edge in one shot | CREATE (a:L)-[:R]->(b:L) |
| Bulk import from a list | UNWIND $rows AS row CREATE (…) |
| Upsert | MERGE |
Nodes
The simplest CREATE — one node, optional labels, optional property map.
CREATE (n:User {name: 'Alice', age: 32}) RETURN n;
CREATE (n:User:Admin {name: 'Bob'}) RETURN n;
CREATE (n:TempOnly) // no properties, no returnLabels are added to the node as-is. Property values follow the standard data-type rules.
Multiple nodes in one query
Comma-separated patterns:
CREATE
(ada:Person {name: 'Ada', born: 1815}),
(grace:Person {name: 'Grace', born: 1906}),
(alan:Person {name: 'Alan', born: 1912})
RETURN ada, grace, alanVariables (ada, grace, …) stay in scope for the rest of the query —
useful if you want to wire them up with a relationship immediately
afterwards.
Typed property values
Properties accept every supported data type, including temporals and points:
CREATE (c:City {
name: 'Amsterdam',
population: 918000,
tags: ['capital', 'port'],
founded: '1275-10-27'::DATE,
location: {latitude: 52.37, longitude: 4.89}::POINT
})Relationships
CREATE for a relationship requires both endpoints to exist — either
bound by an earlier MATCH, or created inline in the same
CREATE.
Match-then-create
Look up both endpoints, then add the edge:
MATCH (a:User {name: 'Alice'}), (b:User {name: 'Bob'})
CREATE (a)-[r:FOLLOWS {since: 2020}]->(b)
RETURN a, r, bRelationships have their own
property maps. r.since = 2020 is stored on
the edge, not on the endpoints.
Pattern-create
A single CREATE can produce both nodes and the relationship between
them:
CREATE (a:User {name: 'Alice'})-[:FOLLOWS]->(b:User {name: 'Bob'})
RETURN a, bMulti-hop pattern-create
Chain freely:
CREATE
(ada:Person {name: 'Ada'}),
(grace:Person {name: 'Grace'}),
(alan:Person {name: 'Alan'}),
(ada)-[:INFLUENCED]->(grace),
(grace)-[:INFLUENCED]->(alan)Direction and type are mandatory
On CREATE:
(a)-[:T]-(b)— error (no direction).(a)-[]->(b)— error (no type).(a)-[:T]->(b)— valid.(a)<-[:T]-(b)— valid (reversed).
See Troubleshooting → Parse errors.
Bulk create with UNWIND
The idiomatic bulk-load shape pairs CREATE with
UNWIND — one row per list element.
Literal list
UNWIND [
{name: 'Ada', born: 1815},
{name: 'Grace', born: 1906},
{name: 'Alan', born: 1912}
] AS p
CREATE (:Person {name: p.name, born: p.born})Parameter list
Pass the list in from the host language:
UNWIND $people AS p
CREATE (:Person {name: p.name, born: p.born})Where $people = [{name: 'Ada', born: 1815}, …]. This is the
recommended way to load hundreds or thousands of rows in one query —
see each binding's parameters section
(Rust,
Node,
Python).
Bulk relationships
Pair an UNWIND with a MATCH to wire up pre-existing nodes:
UNWIND $edges AS e
MATCH (a:User {id: e.from}), (b:User {id: e.to})
CREATE (a)-[:FOLLOWS {since: e.since}]->(b)Uniqueness
CREATE never deduplicates. Running the same CREATE twice
produces two distinct nodes with the same labels and properties unless
a matching uniqueness or key constraint rejects the second write.
CREATE (:User {id: 1})
CREATE (:User {id: 1})
// now there are two :User {id: 1} nodesTo reject duplicates, add a uniqueness constraint:
CREATE CONSTRAINT user_id
FOR (u:User)
REQUIRE u.id IS UNIQUEFor create-if-not-exists semantics use MERGE:
MERGE (u:User {id: 1}) RETURN uRunning that same query twice yields one node. See MERGE pattern caveats for the rules on what it matches.
Returning what you created
CREATE can be followed by RETURN to hand the new
entity back — essential when the host needs its internal ID.
CREATE (u:User {email: $email})
RETURN id(u) AS id, uid() is the internal identity.
Prefer your own stable property (id, email, …) for external
addressing.
Common patterns
Upsert with MERGE + SET
CREATE on its own can't express "insert or update". Combine
MERGE with SET:
MERGE (u:User {id: $id})
ON CREATE SET u.created = temporal.timestamp()
SET u.name = $name, u.updated = temporal.timestamp()
RETURN uCreate + link
MATCH (c:Category {slug: $cat})
CREATE (p:Product {
name: $name,
price: $price
})-[:IN]->(c)
RETURN pClone a node shape
MATCH (src:Template {id: $src})
CREATE (dst:Template)
SET dst = properties(src)
SET dst.id = $new_id
RETURN dstCreate from aggregated input
CREATE can consume rows produced by a preceding stage. This is how
you turn an aggregate into new nodes:
MATCH (o:Order)
WITH o.region AS region, sum(o.amount) AS revenue
CREATE (:RegionStat {region: region, revenue: revenue})One :RegionStat node per region.
Mirror-write: create a node plus several relationships
MATCH (u:User {id: $user_id}),
(cat:Category {slug: $cat})
CREATE (p:Product {
id: $id,
name: $name,
price: $price
})
CREATE (u)-[:OWNS]->(p)
CREATE (p)-[:IN]->(cat)
RETURN pEach CREATE runs once per input row; since the MATCH produces one
row, this creates one product plus two edges in a single query.
Create with conditional properties via CASE
CREATE (u:User {
id: $id,
email: $email,
tier: CASE WHEN $paying THEN 'pro' ELSE 'free' END,
created: temporal.timestamp()
})See CASE expressions.
Edge cases
CREATE over empty graph
Works unconditionally — there's no schema migration step and no entity schema to validate against. Labels and relationship types come into existence implicitly on first use. See Graph model → Schema-free.
CREATE with duplicate variable
Reusing a variable for different entities is an analysis error:
CREATE (n:A), (n:B) // error: variable 'n' already boundCREATE with relationship to a non-existent node
Both endpoints must be in scope. A CREATE (a)-[:R]->(b) where a or
b isn't bound will fail analysis.
Storage considerations
Every node is O(1) to create. Relationships are stored on both
endpoints, so creating one edge is O(1) too. There are no property
indexes (see Limitations) — later MATCH (n {p: v})
lookups without a label are full scans.
See also
- MATCH — pattern matching.
- MERGE — create-if-not-exists.
- UNWIND — bulk-load from a list.
- SET / REMOVE / DELETE — mutate after creation.
- Properties and Data Types — value shapes.
- Query Examples — end-to-end recipes.