Blog · 4 October 2026 · Volodymyr Pavlyshyn

Your vault as a property graph

An Obsidian link records that two notes are related, but not how. Typed Graph lets you write what each link means, give it properties, and query the whole vault like a graph database. Everything stays in plain Markdown.

The missing half of a link

Write [[Bob]] in Alice's note and Obsidian draws a line between them. That line might mean "knows", "manages", "disagrees with" or "cites". The graph view cannot tell these apart, and neither can search. As a vault grows, the meaning of its links ends up in your head instead of in your notes.

Graph databases solved this long ago with the property graph model:

Typed Graph brings this model to an Obsidian vault without a database, without a new file format, and without changing a single note.

1. A typed edge is one line

You write an edge where it belongs, in the note it starts from:

knows:: [[Bob]]
works_at:: [[Acme]], [[Initech]]

The word before :: is the edge type. The note containing the line is the source and the link is the target. This is the syntax the Graph Link Types plugin uses, so vaults that already use it work unchanged, and Typed Graph does not need that plugin installed. Lines in code blocks and frontmatter are ignored. A plain [[link]] in prose stays a plain link.

2. Edges can be negative

Many relationships come in opposites: trusts and distrusts, supports and contradicts, agrees and disagrees. A + or - in front of the type sets the edge's sign:

+trusts:: [[Bob]]
-distrusts:: [[Mallory]]

Negative edges are drawn dashed and red with a flat arrow head. In queries the sign is r.sign, so "show me every conflict in this project" is one line of Cypher.

A typed graph with labeled edges and a negative edge drawn dashed and red
Typed nodes, labeled edges, and a negative edge drawn dashed and red.

3. Edges have properties

Facts about a relationship belong on the relationship. Curly braces after the link hold them:

knows:: [[Bob]] {since: 2020, label: "met at NeurIPS"}
contributes:: [[Ranking]] {hours: 200}
funds:: [[Search]] {amount: 250000, currency: EUR}

Values can be numbers, booleans, quoted or bare strings, or lists. A malformed block never loses the edge: the edge is kept and the problem shows up in Show diagnostics.

Every edge has an id. By default it is derived from the source, type, target and position. For anything you reference long-term, you can pin your own:

knows:: [[Bob]] {since: 2020, id: "alice-knows-bob"}

Edge properties inside your prose

An edge embed shows an edge's property in the middle of a sentence and stays in sync with it:

Alice met Bob in {{edge: alice-knows-bob . since}}.
Bob spent {{edge: Bob -contributes-> Search . hours}} hours on search.

Embeds find an edge by pinned id or by its endpoints and type. Leave out the property to get a small table of all of them. When nothing matches, the embed shows a quiet marker, never an error. If an embed relies on an unpinned edge, diagnostics suggest pinning one, because derived ids can shift.

4. Typed nodes and schema notes

Every note is a node. Its labels come from the type field in frontmatter, and a list gives several:

---
type: [Person, Engineer]
role: engineer
---

To describe a type, add a schema note: a note in Types/ named after the type. It declares properties (with kinds, defaults and required flags), which outgoing edges the type allows, how it looks, and a template for new notes:

---
schema:
  properties:
    role: {kind: text, required: true}
    joined: date
  edges: [knows, works_at, mentors]
  visualization:
    color: "#7c5cff"
    shape: ellipse
    icon: user
    edges:
      knows: {color: orange, line: dotted}
---
## About

Schemas are optional and advisory. A missing required property or an unexpected edge type shows up in the status bar and in Show diagnostics, but nothing is blocked. Your notes stay notes, not database rows. Create note from type uses the schema's defaults and template, and never overwrites a file.

5. Ask the vault questions

A graph-query code block runs openCypher, the query language of Neo4j and most graph databases. You draw the pattern you want with ASCII arrows:

```graph-query
MATCH (p:Person)-[c:contributes]->(proj:Project)
RETURN p.title AS person, proj.title AS project, c.hours AS hours
ORDER BY hours DESC
```
A query result rendered as a table
Scalar results become a table. Nodes, relationships and paths become a graph.

The built-in engine runs inside Obsidian, on desktop and mobile, with no setup. It supports:

Queries are read-only: CREATE, SET and DELETE are rejected, so a query can never touch a note. Errors point at the line and column.

Results are live. Edit an edge and every block that depends on it re-runs, and only the blocks you can see do the work.

Variable-length paths drawn as a graph
Variable-length paths, drawn.

6. Graphs that look like what they mean

Results render with Cytoscape.js. Nodes get colors, shapes and icons from their type. Edges show their type as a label and their sign as a line style. Links to notes that do not exist yet appear as faded stubs, which double as a to-write list.

Each style attribute resolves separately, and the most specific source wins:

  1. the block header, for a one-off view (node.Person: color=red, shape=star)
  2. the type's schema note
  3. plugin settings
  4. built-in defaults: a stable color per type, and red dashed lines for negative edges
A graph restyled by its query block header
One block, restyled from its header without touching the schema.

The Graph view is a full-pane explorer built on the same renderer:

It sits next to Obsidian's own graph view, which Typed Graph deliberately leaves alone.

7. Markdown stays the source of truth

Typed Graph only reads your vault. The graph is rebuilt from your notes in memory and updated incrementally as you type. If you uninstall the plugin, every edge is still a readable line of text. Nothing is locked in a database and nothing leaves your machine.

When you need more, an optional sidecar serves the same graph outside Obsidian:

It has its own article: Your vault as RAG storage.

Try it