---
updatedAt: 2026-08-28T22:32:12.000Z
agentTools:
  projectIndex: https://developer.wafeq.com/llms.txt
---

# Custom Fields

> 📘 Availability
>
> The Custom Fields API is available to organizations on the **Plus, Premium and Enterprise plans** or with the **Advanced Customization add-on**.

# Custom Fields

Custom fields let you attach organization-defined data to Wafeq resources such as invoices, bills and contacts. Each custom field has a type (text, number, date, select, …) and is scoped to one or more resource groups it *applies to*.

This guide walks you through:

1. The custom field data model
2. Supported field types and the resources they can attach to
3. Managing custom field definitions via `/v1/custom-fields/`
4. Reading and writing custom field **values** on documents and line items
5. Validation rules and common gotchas

***

## 1. The custom field data model

A custom field is an organization-scoped definition. It does **not** store values itself — instead, each resource that it applies to carries a `custom_fields` JSON object that maps the custom field ID to the value entered for that resource.

| Field                        | Type      | Description                                                                                                            |
| ---------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------- |
| `id`                         | string    | Short ID, always prefixed with `cf_`                                                                                   |
| `name`                       | string    | English label (max 100 chars). At least one of `name` / `name_ar` is required                                          |
| `name_ar`                    | string    | Arabic label (max 100 chars)                                                                                           |
| `apply_to`                   | string\[] | One or more resource groups this field attaches to — see below                                                         |
| `config`                     | object    | Field type + type-specific metadata (e.g. SELECT choices)                                                              |
| `is_active`                  | boolean   | Inactive fields are ignored when reading/writing values on new documents. Default `true`                               |
| `is_required`                | boolean   | Whether the field must be filled on documents it applies to. Default `false`                                           |
| `is_line_item_field`         | boolean   | `true` → attaches to line items of documents in `apply_to`; `false` → attaches to the document itself. Default `false` |
| `created_ts` / `modified_ts` | datetime  | Timestamps (ISO 8601)                                                                                                  |

***

## 2. Field types and apply-to groups

### Supported field types (`config.field_type`)

| Type        | Stored value | Notes                                                                                       |
| ----------- | ------------ | ------------------------------------------------------------------------------------------- |
| `TEXT`      | string       | Short free text                                                                             |
| `LONG_TEXT` | string       | Multi-line free text                                                                        |
| `NUMBER`    | number       | Validated as a float on write                                                               |
| `DATE`      | string       | ISO 8601 date (`YYYY-MM-DD`)                                                                |
| `SELECT`    | string       | Must match one of the `config.metadata.choices[].value` entries                             |
| `LOOKUP`    | string       | Reference to a related entity (e.g. `EMPLOYEE` or `USER`), configured via `config.metadata` |

### Apply-to groups (`apply_to`)

Each custom field can target one or more of the following groups. The same custom field is automatically available on **every** resource belonging to the selected groups (and on their line items when `is_line_item_field = true`).

| Group       | Resources covered                                                                          |
| ----------- | ------------------------------------------------------------------------------------------ |
| `SALES`     | Invoices, Simplified Invoices, Credit Notes, Recurring Invoices, Delivery Notes, Estimates |
| `PURCHASES` | Bills, Expenses, Debit Notes, Purchase Orders                                              |
| `CONTACTS`  | Contacts                                                                                   |

<br />

## 3. Managing custom field definitions

The endpoint supports the standard CRUD verbs:

| Method          | Path                      | Purpose                                                            |
| --------------- | ------------------------- | ------------------------------------------------------------------ |
| `GET`           | `/v1/custom-fields/`      | List the organization's custom fields (paginated, ordered by name) |
| `POST`          | `/v1/custom-fields/`      | Create a custom field                                              |
| `GET`           | `/v1/custom-fields/{id}/` | Retrieve one custom field                                          |
| `PUT` / `PATCH` | `/v1/custom-fields/{id}/` | Update a custom field                                              |
| `DELETE`        | `/v1/custom-fields/{id}/` | Delete a custom field (only if it is not in use on any document)   |

### Create example

