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

# Edit sync config as JSON

> The keys, transforms, and filters in a sync connection's JSON config, and the order Steerco runs them in.

Each sync connection has one config: its field mapping, its transforms, and its filters. To edit that config as JSON, open the **JSON** view on the connection's **Setup** tab.

Transforms and extra filters have no visual editor, so you edit them here.

## Open the JSON editor

<Steps>
  <Step title="Open the connection">
    On the connector's manage page, click the **Setup** tab.
  </Step>

  <Step title="Switch to JSON">
    In the toolbar, click **JSON**.
  </Step>

  <Step title="Edit and save">
    Change the config, then click **Save** in the bar at the bottom of the page.
  </Step>
</Steps>

Autocomplete lists the connection's tables and fields. Beside the editor, the **Reference** tab describes the transforms and filters, and **Insert example** adds one. The **Problems** tab lists each problem with its line.

## Document shape

A connection's config has three keys:

```json theme={null}
{
  "mapping": {
    "accounts/Account": {
      "companyName": "Name",
      "sourceId": "Id",
      "websiteDomain": "Website",
      "industry": "Industry"
    },
    "accountproducts/Asset": {
      "sourceId": "Id",
      "accountId": "AccountId",
      "productName": "product_name"
    }
  },
  "transformations": [
    {
      "name": "product_name",
      "type": "enrich",
      "stream": "accountproducts",
      "key": "Product2Id",
      "from": {
        "stream": "Product2",
        "key": "Id",
        "fields": { "product_name": "Name" }
      }
    }
  ],
  "filters": [
    {
      "stream": "accounts",
      "condition": "include_values",
      "field": "Type",
      "values": ["Customer", "Partner"]
    }
  ]
}
```

| Key | What it holds |
| - | - |
| `mapping` | One entry per table that syncs. Each entry maps Steerco fields to the table's columns. |
| `transformations` | A list of transforms. Steerco runs them in list order. |
| `filters` | A list of filters. Steerco runs them in list order. |

In this example, the transform copies each asset's product name, and the filter keeps customer and partner companies. The config, each transform, and each filter accept only the keys this page lists. An unknown key is a problem.

## Mapping

Each key in `mapping` reads `"<object>/<table>"`:

* `<object>` is a Steerco object key from the next table, or `object:<slug>` for a custom object.
* `<table>` is the tool's table name, as the table picker shows it.
* A second table for the same object takes a number, such as `activities_2/Event`.

Each value maps Steerco field keys to the table's columns: `{ "steercoField": "sourceColumn" }`. A column can be a dotted path into a JSON column, such as `properties.industry`. It can also name a column that a transform produces, such as `product_name` above.

| Object key | Object on the Setup tab | Required fields |
| - | - | - |
| `accounts` | Companies | `companyName`, `sourceId` |
| `contacts` | People | `email`, `sourceId` |
| `products` | Products | `sourceId` |
| `opportunities` | Opportunities | `sourceId` |
| `opportunityitems` | Opportunity Items | `sourceId` |
| `accountproducts` | Owned Products | `accountId`, `sourceId` |
| `risks` | Risks | `sourceId` |
| `tickets` | Tickets | `sourceId` |
| `users` | Users | `email`, `sourceId` |
| `calls` | Calls | `sourceId` |
| `tasks` | Tasks | `sourceId` |
| `notes` | Notes | `sourceId` |
| `messages` | Messages | `sourceId` |
| `emails` | Emails | `sourceId` |
| `meetings` | Meetings | `sourceId` |
| `activities` | Mixed | `sourceId` |

An object's required fields must be mapped. To add a Steerco field, use **Settings** > **Data configuration**.

## Transforms

Every transform takes these keys:

| Key | Required | What it does |
| - | - | - |
| `type` | Yes | `enrich`, `array_lookup`, `embed`, or `derive`. |
| `stream` | Yes | The table the transform changes. Use the object key from the mapping key, such as `accounts` or `activities_2`, or the table name, such as `Account`. |
| `name` | No | A label for the transform. The Setup tab and the sync logs use it. |

If a transform can't find the table it reads from, or a column it matches on, Steerco skips the transform and logs a warning.

### Lookup tables

`enrich`, `array_lookup`, and `embed` read rows from another table. You name that table in `from.stream`, `lookup.stream`, or a join's `stream`:

* **A table name,** such as `User`, reads the table's own columns. Saving the config adds the table to the sync, even when no object maps it.
* **An object key this connection maps,** such as `users`, reads that object's rows after mapping. The rows carry the source columns and the Steerco field keys.
* **A table another sync connection maps,** such as Zendesk reading Salesforce's `Account`. The other connection must sync that table and have synced at least once, because Steerco reads its last copy.

