> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dexi.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Properties and bases

> The key:: value property syntax, the filter language bases understand, and the .base file format for import and export

Dexi notes have no frontmatter and no properties panel. A property is a line you type in the body, and a **base** is a saved view — table, cards, list or map — over every note whose properties and attributes match a filter. This page is the reference for both; the product side is in the [knowledge base](https://dexi.net/kb/properties-and-bases). Agents reach bases through the MCP tools [`list_bases` and `query_base`](/mcp/tools#list_bases).

## Property syntax

One property per line, at the start of the line, optionally after a list marker:

```text theme={null}
status:: reading
- due:: 2026-09-10
Due Date:: 2026-09-10
authors:: [Kahneman, Tversky]
ref:: [[Thinking, Fast and Slow]]
topic:: #pricing #psychology
```

Rules:

* 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::vector` and `a::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.

| You write | Stored as |
| - | - |
| `true` / `false` | boolean (`yes`/`no` stay strings — an answer of "no" must not become `false`) |
| `4`, `-3`, `2.5` | number (leading zeros such as `007` stay strings) |
| `2026-09-10`, `2026-09-10T14:30` | date / datetime (ISO 8601; naive values read in your profile timezone) |
| `[[Title]]`, `[[Title\|alias]]` | link |
| `#tag` | tag |
| `[a, b, c]`, several links or tags, or the same key on several lines | list |
| anything else | string (a bare comma never splits: `summary:: fast, cheap, good` is prose) |

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 global `filters`, 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](#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](/mcp/tools#query_base).

### 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's `file.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.

**Operators and functions**

| Kind | Available |
| - | - |
| Comparison | `==`, `!=`, `<`, `<=`, `>`, `>=` (typed: number vs number, date vs date, string vs string) |
| Logic | `&&`, `\|\|`, `!`, parentheses |
| Arithmetic | `+`, `-`, `*`, `/`, `%`; dates ± durations |
| Globals | `date()`, `now()`, `today()`, `duration()`, `if()`, `link()`, `number()`, `min()`, `max()` |
| File tests | `file.hasTag("a", …)`, `file.hasLink("Title")`, `file.inFolder("Name")`, `file.hasProperty("key")` — constant arguments only |
| Any value | `.isEmpty()`, `.toString()` |
| Strings | `.contains()`, `.containsAny()`, `.containsAll()`, `.startsWith()`, `.endsWith()`, `.lower()`, `.trim()`, `.length` |
| Numbers | `.abs()`, `.floor()`, `.ceil()`, `.round(n)` (half-up), `.toFixed(n)` (text with `n` decimals) |
| Dates | `.date()`, `.year`, `.month`, `.day`, `.hour`, `.minute`, `.second` |
| Lists | `.contains()`, `.containsAny()`, `.containsAll()`, `.join()`, `.length` |

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 under `formulas` and referenced as `formula.<name>`:

```yaml theme={null}
formulas:
  score: rating * 2
  overdue: due < today()
  ppu: (price / age).toFixed(2)
  status_label: if(formula.overdue, "late", status)
```

Formulas use the same language as filters, can reference other formulas (no cycles), and return whatever the expression returns — a number, text, a date, a boolean or a list. Once declared, a formula is a column (`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:

```yaml theme={null}
views:
  - type: table
    name: By status
    groupBy:
      property: status
      direction: ASC
    summaries:
      rating: Average
      formula.score: Sum
      file.mtime: Latest
```

Groups are ordered by their value in `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:

| Column kind | Summaries |
| - | - |
| Number | `Average`, `Min`, `Max`, `Sum`, `Range`, `Median`, `Stddev` |
| Date | `Earliest`, `Latest`, `Range` (milliseconds between them) |
| Boolean | `Checked`, `Unchecked` |
| Any | `Empty`, `Filled` (the count), `Unique` |

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:

```text theme={null}
status != "done" && file.hasTag("pricing")
due <= today() + "1 week" && !done
rating >= 4 || file.mtime > now() - "30 days"
file.source == "bookmark" && file.domain.endsWith("substack.com")
authors.contains("Kahneman") && year.toString().startsWith("20")
```

Limits: 1,000 characters and 256 tokens per expression, 50 filter conditions per base, 20 views, 5 sort keys, `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:

```yaml theme={null}
filters:
  and:
    - 'status != "done"'
    - file.hasTag("reading")
properties:
  status:
    displayName: Status
  rating:
    displayName: Rating
views:
  - type: table
    name: Queue
    order:
      - file.name
      - status
      - rating
      - file.mtime
    sort:
      - property: file.mtime
        direction: DESC
    limit: 50
  - type: cards
    name: Wall
    imageProperty: file.preview
    cardSize: 240
```

Import is lossy only where Dexi lacks the feature, and says so: custom `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`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.