The open format
Obra Todo stores its entire database as one human-readable JSON document.
The format is open and stable on purpose: agents, scripts, and other tools
may read or rewrite the file directly. This page is the spec; the repository
carries the same contract in FORMAT.md.
Where the file lives
Section titled “Where the file lives”| Environment | Location |
|---|---|
| macOS app | ~/Library/Application Support/studio.obra.todo/todo.json |
| Browser dev mode | localStorage["obra-todo"] |
The app re-reads the file every time its window regains focus, so an external edit shows up as soon as the user switches back. The app saves a few hundred milliseconds after any change, atomically (temp file + rename). Last writer wins — there is no merge.
Top-level shape
Section titled “Top-level shape”{ "version": 1, "projects": [ { "id": "…", "name": "House", "indent": 0 } ], "contexts": [ { "id": "…", "name": "Work", "color": "#4f9cf9" } ], "tasks": [ { "id": "…", "title": "Call the plumber", "…": "…" } ]}Array order is meaningful. Tasks render in array order (it’s an outline);
projects render in array order in the sidebar. There are no separate
order or rank fields.
{ "id": "5f3e0b1c-9a7d-4a2e-b8a1-2f6c1d9e4b7a", "title": "Call the plumber about the boiler", "note": "Ask about the warranty first.", "projectId": null, "contextId": "id-of-a-context-or-null", "indent": 0, "deferDate": "2026-08-01", "dueDate": "2026-08-15", "completedAt": null, "createdAt": "2026-07-25T09:30:00.000Z"}| Field | Type | Meaning |
|---|---|---|
id | string | Unique. The app uses UUIDs; any unique string works. |
title | string | The action itself. |
note | string | Free-form support material. |
projectId | string | null | null = the task is in the Inbox. |
contextId | string | null | The GTD context. null = none. |
indent | integer ≥ 0 | Outline depth relative to surrounding rows of the same list. At most one level deeper than the row above; the app clamps at 4. |
deferDate | "YYYY-MM-DD" | null | Hidden-until date. Deferred tasks dim; the day it arrives the task appears in Today. |
dueDate | "YYYY-MM-DD" | null | The deadline. Due/overdue → Today; future → Upcoming. |
completedAt | ISO 8601 | null | null = open. Set = done. |
createdAt | ISO 8601 | When the task was captured. |
Calendar-day fields (deferDate, dueDate) are local days with no time or
timezone — a deadline is a day, not a moment.
Project
Section titled “Project”| Field | Type | Meaning |
|---|---|---|
id | string | Unique. |
name | string | Display name. |
indent | integer ≥ 0 | Sidebar nesting depth; same one-deeper rule, clamped at 3. |
Deleting a project in the app moves its tasks back to the Inbox — it never deletes tasks.
Context
Section titled “Context”| Field | Type | Meaning |
|---|---|---|
id | string | Unique. |
name | string | Display name (e.g. “Work”, “Private”). |
color | CSS color | The context’s dot, shown everywhere. |
Rules for external writers
Section titled “Rules for external writers”- Write the whole document atomically — write a temp file, then rename
it over
todo.json. The app does the same. - Preserve unknown fields when round-tripping; the app does.
- Omitted optional fields are fine — the app fills in defaults on load
(missing
ids become fresh UUIDs, missingindentbecomes 0, missing nullable fields becomenull). - Invalid JSON is ignored on reload and refused on save, so a botched write can’t destroy data — but validate before writing anyway.
- To complete a task, set
completedAt; to reopen, set it back tonull. Don’t delete completed tasks unless asked — they’re the user’s log. - Never touch the
backups/directory next to the file.
How the app derives its views
Section titled “How the app derives its views”- Inbox —
projectId == null, array order, as an outline. - Today — open tasks where
dueDate <= today, plus tasks whosedeferDate == today. - Upcoming — open tasks with
dueDate > today, grouped by day. - Completed — tasks with
completedAt, newest first. - A project — its tasks in array order (completed stay, grayed).
- A context — open tasks with that
contextId, across all projects.