# How to configure a «List by API» (ActionJail) field?

The **List by API** type loads dropdown options from an **ActionJail action** while the operator edits a record. Use it when values are dynamic (operators, stores, an external catalog) and do not fit a static **List**.

## When you need this

- You need a dropdown backed by changing data (users, external statuses, API catalog).
- A static **List** is not enough — too many values or they update automatically.
- **System** types (User, Subject, …) do not fit — you need a custom jail action.

## What to know

| Editor setting | Purpose |
|----------------|---------|
| **ActionJail action** | Technical action name (`action_…`) that returns the list |
| **List label field** (`label_key`) | Property on each item: **both the dropdown label and the value saved in the field** after selection |

**Where the dropdown works today**

- client fields in the operator dialog;
- dynamic fields in operator forms (Table view, OperatorLine);
- **not** in the record table or record side card yet — value is shown as plain text until list selection is wired there (planned).

**When to pick another type**

- fixed enum → **List**;
- link to another Custom Data table → **Table link**;
- platform user / subject / tag → **System** types.

## Before you start

- [x] Access to **ActionJail** (create or edit an action on the instance).
- [x] **Custom Data model editor** open (or client fields in Settings).
- [x] You know what the list should return and what must be stored (ID, code, label).

## Step 1. Create an ActionJail action

1. Open **ActionJail** → **Create action** (or edit an existing one).
2. Set the **technical name** — the same name you will pick in the field config (e.g. `action_list_stores` or `aj_list_operators`).
3. In the action body:
   - read request params from constants: `query` (search), `limit`, `offset` — passed by the platform when the list opens;
   - build an array of items;
   - store the result as `{ rows, count }` in `result`;
   - return `'success'`.

**Response contract**

```javascript
this.setCurrentStateConstant('result', {
  rows: [
    { id: 1, label: 'Kyiv — Center' },
    { id: 2, label: 'Lviv — Main' },
  ],
  count: 2,
});
```

Each `rows` item is an object. It **must** include a property whose name matches **List label field** (commonly `label`, `name`, or `title`). To **store an ID**, set `label_key` to `id` — the cell will receive the selected row's `id`.

**Minimal action example**

```javascript
const appPath = process.cwd();
const OpModels = require(appPath + '/modules/extra/operator_panel/db/models');

async function action_list_active_operators() {
  const state = await this.getCurrentStateJSON();
  const query = String(state?.const?.query || '').trim();
  const limit = Number(state?.const?.limit) || 20;
  const offset = Number(state?.const?.offset) || 0;

  const where = { active: true };

  const rows = await OpModels.OpUsers.findAll({
    where,
    attributes: [ 'id', 'first_name', 'last_name' ],
    limit,
    offset,
    raw: true,
  });

  const formatted = rows.map((row) => ({
    id: row.id,
    label: [ row.first_name, row.last_name ].filter(Boolean).join(' '),
  }));

  this.setCurrentStateConstant('result', {
    rows: formatted,
    count: formatted.length,
  });

  return 'success';
}

module.exports = action_list_active_operators;
```

The platform calls the action via `GET /kw/tickets/custom_list/<action_name>` with `query`, `limit`, `offset`.

## Step 2. Configure the field in Custom Data

1. In the model editor **Fields** tab, choose **List by API**.
2. In **List source**:
   - **ActionJail action** — your action from step 1;
   - **List label field** — key for label and stored value (`label`, `id`, `name`, …).
3. Save the model.

In `columns_json`:

```json
"store_ref": {
  "type": "STRING",
  "label": "Store",
  "select_api_action": {
    "name": "action_list_stores",
    "label_key": "label"
  }
}
```

## Step 3. Verify

1. Open an operator form where the field is shown (not the data table if the dropdown is not wired there yet).
2. Open the list — rows from the action should appear.
3. Select a value and save — the database should contain the property named in `label_key`.
4. If the list is empty — check action name, ActionJail logs, and that `result.rows` is non-empty.

## Common issues

| Symptom | What to check |
|---------|----------------|
| Empty list | Field action name matches ActionJail; action returns `success` and `result.rows` |
| Labels show `undefined` | Each row includes the `label_key` property |
| Wrong stored value | `label_key` controls both label and stored value — use `id` for IDs |
| Plain text in data table | Expected; dropdown is in operator forms |

## Related

- [Configure field type](./configure-field-type.md)
- [Semantic field types reference](../reference/semantic-field-types-reference.md)
- [Configure system field types](./configure-system-field-types.md)
