Setting fields
Shared by every type:
| Property | Notes |
|---|---|
key |
Stable identifier. It’s what the value is stored under: renaming it is a migration, not a refactor. |
label, help |
What the customer reads. help sits under the field. |
required |
The app stays skipped by the runtime until it has a value. |
group |
Section it belongs to, among those in settingGroups. |
visibleWhen |
{ key, equals }. Presentation only, and incompatible with required. |
{ key: "brand", type: "text", label: "Brand", placeholder: "e.g. Schüco", maxLength: 60, default: "" }Trimmed on save. maxLength is enforced on the server too.
number
Section titled “number”{ key: "spread", type: "number", label: "Spread", unit: "%", min: 0, max: 100, step: 0.5, default: 2 }unit is the suffix drawn inside the field, not a conversion: it’s a label.
While typing, the value stays a string in the form and becomes a number on
save, so a half-written field doesn’t fail validation on every keystroke.
boolean
Section titled “boolean”{ key: "showReference", type: "boolean", label: "Carry over the item number", default: true, example: { /* see Illustrating a setting */ } }Takes an example with exactly two cases, false and true.
select
Section titled “select”{ key: "regime", type: "select", label: "VAT regime", default: "standard", options: [{ value: "standard", label: "Standard" }, { value: "margine", label: "Regime del margine" }] }The saved value must be one of the declared options — checked on the server, so
a modified client can’t get around it. Takes an example with one case per
option, all of them.
multiselect
Section titled “multiselect”{ key: "metals", type: "multiselect", label: "Metals you buy", default: ["gold"], options: [/* … */] }Deduplicated while keeping the order the customer chose. With required, an
empty list is an error.
numberUnit
Section titled “numberUnit”{ key: "fee", type: "numberUnit", label: "Dealer fee", default: { value: 2, unit: "percent" }, units: [{ value: "percent", label: "%" }, { value: "euro", label: "€/g" }] }Stored as { value, unit }. min/max apply to the number, and the unit must
be one of the declared ones.
numberTable
Section titled “numberTable”{ key: "yields", type: "numberTable", label: "Yield by fineness", unit: "%", rows: [{ value: "750", label: "750 (18kt)" }, { value: "585", label: "585 (14kt)" }], default: { "750": 98, "585": 97 } }Stored as Record<rowValue, number>. An empty cell is not a zero: it’s a
row this customer doesn’t use, and it’s left out rather than zeroed. With
required, at least one row must be filled.
What the host does with them
Section titled “What the host does with them”Values are validated with the same code on both sides — validateSettings from
the SDK — so the panel and the server can never disagree about what’s valid.
Unknown keys in storage are ignored rather than failing: an organisation may
hold values written by an older version of your app, and a field you removed
must not block saving everything else.
There is deliberately no secret type. Until setting values are encrypted
at rest, credentials belong in the host’s environment and reach a core app
through ctx.secrets — not in a form, stored in clear, shown on screen.