Field Mapping
Let end users map their application's fields to a connected integration's fields inside the Connect dialog.
Field Mapping lets your end user, inside the Connect dialog, pick a remote object type (e.g. Contacts, Leads) and map your application's fields to the connected integration's fields. The result is saved as a workflow-input value and becomes referenceable as data pills in the embedded workflow builder.
Two halves make up the feature, joined by a single shared object name:
- Design time (you, the workflow author) — declare a
field_mappingworkflow input carrying anobjectName, in the workflow definition. - Runtime (your end user) — the React SDK renders the mapping UI, fetching object types and integration fields live against the user's connection through callbacks you supply on
useConnectDialog.
Key Features
| Feature | Description |
|---|---|
| Object-type selector | The user picks a remote object type; the integration-field dropdown then loads for that type. |
| Live option lists | Object types and integration fields are fetched at runtime against the user's connection — no hardcoded lists. |
| Configurable rows | defaultFields seeds which application-field rows show initially; userCanRemoveMappings lets users remove and re-add them. |
| User-creatable fields | When your application object supports freeform fields or a flexible schema, userCanCreateFields lets users create their own fields beyond your configured set. |
| Object name | The input's objectName is the key your runtime mapObjectFields config must use. |
Scope
This is a deliberately reduced subset of full field mapping. Included: dynamic application fields, the object-type selector, flat (non-paginated) option lists, and configurable/creatable mappings. Not included: paginated / search-as-you-type dropdowns, and any built-in runtime transform — the stored mapping is the input value; your workflow reads it and applies it however you like.
Declared in the definition, not in the input dialog
The workflow editor's Edit Input dialog offers only the scalar types (Boolean, Date, Date Time, Integer, Number, String, Time) and does not carry a Field Mapping type. Declare a field-mapping input in the workflow definition itself — author it in a .json/.yaml file and bring it in with Import Workflow, or export, edit and re-import an existing workflow.
How it fits together
Design time (workflow definition)
input type = "field_mapping"
└─ extensions.objectName → matches the SDK config key
Runtime (end user, Connect dialog)
useConnectDialog({ mapObjectFields: { <objectName>: { objectTypes, integrationFields, applicationFields: { fields, ... } } } })
object-type <select> ← objectTypes.get({ executeAction, search })
row: app field → integration field <select> ← integrationFields.get({ executeAction, objectType, search })
executeAction(...) → runs a component action against the user's live credentials
mapping value → saved via the debounced inputs PUTThe same object name keys both sides: the input's objectName must equal the key in the runtime mapObjectFields config.
Design time — declare a field-mapping input
In the workflow definition, give the input type: field_mapping and an objectName extension. Everything else about the input is ordinary:
{
"inputs": [
{
"name": "contactMapping",
"label": "Contact Mapping",
"type": "field_mapping",
"required": false,
"objectName": "Contacts"
}
]
}objectName is the only design-time value the runtime needs: the SDK looks up your mapObjectFields config under exactly that key. The option lists themselves are never authored here — they are fetched at runtime by the callbacks in the next section.
Import the definition with Import Workflow (from the integration's ⋮ menu or the editor's settings menu), then publish as usual.
Runtime — configure the SDK
Pass mapObjectFields to useConnectDialog, keyed by the same object name you used in the test value. Each entry supplies two get callbacks plus the application fields and optional toggles:
'use client';
import {
useConnectDialog,
type MapObjectFieldsType,
type OptionType,
} from '@bytechef/embedded';
const mapObjectFields: MapObjectFieldsType = {
Contacts: {
// Remote object types — fetched live against the user's connection.
objectTypes: {
get: async ({executeAction}) => {
const objects = await executeAction('hubspot', 1, 'listObjects', {});
return objects.map((object: any): OptionType => ({label: object.name, value: object.id}));
},
},
// Integration fields for the chosen object type.
integrationFields: {
get: async ({executeAction, objectType}) => {
const fields = await executeAction('hubspot', 1, 'listObjectFields', {objectType});
return fields.map((field: any): OptionType => ({label: field.label, value: field.id}));
},
},
// Your application's fields the user maps from (the left column).
applicationFields: {
fields: [
{label: 'Title', value: 'title'},
{label: 'Email', value: 'email'},
],
defaultFields: [], // [] = none shown initially; omit = all shown
userCanRemoveMappings: true, // optional — per-row remove / re-add
userCanCreateFields: true, // optional — let users invent a field
},
},
};
export function ConnectButton({jwtToken, integrationId}: {jwtToken: string; integrationId: string}) {
const {openDialog} = useConnectDialog({
baseUrl: 'https://your-bytechef-host.example.com',
environment: 'DEVELOPMENT',
integrationId,
jwtToken,
mapObjectFields,
});
return <button onClick={openDialog}>Connect</button>;
}The executeAction helper
Both callbacks receive an executeAction function provided by the SDK — you never construct it:
executeAction(componentName: string, componentVersion: number, actionName: string, input: Record<string, unknown>): Promise<unknown[]>It runs the named component action against the connected user's stored credentials and returns the action's result array. It is bound to the current dialog's integration instance (sent as the X-Instance-Id header) and the user's JWT, so:
- You never thread an instance id through your callbacks.
- A callback cannot target another user's instance — the server verifies the connected user (from the JWT
sub) owns the instance and rejects the call otherwise.
Callback arguments
| Callback | Argument | Notes |
|---|---|---|
objectTypes.get | {executeAction, search?} | search is accepted but unused (no server-side search). |
integrationFields.get | {executeAction, objectType, search?} | The integration-field <select> stays disabled until an object type is chosen. |
Config reference
| Field | Type | Required | Description |
|---|---|---|---|
objectTypes.get | (args) => Promise<OptionType[]> | yes | Returns the remote object types for the selector. |
integrationFields.get | (args) => Promise<OptionType[]> | yes | Returns the integration fields for the chosen object type. |
applicationFields | object | yes | Nested object containing your application's field options and toggles (see sub-fields below). |
applicationFields.fields | OptionType[] | yes | Your application's fields (the mappable left column). |
applicationFields.defaultFields | string[] | no | values of the rows visible initially. [] shows none; omitting shows all. |
applicationFields.userCanRemoveMappings | boolean | no | Allow per-row remove and re-add. |
applicationFields.userCanCreateFields | boolean | no | For application objects with a freeform or flexible schema, let users create their own application fields beyond the configured fields. |
OptionType is {label: string; value: string}.
The stored value
The user's mapping is saved as the workflow input's value (through the same debounced inputs PUT the Connect dialog already uses). The shape is self-describing so user-created fields survive:
{
"objectType": "contacts",
"mappings": [
{"applicationField": {"label": "Title", "value": "title", "custom": false}, "integrationField": "first_name"},
{"applicationField": {"label": "Priority", "value": "priority", "custom": true}, "integrationField": "hs_priority"}
]
}This is exported as FieldMappingValueType from @bytechef/embedded.
You apply the mapping yourself
ByteChef stores the mapping but does not transform any payload with it. In your workflow, read this input value and apply it however you need — for example in a Code step, or by feeding integrationField names into a downstream action's parameters.
Example use case
You sync your app's contacts to whatever CRM each customer connects. You expose a single Field Mapping input named after the Contacts object. At runtime, objectTypes.get lists the CRM's object types and integrationFields.get lists the fields of the chosen one — both via executeAction against that user's live credentials. The user maps your Title/Email fields to the CRM's fields, optionally adds a custom field, and your sync workflow reads the stored mapping to build each upsert.
How is this guide?
Last updated on