What this workflow does
This workflow solves a problem you most likely hit when feeding data (leads, prospects, contacts, anything) into an existing database or Google Sheet.
How it works, step by step
1. Manual Trigger (“Set your input”) Starts the run and fans out into two parallel branches. Easy to swap for a scraper, excel file or any other data output. (you can delete the “New input” to)
2. Two data sources, read in parallel
-
Existing Database — reads the rows already in your destination. Set to
executeOnceso it pulls the current state of your database a single time. -
New input (optional) — reads the incoming batch you want to evaluate.
3. Flag each source with a _type field Each branch passes through a dedicated Set node:
-
Flag Database stamps
_type = "Database" on existing records. -
Flag New Data stamps
_type = "New"on incoming records.
Both use Include Other Fields so the original data is preserved and just gets the extra tag. This _type flag is the whole sauce, once both lists are merged into one stream, it’s what lets the Code node tell “already in the database” apart from “incoming/new”.
4. Merge both streams (append mode) A Merge node combines the flagged existing records (Input 1) and flagged new records (Input 2) into one single list.
5. Filter: only new records (0 extra API calls) The heart of the workflow. One Code node does all the deduplication against the merged list:
What it does:
-
Builds a
Setof all existing IDs from the items flaggedDatabase. ASetkeeps lookups fast even on large lists. -
Handles both
Lead_idandlead_idspellings so a casing difference in your header doesn’t break matching — and you can add more variations. -
Normalizes every ID with
.trim().toLowerCase()so whitespace and capitalization don’t cause false “new” matches. -
Keeps only the incoming
Newitems whose ID is not already in the existing set. -
Strips the helper
_typefield back off before output, so your clean records don’t carry the internal tag.
Because everything is compared in memory against data you already loaded, there are no per-row lookups against the API.
6. Remove Duplicates After filtering out records that already exist, a Remove Duplicates node (comparing on lead_id) catches any duplicates within the incoming “new” batch itself. E.g. the same record appearing twice in the new input. Belt and suspenders.
7. Append the clean records (“Database + new data”) The deduplicated output is appended to your destination sheet. Only genuinely new records get added.
Why I built it this way
The obvious approach to dedup is to loop over each incoming row and query the database to check if it exists. That’s slow and burns one API call per row. By loading both lists once, flagging them, merging, and comparing in a single Code node, the whole job runs in one pass with no extra calls. It scales well, is fast and stays cheap.
Notes / things you can adapt
-
Flag values must match the Code node. The strings in Flag Database (
Database) and Flag New Data (New) have to match the values checked in the Code node. Rename both sides together if you want different labels. -
Matching key. Dedup is done on a single ID field (
Lead_id/lead_id). No stable unique ID? Build the key from a combination of fields (e.g.email + company_name) inside the Code node, and point Remove Duplicates at the same logic. -
Trigger. Swap the Manual Trigger and new input for an scraper, excel sheet or any other way you want to implement it into your workflow.
-
Destination. Any append-capable node works here (Sheets, Postgres, Airtable, etc.) — the dedup logic is storage-agnostic.
For questions you can e-mail me on: