Prevent Duplicate Records When a Make Scenario Reruns: 3 Deduplication Patterns

Introduction

You built a Make scenario that watches form submissions and appends each one to a spreadsheet. It works perfectly in testing. Then one scenario run fails on a downstream module, you rerun the incomplete execution, and suddenly every row exists twice. This tutorial is for Make users whose scenarios create records in Google Sheets, Airtable, or a database. You will learn three deduplication patterns that survive reruns, retries, and near-simultaneous submissions: search-then-filter, a data store idempotency ledger, and destination-level upserts.

Quick answer

Before the create module, add a dedup checkpoint: search the destination for the unique key and stop on a match, or write the key to a Make data store and skip repeats, or use a destination upsert.

Evidence

Duplicate creation on retry is the most-asked dedup question in the Make community. On October 9, 2026, a user posted exactly this scenario: a form-processing scenario that must “store the results in a spreadsheet or database without creating duplicate records,” unsure how to handle “cases where multiple submissions arrive close together or the scenario runs again after an error.” The accepted direction, from community member Omnia3, was direct: “in general, my approach would be to use a Search for this, without a data store, since you already keep the submission in a spreadsheet.” That answer is still the best default for simple sheets, and the two stronger patterns below cover the cases where it falls short.

The operation budget is the reason dedup design matters in Make. Make’s pricing page states that the free plan includes 1,000 operations per month (as of October 2026), and every module execution consumes operations, so a naive “search for every row” design gets expensive fast. Community member Donald_Mitchell measured the difference on a Google Sheets sync: by reading the whole sheet once, aggregating keys, and pushing new rows in one bulk add, the scenario used “maybe around 7 Ops” whether it added 1 row or 20, instead of one search operation per item. That single measurement settles the cost argument: dedup is not just correctness, it is operation budget.

Make’s own documentation backs the building blocks. The official Data stores help page describes data stores as Make-native key-value storage you can read and write from inside a scenario, and the official Filters help page describes how filters evaluate per bundle and decide which route continues. All three patterns in this tutorial are assembled only from these documented modules.

Prerequisites

  • A Make account (free tier works for testing) with at least one active scenario.
  • A destination that creates records: a Google Sheets spreadsheet or an Airtable base.
  • A field you can treat as unique: an email address, an order ID, or a submission ID from the form. Dedup is impossible without one; if your data has no natural key, create one (for example, sha256(email) as a computed key).
  • Basic comfort with Make: modules, filters, and the scenario editor.

Pattern 1: Search-then-filter (the default)

This is the simplest pattern and the one the community recommends first. Before the module that creates the record, add a search module on the same destination, then put a filter between them that only lets the bundle through when nothing was found.

Step 1: Decide the unique key

Pick the field that identifies a record: the submitter’s email for a signup form, an order ID for orders, or the form tool’s submission ID. Every later step keys off this value.

Step 2: Add a search module before the create module

Insert a search module that looks up the key in the destination:

  • Google Sheets: Search Rows with a filter on the key column, for example email = {{email}}.
  • Airtable: Search Records on the key field.

Place it directly before the create module in the same route.

Step 3: Add a filter that passes only on zero matches

Between the search module and the create module, add a filter with one condition: the search returned nothing. The search module’s output includes the total number of bundles found. The condition pattern used across community solutions is:

Total number of bundles | Numeric operators: Equal to | 0

Two details matter here. First, use a numeric comparison, not a text comparison. Make is type-sensitive: the total number of bundles is an integer, and comparing it as text silently misbehaves. Community troubleshooting threads repeatedly trace “the filter never passes” back to a text operator on a numeric value. Second, keep the key normalized: trim whitespace and lowercase emails before comparing, or John@Example.com and john@example.com become two records.

Step 4: Test both branches

Run the scenario twice with the same submission. The first run should create the row. The second run should stop at the filter with the note that the filter condition was not met. If the second run still creates a row, recheck the key normalization and the operator type before anything else.

When it falls short

Search-then-filter has a race window: two executions that both search before either writes will both see zero matches and both write. It also costs one search operation per run. For low-volume forms both are fine; for high-concurrency or operation-sensitive scenarios, continue to Pattern 2.

Pattern 2: Data store idempotency ledger

A data store is Make’s built-in key-value storage, documented in the official Data stores help page. Instead of searching the destination, you keep a small ledger of keys you have already processed. The check is fast, Make-native, and independent of the destination app.

Step 1: Create the data store

Create a data store with a single-column structure keyed by your unique key, for example submission_id. Name it something unambiguous like processed_form_submissions.

Step 2: Check the ledger right after the trigger

Immediately after the trigger module, add the Data Stores Check the existence of a record module with the key set to your unique value. This module tells you whether the key is already in the ledger.

Step 3: Filter out known keys

Add a filter right after the check: continue only when the record does not exist. Everything downstream, the create module included, runs only for unseen keys.

Step 4: Write the key after successful creation

At the end of the successful route, add the Data Stores Add/replace a record module and store the key. Place it after the create module, not before. The ordering is the whole point: the ledger entry is the commit record. If the create module fails, the key is never written, the incomplete execution stays incomplete, and the rerun will process it exactly once.

The pending-key variant for crashes between write and create

Pattern 2 as written has a subtle gap: if the ledger write succeeds but the scenario is stopped before the record is created, the rerun will skip a record that was never created. For scenarios where a missed record is worse than a duplicate, write a pending value first, then overwrite it with done after creation, and treat pending as “process me” on rerun. For most forms, the simple version is enough; use the two-phase variant when downstream systems cannot tolerate a gap.

