Skip to content

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.

{ 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.

{ 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.

{ 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.

{ 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.

{ 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.

{ 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.

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.