Skip to content

Template file format (thendoo-template/v1)

Every template on the Thendoo website, and every file the routine builder makes, is a single JSON document in the thendoo-template/v1 format. It’s plain JSON, so you can write one by hand, keep it in Git or generate it from a script.

{
"format": "thendoo-template/v1",
"id": "weekly-reset",
"name": "Weekly reset",
"locale": "en",
"templates": [],
"habits": [],
"categories": []
}
Field Required Meaning
format yes Always thendoo-template/v1. A future breaking change gets a new version.
id yes Stable id in lowercase with hyphens. It matches the website slug and the import link thendoo://import/{id}.
name yes Shown when importing. Up to 120 characters.
locale no The language the text is written in, e.g. en or he. Defaults to en.
templates one of the three Lists of tasks (up to 20).
habits one of the three Recurring habits (up to 50).
categories one of the three Money categories (up to 200).

At least one of templates, habits or categories must have an entry.

Each entry is a list. Its shape is exactly the body of POST /v1/templates in the API, so an entry can be sent as it is.

{
"type": "project",
"name": "Trip prep",
"body": {
"items": [
{
"title": "Pack the bag",
"offset_days": -1,
"checklist": ["Chargers and adapters", "Medication for the whole trip"],
"notes": null
}
],
"body_md": null
}
}
  • type is one of task, project, checklist, routine or note.
    • Task, project and routine: applying the list creates one task per item.
    • Checklist: creates one task, with the items as its checklist.
    • Note: creates a note from body_md and has no items.
  • offset_days counts days from the date the user picks when applying the list. -3 means three days before. null means no due date. The limit is ±3650.
  • Subtasks are written as checklist lines in v1, up to 100 per task.
  • notes can be up to 20,000 characters.

Habits are written the way Thendoo thinks about them: a cue and an action. When I pour my coffee, then I take my vitamins.

{
"key": "vitamins",
"name": "Vitamins",
"cue": "After I pour my morning coffee",
"action_text": "Take the vitamins from the jar next to the kettle",
"trigger": { "type": "after_habit", "habit": "coffee" },
"schedule": { "kind": "daily" },
"streak_rule": { "misses_per_week": 1 },
"color": null,
"icon": null
}
  • key is a short id that is unique within the file. Other habits refer to it.
  • trigger sets when the nudge fires:
    • { "type": "none" }: no automatic nudge.
    • { "type": "time", "at_min": 480 }: a time, in minutes after local midnight (480 = 08:00).
    • { "type": "after_habit", "habit": "<key>" }: right after another habit in the same file.
    • { "type": "event_end", "title_contains": null }: when the day’s last meeting ends, or the last one whose title contains the text.
    • { "type": "place", "event": "arrive", "label": "Home" }: when you arrive at or leave a place. The person chooses the actual place on import. Coordinates are never part of a template.
  • schedule sets how often:
    • { "kind": "daily" }
    • { "kind": "weekdays", "days": ["MO", "WE", "FR"] }
    • { "kind": "x_per_week", "times": 3 }
    • { "kind": "flexible" }
  • streak_rule.misses_per_week (0–3) sets how many misses a week a streak forgives. Thendoo’s default is 1.
[
{ "key": "home", "name": "Home", "kind": "expense", "parent": null, "color": null },
{ "key": "rent", "name": "Rent", "kind": "expense", "parent": "home", "color": "#4F46E5" }
]
  • kind is expense, income or transfer.
  • parent is the key of another category in the same file, which makes the category a subcategory.
  • color is a hex color such as #4F46E5, or null.
  • Every key is unique within its block.
  • after_habit and parent refer to keys that exist, and never to the item itself.
  • No database ids or personal data. The importer creates fresh records and maps keys to the new ids.
  • Strings are trimmed, and unknown fields are rejected, so a typo fails loudly instead of being ignored.

The app doesn’t have an importer for the whole file yet. Until then, send each templates[] entry on its own to POST /v1/templates. Habits and categories need the importer before they can be loaded.