We shipped a “native” CRM integration that worked in the demo environment, and even in a few early customer accounts. Then an enterprise customer installed it, and it failed in the most expensive way: quietly. - The records synced, but into the wrong conceptual buckets. - A handful of fields are never populated. - Some values got “normalized” into nonsense. - Writes started erroring when we hit a custom field that didn’t exist in _this_ tenant. One-line disclosure: the story above is a composite of common failure modes. If you build customer-facing integrations, you’ve seen versions of it. The root problem wasn’t our HTTP client or auth flow. It was treating the target system’s schema as _the schema_, when in practice it’s “the schema plus 5 years of admin decisions plus business semantics that vary by org.” That’s where “native object & field mapping” stops being a nice-to-have settings screen and becomes a durable business contract. Deep integration infrastructure providers (Ampersand is a concrete example) bake this contract into the platform: **object mapping, field mapping, value mapping, and nested mapping**, with both predefined and user-defined configurations surfaced through an embedded UI or headless hooks. --- # Mapping is semantic, not technical: the “hidden business context layer” Most integration problems are not “data plumbing.” They’re semantic mismatch. Here’s the framework that explains why - when you integrate with a SoR like Salesforce, you’re integrating with five layers of context. Missing a layer might hurt the builder quite a bit later. Lets discuss one layer of this context, the vocabulary. ## Vocabulary context: what are the “things” in this business? The obvious part is entity names: Account, Contact, Opportunity, Invoice, Payment, etc. The non-obvious part is the semantics: - What counts as an “Account” in this org? A company? A customer? A prospect? A parent entity with children? - Are “Leads” used, or is everything a Contact? - What constitutes an “Opportunity”? A deal? A renewal? A contract? A quote? A project? In ERPs, it gets even more semantic: - What’s the difference between an invoice, a bill, a sales order, a credit memo? - What is “customer” vs “subsidiary” vs “entity”? - What fields reflect accounting truth vs operational convenience? Rule: if you can’t explain an object in plain business terms, you’re not ready to map. What do deep integrations do differently? They preserve provider-native semantics and introduce a layer of mapping/config so each customer can reflect their reality in your product. ## The takeaway Object/field/value mapping isn’t “nice formatting.” It’s how you preserve both sides of the truth: - the provider’s API semantics (what the CRM/ERP actually stores), - your product’s semantics (what your UX and domain model mean), - and each customer’s org-specific vocabulary (how they configured their instance). Pitfall: If your mapping strategy can’t be explained in business terms (“What is a Company?” “What is a Deal Stage?”), you’ll build something that will struggle in the enterprise reality. --- # What deep integration infra does differently (and the tradeoffs) There are three common patterns teams use before they discover they need “mapping as a product surface.” ### A) Hardcoded one-off integrations You hardcode `Account.Name → companyName` and move on. What breaks in production - Tenants store “the same concept” in different fields (often custom fields). - Admins rename fields or swap workflows, and your assumptions silently drift. - Writes fail when your payload includes unmapped/unknown fields. Tradeoff - Fast to build and demo. - High long-term cost (support, patches, one-offs). ### B) Unified/common models You map every provider into a shared set of objects (“Company, Contact, Deal”). What breaks in production - The shared model flattens semantics (an “Opportunity” might be a renewal, not a new deal). - Customers still want their custom fields and picklist values reflected. - You end up reintroducing provider-specific exceptions anyway. Tradeoff - Great for your internal developer ergonomics. - Weak at representing tenant-specific reality without escape hatches. ### C) Generic “mapping UI” (iPaaS-style) You embed a general-purpose mapping editor and let customers wire everything. What breaks in production - Too much flexibility with too few guardrails. - Users create configs you can’t validate or support. - Mapping becomes a one-off “setup chore,” not a maintained contract. Tradeoff - Powerful, especially for ops-heavy customers. - Risky unless you invest heavily in opinionated UX + validation. ## Deep integration infrastructure (Ampersand-style) Ampersand’s documented approach is to **keep provider-native objects/fields** and add a mapping layer that can be: - pre-defined by you in `amp.yaml`, - user-defined at install time (field mappings), - dynamic per-tenant via UI-provided mapping config, - and reflected back in both read and write flows when you inherit mapping. Design choice: Deep infra doesn’t magically solve semantics. It gives you a structured way to _surface_ semantics to end users, capture their choices, and make the integration behave predictably. This semantic contract is table stakes for AI agents operating in customized enterprise environments. --- # Capabilities tour: object + field mapping as implemented in deep infra ## Object mapping vs field mapping (and why both exist) Object mapping answers: “What is this thing in my product’s vocabulary?” Ampersand lets you map a provider object to a label of your choice. Example: map Salesforce `account` and HubSpot `companies` to `company`. Read results are delivered under `company`, but the original provider object name remains available in `rawObjectName`. Field mapping answers: “What does this attribute mean?” You can map a provider field like `mobilephone` to `phone`. Read results deliver `phone`, and if your write action inherits mapping you can write using `phone` to update the original provider field. ([Ampersand](https://docs.withampersand.com/object-and-field-mapping)) Pitfall: Many integrations implement field mapping and skip object semantics. That’s how you end up syncing “Households” into “Companies” and calling it a data issue. YAML snippet #1: manifest-style mapping (object + field + user-defined mapping + inheritMapping) ```yaml # amp.yaml (excerpt) specVersion: 1.0.0 integrations: - name: mySalesforceIntegration displayName: My Salesforce integration provider: salesforce read: objects: # NOTE: objectName should match the provider's official API docs. - objectName: account mapToName: company # object mapping: account -> company requiredFields: # fieldName should match official provider docs (or be a nested JSONPath). - fieldName: name mapToName: companyName optionalFields: # user-defined field mapping: no fieldName; the user chooses the provider field. - mapToName: notes mapToDisplayName: Account notes prompt: Please select the field that contains notes for this account write: objects: - objectName: account inheritMapping: true # use the same mapping in reverse for writes ``` This is the “contract” move: define your concept keys once (`company`, `companyName`, `notes`) and reuse them across read + write via mapping inheritance. --- ## Predefined mapping vs user-defined mapping Ampersand supports two mapping styles: - Predefined mapping: you specify `fieldName → mapToName` in `amp.yaml`. - User-defined mapping: you define a concept field (like `notes`) and prompt each customer to select which provider field backs it. The docs explicitly note that user-defined mappings are currently supported for fields, not objects. User-defined mapping includes UX hints like: - `mapToDisplayName` (label in UI), - `prompt` (tooltip context), - `default (optional)` What breaks in production: If you force “one true field” for a concept like “Notes,” enterprise tenants will either (a) not use it, (b) use a custom field, or (c) use a field with a different name than you assumed. User-defined mapping makes that variability a supported state. --- ## Required vs optional mapping choices (and what happens when users don’t map) In Ampersand read actions, fields are modeled as: - requiredFields: every installer must grant read access. - optionalFields: installers can choose whether your app reads them. Ampersand also supports a “let users pick from all fields” mode: - `optionalFieldsAuto: all` populates a list of all fields in the object (including custom fields) and lets users select which ones your app can read. Design choice: “Optional” shouldn’t mean “ignored.” It means “non-blocking.” Your product should behave sensibly when optional fields aren’t selected. YAML snippet #2: read action config (required fields + “pick any additional field”) ```yaml # amp.yaml (read action excerpt) read: objects: - objectName: contact destination: contactWebhook schedule: "*/10 * * * *" # cron, as frequent as every 10 minutes requiredFields: - fieldName: firstname - fieldName: lastname - fieldName: email # Everything else is optional; user can choose from all fields (including custom). optionalFieldsAuto: all ``` Two practical implications: 1. You don’t need to enumerate every potential custom field. 1. Your UI can show a truthful “here’s what we can read” list for _this tenant_, not a generic one. ([Ampersand](https://docs.withampersand.com/read-actions)) Pitfall: Teams often treat optional fields as “we don’t care.” Customers treat optional fields as “the whole point.” --- ## Dynamic, per-tenant mapping configuration (because your app isn’t static either) Sometimes _your_ app has tenant-specific semantics: different customers have different enabled modules, so the set of fields you need mapped varies per tenant. Ampersand supports dynamic field mappings via the `fieldMapping` prop on the `InstallIntegration` component. The docs also state that dynamic mappings are optional—users can install without explicitly mapping a field. Design choice: This is how you avoid “integration config sprawl.” The mapping UI is driven by the product state (which fields matter for this customer), not a static checklist. --- ## Mapping enumerated values (Priority, Stage) without hardcoding translations Field names aren’t enough. If your product uses an enum for logic, you need value semantics. Ampersand supports value mapping via `mappedValues`. The docs give an explicit example: your app uses `Very High/High/Medium/Low` while the provider uses `P1/P2/P3/P4`, and you ask the customer to map between them. It also supports mapping values for: - a user-mapped field (field itself is chosen by the user), or - a known provider field (only values need mapping). Ampersand explicitly states it allows one-to-one mapping of values. What breaks in production: If you “normalize” values in code (e.g., `P1 → High`) without customer input, you’ll be wrong in at least one enterprise org—and your automation will behave unpredictably. --- ## Nested field mapping (thinking in JSONPath, not dot-notation guesswork) Some provider APIs return nested structures. Ampersand supports mapping from and to nested fields using JSONPath bracket notation, for example `$['userInfo']['email']`. Bracket notation is recommended for field names containing dots/spaces/special characters. They also show that the same mappings apply in reverse on writes. Pitfall: Teams frequently ship a “flat-field” mapper and then bolt on nested support later. By then, customers have already built workflows around missing data. Important caution (line items/arrays): Ampersand’s docs clearly cover nested objects and bracket-notation paths. They don’t explicitly document array indexing patterns (e.g., mapping `lineItems[0].sku`) in the material above. If your ERP use case involves repeated line items inside a single record payload, you should verify (a) which JSONPath patterns are supported for arrays and (b) how the mapped output represents repeated structures. If the provider exposes line items as separate records/objects, you can model them as separate read actions (the read action docs show an example reading an object named `lineItem`). --- ## How mapping shows up downstream (raw vs mapped) A mapping layer should give you: - stable mapped keys you can code against, and - the raw provider record for debugging and future expansion. Ampersand documents this result shape: non-mapped fields under `fields`, mapped fields under `mappedFields`, and the provider payload under `raw`. ```javascript { "objectName": "Contact", "result": [ { "fields": { // non-mapped fields }, "mappedFields": { "pronoun": "she", // statically mapped (amp.yaml) "source": "LinkedIn", // dynamically mapped (fieldMapping) "priority": "high" // dynamically mapped }, "raw": { // provider payload } } ] } ``` What breaks in production: If you only store the raw record, your product logic becomes provider-specific. If you only store mapped fields, you lose traceability when a customer disputes behavior. You want both. --- ## UX: embedded mapping UI vs going headless Embedded (prebuilt) UI Ampersand’s `InstallIntegration` component supports a `fieldMapping` prop for dynamic mapping and provides `onInstallSuccess` / `onUpdateSuccess` callbacks that include `(installationId, config)` so you can persist or inspect the resulting configuration. ([Ampersand](https://docs.withampersand.com/embeddable-ui-components)) ```typescript import { AmpersandProvider, InstallIntegration } from "@amp-labs/react"; export function IntegrationSetup({ userId, teamId }: { userId: string; teamId: string }) { const fieldMapping = { deals: [ { mapToName: "priority", mapToDisplayName: "Priority", prompt: "Which field do you use to track priority?", mappedValues: [ { mappedValue: "veryhigh", mappedDisplayValue: "Very High" }, { mappedValue: "high", mappedDisplayValue: "High" }, { mappedValue: "medium", mappedDisplayValue: "Medium" }, { mappedValue: "low", mappedDisplayValue: "Low" }, ], }, ], }; return ( <AmpersandProvider options={{ project: "PROJECT", apiKey: "YOUR_KEY_OR_TOKEN_PROVIDER" }}> <InstallIntegration integration="myIntegrationName" consumerRef={userId} groupRef={teamId} fieldMapping={fieldMapping} onInstallSuccess={(installationId, config) => { console.log("Installed", installationId, config); }} onUpdateSuccess={(installationId, config) => { console.log("Updated", installationId, config); }} /> </AmpersandProvider> ); } ``` Design choice: Embedded UI is how you get a consistent, battle-tested configuration flow quickly. It’s especially useful when you want mapping UI “good enough” without building your own from scratch. Headless (build your own UX) Ampersand’s Headless UI library provides React hooks for managing connections and installations. Key pieces for mapping/configuration: - `useInstallation()` provides access to the current installation. - You can access configuration via `installation.config`. - `useManifest()` retrieves object/field metadata from the connected provider (including custom objects/fields) so you can build dropdowns and selectors. - `useLocalConfig()` maintains a draft config state before committing changes. What breaks in production: If you bolt mapping onto a generic “settings” page, customers won’t maintain it, and your integration will drift. Headless lets you integrate mapping into a product-native onboarding flow where it belongs. --- ## Writes: inherit mapping, and handle unmapped fields safely Ampersand’s mapping docs emphasize `inheritMapping: true` so writes can use mapped keys and be translated back to provider fields. On the “what happens if a field isn’t mapped?” question, there’s a nuance worth stating carefully: - The object/field mapping docs say that if you have an optional user-defined mapping and the user chose not to map it, you can still send the mapped key in a write request and Ampersand will strip it to prevent provider errors. - Note: for Write Actions, the “Remove unmapped fields” is a preview feature and you will need to request access to. **Pitfall:** Without a “drop unmapped” capability, you end up littering your write code with conditional payload shaping and still missing edge cases. --- # Three scenarios that separate “demo-ready” from “enterprise-grade” ## Scenario 1: CRM semantics mismatch (“Account” vs “Company”) + custom fields Your product has a first-class “Company.” One customer’s CRM uses Accounts as companies; another uses Accounts as households or parent entities. How mapping helps - Use object mapping to align provider objects to your product vocabulary while retaining `rawObjectName` for traceability. - Use user-defined field mapping for “concept fields” that are org-specific, like `notes`. What breaks in production: Without object mapping, your internal model becomes provider-dependent, and your UI language becomes misleading. --- ## Scenario 2: Lifecycle stages/priorities don’t match (`P1/P2` vs `High/Med/Low`) You want to drive automation off “Priority.” The CRM has a picklist, but the values are org-specific. How mapping helps - Use `mappedValues` so customers map provider values to your app’s canonical values. - Respect the one-to-one constraint and design your canonical enums accordingly (fewer stable buckets beats dozens of brittle micro-states). Design choice: If a value is used for logic (routing, SLAs), don’t guess. Require a value mapping. --- ## Scenario 3: ERP-style nested structures and line items You need to sync invoices, payments, or orders—data that’s often nested. How mapping helps - Use nested field mapping to map nested provider structures into your stable concept fields and back. - Treat line items carefully: - If the provider exposes them as separate objects, model them as separate reads. - If they’re nested arrays inside a record, verify array-path support and shape your product contract accordingly (this isn’t explicitly documented in the sources above). What breaks in production: Line items are where “we’ll do it later” turns into “we can’t support your accounting workflow.” --- ## Mapping as a product surface: one-time setup vs a durable contract A common mistake is treating mapping as a single install step—then never revisiting it. A better mental model: - Mapping is configuration that should be inspectable, updateable, and debuggable. - The output should be predictable: stable mapped keys plus raw payload. - The UX should make “unmapped” a first-class state, not an error condition. Ampersand supports: - installation callbacks returning config (`onInstallSuccess`, `onUpdateSuccess`), - retrieving config via `installation.config`, - and building custom configuration flows via headless hooks. Design choice: The platform can store config; _your product_ must decide how to operationalize it (validation, UX affordances, internal schema expectations). --- # Pragmatic checklist: what to do next (and anti-patterns) ## If you’re building this, do these next 1. Define your canonical objects in business terms. If you can’t explain “Company” or “Invoice” in plain language, stop and do that first. 1. Decide what must be object-mapped vs field-mapped. If your UI says “Company,” object mapping is probably required. 1. Separate requiredFields from optionalFields intentionally. Required means “product can’t function without it.” Optional means “enhancement, but non-blocking.” 1. Use `optionalFieldsAuto: all` when custom fields are inevitable. It’s the difference between “we support custom fields” and “file a ticket for custom fields.” 1. Add value mapping for any enum used in logic. Don’t hardcode translations. 1. Plan for nested data early. If nested structures matter, use nested field mapping and explicitly scope how you handle repeated structures (arrays/line items). 1. Pick your UX approach: - Embedded UI for speed and consistency. - Headless for product-native flows (and note beta + read-action metadata scope). 1. Make mapping observable. In logs and debugging tools, surface both `mappedFields` and `raw`. ## Anti-patterns that will page you later - **Anti-pattern:** “Field mapping = selecting fields to sync.” - **Reality:** mapping is a semantic contract you build product logic on. - **Anti-pattern:** Treating “optional” as “we don’t care.” - **Reality:** optional fields are often the customer’s _real_ workflow data. - **Anti-pattern:** Guessing enum translations. - **Reality:** your automation will be wrong for at least one enterprise org. - **Anti-pattern:** Assuming arrays/line items are “just nested fields.” - **Reality:** repeated structures usually require explicit modeling and clear support boundaries. If you want enterprise-grade integrations, you’re not trying to “move data.” You’re trying to encode (and continuously respect) how each customer’s system represents their business. Mapping - done natively at the object, field, value, and nested-path levels - is the layer that makes that possible without turning your integration codebase into a graveyard of tenant-specific exceptions. If you’ve shipped integrations into Salesforce, HubSpot, NetSuite, or Dynamics, you have a mapping horror story. What broke first for you: object semantics, custom fields, or enums? Share it with us, we are all [ears](https://calendly.com/ayan/ampersand-introduction).