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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 2026-09-03: page published. Nine rules, unchanged since.
- 2026-09-04: a tenth rule, for exports from a notes app. The nine above are unchanged.