Lookup matches ignore case, except the `key` match in `embed`. A whole number stored as a decimal, such as `12345.0`, matches `12345`.

### enrich

Copies fields from a matching row in another table. It matches on one column, or on several.

| Key | Required | What it does |
| - | - | - |
| `key` | For a one-column match | The column on this table to match. |
| `from.stream` | Yes | The table to copy from. |
| `from.key` | For a one-column match | The column on the lookup table that `key` must equal. |
| `from.match` | For a match on several columns | A list of `{ "event_field": "...", "lookup_field": "..." }` pairs. Every pair must match. |
| `from.fields` | Yes | The columns to copy, as `{ "newColumn": "lookupColumn" }`. |
| `from.pick` | No | Which lookup row wins when several match: `last`, the default, or `first`. |

Use `key` with `from.key`, or `from.match`, but not both. A row with no match gets an empty value in each copied column. A copied column replaces a column of the same name. In a match on several columns, a lookup row with an empty match column is skipped.

This copies each Salesforce user's email and name onto the companies they own. A mapping can then read `owner_email`:

```json theme={null}
{
  "name": "owner_details",
  "type": "enrich",
  "stream": "accounts",
  "key": "OwnerId",
  "from": {
    "stream": "User",
    "key": "Id",
    "fields": { "owner_email": "Email", "owner_name": "Name" }
  }
}
```

This matches on two columns:

```json theme={null}
{
  "name": "entitlement_status",
  "type": "enrich",
  "stream": "accountproducts",
  "from": {
    "stream": "Entitlement",
    "match": [
      { "event_field": "AccountId", "lookup_field": "AccountId" },
      { "event_field": "Product2Id", "lookup_field": "Product2Id__c" }
    ],
    "fields": { "entitlement_status": "Status" },
    "pick": "first"
  }
}
```

### array\_lookup

Turns a list of IDs into values from another table.

| Key | Required | What it does |
| - | - | - |
| `field` | Yes | The column that holds the list. |
| `lookup.stream` | Yes | The table to look each item up in. |
| `lookup.key` | Yes | The lookup table's column that each item must equal. |
| `lookup.value` | Yes | The lookup table's column to return. |
| `output_field` | No | The column for the result. Without it, the result replaces `field`. |
| `return` | No | `array`, the default, returns every match as a list. `first` returns the first match as one value. |

The `field` value can be a list or a JSON array, such as `[1, 2]`. It can also be text split by semicolons, such as `a;b`, or a single value. A row with no matches gets an empty value.

This turns a call's participant emails into contact IDs, which a mapping can then read with `"contactIds": "participant_contact_ids"`:

```json theme={null}
{
  "name": "participant_contacts",
  "type": "array_lookup",
  "stream": "calls",
  "field": "participant_emails",
  "lookup": { "stream": "Contact", "key": "Email", "value": "Id" },
  "output_field": "participant_contact_ids"
}
```

### embed

Nests related rows, like ticket comments, inside each record.

| Key | Required | What it does |
| - | - | - |
| `key` | Yes | The column on this table that child rows point to. |
| `from.stream` | Yes | The table that holds the child rows. |
| `from.key` | Yes | The child table's column that must equal `key`. |
| `from.fields` | No | The child columns to keep. Without it, Steerco keeps every column except `from.key`. |
| `output_field` | No | The column for the nested rows. The default is `embedded`. `outputField` also works. |
| `joins` | No | A list of lookups that add a column to each child row. |

Each record gets a list of its child rows. A record with no child rows gets an empty value. The match on `key` is exact, including case.

Each item in `joins` takes these keys:

| Key | Required | What it does |
| - | - | - |
| `childField` | Yes | The child row's column to look up. |
| `stream` | Yes | The table to look it up in. |
| `key` | Yes | That table's column that `childField` must equal. |
| `value` | Yes | That table's column to copy. |
| `as` | No | The name of the new column. The default is the `value` name. |

When you set `from.fields`, list each join's new column there too, or Steerco drops it.

This nests each Zendesk ticket's comments, with each author's name, in `ticket_comments`:

```json theme={null}
{
  "name": "ticket_comments",
  "type": "embed",
  "stream": "tickets",
  "key": "id",
  "from": {
    "stream": "ticket_comments",
    "key": "ticket_id",
    "fields": ["body", "created_at", "author_name"]
  },
  "output_field": "ticket_comments",
  "joins": [
    {
      "childField": "author_id",
      "stream": "users",
      "key": "id",
      "value": "name",
      "as": "author_name"
    }
  ]
}
```

