Vocabularies
A vocabulary is a named set of values your app publishes, which a merchant can extend at runtime. It is the answer to the most common gap in an admin UI: a list of statuses, conditions, reasons or categories that you cannot possibly enumerate correctly for every customer.
A value a merchant adds shows up in the form, in the filter and in the badge label without you shipping a new version.
Reading one over the gateway
Every app that publishes vocabularies serves them on the public gateway:
curl "https://api.revenexx.com/v1/serials/vocabularies/device_conditions" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..."
GET /v1/{app}/vocabularies/{name}
{app} is the app that owns the vocabulary and {name} is the vocabulary name — the part after the dot in its qualified id. The platform's own commerce apps expose the same shape, which is how a storefront or an integration renders a value the merchant invented after the integration was written.
The CLI has it per app too:
revenexx products vocabularies-list
revenexx products vocabularies-get --name conditions
Where it appears in the Cockpit
Two places, and both are one field:
A settings key takes its options from a vocabulary instead of a fixed list:
"default_condition": {
"title": { "en": "Default device condition" },
"type": "select",
"scope": "tenant",
"vocabulary": "device_conditions"
}
A column or a field resolves a stored value to its human label:
{ "name": "condition", "label": { "en": "Condition" }, "type": "badge", "vocabulary": "device_conditions" }
Without the vocabulary, a badge shows the raw stored value — refurb_a rather than "Refurbished (grade A)" — and a hardcoded options list cannot offer a value the merchant created after you wrote the file. That is the practical reason to reach for it: it is one key that keeps a screen correct as the business changes.
Vocabulary or enum_values
Both constrain a value to a list. The difference is who owns the list, and it is a design decision:
| Use | When |
|---|---|
enum_values | Your code branches on the value. Three rounding modes are three code paths, and a fourth one a merchant invents cannot work. |
| A vocabulary | The value is data your code passes through — a label, a classification, a reason. |
The test is one question: would a value you have never seen break your handler?
If yes, it is enum_values, and a merchant needing a fourth option is a feature request. If it would flow through your code untouched into a column, a label and a report, it is a vocabulary — and a merchant knowing their own categories better than you do is the normal case, not the exception.
Where you genuinely need both — a fixed set your code understands and room for the merchant's own detail — model them as two columns: a kind you branch on with enum_values, and a classification from a vocabulary that you only ever store and display.
Handling merchant-extended values
Once a set is extensible, your code has to be honest about not knowing it:
Never assume the set you shipped is the set you will read. No switch with a default that throws, and no exhaustive mapping that silently drops the unknown value.
Render the raw value when a label is missing rather than an empty cell. An operator seeing refurb_c can act on it; a blank badge tells them the app is broken.
Do not validate against a snapshot. If a value has to be a member of the vocabulary, check against the vocabulary as it is now, not against a constant in your source.
Keep the stored value stable. A vocabulary entry's label is what a merchant changes; the value your rows hold should not move underneath them.
Next steps
- Settings —
vocabularyalongsideenum_values. - How the Cockpit renders — where a column's
vocabularyapplies. - Reading settings — resolving the configured value at runtime.