Install

Typed Graph works on desktop and mobile. Pick one:

New to typed graphs? Download the demo vault, unzip it and open the folder as a vault (Open folder as vault). The plugin comes preinstalled; turn on community plugins when Obsidian asks, then open Start Here. It uses every feature and includes a hands-on Cypher manual with live examples.

Quick start

  1. Give a note a type: add type: Person to its frontmatter.
  2. Write an edge line: knows:: [[Bob]] {since: 2020}.
  3. Add a query block anywhere:
    ```graph-query
    MATCH (a:Person)-[r:knows]->(b)
    RETURN a, r, b
    ```
  4. Open the explorer with Typed Graph: Open graph view or the ribbon icon.

Edges

An edge is one line in a note; the note is the source.

knows:: [[Bob]]
knows:: [[Carol]] {since: 2018, label: "met at conf"}
-distrusts:: [[Eve]]
+trusts:: [[Bob]]
- works_at:: [[Acme]], [[Initech]] {role: engineer}
PartMeaning
type::Edge type. Graph Link Types lines work unchanged.
- / + prefixSign: −1 or +1 (default +1). weight is an ordinary property.
{…}Properties: numbers, booleans, quoted or bare strings, lists.
{id: "…"}Pins the edge id; otherwise it is source#type#target#n.
headingThe nearest heading above is recorded with the edge.

Lines in code blocks and frontmatter are ignored. Malformed property blocks keep the edge and appear in Show diagnostics. Links to missing notes become faded stub nodes.

Typed nodes

Every note is a node. Frontmatter type becomes its labels; a list gives several:

---
type: [Person, Employee]
age: 40
---

Frontmatter fields, title and path are node properties.

Schema notes

A note directly in Types/ (configurable) describes the type named by its title:

---
schema:
  properties:
    status: {kind: text, default: active}
    born: {kind: date, required: true}
  edges: [knows, works_at]
  visualization:
    color: "#7c5cff"
    shape: ellipse
    icon: user
    label: name
    edges:
      knows: {color: orange, line: dotted}
---
## Notes

Edge property embeds

We met in {{edge: Alice -knows-> Bob . since}}.
{{edge: met-2020 . since}}
{{edge: met-2020}}

Find an edge by endpoints and type or by pinned id; . property shows one value, otherwise a small table. Put a sign before the type to require it: --distrusts-> (negative), -+knows-> (positive). Embeds referencing unpinned edges get a diagnostic suggesting an id.

Query blocks

A graph-query block is an optional header, a blank line, then the query.

```graph-query
view: table
columns: who, since
backend: builtin
node.Person: color=#e5484d, shape=hexagon
edge.knows: line=dashed

MATCH (a:Person)-[r:knows]->(b)
RETURN a.title AS who, r.since AS since
```
OptionValues
viewauto (graph when the result has nodes, relationships or paths), table, graph
columnsTable columns to show, in order
heightGraph height in pixels (100–4000)
backendbuiltin or ladybug (needs the sidecar)
node.<Type> / edge.<type>Style overrides for this block

Results refresh live. Graphs larger than the element limit fall back to a table.

openCypher subset

Queries are read-only. CREATE, SET, DELETE, MERGE and REMOVE are rejected. UNWIND, UNION, CALL, CASE and =~ are not in the built-in subset; use backend: ladybug for those.

Styling

Every attribute resolves on its own; the most specific source wins:

  1. Block header (node.Person: color=red)
  2. Schema note visualization
  3. Settings → Type styles / Edge styles
  4. Defaults: a stable color per type; negative edges dashed red with a tee arrow

Node attributes: color, shape (ellipse, rectangle, round-rectangle, diamond, hexagon, triangle, star), icon (Lucide name), label (property shown instead of the title). Edge attributes: color, line (solid, dashed, dotted).

Graph view

This is a separate view. Obsidian's core Graph view is left untouched, because it has no plugin API and patching it breaks on Obsidian updates. It still draws one plain line per linked pair of notes, with no edge types, signs or properties.

Color types in the core Graph view

You can still color nodes by type in the core Graph view, without any plugin code. Open the core Graph view, expand Groups, choose New group, and enter a property search such as [type:Person], then pick a color. Add one group per type. The first matching group wins, so order groups from most to least specific.

