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

Source: https://developer.kustomer.com/docs/apps/settings

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:

```json title="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>**

### 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](https://developer.kustomer.com/docs/apps/kviews#call-apis-with-commands). 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.

**Actions and commands**

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}}" }
  }
}
```

**Workflows**

The `setting` helper works in any workflow template, with one space inside each set of braces:

```json
{ "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](https://developer.kustomer.com/docs/workflows/expressions-reference#app-settings).

**Widgets**

```json
{ "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.

**KViews**

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.

**API**

Read and write one setting:

`GET /v1/settings/{app}/{category}/{name}` ([reference](https://developer.kustomer.com/api/settings-and-configurations/get-a-setting))

`PUT /v1/settings/{app}/{category}/{name}` ([reference](https://developer.kustomer.com/api/settings-and-configurations/update-a-setting))

```bash title="cURL"
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:

```json title="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](https://developer.kustomer.com/docs/apps/i18n#settings-page-strings).

## 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:

```json title="settingsPageConfig"
{
  "settingsPageConfig": {
    "title": "Acme Orders",
    "description": "Connect your store and choose what to sync.",
    "url": "https://kustomer.acme.example/settings"
  }
}
```

**settingsPageConfig**

Your page replaces the generated one entirely, so it must load and save any settings itself. Load the [Cards SDK](https://developer.kustomer.com/docs/apps/kviews#the-cards-sdk); the page gets an `app` context and can call the API as the admin:

```js title="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 define                | Admins see                                                                            |
| ------------------------- | ------------------------------------------------------------------------------------- |
| `settings` only           | The generated page                                                                    |
| `settingsPageConfig` only | Your page                                                                             |
| Both                      | Your page. Settings still exist, and your page reads and writes them through the API. |
| Neither                   | No settings page                                                                      |

> **Validate on your server:** Verify credentials from your backend, as above, not from the browser. Your page can then save the key into a `secret` setting so workflows and commands can use it.
