The graph capability stores directed, typed, optionally weighted edges between property-bearing nodes. It sits on the same branch-aware storage as every other capability, so graphs fork, time-travel, and isolate per branch and space like the rest. This guide walks the full command surface — structure, traversal, bulk loading, analytics, and the ontology — against one worked example.
The examples target a durable database at ./social. Graph commands emit JSON by default; add --raw for one-object-per-line output that is easier to pipe.
Graphs
A database holds many named graphs. Create one, list them, read its metadata, and delete it:
strata ./social graph create social
strata ./social graph list
strata ./social graph meta social
{
"created_timestamp": 3,
"created_version": 3,
"edge_count": 0,
"graph": "social",
"node_count": 0,
"updated_timestamp": 3,
"updated_version": 3
}
graph list prints one name per line. graph meta returns live node and edge counts plus the create/update commit coordinates. Reading metadata for a graph that does not exist returns (nil), not an error. Creating a graph that already exists fails with already_exists.engine.graph.
Nodes
A node has an id and optional JSON properties. add-node inserts or replaces:
strata ./social graph add-node social alice --properties '{"name":"Alice","role":"eng"}'
strata ./social graph get-node social alice
{
"graph": "social",
"node_id": "alice",
"properties": {
"name": "Alice",
"role": "eng"
},
"timestamp": 4,
"version": 4
}
Pass --properties-file <path> to read the JSON from a file instead of the command line, and --type <name> to declare the node’s object type (validated once you freeze an ontology — see below). List nodes with graph list-nodes <graph> (accepts --prefix, --limit, --cursor), and remove one with graph remove-node <graph> <id>. A removed or never-written node reads back as (nil).
Edges
An edge connects two existing nodes under an edge type, with an optional weight and properties:
strata ./social graph add-edge social alice follows bob --weight 1.0 --properties '{"since":2021}'
strata ./social graph get-edge social alice follows bob
{
"dst": "bob",
"edge_type": "follows",
"graph": "social",
"properties": {
"since": 2021
},
"src": "alice",
"timestamp": 10,
"version": 10,
"weight": 1.0
}
Both endpoints must already exist; writing an edge to a missing node fails with invalid_argument.engine.graph_edge_endpoint. Remove an edge with graph remove-edge <graph> <src> <edge_type> <dst>.
Neighbors
graph neighbors walks a node’s edges. --direction is outgoing (default), incoming, or both; --edge-type filters by type, and --limit/--cursor paginate:
strata ./social --raw graph neighbors social alice --direction outgoing
{"direction":"outgoing","dst":"bob","edge":{"dst":"bob","edge_type":"follows","graph":"social","properties":{"since":2021},"src":"alice","timestamp":10,"version":10,"weight":1.0},"edge_type":"follows","graph":"social","node":{"graph":"social","node_id":"bob","properties":{"name":"Bob","role":"eng"},"timestamp":5,"version":5},"node_id":"bob","src":"alice"}
Each row carries the traversed edge and the neighbor node in full, so you rarely need a follow-up read.
Bulk insert
For loading many nodes and edges, bulk-insert ingests a JSON payload in chunked commits. Nodes use the key node_id; edges use src, edge_type, dst, and optional weight/properties:
strata ./social graph bulk-insert loaded \
--data '{"nodes":[{"node_id":"n1","properties":{"kind":"a"}},{"node_id":"n2"},{"node_id":"n3"}],"edges":[{"src":"n1","edge_type":"link","dst":"n2","weight":1.0},{"src":"n2","edge_type":"link","dst":"n3"}]}'
{
"commit": { "delete_count": 0, "durable": true, "put_count": 2, "timestamp": 31, "version": 31 },
"commits": 2,
"edges_inserted": 2,
"graph": "loaded",
"nodes_inserted": 3,
"timestamp": 31,
"version": 31
}
Use --file <path> instead of --data for large payloads, and --chunk-size <n> to bound items per commit.
Analytics
The example graph below has two components — a follow cycle among alice, bob, carol (plus frank following alice) and a mutual pair dave/erin. Every algorithm reads a consistent snapshot and accepts --as-of <micros> for time travel.
Weakly connected components (wcc) label each node with its component representative:
strata ./social graph wcc social
{
"component_count": 2,
"components": { "alice": "alice", "bob": "alice", "carol": "alice", "dave": "dave", "erin": "dave", "frank": "alice" },
"graph": "social"
}
Local clustering coefficients (lcc), shortest-path distances from a source (sssp, with --direction), and PageRank (pagerank, with --damping, --max-iterations, --tolerance, and --personalization seed weights) all return per-node maps:
strata ./social graph pagerank social
{
"graph": "social",
"iterations": 20,
"personalized": false,
"ranks": { "alice": 0.2579646389410527, "bob": 0.13463229043004, "carol": 0.249069737295574, "dave": 0.16666666666666666, "erin": 0.16666666666666666, "frank": 0.025 }
}
Community detection by label propagation (cdlp, with --max-iterations and --direction) returns a label per node. A bounded breadth-first traversal (bfs) returns visited nodes, per-node depths, and the traversed edges:
strata ./social graph bfs social alice
{
"depths": { "alice": 0, "bob": 1, "carol": 1 },
"edges": [ { "dst": "bob", "edge_type": "follows", "src": "alice", "weight": 1.0 }, { "dst": "carol", "edge_type": "follows", "src": "alice", "weight": 1.0 } ],
"graph": "social",
"start": "alice",
"visited": [ "alice", "bob", "carol" ]
}
bfs bounds itself with --max-depth (default 100), --max-nodes (default 10000), --direction, and a repeatable --edge-type restriction. Running an algorithm from a node that does not exist fails with not_found.engine.graph_node.
Ontology
A graph can carry an ontology: declared object types and link types. While the ontology is a draft, you add and redraft types freely; nothing is enforced. When you freeze it, subsequent writes validate against it.
This section starts a fresh database at ./org with a graph named org, so the typed examples don’t collide with the untyped social graph above.
strata ./org graph ontology define-object-type org Person \
--properties '{"name":{"value_type":"string","required":true},"level":{"value_type":"integer","required":false}}'
strata ./org graph ontology define-object-type org Team \
--properties '{"name":{"value_type":"string","required":true}}'
strata ./org graph ontology define-link-type org member_of Person Team --cardinality many-to-one
A link type names its source and target object types; --cardinality is an optional hint. graph ontology get <graph> prints the status and every declared type; graph ontology delete-object-type / delete-link-type remove draft types. Add typed nodes with --type, then freeze:
strata ./org graph add-node org p1 --type Person --properties '{"name":"Alice","level":5}'
strata ./org graph add-node org t1 --type Team --properties '{"name":"Platform"}'
strata ./org graph ontology freeze org
{
"commit": { "delete_count": 0, "durable": true, "put_count": 1, "timestamp": 23, "version": 23 },
"graph": "org",
"link_types": 1,
"object_types": 2,
"timestamp": 23,
"version": 23
}
After freezing, a node declaring an undeclared type is rejected:
strata ./org graph add-node org x1 --type Robot --properties '{"name":"R2"}'
failed_precondition.engine.graph_ontology_node_type: node object type `Robot` is not declared in the frozen ontology (err_local_21ffcbc2_000001)
hint: Reload the current state and retry the operation against the latest version.
ref: https://stratadb.org/e/failed_precondition.engine.graph_ontology_node_type
graph ontology summary <graph> adds per-type usage counts (nodes per object type, edges per link type), and graph nodes-by-type <graph> <ObjectType> lists the nodes declaring a given type:
strata ./org graph nodes-by-type org Person
{"graph":"org","node_id":"p1","object_type":"Person","properties":{"level":5,"name":"Alice"},"timestamp":21,"version":21}
Related
- Primitives — how graphs sit alongside the other capabilities
- Branches and Commits — the isolation and time-travel model
- Arrow — export a graph’s nodes and edges to Parquet, CSV, or JSON lines
- Error Handling — reading structured error codes
From Python
The same surface, from the Python SDK — endpoints must exist before an edge, exactly as on the CLI:
import stratadb
db = stratadb.open("./social")
db.graphs.create("social")
db.graphs.add_node("social", "alice")
db.graphs.add_node("social", "bob")
db.graphs.add_edge("social", "alice", "follows", "bob")
db.graphs.neighbors("social", "alice") # Page of GraphNeighborHit rows
db.close()
See namespaces for traversal, analytics, and the ontology surface.
Reference
Every graph command — parameters, returns, errors, and runnable CLI/wire/Python examples — is in the generated Graph command reference.