How Do Unified APIs Handle Custom Fields? (2026 Architecture Guide)
Unified APIs strip custom fields to force rigid schemas. Learn the 3 industry approaches and why declarative JSONata mapping with multi-level overrides wins at enterprise scale.
Your unified API just ate your biggest enterprise deal.
The demo went perfectly. The prospect's VP of Sales was sold. Then their Salesforce admin sent over their org's schema: 73 custom fields on the Contact object, a custom Deal_Registration__c object with nested relationships, and a Revenue_Forecast__c field that drives their entire quarterly planning process. Your unified API provider's common model doesn't support any of it. The passthrough endpoint exists, but now you're writing raw Salesforce SOQL yourself — which defeats the entire purpose of buying a unified API.
So, how do unified APIs handle custom fields? Traditional unified APIs handle custom fields poorly. They strip out non-standard data to force everything into a rigid, lowest-common-denominator schema, leaving developers to write custom code against passthrough endpoints. Modern unified API architectures handle custom fields dynamically by using declarative mapping languages (like JSONata) and multi-level override hierarchies to translate per-customer schema variations without requiring any integration-specific code.
This is the custom fields problem, and it kills more enterprise integration deals than any technical limitation in your core product. This guide breaks down the architectural trade-offs of how the integration industry handles custom fields, why code-first platforms fail at scale, and how to architect a system that adapts to infinite schema variations.
The Enterprise Integration Reality: Custom Fields Are the Rule, Not the Exception
Custom fields are not edge cases. They are the default state of every enterprise SaaS deployment.
Whenever a user creates a custom field in Salesforce, the system appends __c at the end of the field name. Custom objects get the exact same treatment. This suffix is required when writing SOQL queries or using API integrations, as it ensures the system correctly identifies custom fields versus standard objects.
If you are building a B2B SaaS product, you will eventually integrate with Salesforce. And the moment you do, you will hit the wall of __c.
Approximately 90% of Fortune 500 companies use Salesforce, and over 150,000 companies worldwide run their businesses on the platform. Almost none of them use it out of the box. Enterprise orgs routinely have hundreds of custom fields, dozens of custom objects, and complex validation rules that reflect years of business process refinement.
Customer A has Industry_Vertical__c on their Account object. Customer B calls the same concept Sector__c. Customer C has an entirely custom object called Deal_Registration__c with 47 fields that don't exist anywhere else. Your integration code, which worked perfectly in your test org, breaks the moment it encounters a real enterprise deployment. (For a deep dive on this specific challenge, see our guide on how to handle custom fields and custom objects in Salesforce via API.)
And Salesforce is just one CRM. The average enterprise SaaS portfolio now comprises 342 apps, creating immense integration complexity for IT and product teams. Each of those apps potentially has its own custom fields, custom objects, and proprietary data structures that your integration layer needs to handle.
The math is brutal: if you support 10 CRMs and each enterprise customer has an average of 50 custom fields per CRM, you're looking at 500 unique field mappings per customer. Multiply that by your customer base and you have a mapping maintenance problem that scales linearly with revenue — the exact opposite of what a SaaS business should do.
The reality of enterprise SaaS integrations:
- No two instances are alike. Organizations spend hundreds of thousands of dollars customizing their CRMs, HRIS platforms, and ERPs.
- Standard objects are insufficient. If your integration only supports standard objects like
AccountandContact, you are ignoring the actual operational data your enterprise customers care about. - Manual mapping does not scale. Hardcoding custom field logic creates massive technical debt that grows linearly with every new customer.
The Lowest-Common-Denominator Problem: Why Traditional Unified APIs Fail
API schema normalization is the process of translating disparate data models from different third-party APIs into a single, canonical JSON format. The fundamental flaw with most unified APIs is that they solve this problem by stripping everything down to the fields that exist across all providers.
If Salesforce has MobilePhone, HomePhone, AssistantPhone, and OtherPhone on a Contact, but Pipedrive only has a single phone field, a rigid unified schema gives you one phone field. The other four phone numbers? Gone. Or buried in an unstructured blob that you have to parse yourself.
This is the lowest-common-denominator problem. To make 50 different CRMs look exactly the same, providers create a schema that represents the intersection of all features, not the union. If a feature exists in Salesforce but not in Pipedrive, it gets stripped out of the unified model.
Different SaaS platforms organize and encode data in varying formats — mismatched field names and identifiers often result in sync failures or force middleware to drop non-standard data. Enterprise customers heavily modify their SaaS instances. A flat unified schema cannot dynamically handle deeply nested custom JSON objects without manual intervention.
Developing custom API integrations for many SaaS applications can be complex, time-consuming and expensive. The process often involves multiple rounds of development and testing. Maintaining and updating custom integrations over time can lead to technical debt and costly revisions. The whole point of buying a unified API was to avoid this. But when the unified schema can't represent your customers' actual data, you end up writing custom integration code anyway — on top of paying for a unified API.
The result is a worst-of-both-worlds situation: you're locked into a vendor's schema for the 20% of fields that are standard, and you're writing bespoke code for the 80% of fields that your enterprise customers actually care about.
How Do Unified APIs Handle Custom Fields? (The 3 Industry Approaches)
The industry has converged on three main strategies for handling data that doesn't fit into a common model. Each has real trade-offs.
Approach 1: Passthrough / Raw API Access
The most common escape hatch. When a custom field doesn't exist in the unified schema, you bypass the unified layer entirely and make a raw API call to the underlying provider.
How it works: The unified API provider gives you a passthrough endpoint (e.g., POST /passthrough/salesforce/query) that forwards your request with the correct OAuth bearer token directly to the third-party API. You construct the request in the provider's native format (Salesforce SOQL, HubSpot filterGroups, etc.) and parse the raw response yourself.
The trade-off: You get full access to every field, but you lose everything the unified API was supposed to give you — normalized schemas, consistent pagination, abstracted auth. You're back to writing provider-specific code, just with an extra network hop in the middle. The unified API is acting as a dumb proxy, completely defeating the purpose of buying a schema normalization tool in the first place.
Approach 2: Code-First Custom Mapping
Some platforms take the position that custom field mapping is inherently a code problem. They provide SDKs and frameworks for writing per-customer field mappings in TypeScript or Python that execute on the provider's infrastructure.
How it works: You write a script that says if (customer === 'Acme') { return payload.Industry_Vertical__c } else { return payload.Sector__c }.
The trade-off: You are shifting the hosting of your technical debt, not eliminating it. Every time a customer adds a new custom field, your engineering team has to write a code update, review it, test it, and deploy it. This approach does not scale when you have hundreds of enterprise customers with completely different CRM configurations. Maximum flexibility on paper, but maintenance cost scales linearly with every customer variation.
Approach 3: Manual Mapping Portals
Some platforms offer a UI-based approach where end users manually map their custom fields to unified schema fields during the authentication flow.
How it works: When the user connects their account via OAuth, they are presented with a configuration portal asking them to map your application's fields to their CRM's custom fields.
The trade-off: It shifts the mapping burden to the customer, which works for simple field-to-field mappings but breaks down when transformations are needed (splitting a full name into first and last, converting date formats, flattening nested objects). Users often don't know the exact API names of the fields their Salesforce administrators created. If they map something incorrectly, the integration silently fails or syncs garbage data — and you've added friction to onboarding before the customer even gets value.
The Comparison
| Approach | Custom Field Coverage | Maintenance Cost | Deployment Required | Transformation Power |
|---|---|---|---|---|
| Passthrough API | Full (raw access) | High (per-provider code) | Yes | None (raw data) |
| Code-first mapping | Full (custom code) | High (scales linearly) | Yes (per change) | Full (code) |
| Manual mapping portal | Partial (field-to-field) | Low | No | Limited |
| Declarative config | Full | Low | No | Full (expressions) |
There's a fourth approach — declarative, config-driven mapping — that avoids the worst trade-offs of the other three.
The Declarative Approach: Config-Driven Field Mapping With JSONata
The most scalable way to handle custom fields is to treat integration logic as data, not code. As we've detailed in our guide on how to handle custom Salesforce fields across enterprise customers, instead of writing JavaScript functions for each provider or each customer, you write compact transformation expressions that are stored as configuration in a database. No code deployment. No build pipeline. No risk of breaking existing integrations.
JSONata is a declarative open-source query and transformation language for JSON data. Think of it as what XSLT is for XML, but purpose-built for the JSON payloads that every modern API speaks. JSONata's expressions allow for complex transformations, and its declarative approach means you can describe what you want to achieve without getting bogged down in the procedural details.
This is how Truto handles the custom fields problem. Every field mapping — for every provider, every resource, every customer — is a JSONata expression stored in a database. The runtime engine is completely generic: it reads the expression, evaluates it against the raw API response, and produces the unified output. There are no if (hubspot) or switch (provider) statements in the runtime engine. Zero integration-specific code.
When a unified API request comes in, the core engine resolves the context, loads the appropriate mapping expressions, and evaluates them against the payload:
graph TD
A[Incoming Unified Request] --> B[Resolve Integrated Account Context]
B --> C[Load Integration Config]
C --> D[Load JSONata Mapping Expressions]
D --> E[Map Request Query & Body]
E --> F[Execute Proxy Request to 3rd Party]
F --> G[Evaluate JSONata Response Mapping]
G --> H[Return Normalized Unified Response]Example: Handling Custom Fields in Salesforce
Salesforce returns flat PascalCase fields and uses SOQL for filtering. A declarative JSONata mapping can automatically detect custom fields (anything ending in __c) and map them into a custom_fields object without writing a single line of integration code:
response.{
"id": Id,
"first_name": FirstName,
"last_name": LastName,
"name": $join($removeEmptyItems([FirstName, LastName]), " "),
"email_addresses": [{ "email": Email }],
"custom_fields": $sift($, function($v, $k) { $k ~> /__c$/i and $boolean($v) })
}This single expression handles infinite custom fields. Whether the customer has two custom fields or two hundred, the $sift function identifies the __c suffix and bundles them into the unified response.
Example: Handling Custom Fields in HubSpot
HubSpot's API behaves completely differently. It returns data in nested properties objects and uses filterGroups for search syntax. The JSONata mapping handles this by calculating the difference between standard properties and whatever the customer has added:
(
$defaultProperties := ["firstname", "lastname", "email", "phone"];
$diff := $difference($keys(response.properties), $defaultProperties);
{
"id": response.id.$string(),
"first_name": response.properties.firstname,
"last_name": response.properties.lastname,
"email_addresses": [
response.properties.email ? { "email": response.properties.email, "is_primary": true }
],
"custom_fields": response.properties.$sift(function($v, $k) { $k in $diff })
}
)Both expressions produce the exact same unified output shape, despite radically different raw structures. The custom_fields key automatically captures every custom field — for HubSpot, it filters for non-default property keys; for Salesforce, it matches the __c suffix pattern. No hardcoded field lists. No code that needs updating when a customer adds a new custom field. The same generic engine evaluates whatever expression the configuration provides.
Why JSONata? JSONata expressions are pure functions — they transform input to output without modifying state. Because an expression is just a string, it can be stored in a database column, versioned, overridden, and hot-swapped without restarting the application or running a CI/CD pipeline.
Scaling Customization: The Three-Level Override Hierarchy
Abstracting custom fields into a custom_fields object solves the read problem, but what about writing data? What if you need to map a specific, highly-structured custom object into a first-class property in your application? What if Customer A's Salesforce has Industry_Vertical__c and Customer B's has Sector__c, and both need to appear as industry in your unified response?
To handle extreme enterprise variations without forcing developers back to passthrough endpoints, Truto uses a three-level override hierarchy, where each level is deep-merged on top of the previous:
graph TD
A["Platform Base Mapping<br>Default for all customers"] --> B["Environment Override<br>Per-customer environment"]
B --> C["Account Override<br>Per-connected account"]
C --> D["Final Merged Mapping<br>Applied at runtime"]
style A fill:#f0f4ff,stroke:#4a6cf7
style B fill:#e8f5e9,stroke:#4caf50
style C fill:#fff3e0,stroke:#ff9800
style D fill:#fce4ec,stroke:#e91e63Level 1: Platform Base Mapping
The default mapping defined by the unified model (like the Salesforce and HubSpot examples above). This handles the common fields that exist across most instances of a given provider and works out-of-the-box for 80% of use cases.
Level 2: Environment Override
A customer's specific environment can override any aspect of the mapping — response fields, query translations, default values, or resource names — without affecting other customers. If Acme Corp needs Industry_Vertical__c mapped to a top-level industry field, you apply an environment override:
{
"response_mapping": "{ 'industry': Industry_Vertical__c }"
}At runtime, the engine evaluates both the base expression and the override expression against the same input, then merges the results.
Level 3: Integrated Account Override
Individual connected accounts can have their own mapping overrides. This is critical for enterprise deployments where a single customer may have multiple SaaS orgs with different schemas. If Acme Corp has two Salesforce orgs — one for North America, one for EMEA — each connected account gets its own mapping. If one specific instance has a proprietary custom object for deal registration, you can override the routing logic for that single account:
{
"resource": "Deal_Registration__c",
"method": "get",
"response_mapping": "..."
}What can be overridden at each level:
| Override Target | What It Controls | Example |
|---|---|---|
response_mapping |
How response fields are mapped | Add Industry_Vertical__c as industry |
query_mapping |
How filters are translated | Support filtering by custom picklist values |
request_body_mapping |
How create/update payloads are built | Include custom required fields on creation |
resource |
Which API endpoint to call | Route to a custom object endpoint |
before / after |
Pre/post-processing steps | Fetch custom field metadata before the main call |
Here's how the merge works in practice:
// Base mapping produces:
{ "id": "123", "name": "Acme Corp", "email": "info@acme.com" }
// Customer override adds:
{ "industry": "Enterprise Software", "territory": "West" }
// Final merged result:
{ "id": "123", "name": "Acme Corp", "email": "info@acme.com",
"industry": "Enterprise Software", "territory": "West" }This means a customer can add their own custom fields to the unified response, change how filtering works for their specific setup, override which API endpoint is used for a specific operation, or add pre-processing steps to enrich data — all through configuration. No code deployment. No risk of breaking other customers.
Dynamic Resource Resolution and Polymorphic Endpoints
Custom fields are only half the battle. Enterprise SaaS platforms frequently use polymorphic endpoints and dynamic routing that break static API models.
Consider NetSuite. NetSuite treats vendors and customers as separate record types with separate tables. However, from an accounting perspective, they are both "contacts" — entities you transact with.
A rigid unified API forces you to query two different resources. A flexible architecture uses dynamic resource resolution to abstract this complexity. By evaluating query parameters at runtime, the engine can route the request to entirely different third-party endpoints while maintaining a single unified interface.
If a request comes in for GET /unified/accounting/contacts?contact_type=vendor, the mapping layer dynamically routes the proxy call to NetSuite's vendor table. If the query is contact_type=customer, it routes to the customer table. The response mapping normalizes both entity types into a common shape.
This is achieved through a conditional array in the configuration:
{
"resource": [
{ "resource": "vendor", "query_param": "contact_type", "query_param_value": "vendor" },
{ "resource": "customer", "query_param": "contact_type", "query_param_value": "customer" }
]
}When dealing with custom objects, this pattern allows you to dynamically route requests based on the specific shape of the customer's data, entirely bypassing the limitations of static schema generation.
The Safety Net: Why remote_data Prevents Data Loss
No matter how sophisticated your mapping layer is, there will always be edge cases. New custom fields get added to Salesforce orgs weekly. HubSpot properties evolve as marketing teams experiment. A mapping that was comprehensive last month might miss a field that was added yesterday.
If your unified API provider strips out unmapped data, you are stuck. You have to wait for them to update their schema or fall back to building a custom passthrough integration.
To prevent data loss, a production-grade unified API must always attach the original, unmapped third-party response to the normalized output. In Truto, every mapped response object includes a remote_data field containing the raw payload exactly as the provider returned it:
{
"result": [{
"id": "003xx000004TMfGAAW",
"first_name": "Jane",
"last_name": "Chen",
"email": "jane@acme.com",
"custom_fields": {
"Industry_Vertical__c": "Enterprise Software",
"Lead_Score__c": 87
},
"remote_data": {
"Id": "003xx000004TMfGAAW",
"FirstName": "Jane",
"LastName": "Chen",
"Email": "jane@acme.com",
"Industry_Vertical__c": "Enterprise Software",
"Lead_Score__c": 87,
"Custom_Approval_Status__c": "Pending",
"Internal_Notes__c": "Key decision maker for Q3 renewal",
"Territory_Code__c": "US-WEST-007"
}
}]
}Notice the three fields in remote_data that don't appear in the unified response: Custom_Approval_Status__c, Internal_Notes__c, and Territory_Code__c. They weren't mapped to the unified schema, but they aren't lost. Your application can always access them when needed.
This creates a two-tier access pattern:
- Unified fields for the 80% of use cases where normalized data is sufficient
remote_datafor the 20% of cases where you need provider-specific or customer-specific custom fields
If you need high performance and don't need the raw data, you can strip it with truto_ignore_remote_data=true. But for most enterprise use cases, having the full response available means you never lose data to a rigid schema.
When Declarative Mapping Isn't Enough: Multi-Step Orchestration
Some custom field scenarios go beyond simple field mapping. Consider these real-world cases:
- Salesforce with Record Types: The available custom fields change depending on the record type. You need to fetch the record type first, then adjust your field mapping.
- HubSpot with Custom Properties: Custom properties require a separate API call to discover the property definitions before you can interpret the values.
- NetSuite with Custom Segments: Custom segment values are stored in separate custom record type tables that need to be queried independently.
Declarative mapping alone doesn't cover these. You need multi-step orchestration — the ability to chain multiple API calls together, with each step feeding data to the next.
Truto handles this through before and after steps — ordered pipelines of API calls and transformations that run before or after the main request. Each step can make additional API calls to fetch supporting data, evaluate JSONata expressions to transform accumulated data, run conditionally based on query parameters or account context, and reference results from previous steps.
For example, to handle HubSpot custom properties, a before step fetches the property definitions, then the main request fetches contacts, and a response mapping uses the property metadata to correctly label custom field values.
The entire pipeline — the before steps, the main call, the response mapping, the after steps — is driven by the same declarative configuration. No custom code per integration. No deployment required to add a new orchestration step.
Stop Letting Rigid Schemas Dictate Your Roadmap
Schema normalization is the hardest problem in SaaS integrations. The custom fields problem isn't going away. Companies use 106 SaaS applications on average, and in large enterprises, that number can exceed 131. Each of those applications is being customized by the teams using them. Every new enterprise customer you sign brings a unique combination of custom fields, custom objects, and custom workflows that your integration layer needs to handle.
The question isn't whether your unified API can handle standard fields — any provider can map FirstName to first_name. The question is what happens when your biggest customer's Salesforce admin creates Revenue_Forecast_Q3_2026__c tomorrow and your VP of Sales needs that data flowing into your product by Friday.
If the answer involves filing a feature request, waiting for next quarter, or writing custom integration code: your unified API is a bottleneck, not an accelerator.
Here's the decision framework:
- If your customers use SaaS apps out of the box with minimal customization, any unified API approach will probably work. Pick the one with the best coverage for your category.
- If your customers are mid-market with moderate customization, look for a platform that supports per-environment overrides and preserves raw data alongside unified fields.
- If your customers are enterprise with heavily customized SaaS instances, you need declarative mapping, multi-level overrides, multi-step orchestration, and full raw data preservation. Anything less will become a constant source of support tickets and lost deals.
When evaluating unified APIs, look past the marketing claims of "one API for 100 CRMs." Ask how they handle Salesforce's __c fields. Ask what happens when a customer has a custom object that requires hitting a different endpoint. Ask if you have to write and maintain custom code just to read a non-standard field.
The integrations your product supports aren't just a feature checkbox on a comparison spreadsheet. They're a direct reflection of how seriously you take your enterprise customers' actual data. Custom fields are that actual data. Handle them well, and you unlock deals your competitors can't touch.
Frequently Asked Questions
- How do unified APIs handle custom fields in Salesforce?
- Most unified APIs map standard Salesforce fields but strip custom fields (those ending in __c) from their common models. Modern platforms use declarative mapping languages like JSONata to dynamically detect and extract custom fields, grouping them into a custom_fields object and preserving the full raw response as remote_data — no custom code required.
- What is the best way to handle custom fields across multiple CRMs?
- Declarative, config-driven mapping using a transformation language like JSONata lets you define per-provider and per-customer field mappings as data rather than code. This avoids the linear maintenance cost of writing custom integration code for each customer variation, while supporting full transformation power.
- Why do traditional unified APIs struggle with custom objects?
- Traditional unified APIs rely on lowest-common-denominator schemas. If a custom object or field isn't present across all supported CRMs, it gets stripped out of the unified model — forcing developers to use raw passthrough APIs and write provider-specific code, which defeats the purpose of using a unified API.
- Can I customize unified API mappings per customer?
- Some unified API platforms support override hierarchies that let you customize field mappings at the platform, environment, or individual account level. This is essential for enterprise deployments where each customer's SaaS instance has unique custom fields and objects.
- What happens to unmapped custom fields in a unified API response?
- In most rigid unified APIs, unmapped fields are silently dropped. Platforms like Truto preserve the complete original response as a remote_data field on every result, ensuring no custom field data is ever lost even if it isn't part of the unified schema.