ModCapability
The twelve slugs manifest.capabilities.allowed can contain. Every one of the 25
api methods is gated on exactly one of them, and calling a method whose slug is
absent throws Missing mod capability: <capability> synchronously — including for the methods that return
a promise.
12 values, declared as modCapabilitySchema in packages/shared/src/modManifest.ts.
Omitting the capabilities block entirely defaults it to { version: "1", allowed: ["log"] }, so a mod
that declares nothing can call api.log() and nothing else.
This page is the value table. Mod capabilities owns the full capability-to-method matrix, the thirteen detector regexes, the three checkpoints a declaration is validated at, and the exact rejection text — read that page when you are debugging a declaration.
The values#
| Value | Unlocks | Contexts that accept it | Surfaces it applies to |
|---|---|---|---|
log |
log |
manifest.capabilities.allowed; the default when the block is omitted. |
Mod scripting only |
spawn-object |
createObject |
manifest.capabilities.allowed |
Mod scripting only |
register-action |
registerAction |
manifest.capabilities.allowed |
Mod scripting only |
read-context |
getMySeat, getMyTeam, getTurn |
manifest.capabilities.allowed |
Mod scripting only |
read-world |
getSnapshot, getObject, listObjects, getContainerContents, getHandObjects, getZoneObjects — all six least-privileged on every peer |
manifest.capabilities.allowed |
Mod scripting only |
read-hidden-information |
getUnredactedSnapshot — the elevated read: real card faces, pile order and secretMetadata, from the host. Never implied by read-world, never granted by default. |
manifest.capabilities.allowed |
Mod scripting only |
object-action |
objectAction |
manifest.capabilities.allowed |
Mod scripting only |
saved-data |
getSavedData, setSavedData |
manifest.capabilities.allowed |
Mod scripting only |
subscribe-events |
on — and therefore every hook |
manifest.capabilities.allowed |
Mod scripting only |
ui |
getUiState, listUiElements, setUiElement, deleteUiElement |
manifest.capabilities.allowed |
Mod scripting only |
play-sound |
playSound, setObjectSound |
manifest.capabilities.allowed |
Mod scripting only |
plugin-call |
listPlugins, callPlugin — naming a declared function on an installed plugin. Also requires a matching manifest.plugins entry, and is not network access. |
manifest.capabilities.allowed |
Mod scripting only |
Table scripting has no capability model at all. It is restricted instead by a three-type intent
allowlist — spawn, object-action, transform — enforced on the host, so every table-scripting
reference entry carries capability: none and there are no exceptions.
Two things a capability list does not do#
It does not stop you declaring more than you use. The validator walks the capabilities the script
uses and checks each one is declared; it never walks the declared list looking for entries nothing calls.
A manifest can declare all twelve against a script that only logs, and nothing warns. Least privilege here
is a discipline you keep yourself. The one exception runs the other way: declaring plugins without
plugin-call is rejected, because that manifest contradicts itself.
It does not tell you what a mod intends. It tells you what its script was scanned as calling.
read-hidden-informationis the exception to everything below. It is the one grant whose absence genuinely withholds data rather than declaring an intent. Everyread-worldread is redacted to the least-privileged view on every peer, the host included, so a mod cannot reach a hidden card face by arranging to run on the host;getUnredactedSnapshotis the only door, its host-side check runs before the state is served, anddetectScriptCapabilitiesmatches the call by name so an undeclared use is a publish-time rejection. Treat its presence in a listing as "this mod can see hidden cards".
By design. Ten of the twelve grants are enforced in two places.
requireCapabilitythrows from inside the same untrusted JavaScript realm as the mod script (apps/web/src/mods/sandbox/modSandbox.html) — the error you meet while developing — and the host then re-validates the gated message against the mod's grants before acting on it, using the sharedMOD_API_METHOD_CAPABILITIESmap (apps/web/src/mods/SandboxedModRunner.ts). The host check is the authoritative one, because a script that posts to the host directly never runs the in-frame one. That coverslog,spawn-object,register-action,read-world,read-hidden-information,object-action,play-sound,saved-data,uiandplugin-call.
read-contextandsubscribe-eventsare frame-local only. Neither sends the host a message to check:getMySeat,getMyTeam,getTurnandonare answered inside the frame, and the host pushescontextUpdateandhookEventto every running frame unconditionally (apps/web/src/mods/SandboxedModRunner.ts,updateContextanddispatchEvent). A frame granted neither still holds seat, team and turn state and receives every hook payload in its own realm. For those two the declaration is disclosure, not a wall. Read a capability list as least-privilege disclosure all the same: what the platform scanned for, and what a reviewer or player can know about a mod before opening its code. The boundaries that hold regardless of what a manifest declares are separate from all of it — the opaque-origin iframe and its CSP, the neutered DOM, network and storage globals, the five scanner patterns, the host-side ten-action allowlist, the per-mod namespacing of saved data, and the ownership check on sound references. See Known limitations.
See also#
- Mod capabilities — the matrix, the detectors and the rejection text.
- The mod api object — every method, with its capability badge.
- Manifest reference — the
capabilitiesfield's own schema and defaults. - Scanner rule codes — the other thing that rejects a script.
- Sandbox limits — what the capability model is and is not for.
- Calling a plugin from a mod — what
plugin-calldoes and does not grant.
