Airtable to Webflow Sync Without Duplicates: The n8n Upsert Pattern
Image of author
Pavel Vainshtein
Founder @ WebflowForge | Driving Growth with Web Development & AI Automations
With over 9+ years of experience building scalable web platforms and digital products. I specialize in Webflow, WordPress, automations, AI solutions, and RevOps—combining UX, development, and business logic to create high-performing, conversion-focused systems. I help with UI/UX, advanced integrations, CMS/database architecture, and full platform builds. From idea to execution, I turn concepts into production-ready, lead-generating machines built for growth, performance, and scale.
Webflow
Automation
CMS
CRM

Airtable to Webflow Sync Without Duplicates: The n8n Upsert Pattern

Published Date: July 21, 2026

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)

StrategyMatch keyDuplicates?Verdict
Trigger → “Create item” (Zapier/Make default)None❌ Every update & retry duplicatesNever use for two-way data
Match by name or slugName string⚠️ Breaks on rename; slugs collideFragile — names change, IDs don't
Upsert by external ID (this guide)Airtable Record ID stored in a Webflow field✅ Idempotent — re-runs are safeThe 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?

Tell us about your stack and we’ll break down how this playbook would work for you.
See How

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