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 (excerpt)
{
  "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
      }
    }
  }
}
settings.<category>.<name>
typestringrequired
defaultValuestring | number | booleanrequired
requiredboolean
descriptionMarkdown
hiddenboolean

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:

JSON
{
  "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 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 (excerpt)
{
  "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
{
  "settingsPageConfig": {
    "title": "Acme Orders",
    "description": "Connect your store and choose what to sync.",
    "url": "https://kustomer.acme.example/settings"
  }
}
settingsPageConfig
urlURLrequired
titlestring
descriptionstring

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:

settings.js
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' })
})
You defineAdmins see
settings onlyThe generated page
settingsPageConfig onlyYour page
BothYour page. Settings still exist, and your page reads and writes them through the API.
NeitherNo settings page