format

The file format.

This is the whole contract for what the parser reads, written so you could follow it with a text editor and nothing else. Write a file that matches the rules below today, and it stays readable in twenty years, with or without blk/txt still around to open it.

The rules

A date on its own line starts a block

A line that is exactly a date opens a new block, and every line under it belongs to that block until the next date. Seven forms are read, month names case-insensitive:

text
21/03/2024
2024-03-21
2024/03/21
21 March 2024
21 Mar 2024
March 21, 2024
Mar 21, 2024

An abbreviated month is always three letters, Mar rather than Sept, and 03/04/2024 always reads as 3 April, the first form above: there is no separate month/day/year form to tell it apart from day/month/year. A byte order mark some editors add invisibly at the very start of a file does not hide a date on the first line; it is read past automatically.

A blank line ends a block

Inside one date, a blank line closes the block above it and opens a new one. Two paragraphs written under the same date, split by one blank line, become two separate blocks rather than one long one.

text
19/07/2026
First thing this morning.

A second, separate note, same day.

Three backticks fence in context

A line that is exactly three backticks opens a fenced section; the next line of exactly three backticks closes it. Everything between them is kept as context on the block right before the fence, not as a block of its own, so a stack trace or a raw payload can sit next to a note without turning into searchable prose.

text
19/07/2026
Investigated the timeout.
```
ETIMEDOUT at connect (net.js:12)
retry succeeded on the second attempt
```

Timestamped lines are their own mode

Switch on log mode and every line that starts with an ISO date and time opens its own block, dated straight from that stamp: no date header needed. A line that follows without a timestamp, a stack trace, a wrapped message, attaches to the block above it. A blank line does not close a block in this mode.

text
2026-07-19T14:03:22Z ERROR E5001 upstream timeout
    at handler.serve (server.js:42)
2026-07-19T14:03:26Z INFO request ok

A table needs two columns

A comma or tab separated file is read one row at a time. Each row needs a date column and a content column; a row missing either is skipped and named in a warning, never silently dropped.

text
date,content
2026-07-19,Investigated the timeout on checkout.
2026-07-20,Retested after the fix shipped.

Entities are patterns you write

An entity is a name and a regular expression. Every block is checked against every configured pattern, and a match becomes a typed value attached to that block. A pattern with a capturing group stores the group's text; a pattern without one stores the whole match.

text
pattern:  \b[A-Z]{2,10}-\d+\b
matches:  TICK-1842, INC-9021

Metrics are entities with a number

A metric is a pattern with a capturing group around a number. The block's first match contributes one reading; a group that turns out not to be a number is skipped rather than recorded, so a loose pattern degrades to no reading at all, not a broken one.

text
pattern:  weight[:\s]+(\d+(?:\.\d+)?)\s*kg
text:     weight: 82.4 kg
reading:  82.4 kg

The same block twice adds nothing

Every block is hashed from its date and its exact text. Importing the same file again, or the same note pasted into a second file, adds no duplicate. That is what makes re-running an import safe by default, on purpose, every time.

text
19/07/2026
Investigated the timeout.

re-imported: 0 new blocks

An export from a notes app is read as it was written

Four more shapes are read directly, so text you already keep somewhere else does not have to be reformatted before it can be imported. A JSON export with an entries array becomes one block per entry, dated by the entry and in the time zone it was written in. A file that opens with a title line and then a date line becomes one block, title first, with only the date line taken out. A folder of files named for their dates, such as 2026-09-03, dates every block in each file from the filename. And a folder of files with no dates in them at all becomes one block per file, dated by the file.

The only thing any of these adds to your text is a trailing line of tokens: an entry's own tags become a #tag line, and a [[bracketed name]] stays exactly where you wrote it and gets an @name token appended, so both are found by the same tag and mention patterns as anything else. That line is ordinary text on the end of the block, which is why the Markdown export still reads back in as valid input. Everything else is your text, unchanged, with one exception: a note exported as HTML is converted to the text it displays before it is read.

text
file:  2026-09-03.md
- Agreed the retry budget for [[Checkout Queue]] with @dana.
    - Three attempts, then a dead letter

block: dated 2026-09-03, with @Checkout-Queue appended

Text with no date anywhere is never silently dropped

A file with no line that is exactly a date parses to nothing by default, and says why instead of failing quietly. Asked for explicitly on a single import, the whole file is kept as one block instead, dated by the day it was imported.

text
warning: no date line was found, so nothing was imported.
a block starts with a line that is exactly a date,
such as 21/03/2024 or 2024-03-21.

The index is not the source

Every block the parser reads is written into an index: a compiled, queryable copy of what your files already say. The index is derived from your text, never the other way round. Nothing about a block's date, its content, or the entities pulled from it is decided by the index; all of it is decided by the text alone.

That means the index can be deleted and rebuilt from the same files, block for block, entity for entity, and land on the same result. A rebuild reads the source files you configured, so it restores exactly what came out of one of them: a block typed into the composer, posted through the API, or gathered from several places at once has no single file to be read back out of. The round trip that covers all of it is the export described below, because the Markdown it writes is itself valid input, so exporting and reading the file straight back in returns the same corpus. Losing the text loses everything, which is the whole reason the text stays canonical.

Export, in full, on demand

Every account can export its notes as JSON, CSV, or Markdown at any time, from inside the app. The Markdown export is not a lossy summary: it writes the same dated blocks and blank line separators this page describes, so the file it produces is itself valid input, ready to be read straight back in.

The one limit is size, not access: a single export request is capped at 20,000 notes, worked around by narrowing the date range rather than asking for a larger batch.

What the format leaves out

There are no folders and no nesting: every block belongs to exactly one date, full stop. There is no rich text: no bold, no headings, no tables inside a block, nothing a plain text editor cannot already show. And there is no proprietary markup: the date header, the blank line, and the three-backtick fence above are the entire vocabulary. A file that follows them opens correctly in anything that reads text, blk/txt included.

This page only grows

The ten rules above are the whole contract, and the contract only gets longer. A future rule can add a new date form or a new mode; none of the ten above will be narrowed, renamed, or dropped, and a file that is valid today stays valid tomorrow. Any change to this page is dated below.