The .roadmap.json file format
A roadmap is one JSON file holding a schema version, a document id and title, a list of properties, a list of items, and a list of saved views. Field ids are readable slugs rather than opaque keys, unknown fields survive a load and save unchanged, and a file with a newer schema version opens read-only.
A roadmap made here is a single file with a double extension — platform-2026.roadmap.json. The
operating system reads only the final .json, so it opens in any text editor and any JSON
viewer with no special handling, while roadmap makes it recognisable in a downloads folder.
The tables below are generated from the same schema the editor validates against, so they cannot drift from the format the application actually reads.
What a reader may rely on
- Unknown keys survive
- A key this reader does not recognise is preserved through a load and a save, byte for byte. Nothing in a roadmap file is ever silently dropped.
- Ids are readable and stable
- Property and option ids are slugs generated once from the label and never changed afterwards, so `"stage": "now"` still means something in three years.
- Dates are ISO 8601
- A date value is `YYYY-MM-DD`. Whether it is drawn as a day, a month or a quarter is a display setting, not a change to the value.
- A newer file opens read-only
- A reader that meets a `schemaVersion` it does not know still shows the roadmap and still exports it. Only editing is switched off.
- Hiding is presentation
- A property missing from a view's visible fields is hidden in that view and still present in the file. Sharing the file shares everything in it.
A complete minimal file
{
"schemaVersion": 1,
"id": "rm_k3n8fq2p",
"title": "Platform 2026",
"createdAt": "2026-10-01T09:00:00.000Z",
"updatedAt": "2026-10-01T09:00:00.000Z",
"properties": [
{ "id": "title", "label": "Name", "type": "text", "role": "title" },
{ "id": "stage", "label": "Stage", "type": "select",
"options": [{ "id": "now", "label": "Now", "color": "blue" }] },
{ "id": "start", "label": "Start", "type": "date", "role": "startDate" },
{ "id": "end", "label": "End", "type": "date", "role": "endDate" }
],
"items": [
{ "id": "c7m2xp9d", "parentId": null,
"values": { "title": "Card payments", "stage": "now",
"start": "2026-10-01", "end": "2026-12-31" } }
],
"views": [
{ "id": "v_all", "name": "All items", "type": "table",
"filter": { "conditions": [] }, "sort": [],
"colorBy": { "source": "none" }, "order": {},
"table": { "columns": [{ "propertyId": "title", "width": 320 }] } }
]
}The machine-readable JSON Schema is at /roadmap.schema.json, generated from the same source as the tables below.
Every field
Document
| Field | Type | Required | Notes |
|---|---|---|---|
| schemaVersion | integer | Yes | The format version this file was written against. A reader that understands an older version opens the file for viewing and export, with editing disabled. It is never rejected. |
| id | string | No | The document's own id, `rm_` and eight characters. Regenerated when a template or starter is opened, so two copies are never the same document. Optional on import: a file without one is given a new id when opened. |
| title | string | No | The roadmap's name. Also the basis of the download filename, slugified. Optional on import: a file without one opens as `Roadmapstar roadmap`. |
| createdAt | string | No | ISO 8601 timestamp. Written once. Optional on import: a file without one is given the time it was opened. |
| updatedAt | string | No | ISO 8601 timestamp of the last edit. Optional on import: a file without one is given the time it was opened. |
| properties | Property[] | Yes | The fields every item may carry, in the order they were defined. The registry belongs to the document, not to the application. |
| items | Item[] | Yes | The roadmap's items, flat. Hierarchy is expressed by `parentId`, not by nesting. |
| views | View[] | Yes | Saved views over the same items. Each carries its own filter, sort, grouping and visible fields. |
Property
| Field | Type | Required | Notes |
|---|---|---|---|
| id | string | Yes | A readable slug generated once from the label — `Risk level` becomes `risk_level` — and never changed afterwards, including when the label is renamed. A collision takes a numeric suffix. |
| label | string | Yes | What the field is called on screen. Free to change; the id does not follow it. |
| type | "text" | "longtext" | "number" | "select" | "date" | "url" | "checkbox" | Yes | The value type. `longtext` holds a markdown subset; `select` requires `options`. |
| role | "title" | "startDate" | "endDate" | "stage" | "milestone" | No | Marks the property the application should use for a structural job — the item's name, its dates, its stage, or its milestone flag. At most one property per role. |
| options | SelectOption[] | No | For `select` only: the allowed values, each with its own id and colour. |
| multiple | boolean | No | For `select` only: when true, an item may hold several options, stored as an array of option ids. Absent means one. |
| config | object | No | Per-type settings. For a date, `display` chooses whether a value is drawn as an exact date, a month or a quarter. |
SelectOption
| Field | Type | Required | Notes |
|---|---|---|---|
| id | string | Yes | A readable slug, generated from the label at creation and immutable after — so `"stage": "now"` stays meaningful in the file. |
| label | string | Yes | What the option is called on screen. |
| color | "slate" | "blue" | "teal" | "green" | "amber" | "orange" | "red" | "violet" | Yes | One of eight named swatches. The name carries the meaning; the colour only carries the grouping. |
Item
| Field | Type | Required | Notes |
|---|---|---|---|
| id | string | Yes | Eight characters, unique within the document. A duplicate is regenerated on load and the change is reported. |
| parentId | string | null | No — null | The parent item's id, or null for a top-level item. A missing or cyclic parent is reset to null on load and reported. |
| dependsOn | string[] | No | Ids of items this one follows. A self-reference, a missing target or a link that would close a cycle is dropped on load and reported. |
| values | { [propertyId: string]: string | number | boolean | null | string[] } | No — {} | The item's values, keyed by property id. A select with `multiple` holds an array of option ids. A value whose property was deleted stays in the file. |
View
| Field | Type | Required | Notes |
|---|---|---|---|
| id | string | Yes | `v_` and eight characters. |
| name | string | Yes | The view's tab label. |
| type | "table" | "board" | "timeline" | Yes | Which renderer draws this view. The type-specific block below it is kept when the type changes, so switching back restores the old configuration. |
| filter | { conditions: Condition[] } | No — {"conditions":[]} | Conditions, combined with AND. An empty condition list means every item. |
| sort | Sort[] | No — [] | Sort levels in priority order. An empty list leaves the manual order from `order`. |
| groupByPropertyId | string | No | The property whose value groups items — board columns, timeline lanes, table sections. Items with no value form their own visible group. |
| density | "compact" | "default" | "comfortable" | "presentation" | No | The size the view draws itself at. Held on the view, outside the type block, so it survives a type change. |
| showLegend | boolean | No | Draw a key for the colour-by property. |
| colorBy | any | No — {"source":"none"} | Where item colour comes from: nothing, or the swatches of a select property's options. |
| order | { [propertyId: string]: string } | No — {} | Manual item order, as fractional index strings keyed by item id. Only consulted where no sort applies. |
| table | TableSettings | No | Table-specific settings. Present for a table view, and kept after a type change. |
| board | BoardSettings | No | Board-specific settings. |
| timeline | TimelineSettings | No | Timeline-specific settings. |
Condition
| Field | Type | Required | Notes |
|---|---|---|---|
| propertyId | string | Yes | The property being tested. |
| operator | "is" | "is_not" | "contains" | "not_contains" | "is_empty" | "is_not_empty" | "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "before" | "after" | "on_or_before" | "on_or_after" | "is_checked" | "is_unchecked" | Yes | Which comparison to make. The available operators depend on the property's type. |
| value | string | number | boolean | null | No | The value compared against. Absent for the operators that need none, such as `is_empty`. |
Sort
| Field | Type | Required | Notes |
|---|---|---|---|
| propertyId | string | Yes | The property to sort on. |
| direction | "asc" | "desc" | Yes | Ascending or descending. |
TableSettings
| Field | Type | Required | Notes |
|---|---|---|---|
| columns | object[] | Yes | The visible columns, in order, each with its width in pixels. A property absent from this list is hidden in this view — and still present in the file. |
BoardSettings
| Field | Type | Required | Notes |
|---|---|---|---|
| cardProperties | string[] | No — [] | Properties drawn on a card beneath its name. |
| hiddenGroups | string[] | No — [] | Option ids whose column is not drawn at all. |
| columnOrder | string[] | No — [] | Column keys in the order their columns are drawn. Empty is the registry's option order. |
| subGroupByPropertyId | string | No | A second property, drawing lanes across the columns. |
| collapsedSubGroups | string[] | No — [] | Lanes drawn as their header alone. |
| cardColor | "strip" | "fill" | "top" | No | How loudly a card wears its colour. Absent draws a strip. |
| columnHeaderStyle | "panel" | "plain" | No | Absent draws a recessed panel header attached to its cell. |
| showCounts | boolean | No | Counts on the column and lane headers. Absent is true. |
| columnWidth | number | No | Minimum column width in pixels, clamped 140 to 420. Absent is 180. |
| columnColors | { [columnKey: string]: Swatch } | No | A swatch per column, on this board alone, keyed by column key. A column absent from the map is drawn neutral. |
TimelineSettings
| Field | Type | Required | Notes |
|---|---|---|---|
| startPropertyId | string | Yes | The date property a bar starts at. |
| endPropertyId | string | Yes | The date property a bar ends at. An item with a start and no end is drawn as a point. |
| milestonePropertyId | string | No | A checkbox property; a true value draws a diamond instead of a bar. |
| zoom | "week" | "biweek" | "month" | "quarter" | "half" | "year" | No — "quarter" | The axis resolution, from a week to a year per column. |
| axisLabels | "dates" | "weeks" | No | Whether axis columns are labelled with dates or week numbers. |
| highlightWeekends | boolean | No | Shade Saturdays and Sundays behind the lanes. |
| showGridlines | boolean | No | Vertical rules at each axis column. |
| anchor | string | No | The date the visible window opens at. Absent frames the items. |
| laneOrder | string[] | No — [] | Group keys in the order their lanes are drawn. |
| hiddenLanes | string[] | No | Group keys whose lane is not drawn at all, and whose items do not appear in this timeline. |
| laneColors | { [laneKey: string]: Swatch } | No | A swatch per lane, on this timeline alone, keyed by lane key. A lane absent from the map is drawn untinted. |
| collapsedLanes | string[] | No — [] | Lanes drawn as their label alone. |
| showDependencies | boolean | No | Draw dependency arrows in this view. Absent is true. |
| shift | "overlap" | "gap" | "none" | No | How dependants are rescheduled when a date changes. Absent overlaps them. |
| axisBand | boolean | No | A coarser band above the column labels — quarters over months, months over weeks. Absent is false. |
| laneBanding | boolean | No | Alternate lane backgrounds, so a wide row stays readable across. Absent is false. |
| showToday | boolean | No | Draw the Today line and its caption. Absent is true. |
| barHeight | number | No | Bar height in pixels, overriding the density's, clamped 16 to 64. Absent uses the density. |
| barLabels | "inside" | "outside" | "hidden" | No | Where a bar's name is drawn. Absent draws it inside. |
| barDates | boolean | No | Append the bar's dates after its name. Absent is false. |
| laneLabelStyle | "text" | "chip" | No | Draw the lane label as its swatch chip instead of display type. Absent is text. |
| avoidWeekends | boolean | No | Keep shifted items off Saturdays and Sundays. Absent is false. |
| visibleProperties | string[] | No | Which properties a bar and its detail show. Absent shows everything but links and notes. |
Questions
Why publish the format at all?
Because a file format nobody can read is not a file you own. The specification is what makes the document readable without this editor, now or in five years.
What happens to fields a reader does not recognise?
They are preserved verbatim through a load and save. Nothing in a roadmap file is ever silently dropped, including values referencing a property that was deleted.
Can I write my own roadmap file by hand?
Yes. The ids are readable slugs, so a file is understandable in a text editor and reviewable in a diff. An invalid file reports the line that is wrong.
Is the format versioned?
Yes, by an integer schemaVersion at the top of the document. A file declaring a version newer than the reader understands opens for viewing and export, with editing disabled.
Last updated