Upsert Records

airtable.upsert_records
ActionFiles & storageconnection required

Calls the Airtable API with the connection's credentials.

Update Airtable records that match on chosen columns and create the ones that do not, using Airtable's performUpsert.

Connection

Required
Yes — the node will not save without one
Auth type
Bearer token
Providers
Airtable
Secret shape
the Airtable access token
Resolved by
config.connection_id, else node.connection_id

Configuration

6 fields, 4 required. A dotted path is a nested object and name[] an array of objects — the editor renders the same tree.

FieldTypeRequiredDefaultConstraintsExample
base_id
Airtable base id (app…), from the base's API docs URL or airtable.list_bases.
stringYes——appAbCdEfGhIjKlMn
table
Table id (tbl…) or table name. An id survives a rename; a name does not.
stringYes——Tasks
records
The records to write, as a JSON array of {"fields": {column: value}} objects. An entry may carry an 'id' to update that row directly.
arrayYes—array of object[{"fields":{"Email":"ada@example.com","Status":"Open"}}]
fields_to_merge_on
The 1 to 3 columns Airtable matches an incoming record against to decide update-or-create, as a JSON array of field names or ids.
arrayYes—array of string["Email"]
typecast
Let Airtable convert strings into select options, dates and numbers. Off means a value must already match the column's type exactly.
booleanNotrue——
replace
Send Airtable's destructive form (PUT): a matched row has every column the payload does not mention cleared.
booleanNofalse——

Example configuration

Every value below is the contract’s own example or default — nothing invented.

{
  "base_id": "appAbCdEfGhIjKlMn",
  "fields_to_merge_on": [
    "Email"
  ],
  "records": [
    {
      "fields": {
        "Email": "ada@example.com",
        "Status": "Open"
      }
    }
  ],
  "replace": false,
  "table": "Tasks",
  "typecast": true
}

Output

One shape.

result

FieldTypeExample
records[].id
The Airtable record id (rec…).
string—
records[].fields
The record's cell values, keyed by field name.
object—
records[].createdTime
When the record was created (ISO 8601).
string—
count
How many records this call returned.
number—
created_record_ids
Ids of the records Airtable created.
array—
updated_record_ids
Ids of the records Airtable updated.
array—
created_count
How many records were created.
number—
updated_count
How many records were updated.
number—
batches
How many 10-record requests were sent to Airtable.
number—
{
  "batches": 0,
  "count": 0,
  "created_count": 0,
  "created_record_ids": [],
  "records": [
    {
      "createdTime": "",
      "fields": {},
      "id": ""
    }
  ],
  "updated_count": 0,
  "updated_record_ids": []
}

Notes

  • Airtable matches on at most 3 fields, and a record matching more than one existing row fails that request rather than guessing.
  • Airtable caps every batch write at 10 records per request. The batch nodes chunk for you and report how many calls they made in 'batches'; the calls are sequential and not a transaction, so a failure part-way leaves the earlier chunks committed and says how many got through.
  • typecast is what lets a plain string be written into a single-select, date or number column. It is on by default, matching airtable.create_record.
  • Reads page on an opaque 'offset' token, not a page number: feed the offset a call returns back into the next call while 'has_more' is true.
  • The base/table/field nodes use Airtable's metadata API, which needs the 'schema.bases:read' (or ':write') scope on the personal access token. A token minted only for 'data.records:*' gets a 403 from them.

See Upsert Records run in a workflow.

In a technical demo we add the Airtable connection, drop the node on the canvas and let Workflow Health pre-flight it before the first real run.