Airtable to Webflow Sync Without Duplicates: The n8n Upsert Pattern
Airtable–Webflow sync creates duplicates for one reason: the sync has no stable key to match records against existing CMS items. Most no-code recipes trigger on “new or updated record” and blindly call “create item” — so every edit, retry, or re-run mints another copy. Duplicate prevention is an upsert problem: every Airtable record carries its own Record ID; store it on the Webflow item as an external-id field and match on it before every write.
This guide builds the full duplicate-proof pipeline in n8n against the Webflow Data API v2.
Why the naive sync duplicates (and the three strategies)
| Strategy | Match key | Duplicates? | Verdict |
|---|---|---|---|
| Trigger → “Create item” (Zapier/Make default) | None | ❌ Every update & retry duplicates | Never use for two-way data |
| Match by name or slug | Name string | ⚠️ Breaks on rename; slugs collide | Fragile — names change, IDs don't |
| Upsert by external ID (this guide) | Airtable Record ID stored in a Webflow field | ✅ Idempotent — re-runs are safe | The correct pattern |
Setup: one field changes everything
Add a plain-text field external-id to your Webflow collection and drop the Airtable Record ID (record.id, e.g. recXXXXXXXXXXXXXX) into it on every create. That single field is the difference between a sync and a duplicate factory. The acceptance test: run the sync twice — the second run must create zero items.
The n8n build: fetch, diff, upsert
Node 1 — Schedule Trigger
Nightly (or every 15 min for fast-moving catalogs). Scheduled full-diff beats per-record triggers: it self-heals missed webhooks and handles deletes.
Node 2 — Airtable: List records
Use the native Airtable node (or GET https://api.airtable.com/v0/{baseId}/{table}), paginated. Each record arrives with its immutable id.
Node 3 — Webflow: list existing items
GET https://api.webflow.com/v2/collections/{collectionId}/items?limit=100
Authorization: Bearer {{WEBFLOW_TOKEN}}
Paginate with offset past 100 items.
Node 4 — Code: diff into creates / updates / archives
// n8n Code node — upsert diff by external-id
const at = $('Airtable').all().map(i => i.json);
const wf = $('Webflow').all().map(i => i.json);
const wfByExt = {};
for (const item of wf) {
const ext = item.fieldData['external-id'];
if (ext) wfByExt[ext] = item;
}
const creates = [],
updates = [],
seen = new Set();
for (const r of at) {
seen.add(r.id);
const fields = {
name: r.fields.Name,
'external-id': r.id,
description: r.fields.Description || '',
status: r.fields.Status || 'active'
};
const existing = wfByExt[r.id];
if (!existing) {
creates.push({ fieldData: { ...fields, slug: slugify(r.fields.Name) + '-' + r.id.slice(-4).toLowerCase() } });
}
else if (changed(existing.fieldData, fields)) {
updates.push({ id: existing.id, fieldData: fields }); // never resend slug on update }
}
// anything in Webflow but gone from Airtable -> archive, don't deleteconst archives = wf.filter(i => i.fieldData['external-id'] && !seen.has(i.fieldData['external-id']))
.map(i => ({ id: i.id }));
function slugify(s){
return (s||'').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'');
}
function changed(a,b){
return ['name','description','status'].some(k => (a[k]||'') !== (b[k]||'')); }return [{ json: { creates, updates, archives }
}];
Two duplicate-prevention details hiding in that code: the slug gets the last 4 characters of the Record ID appended, so “Acme Inc” and “Acme, Inc.” can never collide into a 409 Conflict; and updates never resend the slug, so item URLs stay stable.
Node 5/6 — Write to Webflow (bulk)
POST /v2/collections/{collectionId}/items/live ← creates (accepts items array)
PATCH /v2/collections/{collectionId}/items/live ← updates (up to 100/call)
Add a Wait node between pages — the Data API allows ~60 req/min. Batch writes make one nightly run cost a handful of requests even for large tables.
Node 7 — Archive, never delete
For records that vanish from Airtable, PATCH the item with isArchived: true. Hard deletes break inbound links and lose history; archiving keeps the CMS clean and reversible.
Want to apply this to your setup?
Deletes, two-way sync, and the questions everyone asks
Gotchas that reintroduce duplicates
- Retries without idempotency. If a create succeeds but the response times out, a naive retry duplicates. Because the diff re-reads Webflow state on every run, the next cycle self-corrects — another reason scheduled-diff beats event triggers.
- Two sources editing the same field. One direction must own each field. Airtable owns synced fields; marketing-only fields in Webflow stay out of the payload so the sync can't overwrite them.
- Airtable base duplicated/restored. Record IDs change when a base is copied — your external IDs orphan. Re-map once with a matching script before the next run.
- Two-way sync. Genuinely hard (conflict resolution, loop suppression). If you only need Webflow edits reflected back for a few fields, use a Webflow → n8n webhook writing to different Airtable fields than the ones you sync down.
Q: How do I prevent duplicates when syncing Airtable to Webflow?
A: Store the Airtable Record ID in an external-id field on each Webflow item and upsert against it: match first, create only when no match exists, update otherwise. Run the sync twice as your test — the second run should create zero items.
Q: Can Zapier or Make do Airtable–Webflow duplicate prevention?
A: Yes, with the same pattern: add a search step (find Webflow item by external ID) before the create step, and branch create-vs-update on the result. The default “new record → create item” templates skip that search — which is exactly why they duplicate.
Q: Should the sync delete Webflow items removed from Airtable?
A: Archive instead of delete. Archiving unpublishes the item while preserving its ID and edit history, so a mistaken removal in Airtable doesn't destroy a URL that's ranking or linked.
Want this sync built for you?
Webflowforge builds duplicate-proof Airtable, Notion, and CRM syncs as part of our Webflow automation services — and the same upsert discipline powers our Webflow HubSpot integration. Tell us about your project and we'll scope it with you.
Related guides: Auto-Sync SaaS Product Data + CRM into Webflow · Automate Lead Capture from Your Website
.avif)


