Anatomy of a mod
This page walks a complete mod file tree top to bottom, combining everything the manifest and files pages cover individually. Every path below is verified against mods/example, the reference mod in this repo.
The whole tree#
my-mod/
├── diceytable.mod.json ← REQUIRED. The one file every mod must have.
├── gametable.mod.json ← optional legacy fallback (recommended: keep byte-identical)
├── setup.json ← entry.setup target — objects present when the table loads
├── scripts/
│ └── main.js ← entry.script target — sandboxed JS, gated by capabilities
├── assets/
│ ├── models/
│ │ ├── meeple.glb ← declared in assets[]
│ │ └── meeple.meta.json ← sidecar: default color/physics for meeple.glb
│ ├── textures/
│ │ ├── board.webp ← declared in assets[]
│ │ └── decks/
│ │ └── tarot/
│ │ ├── cards/
│ │ │ ├── 000-fool.webp ← per-card source images (durable truth)
│ │ │ └── 001-magician.webp
│ │ └── sheet-1.webp ← generated composite sheet
│ └── decks/
│ └── tarot.deck.json ← custom-deck definition, references sheet-1.webp
├── media/ ← RESERVED. Hidden from the editor's file explorer.
│ ├── cover.webp (512×512)
│ ├── cover-thumb.webp (256×256)
│ ├── screenshot-1.webp (1280×720)
│ └── screenshot-2.webp (1280×720)
├── screenshots/ ← photo-mode captures from Preview. Published, never declared.
│ ├── 20260914-153012-k3f9a1c2b7xq.png
│ └── 20260914-153012-k3f9a1c2b7xq.json ← sidecar: camera, size, tier, visibility
└── README.md ← not read by anything; author documentation only
Reading it top to bottom#
diceytable.mod.json is the only file the platform strictly requires. Everything else exists because this manifest's entry and assets[] name it — a different mod's manifest would imply a different, smaller (or larger) required set. See Manifest reference for every field, and Required and conventional files for how treePathsForManifest() computes the canonical path set from the manifest alone.
gametable.mod.json exists purely as a legacy-naming safety net — the scanner reads it only if fetching diceytable.mod.json throws for any reason, including a transient GitHub failure. Keeping the two files byte-identical (as mods/example does) means that fallback, if it ever fires, is harmless.
setup.json is entry.setup's target. The modern format is edit-scene, authored entirely through Edit Mode — hand-writing it isn't the expected workflow. See Setup JSON for the full schema.
scripts/main.js is entry.script's target — the only script format accepted is JavaScript (.js), enforced by unsupported-script-type if you point entry.script at anything else. entry is worth a second look here: both setup and script are optional, so a component-pack shipping only reusable assets (like the meeple.glb/meeple.meta.json pair above) legitimately has neither.
assets/ is pure convention — there's no rule that models live under models/ or textures under textures/. What is real: every path under assets/ must (a) appear in the manifest's assets[] array and (b) have one of the 18 allowed extensions.
meeple.meta.json and tarot.deck.json are sidecars — ordinary declared JSON assets whose content is additionally checked against a schema, because every peer's client reads them directly (spawn defaults, card-face rendering) rather than treating them as opaque bytes.
media/ is the one folder with real, scanner-enforced rules beyond "must be a declared extension": it's hidden from the editor's own file browser, and every file under it must be one of 4 image formats regardless of whether it's separately listed in assets[]. See Cover art and screenshots.
screenshots/ holds the raw captures you take in the editor's Preview photo mode, each with a .json sidecar. Like the editor's generated thumbnails it is published and synced but never declared in assets[] (isDeclarableModAsset excludes the folder), so no player downloads it. It only reaches your store page when you promote a capture into media/ from the Screenshots gallery.
README.md (and anything else you add that no manifest field points at) is inert as far as the platform is concerned — useful for you and other humans browsing the repo on GitHub, invisible to the scanner.
Two real, minimal mods for comparison#
mods/example — a game-pack with both a setup and a script:
diceytable.mod.json
gametable.mod.json (byte-identical)
setup.json
scripts/main.js
README.md
Simpler still, a pack with no script at all:
diceytable.mod.json ← entry: { setup: "setup.json" }, no script key
setup.json
assets/
board.png
Both are valid, complete mods. The only thing that changes between them is what their own manifests declare.
See also#
- Manifest reference — every field, in depth.
- Required and conventional files — the required-vs-conventional table and the file-tree envelope.
- Assets · Sidecars · Cover art and screenshots — deep dives on each file category above.
- Your first mod — the end-to-end authoring walkthrough this page feeds into.
- The mod
apiobject — every method the script named byentry.scriptcan call. - Scripting API — the reference for both scripting surfaces, once you know which files a mod has.
