The property graph model
|
This section documents the current Neo4j This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Neo4j iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
Everything in Neo4j is one of four primitives: a node, a relationship connecting two nodes, a label attached to a node, and a property attached to either a node or a relationship. There is no table, no row, and no foreign key — a relationship is a first-class, stored pointer, not a value joined at query time.
Nodes and relationships
A node represents an entity; a relationship connects exactly two nodes (or a node to itself) and is always directed and always typed — every relationship has one direction and one relationship type fixed at creation time, and both are stored, not inferred.
// https://neo4j.com/docs/getting-started/data-modeling/
CREATE (p:Person:Actor {name: "Ada Lovelace", born: date("1815-12-10")})
CREATE (m:Movie {title: "Loom", released: date("2026-01-01")})
CREATE (p)-[:ACTED_IN {roles: ["Lead"], since: date("2026-01-01")}]->(m)
Direction matters for how a relationship is stored and traversed — Neo4j keeps a dense, doubly-linked
list of relationships per node so that following -[:ACTED_IN]→ from p or ←[:ACTED_IN]- into m
are both O(1) per hop, unlike a foreign-key join. A Cypher pattern, however, can ignore direction by
omitting the arrow, matching the relationship in either stored direction:
// undirected pattern: matches the relationship regardless of which way it was created
MATCH (p:Person)-[:ACTED_IN]-(m:Movie)
WHERE p.name = "Ada Lovelace"
RETURN m.title;
Labels
A label groups nodes for indexing, constraints and pattern matching — roughly Neo4j’s analogue of a
table name, except a node can carry any number of labels at once. \(p:Person:Actor \{name: "Ada
Lovelace"\}) above creates one node with both labels; labels can also be added or removed from an
existing node without touching its properties or relationships:
MATCH (p:Person {name: "Ada Lovelace"})
SET p:Director
RETURN labels(p);
Properties and their types
Both nodes and relationships carry properties — typed key/value pairs. A relationship’s properties
describe the connection itself (roles, since above), not either endpoint, which is why "who played
what role, and since when" belongs on the :ACTED_IN relationship rather than being duplicated onto
:Person or :Movie.
Temporal types
Cypher has dedicated temporal types — DATE, TIME, LOCAL TIME, DATETIME, LOCAL DATETIME and
DURATION — constructed with functions of the same name and stored as native property values, not as
strings:
// https://neo4j.com/docs/cypher-manual/current/values-and-types/temporal/
RETURN date("2026-09-14") AS today,
datetime("2026-09-14T10:15:00Z") AS asOf,
duration({months: 2, days: 3}) AS runtimeGap;
The spatial Point type
POINT stores 2D or 3D coordinates, either geographic (WGS-84 latitude/longitude) or Cartesian, and
supports native distance calculation with point.distance():
// https://neo4j.com/docs/cypher-manual/current/values-and-types/spatial/
MATCH (p:Person {name: "Ada Lovelace"})
SET p.homeBase = point({latitude: 51.5074, longitude: -0.1278})
RETURN p.homeBase;
LIST properties, and why MAP is not one
A property value can be a LIST of a single primitive type — languages and roles above are both
lists. MAP is a first-class Cypher value used for parameters, query results and map projections
(p \{.name, .born\}), but Neo4j does not store a map directly as a node or relationship property: a
property is a primitive or a homogeneous list of primitives, never a nested structure. A value that
genuinely needs map shape is either flattened into scalar/list properties, or modeled as its own node
reached by another relationship if it needs independent identity and querying.
MATCH (p:Person {name: "Ada Lovelace"})
SET p.languages = ["English", "French"]
WITH p
MATCH (p)-[r:ACTED_IN]->(m:Movie)
RETURN p {.name, .born, credits: collect(m.title)} AS profile; // a MAP value, built at query time
A vector embedding is stored the same way as a list of floats, but is common enough, and carries enough of its own ANN indexing and query surface, to warrant its own page — see Vector search & GenAI and the reference Vector type doc.
Schema-optional, not schema-free
None of the above requires an upfront schema: a label can be attached to one node and never used again, and two nodes with the same label can carry entirely different property sets. Neo4j calls this schema-optional rather than schema-free, because constraints can still be layered on to enforce uniqueness, existence or property type once a shape stabilizes — see Indexes & constraints for how to add them without giving up the freedom to evolve the model incrementally.
Contrasting with the document model
A document store keeps one JSON-like document per entity and models relationships by embedding or by an application-level key lookup — see MongoDB Schema Design and Couchbase documents, keys & metadata. The property graph model inverts that: relationships are stored, first-class, independently queryable records rather than embedded sub-structures or resolved keys, which is what makes multi-hop traversals (friend-of-a-friend, shortest path, variable-length pattern matching) a graph strength that a document or relational join has to reconstruct at query time.