Appearance
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[] */ ]
}| Field | Type | Notes |
|---|---|---|
id | string | Unique sheet id. On import, a fresh id is assigned, so any value is fine when authoring. |
name | string | Display 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. |
pages | array | One or more pages. Order is the tab order. |
bgData | object | Map from variable id to background variable. This is the sheet's actual data. |
actions | array | Optional. 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[] */ ]
}griddefines the page's CSS grid:rows×colscells, eachcellSizepixels square. Widgets are positioned and sized in grid units.widgetsis the list of widgets placed on this page.
Widget
Every widget shares these fields:
| Field | Type | Notes |
|---|---|---|
id | string | Unique within the page. |
type | string | One of label, checkbox, checkbox_toggle_pool, checkbox_row_pool, table, button. |
x, y | number | Top-left position in grid units (0-based). |
w, h | number | Size in grid units. |
bgColor | string | CSS color (may itself use ${expression} interpolation). |
align | string | left, center, or right. |
bold | boolean | Optional. |
zIndex | number | Stacking 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; anotes,resource_pool, orexpanddialog.
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 }| Field | Type | Notes |
|---|---|---|
id | string | Must match the bgData key. |
type | string | number, string, boolean, or formula. |
value | number | string | boolean | See below. |
order | number | Optional 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
}| Field | Type | Notes |
|---|---|---|
id | string | Referenced by button widgets' binding.actionId. |
name | string | Display name. |
description | string | Optional. |
parameters | array | Optional. 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. |
operations | array | Ordered 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. |
confirmBeforeExecute | boolean | Optional; 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": []
}