The .mcyc routine format
A routine is a list of steps you repeat. .mcyc is a
plain-JSON file that holds one: its steps, their order, their schedules, and how it resets.
One file, no server, no account. Readable in a text editor and writable by any program.
The format is open
You do not need permission, a license, or a conversation with us to use it.
Free to implement
Anyone may read, write, generate, or validate .mcyc files in their own
software (commercial or not, open source or closed) without asking permission and
without a fee. There is no patent claim and no trademark condition on producing or
consuming the format.
The format is open. The miniCycle application is proprietary. Those are separate things, and only the first one is being granted here.
That split is deliberate. A routine you spent months refining should not be trapped in one
vendor's app, including ours. If miniCycle disappeared tomorrow, a .mcyc file
would still be a readable, documented description of your routine, and anything could open it.
If you build something that reads or writes .mcyc, we would genuinely like to
hear about it, but that is an invitation, not a requirement.
At a glance
| Extension | .mcyc files. A .json extension is also accepted on import. |
|---|---|
| Media type | application/json |
| Encoding | UTF-8, uncompressed. Emoji are fine and widely used in task text. |
| Contents | Exactly one routine per file |
| Required fields | name and tasks. Nothing else. |
| Task ceiling | 150 tasks. Longer files import, truncated to the first 150. |
| Schema | JSON Schema 2020-12. Pinned to 2.5 for CI, or current for editors. |
A complete file
This is valid and complete. Save it as morning.mcyc and import it.
{
"name": "morning_routine",
"title": "☀️ Morning Routine",
"autoReset": true,
"deleteCheckedTasks": false,
"cycleCount": 0,
"tasks": [
{ "id": "t1", "text": "💧 Drink a glass of water", "completed": false },
{ "id": "t2", "text": "🧘 Stretch for five minutes", "completed": false },
{ "id": "t3", "text": "📓 Write down today's top task", "completed": false, "highPriority": true }
]
}
Strip it further if you like. This is the smallest file that imports:
{ "name": "minimal", "tasks": [] }
With a scheduled task
Set recurring and describe the schedule. Only the sub-object matching
frequency is read, so you can leave the others out:
{
"name": "weekly_shop",
"title": "🛒 Weekly Shop",
"autoReset": false,
"tasks": [
{
"id": "t1",
"text": "🧺 Check what's already in the pantry",
"completed": false,
"recurring": true,
"recurringSettings": {
"frequency": "weekly",
"indefinitely": true,
"weekly": { "days": ["Sat"] },
"time": { "hour": 10, "minute": 0, "meridiem": "AM" }
}
}
]
}
You do not need to write a recurringTemplates map. The app derives one from
every task marked recurring when the file is imported.
Field reference
Defaults below are what the importer actually applies to a file that omits the field, which is not always the same as the value you would guess.
Root object
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | required | Identifier for the routine. A file without it is rejected, even when title is present. |
tasks | array | required | Must be an array; empty is valid. Order is the display order. |
title | string | falls back to name | The display name, and where emoji belong. |
autoReset | boolean | true | Auto mode. See the note under surprises: omitting this does not give you a manual routine. |
deleteCheckedTasks | boolean | false | To-Do mode. Wins over autoReset when both are true. |
cycleCount | integer | 0 | Times completed. Ship shared templates with 0. |
theme | string | app default | Vocabulary theme. An unknown value falls back rather than failing. |
recurringTemplates | object | derived | Usually omit. Derived from recurring tasks; anything you supply is merged on top. |
Task object
| Field | Type | Default | Notes |
|---|---|---|---|
text | string | none | What the user reads. Technically repairable, but always supply it. |
id | string | generated | Must be unique within the file. Duplicates make reordering drop a task. |
completed | boolean | false | |
highPriority | boolean | false | |
priorityColor | string / null | null | Hex, e.g. "#dc3545". Ignored unless highPriority is true. |
dueDate | string / null | null | ISO 8601. Invalid values are discarded, not rejected. |
remindersEnabled | boolean | false | |
recurring | boolean | false | Also changes the default for deleteWhenCompleteSettings. |
recurringSettings | object | none | Read only when recurring is true. |
deleteWhenCompleteSettings | object | see below | { "cycle": bool, "todo": bool }. The durable setting. |
deleteWhenComplete | boolean | derived | Do not author. Recomputed on every load. |
schemaVersion | integer | 2 | Optional; the importer sets it. |
recurringSettings
frequency selects which sub-object is consulted, and may be
hourly, daily, weekly, biweekly,
monthly, or yearly. Use indefinitely: true to repeat
forever, or indefinitely: false with a repeatCount. Weekday names
are three letters: Sun through Sat. Full details, including the
monthly and specific-date forms, are in the
full specification.
Modes: how a routine resets
Two booleans at the root decide what happens when everything is checked off. There is no
mode field. The mode is inferred.
| Mode | Set | Behavior |
|---|---|---|
| To-Do | deleteCheckedTasks: true | Finished tasks are removed. Checked first, so it wins over autoReset. |
| Auto | autoReset: true | The routine resets itself the moment every task is done. |
| Manual | both false | You decide when the routine has been completed. |
Three things that surprise people
Each of these is a real behavior of the importer, not a caveat about edge cases.
1. Omitting autoReset gives you an Auto routine, not a Manual one
The importer reads this field as “true unless explicitly false.” If you leave it
out, you get a routine that resets itself. Write "autoReset": false when you
want the user to decide.
2. deleteWhenComplete is derived, so authoring it does nothing
The durable field is deleteWhenCompleteSettings, which holds a separate
boolean per mode. The flat deleteWhenComplete is just whichever entry matches
the routine's current mode, and it is recomputed every time the routine is opened.
A file that sets only "deleteWhenComplete": true will appear to import
correctly and then lose the value the first time the routine loads. Set the per-mode
object instead:
"deleteWhenCompleteSettings": { "cycle": true, "todo": true }
The defaults differ by task type, which is usually what you want:
recurring tasks default to { "cycle": true, "todo": true }, so the occurrence
is removed on reset and the schedule brings it back, while ordinary tasks default to
{ "cycle": false, "todo": true }, so they are unchecked and stay put.
3. Unknown fields are dropped, not preserved
The importer copies a fixed set of fields. Extra keys of your own will not survive a
round trip through the app, so don't use a .mcyc file to carry metadata you
need back later.
Validating a file
A machine-readable schema is published alongside this page, so you can check a file before handing it to anyone.
Both URLs below are JSON Schema, draft 2020-12, and both describe what the importer accepts: a file that validates will import. They carry identical rules today. Pick one based on whether you want it to stay still.
| URL | Use it when |
|---|---|
/schema/mcyc-2.5.schema.json |
Pinned to format 2.5. Never changes. Reference this from CI or a build step, where a validation result that shifts under you is a broken build. |
/mcyc.schema.json |
Current. Tracks the format as it grows, so it may gain fields. Good for editor autocomplete, where you want the newest hints. |
A new format version gets a new filename, never an edit to an existing one. That is the guarantee that makes the pinned URL worth pinning, and it is why the two are served with different cache lifetimes: the versioned file is immutable, the current one stays correctable.
From the command line
# Node, using ajv (pinned: safe in CI)
npx ajv-cli validate -s mcyc-2.5.schema.json -d my-routine.mcyc --spec=draft2020
# Python, using check-jsonschema (reads the URL directly)
pipx run check-jsonschema \
--schemafile https://minicycle.app/schema/mcyc-2.5.schema.json \
my-routine.mcyc
In an editor
Most editors will offer completion and inline errors if you point them at the schema. In VS Code, map the extension once in your settings:
"json.schemas": [
{
"fileMatch": ["*.mcyc"],
"url": "https://minicycle.app/mcyc.schema.json"
}
]
Being strict is on you
The importer is deliberately forgiving: it repairs missing ids, coerces odd types, and discards bad dates rather than refusing the file. That is good for users and unhelpful for authors, because a sloppy file imports quietly. Validate before you publish.
Versioning and stability
Files carry a task-level schemaVersion of 2, matching the app's
current 2.5 data schema. The format has been stable since then, and the importer still reads
older files, including pre-2.5 layouts.
Our intent for anything built on this: existing files keep importing. New fields may be added, so treat unknown keys as ignorable rather than as errors, which is also how the app treats them.
Published schemas follow the same rule. Each format version gets its own permanent URL
(/schema/mcyc-2.5.schema.json today), and a version that has shipped is never
edited in place. If the format reaches 2.6, that becomes a new file and the 2.5 document
stays exactly as it is, so anything pinned to it keeps validating the way it did on the
day you pinned it.
The full specification carries the complete field-by-field reference, every recurring form, worked examples, security notes, and generator scripts for Node and Python. This page is the short version.