Plugins
A plugin brings card data from an external provider onto a DiceyTable table. It is the counterpart to a mod: where a mod ships table content and a sandboxed script, a plugin ships a declaration of where card data comes from and what shape it arrives in.
Like a mod, a plugin is a public GitHub repository, registered against an immutable commit
sha. There is no build step — the reviewed source is what runs, which is only checkable
because anyone can read it. Its manifest is diceytable.plugin.json at the repository root.
Plugins are a smaller and stricter thing than mods, and most games never need one. If your cards are a fixed list you can write down, ship them as a card catalogue in your own mod's repository instead — that path involves no plugin, no registry and no external provider. A plugin earns its complexity only when the card data belongs to somebody else's service.
The two halves#
A plugin has up to two halves, and they run on different machines in different trust domains. They cannot talk to each other directly, and the whole security story rests on that.
| Half | Has | Never has |
|---|---|---|
| Data half | Network access to declared hostnames, from our servers | Table state, deck contents, user identity, auth tokens |
| Table half | Table state, through the existing mod sandbox | Network access of any kind |
The table half is not a second runtime. It is a mod script — the same
SandboxedModRunner iframe, the same capability model, the same static scanner. Everything
on Script safety applies to it unchanged.
The data half runs no author code at all. Its behaviour is a declaration: named endpoints and a field mapping, both interpreted by the platform. See Writing a plugin for the mapping grammar.
The three capabilities#
A plugin manifest declares capabilities.allowed from a closed set of three. These are the
plugin capability slugs and are unrelated to the mod capability
slugs — a plugin's table half declares those separately, in
the ordinary way, for the script it ships.
| Capability | What it permits |
|---|---|
network |
The data half may fetch from the manifest's declared hostnames. The platform hands it no table state. Requires at least one declared origin. |
table |
The plugin ships a table-side script, which runs in the mod sandbox with no network. Requires tableScript to name a .js file. |
combined |
The data half may additionally receive table-derived data from the table half. Requires network and table as well, plus a disclosure block carrying the platform's warning text verbatim. |
combined is allowed and is deliberately the hard path. A plugin that can read table state and
send data to its own servers is, on the host, a cheating vector: it can observe other players'
hands and face-down cards and transmit them off the table. The warning shown to players is
written by the platform, not by the author, and every seated player sees it —
the details, and what the author does own.
network alone is not a promise that nothing table-derived reaches the provider#
The platform hands a network plugin no table state, but the caller chooses the parameter
values, and a declared text parameter is free text. A mod holding both read-hidden-information
and plugin-call can therefore read hidden state and put it into an ordinary search parameter of
an ordinary network plugin.
DiceyTable treats that pairing as combined, because that is what it amounts to. Whenever a table
runs a mod holding both of those capabilities alongside a plugin declaring network, the
room's capability disclosure elevates that plugin to combined: every seated player gets the
verbatim off-table warning before they can sit, and the table blocks on it. Neither party declared
combined — the room did.
Two consequences worth planning for:
- As a plugin author, a mod you have never heard of can make your plugin read as
combinedat someone's table. Nothing about your manifest changes, and nothing you can declare prevents it. - As a mod author, declaring
read-hidden-informationtogether withplugin-callmakes every table that loads your mod alongside any network plugin show the strongest warning DiceyTable has. If your mod does not genuinely need to read hidden information, do not declare it.
Every mod→plugin call additionally emits a server-side flow record: plugin, function, room, calling mod, payload size in bytes, and whether it was accepted. The size is recorded so the volume of a channel like this is visible; the payload itself never is.
How a plugin reaches a table#
Three routes, and they are independent.
- As a game's card source. A mod's
data/cardSchema.jsondeclaressource: { kind: "plugin", pluginId: "…" }. The deck builder then resolves that game's cards through the plugin instead of a catalogue file in the mod's repository. Nothing in the mod's script is involved. - From a mod script. A mod declares the plugin in its own manifest and calls a declared
function with
api.callPlugin(...), under theplugin-callcapability. See Calling a plugin from a mod. - As a deck import. A plugin may declare a
deckImportblock, and a game names it indata/cardSchema.json. A person in the deck builder pastes a deck id or a link and gets a saved, editable deck. No mod code runs on this route, so it needs no mod capability at all — see Deck import.
Both routes go through our servers. Neither one puts a provider's address, a credential, or a raw response in a browser.
Four rules that shape everything else#
These come up on every page below, so they are worth reading once here.
- A plugin never supplies a URL. It declares bare hostnames and named endpoints with typed parameters; the platform composes the request. A plugin has no way to express a destination, which is what makes the origin allowlist unbypassable rather than merely enforced.
- No executable code in the data half. The response mapping is a closed grammar — dotted paths and a fixed list of transforms — with no expression language, no regexes and no callbacks.
- No credential, ever, in the manifest. Plugin source is public and sha-pinned, so a key in the manifest is a published key. Credentials are written server-side, separately.
- The manifest is strictly parsed. An unrecognised key is a parse error, not something quietly ignored. That is what makes rule 3 mechanical instead of advisory.
Where to go next#
- Writing a plugin — the manifest end to end: origins, endpoints, the card mapping, the exposed API, capabilities, credentials, quotas and the failure enum.
- A worked example — one complete, validated manifest for a real card provider, read block by block, including the four things it cannot do yet. Start here if you would rather see a whole plugin than a field table.
breakingVersionand migration — when a version bump forks every saved deck, and what you owe the people whose decks it strands.- Calling a plugin from a mod —
listPlugins,callPlugin, theplugin-callcapability, and why a dynamic call is a publish error.
See also#
- Mod capabilities — the mod-side capability model a plugin's table half lives under.
- Script safety — the five patterns that reject a script, applied to a plugin's table half exactly as to a mod's.
- The mod
apiobject — includinglistPluginsandcallPlugin. - Building a deck — the deck builder a plugin-sourced game feeds.
