ByteChef LogoByteChef
EmbeddedWorkflows

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_mapping workflow input carrying an objectName, 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

FeatureDescription
Object-type selectorThe user picks a remote object type; the integration-field dropdown then loads for that type.
Live option listsObject types and integration fields are fetched at runtime against the user's connection — no hardcoded lists.
Configurable rowsdefaultFields seeds which application-field rows show initially; userCanRemoveMappings lets users remove and re-add them.
User-creatable fieldsWhen your application object supports freeform fields or a flexible schema, userCanCreateFields lets users create their own fields beyond your configured set.
Object nameThe 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 PUT

The 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

CallbackArgumentNotes
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

FieldTypeRequiredDescription
objectTypes.get(args) => Promise<OptionType[]>yesReturns the remote object types for the selector.
integrationFields.get(args) => Promise<OptionType[]>yesReturns the integration fields for the chosen object type.
applicationFieldsobjectyesNested object containing your application's field options and toggles (see sub-fields below).
applicationFields.fieldsOptionType[]yesYour application's fields (the mappable left column).
applicationFields.defaultFieldsstring[]novalues of the rows visible initially. [] shows none; omitting shows all.
applicationFields.userCanRemoveMappingsbooleannoAllow per-row remove and re-add.
applicationFields.userCanCreateFieldsbooleannoFor 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

On this page