Craft

Designing Schemas

This is a technical reference — most players don't need to read this. If you want a better character sheet, just ask Orbit to improve your file type and it will follow these guidelines for you. The details are here for anyone who wants to understand what "better" means, or to make changes by hand.

A schema is the list of fields every file of a type can have — the blueprint behind your characters, items, quests, and factions. The File Types page covers what schemas are and which field types exist. This page is about designing them well: the decisions that make a schema pleasant to play with for years, and the patterns that quietly cause problems later.

Everything below happens in the schema editor on a file type — adding fields, picking their types, setting limits and filters. A few sections show the JSON underneath to make a pattern vivid, but you never have to write it: the editor (or Orbit) does.

Start from the built-ins

Every file of every type already has a name, an optional description, and an optional image. Craft adds these automatically — never define your own copies, and never add fields like title or portrait2 that duplicate them. Character-designated types also get a built-in voice field for AI dialogue.

Classify every field before adding it

Before a field earns a place in the schema, decide which of these it is:

  • Stored state — a choice or a fact that changes during play: current hit points, a quest's status, which armor is equipped. These belong in the schema.

  • A shared definition — a rule or stat block that many files rely on: a class's progression table, an item's weight, a species' traits. These belong on the file that owns them (the class, the item, the species), and other files point at them with reference fields.

  • A derived value — anything you could calculate from other fields or referenced files: an ability modifier, total carry weight, remaining spell slots. These should be computed fields, not stored numbers.

  • Presentation — labels, icons, dot strings like "●●○○○", or pre-formatted text like "STR +3". These do not belong in the schema at all. Showing data nicely is the layout's job (see Designing Layouts).

Most schema problems trace back to one of the last two sneaking into storage: a stored copy of something derivable goes stale the first time a source value changes, and stored presentation strings have to be manually rewritten every time the underlying number moves.

Collections, not numbered fields

If you find yourself adding item1, item2, and item3, stop — that is a list. Use one List field with an item limit instead. Numbered fields cap your design at whatever number you guessed, break layouts when a slot is empty, and make every rule that touches them three times as long.

When slots carry distinct meaning — main hand, off hand, armor — use an Object field with named properties instead of a List. The key is stability: "the field is called mainHand" survives renames and rewording, while "the third item in the list is the armor" does not.

The same idea scales up. Eighteen skills do not need eighteen stored flags, eighteen computed modifiers, and eighteen display fields. Store one field that holds the whole family and compute one list from it — the dos and don'ts below show exactly what this looks like.

