# sync_record

> Source: https://docs.clonepartner.com/reference/steps/sync-record/

<!-- sources: src/steps/sync-record-step.ts, src/steps/hash-utils.ts, src/connectors/sqlite.ts, src/steps/types.ts, references/envoy/step-types.md, references/envoy/patterns.md -->

# sync_record

Performs the full idempotent write cycle for one record: looks up an existing mapping, compares content hashes, creates or updates the destination record, and persists the mapping — in a single step.

## When to use it

Use `sync_record` for migration and sync writes to a destination system. It replaces the [get_destination_id](/reference/steps/get-destination-id/) → create/update → [set_destination_id](/reference/steps/set-destination-id/) triad with one step, so prefer it in new tasks over wiring those three yourself.

## Configuration

| Key | Type | Required | Default | Description |
|---|---|---|---|---|
| `mapping.connector` | string | Yes | — | Connector slot used as the mapping store. Must support `getMapping`/`setMapping` (SQL Connectors: `sqlite`, `postgres`, `mysql`, `mssql`). |
| `mapping.resource` | string | Yes | — | Resource name the mapping is stored under. |
| `mapping.source_id` | string | Yes | — | The source record's ID. Coerced to a string. Missing values fail with `mapping.source_id is required`. |
| `hash.fields` | string[] | No | — | Fields included in the content hash. Omitted, every field not prefixed with `_` is hashed. |
| `hash.algorithm` | string | No | `sha256` | `sha256` or `md5`. |
| `hash.from` | string | No | — | Hash a nested object field of the input instead of the whole record. |
| `destination.connector` | string | Yes | — | Connector slot for the destination system. |
| `destination.create` | object | Yes | — | Create call: `{ method, resource?, args?, id? }`. `method` is called on the destination Connector; `resource` is injected into the args; `id` names the response field holding the new record's ID. |
| `destination.update` | object | No | — | Update call, same shape as `create`. When omitted, mapped records are never re-touched (create-once). |
| `writeback.connector` | string | With `writeback` | — | Connector for an optional source-row update after a successful sync. |
| `writeback.table` | string | No | — | SQL table to update (uses the Connector's `update` method). |
| `writeback.collection` | string | No | — | MongoDB collection to update (uses `update_one` with `$set`). |
| `writeback.key_column` | string | No | `id` | Column/field used to match the source row. |
| `writeback.key` | any | No | — | Key value. Omitted, the input record's `key_column` field is used. |
| `writeback.fields` | object | No | — | Fields to write back. `destination_id` and `hash` are added automatically when not set. |

## Example

```yaml
name: sync_contacts
connectors:
  source:
    type: sqlite
  destination:
    type: freshdesk
  mapping_db:
    type: sqlite
steps:
  - name: read_contacts
    type: call_method
    config:
      connector: source
      method: select
      args:
        sql: SELECT * FROM contacts
  - name: sync_contact
    type: sync_record
    config: |
      {
        "mapping": {
          "connector": "mapping_db",
          "resource": "contact",
          "source_id": input.source_id
        },
        "hash": { "fields": ["name", "email"] },
        "destination": {
          "connector": "destination",
          "create": {
            "method": "create",
            "resource": "api/v2/contacts",
            "args": { "data": { "name": input.name, "email": input.email } }
          },
          "update": {
            "method": "update",
            "resource": "api/v2/contacts",
            "args": { "data": { "name": input.name, "email": input.email } }
          }
        }
      }
```

## Behavior notes

The step runs this flow per item:

1. **Mapping lookup.** Calls `getMapping(resource, source_id)` on the mapping Connector. A record counts as already mapped only when the stored `destination_id` is non-empty (`null`, `''`, `'null'`, and `'undefined'` all count as empty).
2. **Hash comparison.** When `hash` is configured, a content hash of the record (or of the `hash.from` object) is computed. An already-mapped record is re-touched only when an `update` call **and** a hash are configured **and** the stored hash differs from the computed one. Otherwise the item passes through untouched — this covers create-once (no `update`), no-hash, and unchanged-content cases.
3. **Create or update.** Unmapped records trigger `destination.create`; mapped-and-changed records trigger `destination.update` with the existing destination ID injected as `args.id` (unless the call config sets its own `id`). For creates, the new destination ID is read from the response field named by `create.id`, falling back to `id`, `Id`, `contactid`, then `accountid`.
4. **Mapping upsert.** Calls `setMapping(resource, source_id, destination_id, hash)`. An empty destination ID fails the item with `refusing to write empty destination_id` rather than storing a broken mapping.
5. **Writeback (optional).** Updates the source row/document with `destination_id` and `hash` plus any `writeback.fields`.

The step returns the input item unchanged.

### Mapping records

SQL mapping Connectors store mappings in a `resource_mapping` table the Connector creates on init:

| Column | Meaning |
|---|---|
| `resource` | Resource name from `mapping.resource`. |
| `source_id` | Source record ID (string). Unique together with `resource`. |
| `destination_id` | ID of the record created in the destination. |
| `hash` | Content hash stored at the last successful sync; drives change detection. |
| `created_at` / `updated_at` | Timestamps maintained by the Connector. |

Keep `hash.fields` identical across every task that syncs the same resource — a different field selection produces a different hash and forces spurious updates.

## Related

- [get_destination_id](/reference/steps/get-destination-id/) and [set_destination_id](/reference/steps/set-destination-id/) — the manual steps this one replaces
- [compute_hash](/reference/steps/compute-hash/) — standalone hashing with the same field semantics
- [Idempotent sync and the mapping DB](/building/idempotent-sync/) — the full idempotency design guide