```curl
curl -X POST "https://api.wafeq.com/v1/custom-fields/" \
  -H "Authorization: Api-Key $WAFEQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Department",
    "name_ar": "القسم",
    "apply_to": ["SALES", "PURCHASES"],
    "config": {
      "field_type": "SELECT",
      "metadata": {
        "choices": [
          {"label": "Sales"},
          {"label": "Operations"}
        ]
      }
    },
    "is_active": true,
    "is_required": false,
    "is_line_item_field": false
  }'
```

### Create response

```json
{
  "id": "cf_abc123",
  "name": "Department",
  "name_ar": "القسم",
  "field_type": "SELECT",
  "apply_to": ["SALES", "PURCHASES"],
  "config": {
    "field_type": "SELECT",
    "metadata": {
      "choices": [
        { "label": "Sales", "value": "x3axga" },
        { "label": "Operations", "value": "g6imcaq" }
      ]
    }
  },
  "is_active": true,
  "is_required": false,
  "is_line_item_field": false,
  "created_ts": "2026-04-17T10:30:00Z",
  "modified_ts": "2026-04-17T10:30:00Z"
}
```

<Callout icon="💡" theme="default">
  ### **SELECT choices**

  When you create or update a `SELECT` custom field, you may pass choices with only a `label`. Wafeq will automatically generate a stable `value` that you should use when writing the custom field value onto documents.
</Callout>

***

## 4. Reading and writing custom field *values* on documents

Every resource that supports custom fields exposes a `custom_fields` object. Keys are custom field IDs (`cf_…`); values follow the field's declared type.

### Example: invoice with custom field values

```json
{
  "id": "inv_9ykZ2t3sC4nMfJpN",
  "contact": "co_VGaNjD4ksaCUTEveaVyPMo",
  "invoice_date": "2026-04-17",
  "custom_fields": {
    "cf_abc123": "sales",
    "cf_def456": "2026-05-01"
  },
  "line_items": [
    {
      "description": "Consulting",
      "unit_amount": 1000,
      "quantity": 1,
      "custom_fields": {
        "cf_ghi789": "Project Alpha"
      }
    }
  ]
}
```

* Document-level custom fields (`is_line_item_field = false`) appear under the top-level `custom_fields` object.
* Line-item custom fields (`is_line_item_field = true`) appear under `custom_fields` on each line item of `SALES` and `PURCHASES` documents. `CONTACTS` have no line items.

### Writing values

Send the same `custom_fields` object on create/update. Only IDs for **active, non-deleted** custom fields that actually `apply_to` this resource are respected — any other keys in the payload are stripped silently.

```json
{
  "custom_fields": {
    "cf_abc123": "sales",
    "cf_def456": "2026-05-01"
  }
}
```

***

## 5. Validation rules

When you write values onto a document or line item, Wafeq validates each entry against its custom field definition:

| Field type          | Rule                                                                        |
| ------------------- | --------------------------------------------------------------------------- |
| `TEXT`, `LONG_TEXT` | Must be a string                                                            |
| `NUMBER`            | Must parse as a number (float)                                              |
| `DATE`              | Must be an ISO 8601 date (`YYYY-MM-DD`)                                     |
| `SELECT`            | Must match one of the choice `value`s currently defined on the custom field |
| `LOOKUP`            | Must be the ID of an entity of the configured lookup type                   |

Updating a custom field definition has additional safeguards:

* You cannot change `field_type` on a custom field that has values stored on any existing document.
* You cannot remove a `SELECT` choice that is currently used by at least one document.
* You cannot remove an `apply_to` group while documents in that group still reference the field.

> 🚧 Deleting a custom field
>
> `DELETE /v1/custom-fields/{id}/` fails while any document still stores a value for the field. Use `is_active = false` to retire a custom field without removing historical values.

***

## 6. Quick recipe

1. `POST /v1/custom-fields/` — define, for example, a `SELECT` field called *Department* on `SALES` and `PURCHASES`.
2. `GET /v1/custom-fields/` — copy the returned `id` (e.g. `cf_abc123`) and the `value`s from `config.metadata.choices`.
3. `POST /v1/invoices/` — include `"custom_fields": {"cf_abc123": "sales"}` on the payload.
4. `GET /v1/invoices/{id}/` — the same `custom_fields` object is echoed back on every read.

That's it. You now have typed, validated organization-defined metadata flowing through the Wafeq API and the UI together.