Skip to main content

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 nodeCREATE (n:Label {k: v})
Add multiple nodesCREATE (a:L), (b:L)
Add an edge between existing nodesMATCH (a), (b) CREATE (a)-[:R]->(b)
Add nodes + edge in one shotCREATE (a:L)-[:R]->(b:L)
Bulk import from a listUNWIND $rows AS row CREATE (…)
UpsertMERGE

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 return

Labels 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, alan

Variables (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, b

Relationships 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, b

Multi-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} nodes

To reject duplicates, add a uniqueness constraint:

CREATE CONSTRAINT user_id
FOR (u:User)
REQUIRE u.id IS UNIQUE

For create-if-not-exists semantics use MERGE:

MERGE (u:User {id: 1}) RETURN u

Running 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, u

id() 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 u
MATCH (c:Category {slug: $cat})
CREATE (p:Product {
name:  $name,
price: $price
})-[:IN]->(c)
RETURN p

Clone a node shape​

MATCH (src:Template {id: $src})
CREATE (dst:Template)
SET    dst = properties(src)
SET    dst.id = $new_id
RETURN dst

Create 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 p

Each 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 bound

CREATE 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. Declared secondary indexes speed up scoped hot predicates, but unlabeled MATCH (n {p: v}) lookups still have no label scope and fall back to scanning.

See also​