list_bases and query_base.
Property syntax
One property per line, at the start of the line, optionally after a list marker:- The key is 1–3 words (letters, digits, spaces,
_,-), at most 50 characters, followed by::and then whitespace or the end of the line. Keys are normalised:Due Date→due_date, and matching is case-insensitive. - Deliberate non-matches:
std::vectoranda::b(no whitespace after::), URLs and times (10::30— a key can’t contain:or/),::mid-sentence (the key would exceed three words), and> quoted:: text(blockquotes are someone else’s words). - Properties are parsed on every write, whichever path created the note — editor, MCP
create_note/update_note, bookmarklet, email-in, feeds — and stored on the note. They stay in full-text and semantic search. - Limits: 50 properties per note, values up to 500 characters, lists up to 50 items.
Value types
Types are inferred from the shape of the value; there is nothing to declare.
The API returns raw JSON values on
NoteOut.properties and on the MCP note item; repeated keys on one note merge into a list.
Bases
A base has globalfilters, optional formulas (see below), optional display names for properties, and one or more views. Each view is table, cards, list or map with its own extra filters, property order, up to five sort keys, an optional groupBy and per-column summaries (see Grouping and summaries), an optional limit (≤ 500); for cards a cardSize (100–600 px) and an imageProperty; for a list, markers (bullets, numbers or none), a separator between the inline properties (default , ) and indent — the properties rendered as nested sub-items under each note instead of inline. Obsidian’s list view has the same three settings but doesn’t document their YAML keys, so a list view exported from Obsidian imports with its own keys reported as unknown and Dexi’s defaults applied. A map view takes coordinates — the property or formula holding lat, lng text or a [lat, lng] list (the two shapes Obsidian’s map view reads; with separate latitude and longitude properties, point the map at a formula [latitude, longitude], as Obsidian’s guide does) — an optional markerColor property (hex, rgb()/hsl() or a color keyword), and an optional starting center ([lat, lng]) and zoom (0–20); without them the map fits its markers. Notes whose coordinates don’t parse are counted below the map rather than plotted; a map shows at most 500 markers. Tiles come from OpenFreeMap (vector, keyless). In a table, click a property cell on one of your own notes to edit it in place: type the value as you would in the note (done, 4, 2026-09-10, [a, b], [[Title]]), Enter saves, Escape cancels, a blank value removes the property, and boolean cells toggle with one click. Dexi rewrites that key:: value line inside the note (adding one after the note’s last property line when the key is new) and leaves the rest of the note untouched; file attributes, formulas and teammates’ notes aren’t editable. Bases are personal — teammates never see them — but they run over every note you can see, including notes shared with you. The app manages all of this at Bases in the sidebar — and the notes list’s Save as base button turns its active filters (type, tag, folder, team, period, sort) into a new base in one step; the definitions are also reachable over the MCP server.
Filter language
Filters are a subset of the expression language Obsidian uses in.base files. Every expression compiles fully to SQL, so pagination and totals are exact; anything outside the subset is rejected with a clear error naming the construct (and the position inside the expression) rather than approximated.
Operands
note.<key>or bare<key>— a property.formula.<name>— one of the base’s formulas (below).file.name(title),file.ctime,file.mtime,file.tags(hashtags and @mentions),file.links,file.folder(your own notes only),file.size, plus Dexi’sfile.source(note,bookmark,email,feed),file.url,file.domain,file.description,file.preview,file.visibility,file.shared,file.owner.- Literals: strings, numbers,
true/false, lists such as["reading", "done"]or[latitude, longitude],date("2026-09-10"),now(),today(), durations such as"1 week"or"3d","2026-09-10"compared against a real date is coerced to one. A literal list of constants works anywhere (["reading", "done"].contains(status)); one built from properties is a display value for a formula column — it can’t be filtered or sorted on.
Truthiness follows Obsidian: empty strings,
0, false, empty lists and missing properties are falsy, so !note.done matches notes that never had the key. A property holding a value of the wrong shape (a string where a number is expected) simply doesn’t match; it never errors the query.
Not supported yet (rejected with a message): this.* (Dexi has no embedding note), regular expressions (matches), list.map/filter/reduce/sort/unique/flat, slice, reverse, string.replace/split/repeat/title, date.format/relative/time, isType, isTruthy, file(), list(), random(), image(), icon(), HTML helpers, object values.
Formulas
A formula is a named expression computed for every note, declared underformulas and referenced as formula.<name>:
formula.score in a view’s order, renamable through properties), a sort key, and a filter operand (formula.score >= 8) — the Obsidian behavior. A base may hold 20 formulas; names are letters, digits and underscores.
Values are computed per note when a view runs; the API returns them on BaseQueryOut.formulas (note id → name → value) and the MCP note item carries formulas. A formula that fails on one note — a strict date() over a value that isn’t a date — reads as empty for that note only.
One rule keeps totals exact: a formula used in a filter, sort, group or summary must have a SQL form. Almost every expression does; the exceptions are constructs the row evaluator can compute but SQL can’t, such as if() with branches of different types. Such a formula is display-only: it still shows in columns, and using it in a filter, sort, group or summary is rejected with a message naming the formula.
Grouping and summaries
A view can group its rows by one property, file attribute or formula (groupBy: {property, direction}), and summarize any column (summaries: {column: Name}) — Obsidian’s semantics:
direction (numbers, then dates, then text — the same order a sort uses), and notes without a value form a None group at the end. Dates group by calendar day in your timezone — a due property or file.mtime gives one group per day, not one per timestamp. Grouping is a leading sort, so the view’s own sort keys order the rows within each group. The built-in summaries are exactly Obsidian’s names:
Summaries are computed over the rows the view shows — the whole filtered result, capped by the view’s
limit — never just the current page; a grouped view carries each group’s summaries on the group plus the grand total. Custom summary formulas (Obsidian’s top-level summaries: using values) are not supported yet and are dropped with a warning on import.
The API returns groups ([{key, label, count, summaries}] in display order), group_keys (parallel to items) and summaries on BaseQueryOut; the MCP query_base result carries groups, summaries, and group on each item. A view with more than 500 groups is rejected rather than run.
Examples:
limit ≤ 500. Queries run under a statement timeout and report a timeout rather than a partial page.
The .base file format
Export produces, and import accepts, Obsidian’s .base YAML:
summaries formulas are dropped with a warning, plugin view types (kanban, …) become tables with a warning, unknown keys and unknown summary names are reported, and every unsupported expression — in filters, sort keys, formulas, groupBy or summaries — is listed at once instead of stopping at the first. formulas, per-view groupBy, built-in summaries, and list/map views import as-is (their view settings under Dexi’s keys). Files are capped at 64 KB. Export of an imported file round-trips: import(export(x)) == x for everything Dexi keeps, emitted in Obsidian’s key order.
In the app: Import… and Export .base on a base’s toolbar. Over the API (JWT-authenticated app routes, not part of the public REST surface): POST /api/v2/bases/import and GET /api/v2/bases/{id}/export.