Query cheat sheet
What a query can use without declaring anything: variables of the note a block sits in, parameters the host binds, and the properties every node and edge gets automatically.
Note variables {{…}}
In a graph-query block in Obsidian, {{name}} is replaced by a value of the note holding the block, written as a Cypher literal, before the query is parsed. Values below are for Projects/Atlas/Plan.md with this frontmatter:
---
type: Project
title: Atlas plan
status: active
budget: 1200
draft: false
tags: [graph, ml]
address: {city: Lviv}
---
| Variable | Becomes | Notes |
|---|---|---|
{{path}} | "Projects/Atlas/Plan.md" | Vault-relative path. The safest way to match this note: (me {path: {{path}}}). |
{{name}} | "Plan.md" | File name with extension. |
{{title}} | "Plan" | File name without extension. It ignores frontmatter title; use {{fm.title}} for that. |
{{folder}} | "Projects/Atlas" | "" for a note at the vault root. |
{{extension}} | "md" | "adoc" for an AsciiDoc note. |
{{fm.status}} | "active" | Any frontmatter key. {{frontmatter.status}} is the long form. |
{{fm.title}} | "Atlas plan" | |
{{fm.budget}} | 1200 | Numbers and booleans are written plain. |
{{fm.draft}} | false | |
{{fm.tags}} | ["graph", "ml"] | A list of strings, numbers or booleans becomes a list. A nested list inside it becomes null. |
{{fm.address.city}} | "Lviv" | Dots walk into maps. |
{{fm.address}} | null | A map is not a literal, so it becomes null. |
{{fm.owner}} | null | A missing key becomes null; so does any unknown name. |
- Strings are quoted and escaped, so a value with quotes cannot break or extend the query.
- Spaces inside the braces are allowed:
{{ fm.status }}. - Only names and dots are substituted. Nothing is evaluated:
{{fm.budget * 2}}stays in the text and fails to parse. Write{{fm.budget}} * 2instead. - Line numbers are kept, so error positions still point into the block.
- Substitution happens before the backend is chosen, so variables work with
backend: ladybugtoo. - Blocks re-run when the note changes, so a block that uses
{{fm.*}}follows edits to its own frontmatter.
Parameters $…
A $name is a real Cypher parameter, bound by the host at run time.
| Parameter | Where | Value |
|---|---|---|
$activeFile | Graph view (full pane) | Vault-relative path of the last active Markdown note. It keeps that path when focus moves away from Markdown, and is null before any note has been active. |
any $name | Sidecar POST /query and the MCP tool cypher_query | Whatever you pass in params. |
graph-query blocks and tg cypher bind no parameters. A block that uses $x fails with Missing parameter $x, so use a {{…}} variable there.
curl -s localhost:8765/query \
-H "Authorization: Bearer $OBSIGRAPH_TOKEN" \
-H 'content-type: application/json' \
-d '{"query": "MATCH (n {path: $p})-[r]->(x) RETURN type(r), x.title",
"params": {"p": "People/Alice.md"}}'
Node properties
Every note node in a vault (Obsidian plugin, sidecar) has these without any schema:
| Use | Value |
|---|---|
n.path | Vault-relative path, such as "People/Alice.md". null for a stub. |
n.title | Frontmatter title, else an AsciiDoc = Title, else the file name without extension. A stub's title is its link text. |
n.stub | true for a link target with no note behind it. |
n.<key> | Every frontmatter key as written: n.type, n.tags, n.aliases, n.status, generated ids such as n.uuid. |
id(n) | The note path; for a stub, its link text. |
labels(n) | From frontmatter type and types, or the folder and tag fallbacks in settings. |
n:Person | Label test, usable in WHERE. |
path and title are always set by Typed Graph, and a frontmatter path key is replaced. Headings and blocks are not nodes.
Edge properties
Written as -distrusts:: [[Mallory]] {since: 2021, id: d-1}:
| Use | Value |
|---|---|
type(r) | The edge type without its sign: "distrusts". |
r.sign | 1, or -1 for a type written with a leading -. |
r.id | The pinned {id: …}, an edge note's path, or a derived <source>#<type>#<target>#<n>. |
r.<key> | Each {key: value} property, or an edge note's frontmatter: r.since. |
[:knows|likes] | Type alternation in a pattern. |
With Plain links as links_to edges on, every prose link is also an untyped links_to edge with sign 1. The heading and line an edge was written under are not properties.
Functions over metadata
| Function | Returns |
|---|---|
keys(n), keys(r) | Property names, including path and title. |
properties(n) | A map of all properties. |
startNode(r), endNode(r) | The endpoints of an edge. |
labels(n), type(r), id(x) | Labels, edge type, node or edge id. |
coalesce(n.subtitle, n.title) | First non-null value. |
x IN n.tags | List membership, for tags or aliases. |
The first three are built-in engine only; see Ladybug backend.
tg cypher graph (code projects)
tg cypher and the tg_cypher MCP tool query a graph of lat.md/ sections, spec requirements and, with --code annotated|all, source code. Every node also has path (its id) and title, plus type.
| Label | Properties | Id looks like |
|---|---|---|
Section, plus the file's type: labels for sections below the title | section, title (the heading), file, depth, startLine, endLine, summary (first paragraph) | lat.md/cli#CLI#Requirement trace |
Requirement, or Decision for an ADR, plus the framework's own label (UserStory, ADR, …) | requirement, framework, capability, name, text, file, line, status, change, removedBy | openspec:auth#Login, adr:0003 |
Scenario (spec) | scenario, requirement, name, file, line | openspec:auth#Login#Valid password |
CodeFile | lang, lines (line count) | src/auth/token.ts |
CodeSymbol | name, kind, symbol, lang, lines ("12-40"), startLine, endLine, signature | src/auth/token.ts#refresh |
- Edges:
contains(parent section to child, requirement to scenario),references(plain wiki links andopenspec:/specs:frontmatter), and every typed edge line with its{properties}. @tg:edges from code carry what you wrote, such asr.since. When the comment sits above a test call they also carryr.test, the test's name.- Test-spec sections in a file typed
Scenarioare labelledScenariotoo. Uses.scenario IS NOT NULLto keep only spec scenarios.
Ladybug backend
With backend: ladybug the names are the same and the same text runs. The differences:
keys,properties,startNodeandendNodeare not available.n.title,n.stub,r.idandr.signare stored columns. Other properties get a typed column per kind, so one property holding numbers in some notes and text in others is read as one kind, with a notice.UNWIND,CASEand other read Cypher outside the built-in subset run there.
Recipes
This note's neighbourhood
```graph-query
MATCH (me {path: {{path}}})-[r]-(x)
RETURN me, r, x
```
Typed backlinks as a table
```graph-query
view: table
MATCH (x)-[r]->(me {path: {{path}}})
RETURN x.title AS from, type(r) AS edge,
r.sign AS sign
```
Other notes in this folder
```graph-query
MATCH (n)
WHERE n.path STARTS WITH {{folder}} + '/'
AND n.path <> {{path}}
RETURN n.title ORDER BY n.title
```
For a note at the vault root, {{folder}} is "", so this matches nothing.
Same status as this note
```graph-query
MATCH (p:Project)
WHERE p.status = {{fm.status}}
AND p.path <> {{path}}
RETURN p.title, p.budget
```
Notes with a tag
MATCH (n) WHERE 'ml' IN n.tags RETURN n.title
Negative edges
MATCH (a)-[r]->(b) WHERE r.sign = -1 RETURN a.title, type(r), b.title
Links to missing notes
MATCH (a)-[r]->(s) WHERE s.stub RETURN a.path, type(r), s.title
Graph view: follow the open note
MATCH p = (n {path: $activeFile})
-[:knows*1..2]->(y)
RETURN p
Sections of one doc file
tg cypher "MATCH (s:Section) WHERE s.file = 'lat.md/cli.md' RETURN s.title, s.startLine"
Unimplemented requirements
tg cypher "MATCH (q:Requirement) OPTIONAL MATCH (c)-[i:implements]->(q) WITH q, count(i) AS n WHERE n = 0 RETURN q.requirement, q.status"