> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kombo.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced Mappings

> Use a TypeScript formula to change data from the connected system into the format your product needs.

<Note>
  Formulas are available on request. Contact [Kombo support](/support) to enable
  them for your environment.
</Note>

## Why use an advanced mapping?

[Custom fields](./custom-fields) already give you one stable key across integrations. You create a Kombo custom field with the key `t_shirt_size`, then map **T-Shirt Size** from one connected system and **Workwear size** from another to that field. Your application always reads `custom_fields.t_shirt_size`.

However, sometimes this 1:1 mapping isn't enough. If we take our T-shirt example, some tools might already store the size `L`, but others might only store height in centimetres, such as `178`.

This is where advanced mappings come in. Instead of copying a value, you write a short TypeScript formula that changes it as part of the mapping, so your application still reads the same key and the same kind of value.

For a tool that only stores height, you map `t_shirt_size` with a formula:

```typescript theme={null}
function transform({ remote_data }: FormulaInput): FormulaResult {
  const heightCm = remote_data['/employee']?.height_cm

  if (heightCm == null) return null
  if (heightCm < 165) return 'S'
  if (heightCm < 175) return 'M'
  if (heightCm < 185) return 'L'
  return 'XL'
}
```

A height of `178` then becomes `L` under `custom_fields.t_shirt_size`. Tools that already store `L` keep a 1:1 mapping.

You might also need to:

* combine `first_name` and `last_name` into one display name
* split a single `30000 EUR` field into an amount and a currency
* calculate an FTE from weekly contract hours
* translate values into your own naming convention, for example turn `Vollzeit`, `full time`, or `FT` into the same `full_time` value
* read a value that sits inside a nested response, especially useful for systems like Workday or SuccessFactors

Today, an advanced mapping means a formula on a custom field. It calculates the value of a [custom field](./custom-fields) that you define, and it never changes Kombo's standard unified fields.

## How advanced mappings work

A regular custom field mapping copies the value of one field from the connected system. A formula mapping can read several fields, transform them, and return a new value.

Formulas are configured for a specific integration. They run whenever Kombo upserts a record for that model: during a [sync](../guides/sync), and when an incoming [webhook](../guides/webhooks) writes the same record. They do not run on write actions such as creating a candidate. Those records get the formula value on the next sync.

The model must have `custom_fields` in scope, and the connector must support custom fields for that model. Formulas run after [field remapping](./remapping/introduction).

A formula and a direct field mapping should not fill the same custom field. If you pick a field in the dashboard, Kombo discards the formula. Automatic mapping rules also skip fields that already have a formula. Setting a mapping through the public custom-field-mappings API does not remove a formula: both can exist, and the formula still wins when Kombo writes the record.

### Input and result

Every formula receives a `FormulaInput` and must return a `FormulaResult`. The editor injects these types; you do not import them.

```typescript theme={null}
function transform({
  unified_fields,
  remote_data,
}: FormulaInput): FormulaResult {
  return null
}
```

* `unified_fields` is the record's Kombo unified model. It does not include `id`, `remote_id`, `changed_at`, `custom_fields`, `integration_fields`, `remote_data`, or relation foreign keys. Date fields arrive as ISO strings.
* `remote_data` is the raw payload Kombo used to build the record. Keys always start with `/`. They can be a short path such as `/employee`, or a full templated URL such as `/ta/rest/v2/companies/{companyId}/employees/{employeeId}`. One record can have several keys. Use the input snapshot in the editor to see which keys this integration actually produces. A job formula reading `/candidates` is not a typo: that is the path the connected system used for that record.

A formula reads the raw payload as it passes through the sync, before Kombo decides what to store, so it works with [Remote Data](../getting-started/remote-data) turned off. In the editor, Kombo fetches the record live so you can preview against current data.

`FormulaResult` can be text, a number, `true` or `false`, `null`, an object, or an array. `undefined` is stored as `null`. Kombo rejects `NaN`, `Infinity`, and functions. The serialized result cannot exceed 10,000 characters.

TypeScript types help you in the editor only. Kombo strips them before running the formula and does not type-check it on the server, so a formula with the wrong return type can still be saved.

## Create an advanced mapping

### Create the target custom field

