n8n Airtable Node: Advanced Patterns for CRM Sync, Approval Flows, and Multi-Table Joins (Free Workflow JSON)
Prerequisites
- n8n (self-hosted or cloud)
- An Airtable account with at least one base
- An Airtable Personal Access Token (PAT) - create one at
airtable.com/create/tokens
Scopes you need on the PAT: data.records:read, data.records:write, schema.bases:read
In n8n: Credentials โ New โ Airtable Token API โ paste your PAT.
Operations at a Glance
| Operation | What It Does |
|---|---|
| Create Record | Add a new row to a table |
| Get Record | Fetch one record by its Airtable record ID |
| Get Many Records | List records with optional filtering, sorting, and field selection |
| Update Record | Overwrite specified fields on an existing record |
| Upsert Record | Create or update based on a field match |
| Delete Record | Remove a record by ID |
| Search | Find records matching a formula filter |
Key Configuration Concepts
Finding Your Base ID and Table Name
Your Base ID is in the Airtable URL: https://airtable.com/appXXXXXXXXXXXX/... - the appXXXXXXXXXXXX part. Your Table Name is the tab label in the interface.
In n8n v4+, the Airtable node has a dropdown to select Base and Table. Use it - it validates your credential scope and saves you from typo bugs.
Filter Formulas
Airtable uses its own formula syntax for filtering, identical to what you'd use in Airtable views. Examples:
{Status} = "Active"- string matchAND({Status} = "Active", {MRR} > 100)- compound filterIS_AFTER({Created}, "2026-06-01")- date comparisonSEARCH("n8n", {Tags})- substring search
Pass these as the Filter by Formula field in the Get Many Records operation.
3 Production Workflow Patterns
Pattern 1: Webhook-to-Airtable CRM Sync
Use case: A prospect fills out a Typeform, Tally, or your own form. You want them in an Airtable CRM base - but only once (no duplicates), and you want to route new vs. returning leads differently.
Workflow:
- Webhook Trigger - receives the form submission (name, email, company, message).
- Airtable โ Search Records - filter formula:
{Email} = "{{ $json.email }}". Returns matching records if this lead already exists. - IF node - checks
{{ $json.length > 0 }}(lead exists) vs. empty result (new lead).- True branch (existing lead): Airtable โ Update Record - set Last Contact to today, append to a Notes field with the new message.
- False branch (new lead): Airtable โ Create Record - write all fields; then send a Slack notification or welcome email via Resend.
- Merge node - rejoin both branches for any shared post-processing (e.g., log to a Sheet, notify the sales owner).
Key config on Search:
- Operation: Search
- Filter Formula:
{Email} = "{{ $json.email }}" - Fields to Retrieve:
id,Email,Name,Last Contact,Notes
Pattern 2: Multi-Table Record Join
Use case: You have an Orders table and a Customers table in Airtable. An order comes in via webhook; you need to enrich it with customer data from the Customers table before writing it to a reporting sheet. Airtable's linked record fields give you related record IDs, not values - you have to fetch those values yourself in n8n.
Workflow:
- Webhook Trigger - receives order data (order ID, customer record ID, amount, SKU).
- Airtable โ Get Record (Customers table) - fetch the customer record using the linked record ID from step 1:
{{ $json.customer_record_id }}. - Merge node (Combine mode: Keep All) - merges the order item with the customer record fields. Now you have
{ order_id, amount, sku, customer_name, customer_email, customer_tier }in one item. - Set node - normalize and rename fields as needed.
- Google Sheets โ Append Row - write the enriched row to your reporting sheet.
- Airtable โ Update Record (Orders table) - mark the order as
Synced: trueso re-runs skip it.
Why this matters: Airtable's native automations can't easily pull linked record fields across tables and push them elsewhere. n8n fills this gap cleanly.
Pattern 3: Airtable Record Approval Flow
Use case: A team member creates a record in Airtable (e.g., a vendor invoice, a content piece, a PTO request). A manager needs to approve it before a downstream action fires (pay the invoice, publish the content, update the HR system).
Workflow:
- Airtable Trigger - listen for records where Status changes to
Pending Approval. (Use the Airtable Trigger node - it polls every minute by default.) - Slack node (or Gmail) - send the manager a message: "New approval request: [Title]. Approve:
{{ $env.N8N_WEBHOOK_URL }}/approve?id={{ $json.id }}| Reject:{{ $env.N8N_WEBHOOK_URL }}/reject?id={{ $json.id }}" - Wait node - pause execution waiting for the webhook callback (set a timeout, e.g., 48h).
- Webhook node (resume trigger) - the manager clicks Approve or Reject, which hits the webhook.
- Switch node - routes on
{{ $json.query.action }}:approveorreject.- Approve branch: Airtable โ Update Record - set Status = "Approved". Fire the downstream action (e.g., send the invoice to accounting via email).
- Reject branch: Airtable โ Update Record - set Status = "Rejected", write the rejection reason. Notify the submitter.
Gotcha on Wait node: n8n's Wait node supports resuming via webhook. In self-hosted n8n, your instance must be publicly accessible for the manager to click the resume URL. Use a tunnel (ngrok/Cloudflare Tunnel) in development.
6 Common Gotchas
Field names are case-sensitive and space-sensitive.
{First Name}is not the same as{firstname}. If your filter returns no results, double-check the exact field name in Airtable - include spaces and capitalization exactly.Linked record fields return arrays of record IDs, not values. If a field is a linked record type (e.g., Customer linked to a Customers table), the value you get is
["recXXXXXXXXXXXXXX"]- a record ID, not a name. Fetch the linked record separately with a Get Record operation to get its fields.Rate limits: 5 requests/second per base. Airtable enforces 5 req/s per base. If you're looping over hundreds of records, add a Wait node (200ms) between iterations, or batch your reads with Get Many Records + an Item Lists node to avoid 429 errors.
Airtable Trigger polls, not webhooks. The n8n Airtable Trigger uses polling (every 1โ60 minutes configurable), not true push webhooks. If you need near-real-time triggers, use Airtable's native automations to call an n8n Webhook Trigger instead - far faster than polling.
Record IDs are unstable across base copies. If you duplicate an Airtable base, record IDs change. Don't use record IDs as business keys in external systems - use a dedicated unique field (e.g., a UUID you generate on creation, or a formula field).
Empty select/multiselect fields return null, not an empty array. Check for null before trying to access select values:
{{ $json.Tags ? $json.Tags.join(", ") : "" }}. Failing to null-check crashes the expression.
Free Workflow JSON
This implements Pattern 1 (Webhook โ Airtable CRM sync with dedup). Import via n8n โ Import from JSON.
{
"name": "Webhook โ Airtable CRM Sync (Dedup)",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "crm-intake",
"responseMode": "responseNode"
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [240, 300]
},
{
"parameters": {
"operation": "search",
"base": { "value": "YOUR_BASE_ID" },
"table": { "value": "Leads" },
"filterByFormula": "={Email} = \" {{ $json.body.email }} \" "
},
"name": "Search Existing Lead",
"type": "n8n-nodes-base.airtable",
"typeVersion": 2,
"position": [460, 300]
},
{
"parameters": {
"conditions": {
"number": [{
"value1": "={{ $json.length }}",
"operation": "larger",
"value2": 0
}]
}
},
"name": "Lead Exists?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [680, 300]
},
{
"parameters": {
"operation": "update",
"base": { "value": "YOUR_BASE_ID" },
"table": { "value": "Leads" },
"id": "={{ $json.id }}",
"columns": {
"mappingMode": "defineBelow",
"value": {
"Last Contact": "={{ $now.toISO() }}"
}
}
},
"name": "Update Last Contact",
"type": "n8n-nodes-base.airtable",
"typeVersion": 2,
"position": [900, 200]
},
{
"parameters": {
"operation": "create",
"base": { "value": "YOUR_BASE_ID" },
"table": { "value": "Leads" },
"columns": { "mappingMode": "autoMapInputData" }
},
"name": "Create New Lead",
"type": "n8n-nodes-base.airtable",
"typeVersion": 2,
"position": [900, 400]
}
],
"connections": {
"Webhook": {
"main": [[{ "node": "Search Existing Lead", "type": "main", "index": 0 }]]
},
"Search Existing Lead": {
"main": [[{ "node": "Lead Exists?", "type": "main", "index": 0 }]]
},
"Lead Exists?": {
"main": [
[{ "node": "Update Last Contact", "type": "main", "index": 0 }],
[{ "node": "Create New Lead", "type": "main", "index": 0 }]
]
}
}
}
Replace YOUR_BASE_ID with your Airtable base ID and configure your Airtable Token credential.
Wrapping Up
The n8n Airtable node handles the full record lifecycle - but the patterns that deliver the most value are the ones that bridge Airtable to other systems: enriching records from linked tables, deduplicating inbound webhook data, and building approval flows that Airtable's native automations can't express.
The gotcha to internalize: linked record fields give you IDs, not values - always fetch the linked record when you need its data.
Want the full n8n Workflow Starter Pack? 25+ production-ready workflow templates covering lead capture, Stripe automation, AI pipelines, and more - $29 on Gumroad.
Need a custom n8n workflow built for your business? Done-for-you service - $99. Describe your automation, get a working workflow delivered.
Comments
No comments yet. Start the discussion.