Choosing between similar field types

  • Enum vs. Text. Use an Enum when the set of valid values is closed and you want Craft (and the GM) to enforce it — a quest status of open/active/complete, a size category, a damage type. Use Text when values are open ended. Enums support up to 50 options; if your vocabulary is bigger than that, or entries deserve their own descriptions and images, they should be files of their own type instead.

  • Enum vs. Reference. The moment a "category" needs its own lore, image, or stats — classes, factions, spells — promote it from an enum option to a file type, and point at it with a Reference field. References give you navigation, backlinks, and let computed fields read the target's data.

  • Object vs. separate file. Keep data embedded in an Object field when it only ever matters to this one file (a stat block's six abilities). Split it into its own file type when multiple files need to share it, when the GM should be able to look it up on its own, or when players will care about it as a thing in the world.

  • Images. Always use the Image field type for pictures — never a Text field holding a URL. Image fields get generation, focal-point cropping, and proper rendering for free.

Model relationships with references

The links between things — a character and their spells, a shop and its wares, a quest and its villain — are where a project starts feeling like a world instead of a pile of documents. The pattern is always the same: the thing itself is a file, and everyone who has it, knows it, or sells it stores a Reference to it. A character's knownSpells is a List of References to Spell files; their inventory is a List whose rows pair an Item reference with a quantity Number, because the quantity belongs to the relationship, not the item.

Reference fields can also carry filters that narrow what their picker offers — including dynamic rules like "only Feats whose class matches this character's class." The Reference Fields page has the full guidance, including a worked example of a character's spells and inventory end to end.

Constraints and defaults

Fields can enforce their own rules: Number fields take a minimum and maximum, Text fields a length limit, List fields a cap on how many items they hold. Text, Number, Boolean, and Enum fields can also set a default, applied when a new file is created without that field — a new character starts at level 1, a new quest starts open.

Constraints are worth setting early: they keep the AI GM honest when it updates files during play, and they catch typos before they become world facts.

Dos and don'ts

The principles above, shown as the four refactors we most often end up making in real projects. If one of the "before" snippets looks like your schema, that is a cleanup worth asking the assistant for.

Don't: one field per thing. Do: one collection.

The most common way schemas go wrong is multiplying fields across a family of similar things. It starts innocently with one skill:

{
  "hasProficiencyInFishing": { "type": "boolean" },
  "hasProficiencyInFishingDisplay": { "type": "string" },
  "hasProficiencyInAthletics": { "type": "boolean" },
  "hasProficiencyInAthleticsDisplay": { "type": "string" },
  "hasProficiencyInStealth": { "type": "boolean" },
  "hasProficiencyInStealthDisplay": { "type": "string" }
}

…and fifteen skills later the type has thirty fields, every new skill is a schema change, the layout lists each one by hand, and the "display" copies drift out of sync with the booleans they mirror. The fix is one field that holds the whole family: a List of Enum options — proficiencies: ["fishing", "athletics", "stealth"] — rendered as tappable chips by the layout. Adding a sixteenth skill is one new enum option, not two new fields. And the "Display" fields simply vanish — showing a checkmark or a label is the layout's job, never a second stored field.

When each entry carries data of its own — a rating, a specialty — use an Object field as a map keyed by the thing's id instead:

{
  "skills": {
    "fishing":   { "rating": 3, "specialty": "fly fishing" },
    "athletics": { "rating": 1 }
  }
}

If the sheet needs a flat list to render (every skill with its rating and modifier), add one computed field that assembles that list from the map — not one computed field per skill. Two fields total, no matter how many skills the game has.

Don't: numbered fields. Do: a list with a limit.

item1, item2, item3 is a list wearing a disguise. Replace the three fields with one List field of Item references and set its limit to 3. The three-slot rule survives — still enforced, but in one place, with no empty slots haunting the layout. The exception: when slots carry distinct meaning, name them — mainHand,offHand , and armor as three Reference fields is better than a list, because "the third item is the armor" is exactly the kind of convention that breaks silently.

Don't: store what can be computed.

A stored strengthModifier: 3 is wrong the moment strength changes. Anything derivable from other fields — or from referenced files — should be a computed field with an expression:

{
  "strength": { "type": "number" },
  "strengthModifier": {
    "type": "number",
    "expression": "floor(($self.strength - 10) / 2)"
  }
}

The same rule catches subtler copies: a character storing an item's weight (read it from the referenced item instead), a quest storing its objective count (count the list), a total that sums other fields. If you can write the sentence "this field should always equal…", it should be an expression.

Don't: per-class branches. Do: put rules on the class.

barbarianRagePoints, monkKiPoints, wizardSpellSlots, and their maximums — six-plus fields on every character, most of them irrelevant to any given one. The rules ("a level 5 monk has 5 ki") don't belong on the character at all: they belong on the Class file type, as data rows every character of that class shares. The character keeps two fields — a Reference to its class, and one Object field holding the current value of each resource it actually has:

{
  "class": "monk",
  "resourceState": {
    "ki": { "current": 3 }
  }
}

A computed field then combines the class's resource definitions with the stored current values into one list the layout renders as trackers. New class, new resource, no character schema change — that is the test a good design passes.

Changing a schema that already has files

Adding an optional field is always safe — existing files simply do not have it yet. The risky changes are removing a field, renaming one, or making a field required: every existing file of the type is checked against the new schema, and files that no longer fit will block the change until their content is updated. When you ask the assistant for a bigger reshape — "turn these numbered item fields into an inventory list" — it plans the schema change and the content migration together, which is the right way to think about it: a schema change is a change to every file of the type.

Keep it lean

A good schema is the smallest set of fields that captures what the world needs to remember. Every field you add is something creators must fill in, the GM must consider, and layouts must present. When in doubt, leave it out — adding an optional field later is painless. There is also a hard ceiling on top-level fields per type (see Platform Limits), and a schema pushing toward it almost always contains one of the don'ts above — a field family that wants to be a map, stored copies of computable values, or rules that belong on a referenced type.