Preferences
User-configurable settings, shown in Settings, resolved and handed to every
update()/onEvent/onAction call as ctx.preferences (a plain object keyed by
name).
"preferences": [ { "name": "apiKey", "title": "API key", "type": "password", "required": true, "placeholder": "sk_live_…", "description": "Used to authenticate with the upstream API.", "link": { "title": "Create a key", "url": "https://example.com/settings/keys" } }, { "name": "workspace", "title": "Workspace", "type": "textfield", "default": "default" }, { "name": "currency", "title": "Currency", "type": "dropdown", "default": "usd", "data": [{ "title": "US Dollar", "value": "usd" }, { "title": "Euro", "value": "eur" }] }, { "name": "includeRefunds", "title": "Include refunds", "type": "checkbox", "default": false }, { "name": "staleAfterMinutes", "title": "Stale after", "type": "number", "default": 10 }]type |
Renders as | Value in ctx.preferences[name] |
|---|---|---|
textfield |
A text field | string |
password |
A text field whose value is stored in the Keychain, never in the manifest’s resolved-preferences JSON on disk | string |
checkbox |
A toggle | boolean |
dropdown |
A picker; needs a data: [{title, value}] array |
string (the chosen value) |
number |
A numeric field | number |
link (optional, https only) puts a “where to get it” link under the field. Give every
required key one: a field that asks for a token without saying where to get it is a dead end.
Settings shows every preference. An item opened in the notch shows its source’s options
too (everything but secrets), right there: blips says which of your blips a preference
belongs to (keys, or key prefixes like "project."), so a sales option shows on Sales and
not on Revenue. Left out, it shows on all of them. "blips": [] keeps it to Settings: an
address, an account, a region or a team is set once and doesn’t belong beside the blip.
required: true blocks the extension from loading until it’s set. A preference
change re-runs update() with ctx.trigger = { type: "preferences" }, so you can
react to it (re-fetch with a new API key, reset cached state) rather than just
reading the new value next time the interval fires.
// ctx.preferences is untyped (Record<string, string | number | boolean | undefined>):// narrow it yourself at the point you read it.const workspace = typeof ctx.preferences.workspace === "string" ? ctx.preferences.workspace : "default";