Enable Custom Fields
Different customers have different custom fields in their HR/ATS/CRM systems — salary bands, department codes, custom job levels. Rather than hardcoding per-customer fields, a scalable pattern lets customers select which custom fields to enable through a settings UI.
Prerequisites
Custom fields fundamentals loaded (../implementing-post-connection/references/post-connection-fundamentals.md).
Before Proceeding
Three pieces of information are needed before generating any code.
If invoked from implementing-post-connection, the first two were answered in Step 1 — use that context. Otherwise, gather them now:
- Categories: Which Merge categories need custom fields? (
hris, ats, crm, accounting, ticketing)
- Backend Merge SDK installed? Search for the language-appropriate package:
@mergeapi/merge-node-client, MergePythonClient, dev.merge:merge-java-client, merge-go-client, merge_ruby_client, or Merge.Client. Drives whether examples below use the SDK or raw HTTP.
- Existing field-mapping or settings UI? If a settings page already exists (see
post-connection-build-settings-page), the field selector inserts there rather than creating a new page.
Then choose the approach (Approach A vs B below) before writing any code — see "Choose Your Approach" immediately after.
Choose Your Approach
Before implementing, ask the user: "There are two approaches for custom fields:
- Approach A (Remote Data): raw custom field data arrives in
remote_data alongside each record. Requires a Professional or Enterprise plan, enabling Remote Data per model in Scopes, and passing include_remote_data=true on each request.
- Approach B (Field Mapping API): Gives customers a UI to select and map specific fields. Configuring Field Mappings via API requires a Merge Enterprise plan.
Which approach would you like to use — A, B, or both?"
Wait for the user's answer before continuing.
Two Approaches
Choose based on your use case — you can also combine them.
Approach A: Remote Data (simpler)
Merge passes raw custom field data alongside standard models in the remote_data field.
- Requires a Professional or Enterprise plan. On Launch the Scopes toggle isn't available and
remote_data comes back null.
- Two steps to turn on: enable Remote Data for the model in Configuration → Scopes, then pass
include_remote_data=true on the request. Neither alone is enough — with the scope off, the query param returns null; without the param, the field is omitted even when the scope is on.
- Best for: read-only access to all custom data, no customer selection needed
- Implementation: parse
remote_data from API responses and store/display as needed
- Limitation: field names vary by integration; no guaranteed consistency across customers. Address values by their API path rather than by array index — provider ordering is not stable across syncs.
Approach B: Field Mapping API (recommended for customer-facing selection)
Requires Merge Enterprise plan. Before proceeding, confirm with the user: "Approach B uses Merge's Field Mapping API, which requires an Enterprise plan. Do you have Enterprise access? If unsure, check with your Merge account team before implementing."
Gives customers a native mapping UI inside your product.
- Fetch available fields via
GET /api/{category}/v1/remote-fields using the account token (add include_example_values=true for sample values)
- Display available fields in a settings UI for customers to enable and map
- Customer selects which fields to include and how to map them to your schema
- Save selections via
POST /api/{category}/v1/field-mappings
- Merge returns mapped field values in the
field_mappings object on subsequent API responses
- Best for: customer-controlled field selection, structured mapping to your data model
Implementation — Approach B
GET /api/{category}/v1/remote-fields?include_example_values=true — fetch available fields for the linked account
The response is an object keyed by Common Model name, not a flat list. Each value is an array of remote fields for that model:
{
"Employee": [
{
"remote_key_name": "customFieldTShirtSize",
"schema": { "type": "string" },
"remote_endpoint_info": {
"method": "GET",
"url_path": "/v1/employees",
"field_traversal_path": ["customFieldTShirtSize"]
},
"example_values": ["Large"],
"advanced_metadata": null,
"coverage": 0.33
}
],
"Employment": []
}
| Field |
Type |
Notes |
remote_key_name |
string |
Field name in the source system |
schema |
object |
JSON-schema fragment, e.g. {"type": "string"} — the type lives here, there is no remote_field_type |
remote_endpoint_info |
object |
{method, url_path, field_traversal_path} — you pass all three back on create |
example_values |
array or null |
Only populated when include_example_values=true |
coverage |
number or null |
Fraction of records where the field has a value |
⚠️ example_values is empty unless you pass include_example_values=true. Without it, step 2's "sample value" column renders blank. Narrow the response with common_models=Employee,Employment when you only need specific models.
⚠️ There is no remote_field_name, remote_field_type, or common_model field on an entry. The Common Model comes from the key the entry sits under.
Render a list: field name (remote_key_name), which Common Model it came from (the response key), data type (schema.type), sample value (example_values[0])
Customer toggles which fields to enable and assigns a target name
POST /api/{category}/v1/field-mappings — save each enabled field + target mapping
Required body fields: common_model_name, remote_field_traversal_path, remote_method, remote_url_path — the last three come straight off the remote_endpoint_info object you got in step 1. Then either target_field_name + target_field_description to create a new Linked Account-specific target, or organization_wide_target_field to attach to an existing organization-wide target.
{
"common_model_name": "Employee",
"target_field_name": "t_shirt_size",
"target_field_description": "Employee t-shirt size for swag",
"remote_field_traversal_path": ["customFieldTShirtSize"],
"remote_method": "GET",
"remote_url_path": "/v1/employees"
}
Optional: advanced_mapping_expression (a JSONata expression to transform the value) and is_integration_wide (applies the mapping to every Linked Account on that integration — requires organization_wide_target_field). The jmes_path field is deprecated; use advanced_mapping_expression.
In sync logic: read field_mappings.linked_account_defined_targets and store mapped values alongside standard records
// Reading linked account-defined targets during sync
const laTargets = record.field_mappings.linked_account_defined_targets || {};
const customFields = Object.entries(laTargets).map(([key, obj]) => ({
key,
value: obj.value
}));
Critical Gotcha
Custom fields vary per linked account — never hardcode field names. Always fetch available fields dynamically from Merge's API for the specific account.
Testing Checklist
1---2name: post-connection-enable-custom-fields3description: Build a settings UI that lets customers select and enable custom fields from their connected system using Merge's Field Mapping API. Use as Step 5 of post-connection implementation when customers need access to fields beyond Merge's common models.4license: MIT5---67# Enable Custom Fields89Different customers have different custom fields in their HR/ATS/CRM systems — salary bands, department codes, custom job levels. Rather than hardcoding per-customer fields, a scalable pattern lets customers select which custom fields to enable through a settings UI.1011## Prerequisites1213Custom fields fundamentals loaded (`../implementing-post-connection/references/post-connection-fundamentals.md`).1415## Before Proceeding1617Three pieces of information are needed before generating any code.1819If invoked from `implementing-post-connection`, the first two were answered in Step 1 — use that context. Otherwise, gather them now:2021- **Categories**: Which Merge categories need custom fields? (`hris`, `ats`, `crm`, `accounting`, `ticketing`)22- **Backend Merge SDK installed?** Search for the language-appropriate package: `@mergeapi/merge-node-client`, `MergePythonClient`, `dev.merge:merge-java-client`, `merge-go-client`, `merge_ruby_client`, or `Merge.Client`. Drives whether examples below use the SDK or raw HTTP.23- **Existing field-mapping or settings UI?** If a settings page already exists (see `post-connection-build-settings-page`), the field selector inserts there rather than creating a new page.2425Then choose the approach (Approach A vs B below) before writing any code — see "Choose Your Approach" immediately after.2627## Choose Your Approach2829Before implementing, ask the user: "There are two approaches for custom fields:3031- **Approach A (Remote Data)**: raw custom field data arrives in `remote_data` alongside each record. Requires a Professional or Enterprise plan, enabling Remote Data per model in Scopes, and passing `include_remote_data=true` on each request.32- **Approach B (Field Mapping API)**: Gives customers a UI to select and map specific fields. Configuring Field Mappings via API requires a Merge Enterprise plan.3334Which approach would you like to use — A, B, or both?"3536Wait for the user's answer before continuing.3738## Two Approaches3940Choose based on your use case — you can also combine them.4142### Approach A: Remote Data (simpler)4344Merge passes raw custom field data alongside standard models in the `remote_data` field.4546- **Requires a Professional or Enterprise plan.** On Launch the Scopes toggle isn't available and `remote_data` comes back null.47- Two steps to turn on: enable Remote Data for the model in **Configuration → Scopes**, then pass `include_remote_data=true` on the request. Neither alone is enough — with the scope off, the query param returns null; without the param, the field is omitted even when the scope is on.48- Best for: read-only access to all custom data, no customer selection needed49- Implementation: parse `remote_data` from API responses and store/display as needed50- Limitation: field names vary by integration; no guaranteed consistency across customers. Address values by their API path rather than by array index — provider ordering is not stable across syncs.5152### Approach B: Field Mapping API (recommended for customer-facing selection)5354**Requires Merge Enterprise plan.** Before proceeding, confirm with the user: "Approach B uses Merge's Field Mapping API, which requires an Enterprise plan. Do you have Enterprise access? If unsure, check with your Merge account team before implementing."5556Gives customers a native mapping UI inside your product.5758- Fetch available fields via `GET /api/{category}/v1/remote-fields` using the account token (add `include_example_values=true` for sample values)59- Display available fields in a settings UI for customers to enable and map60- Customer selects which fields to include and how to map them to your schema61- Save selections via `POST /api/{category}/v1/field-mappings`62- Merge returns mapped field values in the `field_mappings` object on subsequent API responses63- Best for: customer-controlled field selection, structured mapping to your data model6465## Implementation — Approach B66671. `GET /api/{category}/v1/remote-fields?include_example_values=true` — fetch available fields for the linked account6869 **The response is an object keyed by Common Model name**, not a flat list. Each value is an array of remote fields for that model:7071 ```json72 {73 "Employee": [74 {75 "remote_key_name": "customFieldTShirtSize",76 "schema": { "type": "string" },77 "remote_endpoint_info": {78 "method": "GET",79 "url_path": "/v1/employees",80 "field_traversal_path": ["customFieldTShirtSize"]81 },82 "example_values": ["Large"],83 "advanced_metadata": null,84 "coverage": 0.3385 }86 ],87 "Employment": []88 }89 ```9091 | Field | Type | Notes |92 |---|---|---|93 | `remote_key_name` | string | Field name in the source system |94 | `schema` | object | JSON-schema fragment, e.g. `{"type": "string"}` — the type lives here, there is no `remote_field_type` |95 | `remote_endpoint_info` | object | `{method, url_path, field_traversal_path}` — you pass all three back on create |96 | `example_values` | array or null | Only populated when `include_example_values=true` |97 | `coverage` | number or null | Fraction of records where the field has a value |9899 ⚠️ **`example_values` is empty unless you pass `include_example_values=true`.** Without it, step 2's "sample value" column renders blank. Narrow the response with `common_models=Employee,Employment` when you only need specific models.100101 ⚠️ **There is no `remote_field_name`, `remote_field_type`, or `common_model` field on an entry.** The Common Model comes from the key the entry sits under.1021032. Render a list: field name (`remote_key_name`), which Common Model it came from (the response key), data type (`schema.type`), sample value (`example_values[0]`)1043. Customer toggles which fields to enable and assigns a target name1054. `POST /api/{category}/v1/field-mappings` — save each enabled field + target mapping106107 **Required body fields:** `common_model_name`, `remote_field_traversal_path`, `remote_method`, `remote_url_path` — the last three come straight off the `remote_endpoint_info` object you got in step 1. Then either `target_field_name` + `target_field_description` to create a new Linked Account-specific target, or `organization_wide_target_field` to attach to an existing organization-wide target.108109 ```json110 {111 "common_model_name": "Employee",112 "target_field_name": "t_shirt_size",113 "target_field_description": "Employee t-shirt size for swag",114 "remote_field_traversal_path": ["customFieldTShirtSize"],115 "remote_method": "GET",116 "remote_url_path": "/v1/employees"117 }118 ```119120 Optional: `advanced_mapping_expression` (a JSONata expression to transform the value) and `is_integration_wide` (applies the mapping to every Linked Account on that integration — requires `organization_wide_target_field`). The `jmes_path` field is deprecated; use `advanced_mapping_expression`.1215. In sync logic: read `field_mappings.linked_account_defined_targets` and store mapped values alongside standard records122123```javascript124// Reading linked account-defined targets during sync125const laTargets = record.field_mappings.linked_account_defined_targets || {};126const customFields = Object.entries(laTargets).map(([key, obj]) => ({127 key,128 value: obj.value129}));130```131132## Critical Gotcha133134Custom fields vary per linked account — never hardcode field names. Always fetch available fields dynamically from Merge's API for the specific account.135136## Testing Checklist137138- [ ] Available custom fields fetched and displayed in settings UI139- [ ] Customer can enable/disable individual fields140- [ ] Field selections persist per linked account (not globally)141- [ ] Enabled fields appear in subsequent sync data142- [ ] Field list refreshes when integration is relinked