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

# persistToDatabase

> Write workflow results to your app's database

Write data from a workflow output table to your app's database:

```typescript theme={null}
{
  persistToDatabase: {
    appId: "{{var.appId}}",
    tableName: "Product",
    sourceTable: "products",
    mode: "upsert",
    upsertKey: ["id"],
    fieldMapping: {
      id: "id",
      title: "title",
      vendor: "vendor",
      category: "category",
      createdAt: "createdAt",
      updatedAt: "updatedAt",
    },
  },
}
```

## Configuration

| Field          | Required    | Description                                                                                                                                           |
| -------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `appId`        | Yes         | App identifier (typically `"{{var.appId}}"`)                                                                                                          |
| `tableName`    | Yes         | Target table in app database                                                                                                                          |
| `sourceTable`  | Yes         | Workflow table from a previous step                                                                                                                   |
| `mode`         | Yes         | Write strategy: `replaceAll`, `append`, or `upsert`                                                                                                   |
| `fieldMapping` | Yes         | Maps target database columns to source table columns. Supports [generated values](/docs/platform-docs/workflows-framework/field-mapping#generated-values). |
| `upsertKey`    | Conditional | Required when mode is `upsert` — columns that form the unique constraint                                                                              |
| `replaceWhere` | Conditional | Required when mode is `replaceAll` — scopes the delete operation                                                                                      |
| `onRowError`   | No          | `"fail"` (default) or `"skip"`                                                                                                                        |

## Write modes

### upsert

Insert new rows, update existing ones based on `upsertKey`:

```typescript theme={null}
{
  mode: "upsert",
  upsertKey: ["id"],
}
```

The `upsertKey` must match a unique constraint on the target table.

### replaceAll

Atomically delete matching rows, then insert all source rows:

```typescript theme={null}
{
  mode: "replaceAll",
  replaceWhere: {
    matchColumns: ["orgId"],
  },
}
```

`replaceWhere` scopes the `DELETE` to rows matching values from the source data.

### append

Insert all rows without checking for duplicates:

```typescript theme={null}
{
  mode: "append",
}
```

## Validation

| Check                                                                               |
| ----------------------------------------------------------------------------------- |
| `appId`, `tableName`, `sourceTable`, `mode`, `fieldMapping` are required            |
| `sourceTable` exists in previous steps                                              |
| `fieldMapping` keys exist in target schema                                          |
| `upsertKey` is required when mode is `upsert`                                       |
| `replaceWhere.matchColumns` exist in `fieldMapping`                                 |
| Target table exists in app database                                                 |
| All required target columns are mapped                                              |
| `upsertKey` has a corresponding unique constraint on the target table               |
| Source and target column types are compatible                                       |
| For user-triggered jobs, `replaceAll` mode requires `replaceWhere` to scope deletes |