First, [create the Kombo custom field](./custom-fields#setting-up-custom-fields-in-kombo) that should contain the transformed value.

For example, if your product expects one display-ready location for every job, create a job custom field with the key `formatted_location`. The rest of this walkthrough uses that field.

Make sure `custom_fields` is enabled for the relevant model in the integration's [scope configuration](./scopes). Run at least one sync so Kombo has discovered fields and records you can use to preview the formula. Until that first sync, the custom field mapping control is disabled.

### Open the formula editor

Open the integration in the Kombo dashboard and go to **Custom Field Mappings**. Find the target custom field and open its mapping dropdown.

There is no **Formula** option in the field list. Use the banner button instead:

* **Formula**, when the Custom Field Explorer is available for this integration
* **Open Formula Editor**, when it is not

Only users who can administrate the environment can create or edit formulas.

<Frame>
  <img
    src="https://mintcdn.com/kombo/OkHiwPlKQ9lmtUhO/images/advanced-mappings/open-formula-editor.png?fit=max&auto=format&n=OkHiwPlKQ9lmtUhO&q=85&s=1bc3e5fb7a5e1dc965c761006f028085"
    alt="Formula button in a custom field mapping
dropdown"
    width="2880"
    height="2000"
    data-path="images/advanced-mappings/open-formula-editor.png"
  />
</Frame>

### Select an example record

Nothing is preselected. Choose an example record at the top of the formula editor. Each choice is audit-logged, and the editor needs at least one already synced record.

Kombo then shows an **Input snapshot** with the data available to the formula. Use it to see the `unified_fields` and `remote_data` paths for this integration. They differ between connected systems.

### Write and preview the formula

The entry point must be `function transform`. `const transform = () => { … }` is refused.

<Warning>
  Do not write `async function transform`. An async function returns a Promise,
  which Kombo stores as `{}` with no error.
</Warning>

There is no `console`, `require`, `import`, or `fetch`. `console.log` fails the record because `console` is undefined. The editor preview is the feedback loop: it runs automatically about 500ms after you stop typing.

The editor suggests the available fields while you type. For example, this formula reads a nested address from the connected system and formats it as one value:

```typescript theme={null}
function transform({ remote_data }: FormulaInput): FormulaResult {
  const address = remote_data['/candidates']?.location

  if (!address) return null

  return [address.street_1, address.city, address.country]
    .filter(Boolean)
    .join(', ')
}
```

Return `null` when the source record does not contain a value.

The **Use formula** button becomes available once the current formula evaluates successfully, including when it returns `null`. The default template returns `null` and is enough to enable the button. Before using it, select a few representative records to check how the formula handles empty fields and different values.

A preview can succeed and **Save changes** can still refuse the mapping. Saving checks that this connector supports `custom_fields` for the model; preview does not.

<Frame>
  <img
    src="https://mintcdn.com/kombo/OkHiwPlKQ9lmtUhO/images/advanced-mappings/formula-preview.png?fit=max&auto=format&n=OkHiwPlKQ9lmtUhO&q=85&s=ca006314144159fd7da8bd73311fa47f"
    alt="Formula editor with an input snapshot and successful preview
result"
    width="2400"
    height="1650"
    data-path="images/advanced-mappings/formula-preview.png"
  />
</Frame>

### Save the mapping

Click **Use formula** to return to the custom field mapping page, then click **Save changes**.

Kombo schedules a refresh sync after the mapping changes. Once the sync finishes, the formula result appears under the custom field's key in the Unified API:

```json {5} theme={null}
{
  "id": "ABDhovHrawy5bnP6dpVLH7ow",
  "name": "Data Scientist",
  "custom_fields": {
    "formatted_location": "Hackescher Markt 1, Berlin, Deutschland"
  }
}
```

## Common transformations

### Normalize a value

You can turn a value from the connected system into the vocabulary your product uses. For example, this formula groups weekly working hours into two values:

```typescript theme={null}
function transform({ unified_fields }: FormulaInput): FormulaResult {
  const hours = unified_fields.weekly_hours

  if (hours == null) return null

  return hours >= 35 ? 'full_time' : 'part_time'
}
```

### Use a fallback

Use the first available value when customers store the same information in different places:

```typescript theme={null}
function transform({ unified_fields }: FormulaInput): FormulaResult {
  return unified_fields.work_email ?? unified_fields.personal_email ?? null
}
```

### Read a nested value

Use `remote_data` when the value is available in the connected system but not in Kombo's unified model. The path and field names depend on the connected system and are shown in the input snapshot.

```typescript theme={null}
function transform({ remote_data }: FormulaInput): FormulaResult {
  return remote_data['/employee']?.home_address?.zip_code ?? null
}
```

## Limits

| Limit            | Value             |
| ---------------- | ----------------- |
| Formula source   | 10,000 characters |
| Input per record | 512 KB            |
| Result           | 10,000 characters |
| CPU time per run | 15 ms             |
| Memory per run   | 8 MB              |

Formulas should perform small, deterministic transformations. They cannot call external APIs or use the current date or random values. Treat every field in the input snapshot as optional: another record may have a different shape or an empty value.

## If a formula fails

If a formula throws or returns an invalid value for a record, Kombo skips writing that record. Previously stored values stay as they were; the custom field is not set to `null`. Other records in the resource still process.

If more than 5% of a resource's records fail, the sync is marked `FAILED`. A formula that fails for every record will fail the sync.

On a full or default sync, any of these errors also skip [deletion tracking](./deletion-policy) for that run. Kombo will not mark missing records as deleted until a later successful sync.

## Debug a formula

There is no `console.log`. Use the editor preview against several records.

If a formula fails during a sync, open [Logs](./logs). The customer-facing entry is a resource-level parsing error: `Parsing the resource "…" failed`. It does not include the formula text, the record data, or the detailed error.

<CardGroup cols={2}>
  <Card title="Custom Fields" icon="square-plus" href="./custom-fields">
    Create the target field and learn how mappings work.
  </Card>

  <Card title="Logs" icon="scroll" href="./logs">
    Investigate a formula that failed during a sync.
  </Card>
</CardGroup>
