Pack Config
What it is and why you’d want it
Section titled “What it is and why you’d want it”Pack Config lets a broadcaster tune your Pack without touching its code.
Limits, labels, default toggles: anything the streamer should be able to set
once and have stick. You declare the settings in pack.toml, the platform
renders an editor in the Dashboard, stores the values per broadcaster, and
delivers them to your Adapter.
Reach for Pack Config when a value belongs to the broadcaster, not to each redeem. A bot-spawn cap, a chat prefix, an asset path: the streamer sets these once and every Invocation inherits them.
Config vs. params
Section titled “Config vs. params”These are two different inputs, and picking the wrong one frustrates everyone.
- Config is broadcaster-wide. One value per broadcaster, set in the
Dashboard, applies to every Invocation. Declare it under
[[pack.config.section]]. Read it in the Adapter with whatever you store the latchedconfigframes into. - Params are per-Invocation. A fresh input on each redeem, validated
against the event’s
params_schema, carried in theinvocationframe.
Rule of thumb: if a viewer supplies it at redeem time, it is a param. If the streamer sets it in their dashboard and forgets it, it is config.
Minimal example
Section titled “Minimal example”Config is an ordered array of titled sections. Each section owns an ordered array of fields.
[[pack.config.section]]key = "effects"title = "Effects"subtitle = "How hard viewers can grief"[[pack.config.section.field]]key = "max_bots"type = "integer"minimum = 1maximum = 8default = 4description = "Cap on bots a single redeem can spawn."
[[pack.config.section]]key = "chat"title = "Chat"[[pack.config.section.field]]key = "chat_prefix"type = "string"maxLength = 16default = "[chat]"description = "Prefix prepended to viewer messages in game chat."The Dashboard renders one panel per section, with the section title and
subtitle as headings, and one editor widget per field.
Field reference
Section titled “Field reference”A [[pack.config.section]] table:
| Key | Required | Meaning |
|---|---|---|
key | yes | Section identifier, matches ^[a-z][a-z0-9_]*$. Stable handle for the section. |
title | no | Panel heading shown in the Dashboard. |
subtitle | no | Sub-heading shown under the title. |
A [[pack.config.section.field]] table is an inline JSON Schema once you strip
its two meta keys:
| Key | Required | Meaning |
|---|---|---|
key | yes | Field identifier, matches ^[a-z][a-z0-9_]*$. This is the storage key the value is delivered under. |
required | no | If true, the broadcaster must set a value before they can save. |
Every other key in the field table is JSON Schema (type, minimum,
maxLength, enum, default, description, x-mobrule-* annotations).
Set a default so the field arrives with a sensible value before the
broadcaster touches it.
Schemas are inline only. There is no external schema = "path" form.
Order matters
Section titled “Order matters”Section order and field order are significant. They ride the canonical
manifest_hash, so reordering sections or fields re-pins the hash (the same
way the order of events does). Declare them in the order you want them to
appear, and treat that order as part of your Pack’s identity.
Field keys are one flat namespace
Section titled “Field keys are one flat namespace”Field keys are flat across every section, because the field key is the storage
key. The same key in two sections is a DuplicateConfigFieldKey error at load
time. max_bots in the effects section and max_bots in another section
will not load. Keep field keys unique Pack-wide.
How the Adapter consumes config
Section titled “How the Adapter consumes config”The bridge latches config values and replays them to your Adapter as config
frames right after hello_ack, then pushes a fresh frame on every Dashboard
edit. Each frame carries one key and its value.
Store the frames as they arrive and read from your store wherever you need the setting. The Doom Pack keeps a dict seeded with the same defaults as the schema, applies each frame on receipt, and reads with a small getter:
_config = {"max_bots": 4, "chat_prefix": "[chat]"}
def config_set(key, value): if key in _config: _config[key] = value
# ... in the MPP read loop:elif msg.get("type") == "config": config_set(msg.get("key"), msg.get("value"))Then a handler reads the latched value:
count = min(int(params["count"]), _config["max_bots"])Seed your Adapter’s defaults to match the schema defaults. The defaults
cover the window before the first frame arrives, and the broadcaster’s saved
values cover everything after.
See also
Section titled “See also”- Manifest reference:
[[pack.config.section]]lives alongside[events.*]. - Events: per-Invocation params, the other half of the config/params split.
- UI hints:
x-mobrule-*annotations that affect how the config editor renders fields.