The sidecar

An optional headless Node service over a read-only copy of your vault. It powers the Ladybug backend, vector search, GraphRAG and MCP.

npm install && npm run build:sidecar
OBSIGRAPH_VAULT=/path/to/vault OBSIGRAPH_DATA=/path/to/data OBSIGRAPH_TOKEN=change-me \
  node packages/sidecar/dist/server.mjs

Docker: packages/sidecar/Dockerfile and compose.example.yaml mount the vault :ro, run as non-root and publish on host loopback only.

EndpointPurpose
GET /healthLiveness (no token)
GET /statusGraph, mirror and vector state
POST /query{query, params, backend} → {columns, rows}
POST /searchVector search, optional then Cypher with $hits
POST /retrieveGraphRAG retrieve
POST /vectors/rebuildRe-embed after a model change
POST /mcpMCP over streamable HTTP
VariableDefault
OBSIGRAPH_VAULT, OBSIGRAPH_DATA/vault, /data
OBSIGRAPH_TOKEN / OBSIGRAPH_TOKEN_FILErequired (except --stdio)
OBSIGRAPH_HOST, OBSIGRAPH_PORT127.0.0.1, 8765
OBSIGRAPH_POLL_MS0 (set e.g. 5000 on macOS bind mounts)
OBSIGRAPH_LADYBUG, OBSIGRAPH_VECTORSon; set 0 to disable
OBSIGRAPH_EMBED_PROVIDER, _URL, _MODEL, _KEY/_KEY_FILEollama, http://localhost:11434, nomic-embed-text

Ladybug backend

The sidecar mirrors the graph one-way into LadybugDB. Set the sidecar URL and token in plugin settings, then use backend: ladybug in a block. Queries the built-in parser reads are translated so they return the same results. A 52-query conformance suite keeps the two engines in agreement.

Other read Cypher (UNWIND, CASE, UNION, =~) runs on LadybugDB untranslated, against the mirror layout. The result carries a notice saying so. In that layout:

```graph-query
backend: ladybug
view: table

MATCH (p:Node)-[c:contributes]->(proj:Node)
WHERE list_contains(p.labels, "Person")
RETURN p.title AS person, proj.title AS project,
       CASE WHEN c.p_hours_n >= 100 THEN "core" ELSE "helper" END AS involvement
```

Vector search

Notes are chunked by heading with title, type and frontmatter as context; each edge becomes a sentence like Alice (Person) knows Bob (Person) - met at NeurIPS. Embeddings come from local Ollama by default.

POST /search
{"query": "machine learning", "target": "nodes", "k": 5, "types": ["Person"],
 "then": "MATCH (p)-[:works_at]->(c) WHERE id(p) IN $hits RETURN c.title"}

GraphRAG

POST /retrieve
{"question": "Who works with Alice on search?", "k": 5, "depth": 1}

Returns the best chunks of the vector hits and of their graph neighbors (hits first, then by distance), each with path, heading, score and role, plus the connecting edges. Hub expansion is capped and flagged.

MCP for agents

claude mcp add typed-graph \
  -e OBSIGRAPH_VAULT=/path/to/vault -e OBSIGRAPH_DATA=/path/to/data \
  -- node /path/to/packages/sidecar/dist/server.mjs --stdio

Tools: cypher_query, vector_search, graphrag_retrieve — all read-only. Over HTTP use POST /mcp with the bearer token.

Security

Settings

Maximum graph elementsAbove this, graphs show as tables.
Maximum path depthCap for unbounded * patterns (default 10).
Default query backend, Sidecar URL, Sidecar tokenFor backend: ladybug.
Schema folder, Show diagnosticsType schemas and the issue counter.
Type styles, Edge stylesJSON style defaults.
Refresh delayDebounce before blocks re-run.

FAQ

Does it change my notes?

No. The plugin and the sidecar only read notes. Only Create note from type and Create schema note create new files, on request.

Do I need Graph Link Types or Dataview?

No. Typed Graph reads the same type:: [[Target]] lines on its own.

Does my vault leave my machine?

Not by default. Embeddings use local Ollama; a hosted endpoint is only used if you configure one.