# Klasses

> Define custom object types and add namespaced attributes to customers, companies, conversations, messages and users from an app definition.

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

The `klasses` array does two jobs. An entry with a new name creates a **custom Klass**: a type of custom object, such as an order, whose records (KObjects) appear on the customer timeline. An entry named after a standard object, with `metadata.standard: true`, adds **custom attributes** to customers, companies, conversations, messages or users instead.

For how KObjects behave once they exist (creating them, linking them to customers and companies, searching them) see [Custom objects](https://developer.kustomer.com/docs/concepts/custom-objects).

## Choose a Klass or an attribute

| You need to store                                                              | Use                            |
| ------------------------------------------------------------------------------ | ------------------------------ |
| One value per customer, such as a loyalty tier                                 | An attribute on `customer`     |
| One value per conversation, such as an order number the customer asked about   | An attribute on `conversation` |
| Many records per customer, such as orders, shipments or bookings               | A custom Klass                 |
| A record with its own lifecycle and several fields that agents review together | A custom Klass                 |
| Data a KView or workflow needs to look up by its own `externalId`              | A custom Klass                 |

## Define a custom Klass

```json title="klasses (excerpt)"
{
  "klasses": [
    {
      "name": "acme_orders-order",
      "icon": "cart",
      "color": "#5B6CF9",
      "metadata": {
        "properties": {
          "orderNumberStr": { "displayName": "Order number" },
          "statusStr": { "displayName": "Status" },
          "totalNum": { "displayName": "Total" },
          "placedAt": { "displayName": "Placed" },
          "giftMessageTxt": { "displayName": "Gift message" },
          "isGiftBool": { "displayName": "Gift" },
          "trackingUrl": { "displayName": "Tracking" }
        }
      }
    }
  ]
}
```

**Klass**

After install, admins can change the icon, color and attribute display names, and turn attributes on and off, in **Settings › Platform › Klasses**. They can't rename the Klass or its attributes.

### Metadata properties

Each key is an attribute's system name in camelCase. Its suffix sets the type, exactly as for any [custom attribute](https://developer.kustomer.com/docs/concepts/custom-attributes#suffixes):

| Suffix | Type          | Example          |
| ------ | ------------- | ---------------- |
| `Str`  | Short string  | `statusStr`      |
| `Txt`  | Long text     | `giftMessageTxt` |
| `Num`  | Number        | `totalNum`       |
| `At`   | Date and time | `placedAt`       |
| `Bool` | Boolean       | `isGiftBool`     |
| `Url`  | URL           | `trackingUrl`    |

Each value is `{ "displayName": "…" }`, the label agents see. The system name is what the API, KViews, workflows and search use, so choose it carefully: you can't rename it later without migrating data.

Set more attribute metadata, such as allowed values or a reference target for `Id` attributes, with [`PUT /v1/metadata/kobject.<klass>`](https://developer.kustomer.com/api/core-resources/update-custom-attribute-metadata) after install.

## Write and read records

KObjects belong to a customer or company. Use the Klass name from your definition (it isn't namespaced) in the path:

`POST /v1/customers/{id}/klasses/{name}` ([reference](https://developer.kustomer.com/api/core-resources/create-customer-kobject-custom-object))

```bash title="cURL"
curl -X POST "https://acme.api.kustomerapp.com/v1/customers/$CUSTOMER_ID/klasses/acme_orders-order" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "4417",
    "title": "Order #4417",
    "description": "2 items · shipped",
    "custom": {
      "orderNumberStr": "4417",
      "statusStr": "shipped",
      "totalNum": 86.5,
      "placedAt": "2026-10-19T14:02:00Z",
      "trackingUrl": "https://track.example/1Z999"
    }
  }'
```

```bash title="kusty"
kusty api POST /v1/customers/$CUSTOMER_ID/klasses/acme_orders-order --data @order.json
```

Set `externalId` to your own ID so you can find and update the record later with [`GET /v1/klasses/{name}/externalId={externalId}`](https://developer.kustomer.com/api/core-resources/get-kobject-by-external-id). Most apps write records from a workflow instead, with the `kustomer.kobject.create-with-customer` and `kustomer.kobject.update` actions; see [Workflows, actions and triggers](https://developer.kustomer.com/docs/apps/workflows-actions-triggers#workflows-wire-it-together).

## Add attributes to standard objects

Name the entry after a standard object and set `metadata.standard` to `true`:

```json title="klasses (excerpt)"
{
  "identifier": "acmeord",
  "klasses": [
    {
      "name": "customer",
      "metadata": {
        "standard": true,
        "properties": {
          "loyaltyTierStr": { "displayName": "Acme loyalty tier" },
          "lifetimeValueNum": { "displayName": "Acme lifetime value" }
        }
      }
    },
    {
      "name": "conversation",
      "metadata": {
        "standard": true,
        "properties": {
          "orderNumberStr": { "displayName": "Acme order number" }
        }
      }
    }
  ]
}
```

`name` must be `company`, `conversation`, `customer`, `message` or `user`.

> **Forgetting standard: true creates a custom Klass:** Without `"standard": true`, an entry named `customer` defines a new custom Klass called `customer` rather than adding attributes to customers.

### Attributes are namespaced

Several apps can each add a `sectorStr` attribute to companies. To keep them apart, Kustomer renames every attribute an app adds to a standard object to `@` + the app's `identifier` + the name with its first letter capitalized:

| In your definition                 | With `"identifier": "acmeord"` |
| ---------------------------------- | ------------------------------ |
| `loyaltyTierStr`                   | `@acmeordLoyaltyTierStr`       |
| `lifetimeValueNum`                 | `@acmeordLifetimeValueNum`     |
| `orderNumberStr` on `conversation` | `@acmeordOrderNumberStr`       |

Use the namespaced name everywhere you read or write the value: the API, KViews, workflows and search.

```json title="PATCH /v1/customers/{id}"
{
  "custom": {
    "@acmeordLoyaltyTierStr": "gold",
    "@acmeordLifetimeValueNum": 1240
  }
}
```

```json title="Workflow step params"
{
  "action": "kustomer.conversation.update",
  "params": {
    "id": "/#steps.1.id",
    "custom": { "@acmeordOrderNumberStr": "/#steps.lookup.body.orderNumber" }
  }
}
```

Set `identifier` yourself: 2 to 8 lowercase letters, digits or underscores, ideally an abbreviation of the app name. If you leave it out, Kustomer generates one at first registration. Either way it's permanent, so every version keeps the same attribute names.

Only `displayName` is kept for attributes on standard objects; other keys in each property are dropped.

## Changes between versions

| Change                                   | Effect when an org updates                                                          |
| ---------------------------------------- | ----------------------------------------------------------------------------------- |
| Add an attribute                         | Added to the Klass                                                                  |
| Stop declaring an attribute              | Values already written stay on existing records                                     |
| Remove a whole Klass from the definition | The Klass and its records stay. Kustomer never deletes Klasses when an app updates. |
| Rename a Klass                           | A new, empty Klass is created next to the old one                                   |

Plan names before your first release, because you can't take them back. To retire an attribute, stop writing to it and tell admins they can deactivate it.

## Troubleshooting

| Symptom                                               | Check                                                                                                                                                    |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| An attribute isn't on the record                      | Is its suffix valid? Is it active in **Settings › Platform › Klasses**? Did you write the namespaced name for a standard object?                         |
| A second Klass named after a standard object appeared | `metadata.standard: true` is missing.                                                                                                                    |
| `400` writing a KObject                               | The Klass name in the path doesn't match the definition, or a value doesn't match its suffix's type.                                                     |
| Search or a workflow condition finds nothing          | Use the system name, not the display name, such as `kobject_custom_acme_orders-order_statusStr`. For standard objects, include the `@identifier` prefix. |
