Custom Fields

📘

Availability

The Custom Fields API is part of Wafeq Public API v1 and is available to organizations on the Plus plan and above.

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

Base URL & authentication

All examples in this guide use the Wafeq Public API v1 base URL:

https://api.wafeq.com/v1/

Every request must include your Wafeq API key — see Get your API Key:

Authorization: Api-Key <YOUR_API_KEY>

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.

FieldTypeDescription
idstringShort ID, always prefixed with cf_
namestringEnglish label (max 100 chars). At least one of name / name_ar is required
name_arstringArabic label (max 100 chars)
apply_tostring[]One or more resource groups this field attaches to — see below
configobjectField type + type-specific metadata (e.g. SELECT choices)
is_activebooleanInactive fields are ignored when reading/writing values on new documents. Default true
is_requiredbooleanWhether the field must be filled on documents it applies to. Default false
is_line_item_fieldbooleantrue → attaches to line items of documents in apply_to; false → attaches to the document itself. Default false
created_ts / modified_tsdatetimeTimestamps (ISO 8601)

2. Field types and apply-to groups

Supported field types (config.field_type)

TypeStored valueNotes
TEXTstringShort free text
LONG_TEXTstringMulti-line free text
NUMBERnumberValidated as a float on write
DATEstringISO 8601 date (YYYY-MM-DD)
SELECTstringMust match one of the config.metadata.choices[].value entries
LOOKUPstringReference 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).

GroupResources covered
SALESInvoices, Simplified Invoices, Credit Notes, Recurring Invoices, Delivery Notes, Estimates
PURCHASESBills, Expenses, Debit Notes, Purchase Orders
CONTACTSContacts
📘

Supported groups

Only SALES, PURCHASES and CONTACTS are currently exposed through the Public API v1. Additional groups may be enabled in future versions.


3. Managing custom field definitions

Custom field definitions live at:

https://api.wafeq.com/v1/custom-fields/

The endpoint supports the standard CRUD verbs:

MethodPathPurpose
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 -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

{
  "id": "cf_abc123",
  "name": "Department",
  "name_ar": "القسم",
  "field_type": "SELECT",
  "apply_to": ["SALES", "PURCHASES"],
  "config": {
    "field_type": "SELECT",
    "metadata": {
      "choices": [
        { "label": "Sales", "value": "sales" },
        { "label": "Operations", "value": "operations" }
      ]
    }
  },
  "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"
}
💡

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.


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

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

{
  "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 typeRule
TEXT, LONG_TEXTMust be a string
NUMBERMust parse as a number (float)
DATEMust be an ISO 8601 date (YYYY-MM-DD)
SELECTMust match one of the choice values currently defined on the custom field
LOOKUPMust 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 values 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.