### derive

Sets a value from rules: map values, match a prefix, or extract with a regular expression.

| Key | Required | What it does |
| - | - | - |
| `output_field` | Yes | The column to set. |
| `rules` | Yes | A list of at least one rule. Steerco tries them in order and takes the first rule that returns a value. |

Each rule takes these keys:

| Key | Required | What it does |
| - | - | - |
| `source` | Yes | The column to read. It can be a dotted path into a JSON column, such as `event_properties.path`. |
| `regex` | No | A regular expression. The rule returns the first capture group, or the whole match when the pattern has no group. |
| `prefix_match` | No | A `{ "prefix": "value" }` object. The first prefix in the list that the text starts with sets the value. |
| `value_map` | No | A `{ "from": "to" }` object, applied to the rule's result. A result that isn't in the map returns no value. |

A rule takes `regex` or `prefix_match`, not both. A rule with neither returns the source as text. When no rule returns a value, the column is empty.

This sets `segment` from the segment code. When the code is empty or unknown, it reads a tag in the description, then checks the company type:

```json theme={null}
{
  "name": "segment",
  "type": "derive",
  "stream": "accounts",
  "output_field": "segment",
  "rules": [
    {
      "source": "Segment__c",
      "value_map": { "ENT": "Enterprise", "MM": "Mid-market", "SMB": "Small business" }
    },
    { "source": "Description", "regex": "Segment: ([A-Za-z-]+)" },
    { "source": "Type", "prefix_match": { "Partner": "Partner", "Reseller": "Partner" } }
  ]
}
```

## Filters

Every filter takes these keys:

| Key | Required | What it does |
| - | - | - |
| `stream` | Yes | The table to filter. Use the object key or the table name, as for transforms. |
| `condition` | Yes | `include_values`, `exclude_values`, `not_empty`, or `exclude_all`. |
| `field` | For every condition but `exclude_all` | The column to test. It can be a source column, a Steerco field key, or a transform's output. |
| `values` | For `include_values` and `exclude_values` | A list of at least one string, number, or boolean. Steerco compares each one as text. |

| Condition | Keeps |
| - | - |
| `include_values` | Rows whose field is in `values`. Empty values are dropped. |
| `exclude_values` | Rows whose field isn't in `values`. Empty values are kept. |
| `not_empty` | Rows whose field isn't empty. Empty means missing, an empty string, or an empty list. |
| `exclude_all` | No rows. The table doesn't sync at all. |

If the field isn't on the table, the filter has no effect.

```json theme={null}
[
  { "stream": "accounts", "condition": "include_values", "field": "Type", "values": ["Customer", "Partner"] },
  { "stream": "tickets", "condition": "exclude_values", "field": "status", "values": ["deleted"] },
  { "stream": "Contact", "condition": "not_empty", "field": "Email" },
  { "stream": "Lead", "condition": "exclude_all" }
]
```

### Company scope

A filter whose `stream` is `accounts`, or the table mapped to it, such as `Account`, also decides which companies sync. The company filter on the Setup tab saves its choice on `accounts`. After the filters run, other objects keep only rows linked to a kept company:

* An object with a company column, `accountId` or `accountIds`, keeps rows linked to a kept company. A row linked to no company is dropped.
* An object with only an opportunity column, `opportunityId`, keeps rows whose opportunity belongs to a kept company. Opportunity Items work this way.
* Custom objects aren't scoped.

A filter on the table mapped to `accounts` sets the scope the same way as one on `accounts`. A filter on any other table limits only that table. The scope reads company rows before this run's transforms, so test a source column or a Steerco field key there.

When `exclude_all` is the only filter on `accounts`, no company syncs, and neither does anything linked to one.

## Processing order

For each table that syncs, Steerco runs these steps in order:

1. **Mapping.** Each Steerco field copies its source column. The source columns stay on the row, so later steps can read either name.
2. **Transforms.** The transforms whose `stream` names this table run in list order. Each one can read the columns an earlier one produced.
3. **Mapping again.** A Steerco field that reads a transform's output gets its value, if step 1 couldn't set it.
4. **Filters.** The filters whose `stream` names this table run in list order.
5. **Company scope.** If filters on `accounts` narrowed the companies, rows linked to other companies drop out.

Before these steps, Steerco prepares each table that a transform looks up. After them, Steerco loads the rows into your records, or into staging when staging is on.

<Note>
  Step 3 only sets Steerco fields that step 1 couldn't. If a transform changes a column that step 1 already mapped, the Steerco field keeps the value from before the transform. To map a transform's result, write it to a new column and map that column.
</Note>
