Custom Fields
AvailabilityThe 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:
- The custom field data model
- Supported field types and the resources they can attach to
- Managing custom field definitions via
/v1/custom-fields/ - Reading and writing custom field values on documents and line items
- 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.
| 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)
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)
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 |
Supported groupsOnly
SALES,PURCHASESandCONTACTSare 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:
| 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 -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 choicesWhen you create or update a
SELECTcustom field, you may pass choices with only alabel. Wafeq will automatically generate a stablevaluethat 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-levelcustom_fieldsobject. - Line-item custom fields (
is_line_item_field = true) appear undercustom_fieldson each line item ofSALESandPURCHASESdocuments.CONTACTShave 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 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 values 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_typeon a custom field that has values stored on any existing document. - You cannot remove a
SELECTchoice that is currently used by at least one document. - You cannot remove an
apply_togroup 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. Useis_active = falseto retire a custom field without removing historical values.
6. Quick recipe
POST /v1/custom-fields/— define, for example, aSELECTfield called Department onSALESandPURCHASES.GET /v1/custom-fields/— copy the returnedid(e.g.cf_abc123) and thevalues fromconfig.metadata.choices.POST /v1/invoices/— include"custom_fields": {"cf_abc123": "sales"}on the payload.GET /v1/invoices/{id}/— the samecustom_fieldsobject 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.
Updated about 1 hour ago