Skip to content

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";