Pattern 3: Destination upsert (the strongest)

The only truly race-proof dedup lives at the destination, because only the destination can serialize the check and the write. If your destination supports an upsert (update-or-insert) module, prefer it.

Airtable: Upsert a record

Airtable’s Upsert a record module in Make matches on a field you designate as unique. If a record with that value exists, it updates it; if not, it creates it. Two runs with the same key converge to one record, no filter needed. This is the pattern community experts recommend when the destination is Airtable.

Google Sheets: search once, bulk write

Google Sheets has no native upsert module, but you can get the bulk pattern that Donald_Mitchell measured:

  1. Search Rows once to read the sheet (no per-item search).
  2. Aggregate the existing keys into an array with an Array Aggregator.
  3. For each new item, check membership in the array and collect only the missing rows.
  4. Push them with Bulk Add Rows in one call.

The official Google Sheets help page documents these modules, and the measured result was around 7 operations for up to 20 new rows. This is both the cheapest and the most robust Sheets pattern.

Choosing between the three patterns

SituationPick
Simple form to sheet, low volumePattern 1 (search-then-filter)
High-frequency runs, operation budget mattersPattern 2 (data store ledger) or Pattern 3 bulk for Sheets
Airtable destinationPattern 3 (Upsert a record)
Near-simultaneous submissions possiblePattern 3, or Pattern 2 with serialized execution

The official Filters help page is worth a read before you build Pattern 1 or 2, because every checkpoint in this tutorial is a filter, and filter semantics (per-bundle evaluation, type-sensitive operators) are where most dedup setups break.

Troubleshooting

1. The filter never lets new records through

Cause: a text operator on a numeric value, or a key that never matches because of whitespace or case differences. Fix: switch the condition to a numeric “Equal to 0” on the total number of bundles, and normalize the key on both sides (lowercase, trim). Re-run with the same test submission and watch the filter’s evaluation detail.

2. Duplicates appear when two submissions arrive at the same time

Cause: the check-then-act race window. Both executions search before either writes, both see no match, both create. Fix: move to Pattern 3 (destination upsert), which serializes the decision at the destination. If you must stay on Pattern 1 or 2, enable sequential processing in the scenario settings so runs execute one at a time instead of concurrently.

3. Rerunning an incomplete execution duplicates rows created before the failure

Cause: a rerun re-executes modules that already succeeded. If the create module ran before the failure point, the row is created again. Fix: make the write idempotent with any of the three patterns, so the rerun converges instead of duplicating. For the error-handling side of the same problem, see our guide to Make error handlers: skip, retry, resume, rollback, and commit, which explains how each directive interacts with reruns.

4. The data store check says “not exists” for a key you already processed

Cause: the key written and the key checked are not byte-identical, usually trailing spaces, different casing, or a formatted vs raw value (for example, +1 555... vs 1555...). Fix: build the key once in a Set Variable module at the top of the scenario and reference that variable everywhere, so the write path and the check path can never diverge.

FAQ

The four questions above cover the most common follow-ups. Two more worth knowing: a data store ledger complements rather than replaces the destination as the system of record, and a webhook trigger plus one dedup pattern beats a polling trigger with epoch fiddling for form workloads, because pushes do not re-deliver old items the way a mis-set epoch panel can.

Conclusion

Dedup in Make is a one-time design decision, not a per-run hope: search-then-filter for simple forms, a data store ledger when operations or frequency matter, and a destination upsert when correctness is non-negotiable. Build the checkpoint before you need it, because the first rerun after a failure is when duplicates strike.

Next, harden the other half of the problem: read our Make error handlers guide to choose the right directive when a module fails, and the n8n idempotency-key tutorial if you run the same automation patterns in n8n. For the bigger picture of how the two platforms compare, see our n8n vs Make for AI agents comparison.

Frequently asked questions

Does Make have a built-in deduplicate setting for scenarios?

No. Make runs every module once per bundle and has no scenario-level deduplication toggle. You add dedup yourself: a search module plus a filter, a data store idempotency ledger, or an upsert at the destination. Pick one of these three patterns and make it the default in any scenario that creates records.

Which deduplication pattern costs the fewest Make operations?

Search-then-filter adds exactly one operation per run for the search module. The data store pattern also costs operations for the check and the write, but it avoids a full destination search. One community benchmark found that reading the whole sheet once and pushing new rows in a single bulk add kept a 20-row sync to around 7 operations, instead of one search per row.

Can a Make data store replace my spreadsheet or database?

No. Use the data store as an idempotency ledger (a small set of keys you have already processed), not as the system of record. The authoritative data stays in your spreadsheet or database, and the data store answers one question: have I seen this key before?

Why do rerun scenarios create duplicates even with error handlers?

Error handlers control what happens when a module fails, but a rerun still re-executes modules that already succeeded. If the create module ran before the failure point, the rerun creates the row a second time. Pair error handling (skip, retry, resume, commit) with an idempotent write, so a repeated execution converges to the same final state.

Should I use a polling trigger or a webhook to avoid reprocessing?

Webhooks avoid reprocessing because the source pushes each event once. Polling triggers re-fetch items on a schedule, and Make's epoch panel (right-click the trigger, choose where to start) decides which items count as new. If a polling scenario re-runs with the wrong epoch, old items come back. For forms, an instant webhook trigger plus one dedup pattern is the most robust combination.

TP
ToolPilot Team

We install, break, and fix automation tools so you don't have to. Every tutorial on ToolPilot is tested hands-on before publishing.

Keep reading