- reviewed
- 2026-07-10
- section
- reference
- rating
- 9
- created
- 2026-07-26
- updated
- 2026-10-30
Query
Query
Queries turn the vault into a small database: pick columns, filter by tags and typed
Properties, sort, and get a table back. One engine evaluates the same expression everywhere —
the track query CLI command, an embedded track-query block in the Web workspace, and the
published static site. If you know Obsidian's Dataview TABLE queries or org-mode column view,
this is the same idea, kept deliberately tiny.
This page carries a live demo below — the tables you see are computed from this help site's own pages at build time.
rating:: 9
Back to track.
The grammar
A query is a single expression:
TABLE <column>[, <column>...]
[FROM #tag]
[WHERE <cond> [AND <cond>]...]
[SORT <key> [DESC]]
[LIMIT <n>]- Columns and keys come from two separate namespaces. A bare identifier is a note attribute:
title(rendered as a link to the note),tags, andbody(see below). A user property (a sidecar value or an inline field) is writtenprops.<key>—props.status,props.rating— and that is the only way to reach one. Keeping properties underprops.means a bare word never silently picks up a property, and new note attributes can be added later without ever colliding with your property names. A multi-valued property fills its cell witha, b; aprops.<key>column shows just<key>in the table header. - An unknown bare key is an error, not an empty column.
TABLE statusfails withunknown key "status": note attributes are title, tags; query a property as props.status— so a mistyped or mis-namespaced key is caught loudly instead of quietly returning nothing. FROM #tagkeeps only notes carrying the tag — or any descendant, since tags are hierarchical:#amatches#aand#a/b, never#ab.WHEREconditions areAND-combined. Each one is a#tagfilter, a comparison (key = value,key != value,key < value,key > value), or a barekey(the note has the property). A comparison may also put the value first —2026-05-31 < props.reviewed— so a date range reads in number-line order. Values compare by type: numbers numerically, everything else as text — which ordersYYYY-MM-DDdates chronologically. Quote a value with spaces:owner = "Ada Lovelace".bodymatches note bodies by full text.body = "text"keeps notes whose body satisfies the expression;body != "text"keeps the rest. Inside the value, terms join withAND(implicit) and an uppercaseORseparates alternatives — the same grammar as full-text search:body = "quick fox OR turtle"means(quick AND fox) OR turtle. The live workspace resolves it through the same SQLite FTS5 index body search uses, so it stays fast on a large vault; on a published site the search runs over the published pages.bodyis not a column or a sort key — it is aWHERE-only filter, soTABLE bodyis a loud error.SORT key [DESC]orders by a key's first value; notes without the key sort last. WithoutSORT, notes come most recently updated first.LIMIT ncaps the row count.
Keywords are uppercase, so lowercase words are always keys or values.
TABLE title, props.status, props.due FROM #project WHERE props.status != done AND props.due < 2027-01-01 SORT props.due LIMIT 10From the command line
track query prints the result as JSON, ready for scripts and editor integrations:
track query 'TABLE title, props.status FROM #project WHERE props.status = open SORT title'{"columns":["title","props.status"],"rows":[{"note_id":1781310000000,"title":"Alpha","cells":["Alpha","open"]}],"count":1}Embedded query blocks
Fence a block with track-query and write the expression inside — the same way a mermaid fence
embeds a diagram. The block renders as a live result table wherever the note renders:
the web workspace and the published static site alike. This block's rows are this site's own
visualization pages — note how #help/visualization also matches the nested
#help/visualization/charts tag, because tags are hierarchical:
```track-query
TABLE title, tags FROM #help/visualization SORT title
```It renders as (live):
| title | tags |
|---|---|
| Charts | help/visualization/charts |
| Diagrams | help/visualization/diagrams |
| Embeds | help/visualization/embeds |
| Maps | help/visualization/maps |
| Visualization | help/visualization |
Property comparisons work the same way, written under props.. Two pages of this site carry a
rating inline field (this page is one of them — see the strip at the top), so this block finds
the pages rated between 5 and 10 — the range reads in number-line order, and the header still
reads rating, not props.rating:
```track-query
TABLE title, props.rating WHERE 5 < props.rating AND props.rating < 10 SORT props.rating DESC
```It renders as (live):
| title | rating |
|---|---|
| Query | 9 |
| Properties | 8 |
In the live workspace the table recomputes as the vault changes; the static export bakes the result in at build time, over the published notes only — a published query never leaks an unpublished note. A typo shows an inline error with the expression at the block position, so it never hides your text.
Layouts
A block renders as a table by default. A :layout header argument on the fence — the same
Org-style header arguments Babel blocks use — picks a different shape: open the fence with
track-query :layout board :by status instead of plain track-query, and the same expression
renders as a board instead of a table.
:layout table(the default) — the Markdown table above.:layout list— a compact one-row-per-note list. The title remains a link, but the redundanttitletable header is omitted; this is useful when the query has onlyTABLE title.:layout board— a kanban-style board: one lane per value of the grouping column, cards in the query's order.:layout gallery— a grid of cards showing each note's cover image (theimagein its sidecar). A note without one shows its icon as the card face; with no icon either, track's built-in no-image face.:layout calendar— rows placed on a month grid by a date-valued column; one grid per month that has rows.
:by <column> names the grouping column (board) or the date column (calendar) and must be one of
the TABLE columns, written exactly as in TABLE — so a property column is :by props.status, not
:by status; it defaults to the first non-title column. Every layout is read-only — cards, list
entries, and day entries link to their notes, like a table's title cells. If you know Obsidian's
Bases views or org-mode's agenda, this is the same shape of idea.
Card titles are visible by default. Board and gallery blocks may opt into a title-free card when the other card content already identifies the note:
```track-query :layout gallery :title hide
TABLE title, props.status FROM #project
```:title show is the default. :title hide only changes the visual title in board/gallery cards;
the card link remains available. Lists and calendars always show their title link because it is the
primary visible identity of each entry.
Each sample below shows the block's source, then its live result, computed from this help site's pages.
Board
Several pages of this site carry a section property in their sidecar props; the board lanes them
by it. Because section is a user property, it is written
props.section — bare section would be an unknown-key error (the board lane header still reads
section):
```track-query :layout board :by props.section
TABLE title, props.section, props.rating WHERE props.section SORT title
```It renders as (live):
Gallery
The visualization pages each carry a cover image and draw it as the card face; the other pages — like most notes in a real vault — set none, and fall back to their icon. Syntax and Query set no icon either, so their cards show all three states side by side: cover, icon, and track's built-in no-image face:
```track-query :layout gallery
TABLE title FROM #help SORT title
```It renders as (live):
Calendar
Some pages carry a reviewed date property; the calendar places them on their day, one grid per
month that has rows. Left unbounded, that stacks a grid for every month your dates span — so bound
the range in WHERE, where date values compare chronologically. This block keeps June 2026 only:
two of this site's five reviews fall outside it and stay off the grid (reviewed is a user
property, so it is written props.reviewed):
```track-query :layout calendar :by props.reviewed
TABLE title, props.reviewed WHERE 2026-05-31 < props.reviewed AND props.reviewed < 2026-07-01
```It renders as (live):
Saved queries
Name queries you reuse in the vault config (<vault>/.track/config.yml). Quote the expression — an unquoted # starts a YAML
comment and would silently truncate it:
queries:
open-projects: "TABLE title, props.status, props.due FROM #project WHERE props.status != done SORT props.due"Run one by name with track query --saved open-projects, or reference it from a block whose body
is a single saved: line — it renders that saved query's table, and takes :layout header
arguments like any other block:
```track-query :layout board :by props.status
saved: open-projects
```Saved queries come from the vault config, so a saved: fence expands on a published site too — this
site's own vault config is what would name one.
Tags: hierarchy and tag pages
Tags nest with / — #help/visualization/charts is a charts tag under help/visualization —
and every tag filter (search #tag queries, FROM, WHERE #tag) matches a tag or any of its
descendants by prefix.
Every rendered tag links to its tag page, /tags/<tag>, which lists the notes carrying that
tag or a descendant. The Web workspace serves tag pages live; the static export publishes a
real page per used tag (ancestors included) — the tag beside this page's title is a working example.
Tags come from note metadata (track new --tag, track append --tag, the sidecar's tags) and
from the body — a hierarchical #a/b/c written in prose is indexed too, matching FROM #a and
WHERE #a exactly as if it were a metadata tag. Only prose counts: #tags inside fenced or inline
code stay examples, not data, and a bare #2026, a Markdown heading, or a foo#bar fragment is
never a tag.