The caputchin.json manifest
caputchin.json at your repository root is the author and indexer source of truth for a marketplace game. The indexer reads it server-side to learn the game's identity, where its bundle lives, which presets it offers, and how to replay it. It is never read in the browser; the SDK does not load it at runtime.
This page is the field reference. For the build, see Build a marketplace game; for publishing, see Publish to the marketplace. The exact types are exported from the SDK as GameManifest (see the SDK reference).
A minimal single game
{
"terms_accepted": true,
"license": "MIT",
"marketplace": {
"name": "Leaf Memory",
"description": "Match pairs of tropical leaves before the timer runs out.",
"preview": "preview.png"
},
"npm": "@your-org/leaf-memory",
"entry": "dist/leaf-memory.js"
}Top-level fields
| Field | Required | Purpose |
|---|---|---|
terms_accepted | for indexing | Must be the literal true to confirm you accept the Marketplace Submission Terms. Any other value (or missing) drops the manifest from the index. Self-hosted-only manifests may omit it. |
license | for indexing | An SPDX identifier or expression covering your code and bundled assets. Must evaluate to an approved identifier (see the publish errors reference). |
marketplace | for indexing | Presence is the "index me" signal. Absent means a valid self-hosted game that the marketplace ignores. See The marketplace block. |
npm | with entry | npm package coordinate the indexer resolves to a jsDelivr URL. |
entry | with npm | Repo-relative path to the built bundle. entry, npm, or both must be present for a runnable game. |
games | for collections | Sub-manifest paths for a collection wrapper. Mutually exclusive with entry / npm. See Collections. |
run | optional | A dedicated headless replay artifact. See The run artifact. |
preferred | optional | Presentation hints the host MAY honor. See The preferred block. |
locales / skins / configurations | optional | Preset blocks consumed by the SDK in the iframe. See Preset blocks. |
There is no version field: the indexer pins the bundle to an immutable ref itself (the published npm version or the resolved commit SHA).
The marketplace block
| Field | Purpose |
|---|---|
name | Card title. Falls back to the repo name (single / wrapper) or leaf-dir name (collection child). |
description | Card subtitle and detail-page body. |
preview | Image path (relative to repo root) or absolute URL. Around 600x315, subject centered. |
version | Display-only version string for the detail page. Optional. |
support | Author-declared compatibility flags (responsive, touch, accessible, audio, ...), never verified by the platform; surfaced as filters and card icons. |
author | Optional { name?, url?, email? }. name / url render as an author byline on the detail page; email is never shown and is the opt-in for publish-failure emails. All three subfields are independent. |
The block is fully optional. Omit name / url and the detail page simply shows no author byline (the GitHub owner shown elsewhere on the page is a separate, always-present identity, not a fallback for this byline). Omit email and you receive no publish-failure notifications. Set only the subfields you want.
Distribution pointers
The indexer pins each game's bundle to an immutable ref so the stored integrity hash stays valid, and re-resolves on every run (daily cron plus manual "Publish or update"):
entry+npmresolve tocdn.jsdelivr.net/npm/<npm>@<resolved-version>/<entry>.entryonly resolves tocdn.jsdelivr.net/gh/<owner>/<repo>@<commit-sha>/<entry>.npmonly resolves to the package default entry at the pinned version.
There is no user-side version pinning; to freeze a build, a user self-hosts it via the widget's game-src (custom game).
The run artifact
By default the replay self-check runs your live entry bundle. When your game is large or framework- or WASM-based, ship a dedicated lean headless run artifact instead:
{
"run": {
"entry": "dist/run.js",
"modules": [
{ "name": "sim.wasm", "type": "wasm", "path": "dist/sim.wasm" }
]
}
}| Field | Meaning |
|---|---|
run.entry | Repo-relative path to the headless run bundle (the replay contract run export). |
run.modules | Optional array of module entries the run imports by name. |
Constraints the indexer enforces
These are validated at index time; a violation fails the publish with a manifest-error (see the publish errors reference).
run.entry:
- Must be JavaScript: the basename must match
[a-zA-Z0-9_-]+.js. The run artifact is always JS (WASM ships as a module, below). - Must be a clean repo-relative path: no leading slash, no
..traversal, no whitespace, no?/#, noscheme://prefix.
Each run.modules[] entry is { name, type, path }:
namemust match[a-zA-Z0-9_-]+.(wasm|js), a module is either JS or WASM, nothing else. Thenameis the import specifier the entry uses.typemust agree with the extension:wasmfor a.wasmname,jsfor a.jsname.namemust not be a reserved name (entry.js,artifact.js) the replay host uses internally.namemust be unique within the array (no duplicates).pathmust be a clean repo-relative path (same rules asrun.entry).- At most 16 modules.
Omit run to replay the live entry directly. See the replay contract for what the artifact must export.
The preferred block
The optional preferred block carries presentation hints. Every key is advisory: the host MAY honor it, and it never overrides an explicit embed attribute.
{
"preferred": { "width": 360, "height": 480, "layout": "modal" }
}A responsive game that should span its container by default can declare "full" on either axis:
{
"preferred": { "width": "full", "height": 480 }
}| Field | Meaning |
|---|---|
width / height | A pixel footprint, or the literal "full". The widget applies it when the embed leaves width / height unset (an explicit embed value, including full, wins instead). A pixel value sizes the iframe to that count; "full" stretches that axis to fill the parent, the same effect an embed width="full" has. Omit to fall back to the widget's built-in default footprint. |
layout | The shell the widget builds around the game: inline, modal, or fullscreen. Used only when the embed leaves layout unset (its default auto). Resolution order: the embed's layout attribute, then this preferred layout, then inline. |
These hints are honored only for games the platform resolves server-side (marketplace games, or a game id given without a site key). A self-hosted game-src bundle the platform cannot read before mount ignores both the footprint and the layout hint.
Preset blocks
locales, skins, and configurations each declare an optional schema (per-key types and documentation) plus presets (the named option banks). The widget resolves the visitor's choice against these and hands your game the flattened result as ctx. The full field-type catalogue is the shared customization schema reference; the same types drive a custom game's dashboard schema.
A skins.schema field may be a color, an asset (image / audio / video), or a scalar (boolean, number, range, list) using the same constraint forms as configurations ({ "type": "range", "min": 0, "max": 24 }, ["dots","stripes"], and so on). Scalar skin values resolve to their typed value in ctx.skin (a number is a real number), exactly like a configuration value; color and asset values resolve to strings.
A skin preset's _theme declares the mode it works in: light, dark, or any (omitting it means any). A light or dark preset shows only in that mode; an any preset reads on either background and is eligible for both. There is one default per mode. A preset marked _default: true is the default for whichever mode(s) it is eligible for, so an any default covers both; when several eligible presets are flagged default for a mode, the first one in declaration order wins it. List a mode-specific preset above an any one to give that mode a dedicated skin while the any preset falls through to the other.
The .caputchin/ split files
Preset blocks (especially full locale sets) make caputchin.json long. Move any axis into its own file under a .caputchin/ folder, leaving the manifest short:
caputchin.json
.caputchin/locales.json
.caputchin/skins.json
.caputchin/configurations.jsonEach file's top-level object is that axis block ({ schema?, presets }). All three are optional. Precedence is whole-axis replace, caputchin.json wins: if an axis is declared both inline and as a file, the inline block is used and the file is ignored (publishing warns so you know it was dead). Keep each axis in exactly one place.
Collections
A repo can ship several games. A collection wrapper declares child paths instead of entry / npm:
{
"marketplace": { "name": "Caputchin Core Pack", "description": "The official pack." },
"games": ["./packages/leaf-memory", "./packages/dino-runner"]
}Each path points at a child directory holding its own caputchin.json. Child ids are owner/repo/<leaf-dir>. Omit the wrapper's marketplace block to index the children without a collection page.
See also
- Build a marketplace game: producing the bundle this manifest points at.
- The replay contract: what the
runartifact must export. - Publish errors reference: every manifest validation error and the approved license list.
- Customization schema reference: the field types the preset blocks use.