Settings
Declare per-org configuration such as API keys and subdomains, get an auto-generated settings page or embed your own, and read the values from actions, commands, workflows, widgets and KViews.
Settings hold what differs between the orgs that install your app: an API key, a store domain, a threshold, a feature toggle. You declare them in the definition; Kustomer generates a settings page for admins, stores the values per org, and fills them in wherever your app references them, server-side for secrets.
Declare settings
Settings are grouped into categories. Use default if you only need one:
{
"settings": {
"default": {
"apiKey": {
"type": "secret",
"defaultValue": "",
"required": true
},
"storeDomain": {
"type": "string",
"defaultValue": "",
"required": true,
"description": "Your store's domain, such as `shop.acme.example`."
},
"refundLimit": {
"type": "number",
"defaultValue": 200
},
"createOrders": {
"type": "boolean",
"defaultValue": true
},
"lastSyncCursor": {
"type": "string",
"defaultValue": "",
"hidden": true
}
}
}
}typestringrequiredstring,number,booleanorsecret.defaultValuestring | number | booleanrequired- The value before an admin changes it. Use
""ornullfor no default. requiredboolean- Admins must fill this in after installing for the app to work.
descriptionMarkdown- Help text on the settings page. Prefer an i18n key so it can be translated.
hiddenboolean- Keep the setting off the generated page. Useful for state your app manages itself through the API.
Secrets
secret settings are write-only for people: admins can set them, but reading one through the API returns a placeholder. Only two features use the real value, both on Kustomer's servers:
- Workflow actions, through
appSettings. - Commands, through
appSettings.
Reference secrets only from those two. To use a key from a workflow, wrap the call in an app action. To use it from your UI, call your API through a command. Never put a secret in a widget URL.
Use settings
Each feature has its own syntax. The key is always <app>.<category>.<name>, with your app's name as you wrote it; Kustomer adjusts it for private-app namespacing.
Map settings to template variables with appSettings, then use them in the request:
{
"appSettings": {
"apiKey": { "key": "acme_orders.default.apiKey" },
"storeDomain": { "key": "acme_orders.default.storeDomain" }
},
"inputTemplate": {
"uri": "https://{{storeDomain}}/api/orders/{{orderId}}",
"headers": { "Authorization": "Bearer {{apiKey}}" }
}
}The setting helper works in any workflow template, with one space inside each set of braces:
{ "uri": "https://{{{ setting acme_orders.default.storeDomain }}}/api/orders/{{steps.1.orderId}}" }It resolves before the rest of the template renders. Use it for non-secret settings; secrets belong in an app action. See Expressions reference.
{ "url": "https://kustomer.acme.example/widget?store={{{settings.default.storeDomain}}}" }Widgets can only read their own app's settings. Don't reference secrets here: the URL is loaded in the agent's browser.
Templates receive the app's settings for the org as appSettings. Like everything in a template, they're visible in the agent's browser, so keep secrets out of them.
Read and write one setting:
get/v1/settings/{app}/{category}/{name}put/v1/settings/{app}/{category}/{name}curl -X PUT "https://acme.api.kustomerapp.com/v1/settings/acme_orders/default/lastSyncCursor" \
-H "Authorization: Bearer $KUSTOMER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"value": "c_20261021T1500"}'The generated settings page
When an app has settings and no settingsPageConfig, Kustomer builds a page under Settings with a field for each setting that isn't hidden, matched to its type. The page title, description, and each field's label and help text come from i18n keys:
{
"i18n": {
"en_us": {
"acme_orders.settings.page.title": "Acme Orders",
"acme_orders.settings.page.description": "Connect your Acme store to Kustomer.",
"acme_orders.settings.path.default.apiKey.displayName": "API key",
"acme_orders.settings.path.default.apiKey.description": "Create one in Acme under **Settings › API**.",
"acme_orders.settings.path.default.storeDomain.displayName": "Store domain",
"acme_orders.settings.path.default.refundLimit.displayName": "Largest refund agents can issue"
}
}
}Without these keys, admins see raw setting names. See Internationalization.
Build your own settings page
For setup that needs more than form fields (validating a key against your API, choosing from a list your API returns, registering the org's hook URL with your platform) host your own page and point settingsPageConfig at it:
{
"settingsPageConfig": {
"title": "Acme Orders",
"description": "Connect your store and choose what to sync.",
"url": "https://kustomer.acme.example/settings"
}
}urlURLrequired- Your page. Kustomer embeds it in an iframe under Settings.
titlestring- The menu entry and page heading.
descriptionstring- A summary of the page.
Your page replaces the generated one entirely, so it must load and save any settings itself. Load the Cards SDK; the page gets an app context and can call the API as the admin:
const base = '/v1/settings/acme_orders/default'
const valueOf = (res) => (res?.data ?? res)?.attributes?.value
Kustomer.initialize(async () => {
const domain = await Kustomer.request({ url: `${base}/storeDomain`, method: 'get' })
form.storeDomain.value = valueOf(domain) ?? ''
Kustomer.resize()
})
form.addEventListener('submit', async (e) => {
e.preventDefault()
const ok = await fetch('https://kustomer.acme.example/api/verify-key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ domain: form.storeDomain.value, key: form.apiKey.value }),
}).then((r) => r.ok)
if (!ok) return Kustomer.handleTriggerToast({ type: 'error', message: 'That key was rejected by Acme.' })
await Kustomer.request({ url: `${base}/storeDomain`, method: 'put', body: { value: form.storeDomain.value } })
await Kustomer.request({ url: `${base}/apiKey`, method: 'put', body: { value: form.apiKey.value } })
Kustomer.handleTriggerToast({ type: 'success', message: 'Saved' })
})