Custom fields & formData
Leads, contacts and product/services all accept writes through a single formData object that mixes system fields and custom fields. Understanding how the two are told apart is the key to the whole write API.
The rule
Inside formData, the server looks at each key:
- A key that is a valid 24-character ObjectId → a custom field, matched by its
_id. - Anything else → a system field on the schema.
{
"formData": {
"stage": "Qualified",
"priority": "High",
"665f1a2b3c4d5e6f70819200": "Enterprise"
}
}
stage and priority are system fields; 665f1a... is a custom field id. There is no separate customFields block — one flat object handles both.
Discovering custom fields
curl "https://api.leadx.in/api/key/protected/custom-field?filters=%7B%22panel%22%3A%22lead%22%7D" \
-H "apikey: $KEY"
panel is one of lead, contact, productService. Each definition carries:
| Field | Notes |
|---|---|
_id | The key you use in formData. |
name | Display label. |
fieldType | text, number, textarea, select, multi-select, file, checkbox, date, email, url, object, array |
valueType | string, number, boolean, date, member, file, array, object |
options | For select / multi-select — the allowed { label, value } entries. |
required | Whether a write must supply it. |
defaultValue | Applied when omitted. |
uniqueFieldID | Stable slug, handy for mapping to your own system. |
section, order, hide | Presentation only. |
:::tip Cache the definitions, not the ids
Custom field _ids are stable, but building your mapping from uniqueFieldID or name at startup keeps your integration readable and survives a field being recreated.
:::
Reading custom field values
Values come back on customFieldsMap, keyed by field id:
{
"_id": "665f...",
"stage": "Qualified",
"customFieldsMap": {
"665f1a2b3c4d5e6f70819200": {
"value": "Enterprise",
"label": "Enterprise",
"sortValue": "enterprise"
}
}
}
value is what you wrote. label resolves select options to their display text. sortValue is a normalized form the server uses for sorting — ignore it.
The legacy fields array is still present on documents for backward compatibility. Read customFieldsMap; fields is the older representation.
Filtering and sorting by custom field
Use the field id as the path, exactly as in formData:
?filters={"665f1a2b3c4d5e6f70819200":"Enterprise"}
?sortBy=665f1a2b3c4d5e6f70819200&sortOrder=asc
Appending instead of replacing
For array-valued custom fields, a bulk update can add rather than overwrite:
{ "ids": ["665f..."], "updateType": "append", "formData": { "665f1a...": ["Tag A"] } }
updateType defaults to replace.
Managing definitions
| Method | Endpoint | Notes |
|---|---|---|
GET | /custom-field | List. Filter by panel. |
POST | /custom-field | Create. name and fieldType required. |
GET | /custom-field/:id | Read one. |
PUT | /custom-field/:id | Update. |
DELETE | /custom-field | Delete — { "ids": [...] }. |
PUT | /custom-field/modify | Bulk-modify definitions. |
PUT | /custom-field/reorder | Reorder within a section. |
PUT | /custom-field/reset-system | Reset system field configuration to defaults. |
Sections (/custom-section) group fields for display and follow the same CRUD-plus-reorder shape.
System fields worth knowing
Lead
stage · status · priority · source · notes · enquiryAt · contact · productServices · team · teamMember · plus ad attribution (ad_id, adset_id, campaignName) and UTM fields (utmSource, utmMedium, utmCampaign, utmTerm, utmContent, utmUrl).
| Field | Allowed values |
|---|---|
stage | New · Contacted · Follow-up Required · Qualified · Interested · Proposal Sent · Negotiation · Won · Lost · On Hold · Closed |
status | Hot · Warm · Cold |
priority | High · Medium · Low |
source | Bulk Import · Offline · Facebook · Website · AI Generated · Instagram · Whatsapp · SMS · Gmail · Zoho · LinkedIn · Twitter · Threads |
An unrecognised source is dropped rather than rejected.
Contact
name · email · jobTitle · address · country · state · city · postalCode · description · tags · contactcategories, plus nested groups:
contactNumber,work_phone_number,whatsapp_contact_number— each{ countryCode, number }companyData—{ name, website, industry, otherIndustry, size, location }social—{ facebook, twitter, linkedin, instagram, youtube, tiktok, personalWebsite }
| Field | Allowed values |
|---|---|
companyData.size | Self-employed · 0-10 · 11-50 · 51-200 · 201-500 · 501-1000 · 1001-5000 · 5001-10000 · 10000+ |
companyData.industry | Information Technology (IT) · Healthcare · Financial Services · E-commerce · Manufacturing · Education · Real Estate · Logistics · Media & Entertainment · Telecommunications · Automotive · Marketing · Legal Services · NGO · Other |
Nested paths can be written either nested or dotted — {"companyData": {"name": "Acme"}} and {"companyData.name": "Acme"} are equivalent.