Skip to content

Sheet JSON format

A minisheet sheet is a single JSON object. This is exactly what you get from Export and what Import expects (see Import & export). Everything about a sheet — its pages, the widgets on them, the background data, and the actions — lives in this one object, with no external references.

This page documents that structure so you can read, hand-edit, or generate sheets programmatically — including with AI tools. If you describe a character sheet to an AI assistant and ask it to produce JSON in this format, you can Import the result directly.

Top level

json
{
  "id": "sheet-abc123",
  "name": "Tuka, Barbarian",
  "pages": [ /* Page[] */ ],
  "bgData": { /* Record<varId, BGVar> */ },
  "actions": [ /* Action[] */ ]
}
FieldTypeNotes
idstringUnique sheet id. On import, a fresh id is assigned, so any value is fine when authoring.
namestringDisplay name. Import takes the sheet's name from the filename you import, so this is really the name of the file you get back from Export.
pagesarrayOne or more pages. Order is the tab order.
bgDataobjectMap from variable id to background variable. This is the sheet's actual data.
actionsarrayOptional. User-defined actions bound to buttons.

Computed formula results are never part of the JSON — they are always recalculated from bgData when the sheet loads.

Ids have to be unique within their scope (variable ids across bgData, widget ids within a page, and so on). Sheet, page, widget, and action ids can be any string. Background variable ids and table column ids are stricter, because expressions have to be able to name them: they must start with a letter or underscore, contain only letters, digits, and underscores, and must not collide with a built-in function name (min, max, floor, ceil, round, abs, pow, sqrt, sin, cos, tan, log, length, signed, contains, filter, map, reduce, concat). A variable or column whose id breaks those rules is dropped when the sheet is imported, so stick to short slugs like hp, str_mod, or spell_list.

Page

json
{
  "id": "page-1",
  "name": "Front",
  "grid": { "rows": 24, "cols": 16, "cellSize": 32 },
  "widgets": [ /* Widget[] */ ]
}
  • grid defines the page's CSS grid: rows × cols cells, each cellSize pixels square. Widgets are positioned and sized in grid units.
  • widgets is the list of widgets placed on this page.

Widget

Every widget shares these fields:

FieldTypeNotes
idstringUnique within the page.
typestringOne of label, checkbox, checkbox_toggle_pool, checkbox_row_pool, table, button.
x, ynumberTop-left position in grid units (0-based).
w, hnumberSize in grid units.
bgColorstringCSS color (may itself use ${expression} interpolation).
alignstringleft, center, or right.
boldbooleanOptional.
zIndexnumberStacking order.

The remaining fields depend on type. See the widget types reference for behavior; here is the shape of each variant's type-specific fields.

label

json
{
  "type": "label",
  "content": "STR mod: ${floor((STR - 10) / 2)}",
  "editable": false,
  "binding": { "bgVarId": "character_name" },
  "dialog": { "type": "notes", "content": "…", "dialogTitle": "Rage" }
}
  • content — text, with {varId} and ${expression} interpolation.
  • editable + binding — optional; makes the label write back to a variable in Play mode.
  • dialog — optional; a notes, resource_pool, or expand dialog.

checkbox

json
{ "type": "checkbox", "binding": { "bgVarId": "inspiration" } }

checkbox_toggle_pool

json
{
  "type": "checkbox_toggle_pool",
  "binding": { "remainingBgVarId": "rage_remaining", "stateBgVarId": "rage_active" }
}

checkbox_row_pool

json
{
  "type": "checkbox_row_pool",
  "binding": { "remainingBgVarId": "slots_remaining", "maxBgVarId": "slots_max" }
}

table

json
{
  "type": "table",
  "binding": { "tableId": "spells", "filter": "prepared == true", "autoHeader": true }
}

Bind template widgets to the table's column ids by placing them inside this widget's rectangle — see Table.

button

json
{
  "type": "button",
  "label": "Take a hit",
  "binding": { "actionId": "apply_damage", "parameters": { "amount": "5" } }
}

parameters maps each of the action's parameter names to an expression string evaluated when the button is clicked.

Background variable

bgData maps a variable id to one of two shapes. The map key must equal the variable's id.

Scalar variable

json
"hp_max": { "id": "hp_max", "type": "number", "value": 45, "order": 0 }
FieldTypeNotes
idstringMust match the bgData key.
typestringnumber, string, boolean, or formula.
valuenumber | string | booleanSee below.
ordernumberOptional display order in the data editor.

value depends on type:

  • number / boolean — a literal.
  • string — a string that may contain {varId} and ${expression} interpolation.
  • formula — a raw expression string (no ${} wrapper), e.g. "floor((STR - 10) / 2)".

Table variable

json
"spells": {
  "id": "spells",
  "type": "table",
  "value": {
    "columns": [
      { "id": "name", "type": "string" },
      { "id": "level", "type": "number" },
      { "id": "prepared", "type": "boolean" }
    ],
    "rows": [
      { "name": "Fireball", "level": 3, "prepared": true },
      { "name": "Shield", "level": 1, "prepared": false }
    ]
  },
  "order": 5
}

Each row is an object keyed by column id. Columns hold plain values only — number, string, or boolean — and every cell is coerced to its column's type on load. Anything derived belongs in a formula variable that reads the table (e.g. filter(spells, row => row.prepared).length), or in an action that writes a computed value back into a column.

Action

json
{
  "id": "apply_damage",
  "name": "Apply damage",
  "description": "Subtract incoming damage from HP",
  "parameters": [
    { "name": "amount", "type": "number", "defaultValue": 0 }
  ],
  "operations": [
    { "bgVarId": "hp", "expression": "hp - amount" }
  ],
  "confirmBeforeExecute": false
}
FieldTypeNotes
idstringReferenced by button widgets' binding.actionId.
namestringDisplay name.
descriptionstringOptional.
parametersarrayOptional. Each { name, type, defaultValue? }; parameter names are available as variables in operations expressions. A parameter with no defaultValue must be supplied by the button, or the action fails without changing anything.
operationsarrayOrdered list of { bgVarId, expression } — each assigns the evaluated expression to a background variable. Operations run in order, and each one sees the values written by the ones before it.
confirmBeforeExecutebooleanOptional; prompts before running.

An operation's bgVarId can also target table data instead of a scalar variable: "spells.prepared" applies the expression to that column in every row, and "spells[2].prepared" applies it to one row only (1-based). See Actions for how these run.

Minimal example

A complete, importable sheet with one page, one variable, and one label:

json
{
  "id": "sheet-example",
  "name": "Minimal",
  "pages": [
    {
      "id": "page-1",
      "name": "Main",
      "grid": { "rows": 12, "cols": 12, "cellSize": 32 },
      "widgets": [
        {
          "id": "w1",
          "type": "label",
          "x": 0, "y": 0, "w": 4, "h": 1,
          "bgColor": "transparent",
          "align": "left",
          "zIndex": 0,
          "content": "HP: {hp}"
        }
      ]
    }
  ],
  "bgData": {
    "hp": { "id": "hp", "type": "number", "value": 10, "order": 0 }
  },
  "actions": []
}

minisheet is offline-first — your sheets live on your device, with optional cloud backup.