How it works
- Step 1Define your canonical schema — Pick the column names your downstream ERP / WMS / Shopify / dropshipping platform expects. A common canonical set:
SKU,UPC,Stock,Cost,Price,Vendor,Product Name,Category. Document it once and reuse it — the entire value of this workflow is that every supplier feed maps into ONE schema you control. Put your unique identifier (SKU) first so Step 3's dedup keys on it cleanly. - Step 2Fork the workflow — Click
Fork. Forking is available to any signed-in user (no paid tier required) and copies the 4-node graph plus the starter rename map verbatim into a new private draft you own. The fork is snapshotted as version 1 and aforkedevent is written to the audit trail tagged with this blueprint's slug. - Step 3Drop every supplier file onto Step 1 — csv-merger is the first node and the only multi-file consumer. Select all supplier CSVs at once. In
unionmode the merger keeps the union of every header and pads missing cells empty. Header matching is whitespace- and case-insensitive, soSKUandskualready collapse; only genuinely different names (product_sku,Part Number) survive as separate columns for the next step. - Step 4Extend the combined rename map in Step 2 — Inspect the merged file's header row, then extend Step 2's single renames JSON so every variant header maps to its canonical name. Because suppliers rarely change schemas mid-relationship, the map is reusable next month. Remember rename relabels — it does not coalesce — so if two suppliers used different spellings of the same field you will get two same-named columns; pick one canonical spelling and use `csv-column-merger` only if you must combine their values.
- Step 5Resolve overlaps in Step 3 — csv-deduplicator collapses duplicate rows on your key column, keeping the first occurrence (case-insensitive). To make the cheapest supplier win instead of input-order, insert a `csv-sorter` step on
Costascending before this step — keep-first then keeps the lowest-cost row per key. - Step 6Finalise in Step 4 and run — csv-cleaner trims, drops blank rows, and repairs malformed rows. Run the workflow by dropping files on the canvas, or enqueue it from the Run Center (
source: manual) so your paired @jadapps/runner executes it locally and records arun_triggeredaudit event. Output is your unified inventory feed ready for import.
The real 4-node chain (as the blueprint wires it)
Order matters: csv-merger is first because it is the only node that consumes the multi-file drop. The rest post-process the single merged CSV. Edges are auto-wired first-out to first-in when you fork.
| Step | Tool | Blueprint config | What the runtime actually does |
|---|---|---|---|
| 1 | `csv-merger` | mode: union | Unions all dropped files by header (trim().toLowerCase() key); pads missing columns with empty cells; summary reports files-in → rows, union column count |
| 2 | `csv-header-rename` | renames: {product_sku→SKU, item_number→SKU, Part Number→SKU, sku→SKU, stock_level→Stock, quantity→Stock, qty→Stock, wholesale_price→Cost, unit_cost→Cost, cost→Cost} | Relabels matched header cells by index. Does NOT merge two columns into one — produces one header per matched index |
| 3 | `csv-deduplicator` | columns: SKU, strategy: first | Keeps the FIRST matching row, case-insensitive. The strategy field is honored as keep-first; keep-last is a UI option the runtime does not apply |
| 4 | `csv-cleaner` | trimWhitespace: true, removeEmptyRows: true, repairRows: true, deduplicate: false, normalizeSmartQuotes: true | Runtime trims (reads trimWhitespace), always removes empty rows + repairs rows, keeps dedup off. It does NOT read normalizeSmartQuotes here — use `csv-find-replace` for quote folding |
Common supplier column-name variants → canonical schema
Build your Step 2 rename map from this. The merger already folds case/whitespace duplicates; only genuinely different spellings need a rename entry.
| Canonical column | Variants seen in supplier feeds | Notes |
|---|---|---|
SKU | SKU, sku, product_sku, item_number, Part Number, Item No., MPN, ProductCode | Internal identifier. Make it column 1 so Step 3 dedups on it. Some suppliers conflate SKU and MPN — pick one as canonical |
UPC | UPC, EAN, Barcode, barcode, GTIN, UPC-A, EAN-13 | 12-digit (UPC) / 13-digit (EAN). Treat as text — every cell stays a string through the chain, so leading zeros survive unless Excel touches the file |
Stock | Stock, stock_level, quantity, qty, Available, On Hand, In Stock, inventory | Integer. Some suppliers ship 0/1 or In Stock/Out of Stock flags rather than counts — rename does not convert values, only headers |
Cost | Cost, wholesale_price, unit_cost, wholesale, Buy Price, Dealer Cost, Net Cost | Decimal 10.99 (period separator, no symbol). EU suppliers using 10,99 need `csv-find-replace` before merging |
Price | Price, MSRP, RRP, retail_price, selling_price, List Price | Retail price, separate from cost. Absent in pure cost-feed suppliers |
Vendor | Vendor, Supplier, Brand, Manufacturer, vendor_name | Brand / supplier identifier — keep it so you can filter by supplier downstream |
Product Name | Product Name, Title, Item Name, Description, Name, product_title | Display name. Suppliers truncate at different lengths |
Category | Category, Department, Product Type, Classification, category_path | Taxonomy. Cross-supplier taxonomy mapping is usually 1:many and not solvable by rename alone |
Fork / run / schedule mechanics for this workflow
Grounded in the orchestrator runtime — what actually gates each action for this all-CSV workflow.
| Action | Requirement | What happens |
|---|---|---|
| Fork to a private draft | Signed in (any tier) | Graph + config copied verbatim, snapshotted as v1, forked audit event written with the blueprint slug |
| Run it | Signed in (any tier — every node is a free CSV tool, no minTier) | Drop files on the canvas to run in-browser, or enqueue from the Run Center; the runner claims it from the queue and executes locally |
| Schedule it | Set a cron on your forked copy | This blueprint ships with scheduleCron: null (manual / drag-drop). The orchestrator supports cron (the enqueue endpoint accepts source: cron), but you must add a schedule yourself — it is not cron-scheduled out of the box |
| Audit / trace | Run history per workflow | forked, run_triggered, and other events are recorded; each run is enqueued with a queue id you can trace |
Cookbook
Real supplier-feed scenarios with the exact node configuration. All configs assume the real merge-first chain (merge → rename → dedupe → clean).
5 dropship suppliers, 5 different schemas, one drop
ExampleAcme uses SKU/Stock/Cost; BestBrand uses product_sku/qty/wholesale_price; CheapStock uses Part Number/On Hand/Dealer Cost; etc. Drop all five at once on Step 1. One combined rename map in Step 2 folds the variants.
Step 1 csv-merger
mode: union (drop acme.csv, bestbrand.csv, cheapstock.csv, ...)
Step 2 csv-header-rename (single combined map)
{
"product_sku":"SKU", "Part Number":"SKU", "item_number":"SKU",
"qty":"Stock", "On Hand":"Stock", "stock_level":"Stock",
"wholesale_price":"Cost", "Dealer Cost":"Cost", "unit_cost":"Cost"
}
Step 3 csv-deduplicator columns: SKU (keep first)
Step 4 csv-cleaner trimWhitespace: truePreferred supplier wins on overlapping SKUs
ExampleAcme and BestBrand both stock WIDGET-001. Business rule: Acme is preferred. The dedup keeps the FIRST occurrence, so order the merger inputs with Acme first.
Step 1 input order (this is what decides the winner): 1. acme.csv (preferred -> merges first -> kept) 2. bestbrand.csv 3. cheapstock.csv Step 3 csv-deduplicator columns: SKU -> WIDGET-001 keeps Acme's row; later suppliers' rows dropped
Lowest-cost wins (override input order)
ExamplePick the cheapest Cost per SKU regardless of supplier. Insert a sorter between merge+rename and dedupe so the cheapest row sits first and keep-first keeps it.
Step 1 csv-merger mode: union Step 2 csv-header-rename (fold to SKU / Stock / Cost) Step 2.5 csv-sorter column: Cost, direction: asc, numeric: true Step 3 csv-deduplicator columns: SKU (keep first -> cheapest) Step 4 csv-cleaner trimWhitespace: true
EU supplier with comma decimals
ExampleAn EU feed ships 10,99 for Cost and ; as the delimiter. The merger auto-detects the semicolon, but 10,99 is a string, not a number. Fix Cost BEFORE merge (or right after) with csv-find-replace in regex mode.
csv-find-replace (run on the EU file or post-merge on Cost) useRegex: true find: (\d+),(\d+)$ replace: $1.$2 -> 10,99 becomes 10.99 Then the standard chain: merge -> rename -> dedupe -> clean
Two spellings of the same field after merge
ExampleSupplier A used sku, Supplier B used product_sku. After Step 1 the merged file has BOTH columns. Rename maps both to SKU but does not coalesce them — you get two SKU columns. Coalesce the values with csv-column-merger if you need one populated column.
After merge: ... | sku | ... | product_sku | ... After rename: ... | SKU | ... | SKU | ... (two columns!) Fix (optional): csv-column-merger columns: [sku-index, product_sku-index] separator: "" (one is empty per row) newHeaderName: SKU -> single populated SKU column, then dedupe
Edge cases and verbatim errors
Variant headers become duplicate columns after rename
Expected limitationcsv-header-rename relabels header cells by index; it does not merge columns. If two suppliers used different spellings of the identifier (sku vs product_sku), the merged union file carries both columns and rename produces two columns both titled SKU. Step 3 then dedups on the FIRST SKU column only. Mitigation: standardise on one spelling per field, or coalesce the two columns with `csv-column-merger` before dedup.
Dedup keys on the wrong column because columns was passed as a bare string
Invalid key resolutionThe runtime reads the dedup key from the first element of the columns value. A proper tag/array like ["SKU"] resolves to the SKU column; a bare string can resolve to its first character and silently fall back to column index 0. Mitigation: make your identifier the first column (the safest default), or set columns as a tag in the node config so it resolves to the named column.
csv-cleaner did not strip the curly quotes I expected
Config not applied at runtimeThe standalone CSV Cleaner exposes normalizeSmartQuotes, but the orchestrator's cleaner handler reads only trimWhitespace and hard-codes removeEmptyRows + repairRows on and dedup off. Smart-quote / en-dash folding is not applied inside this workflow's Step 4. Mitigation: fold quotes with `csv-find-replace` (replace the curly characters with ASCII), or run the file through the standalone cleaner outside the chain.
Strict mode rejected the merge
Merge rejectedStrict mode throws if any file's column set differs from the first file's. Realistic supplier feeds rarely share identical schemas, so strict almost always fails here. The blueprint default is union, which keeps every column and pads missing cells empty. Keep union unless you have deliberately stripped all files to an identical schema first.
Stock value is `Yes` / `In Stock` instead of an integer
Different semanticsSome suppliers report availability flags rather than counts. Renaming the header to Stock does not convert the values. Map flags to numbers with `csv-find-replace` (e.g. In Stock to 99, Out of Stock to 0) before merge, or keep the flags if your downstream system understands them.
UPC / EAN corrupted by Excel mid-workflow
Excel 15-digit precision13-digit EANs coerce to scientific notation in Excel and 16-digit codes lose digit 16 permanently. The workflow itself keeps every cell as a string, so codes survive the chain — the damage happens only if you open the CSV in Excel. See the Excel precision details. Mitigation: keep the file as CSV throughout; if you must view in Excel, import with the barcode column set to Text.
EU supplier with comma decimal separator
Localisation mismatchEU CSVs often use ; delimiter and , decimal: WIDGET-001;42;10,99. The merger auto-detects the semicolon, but Cost: 10,99 parses as the string "10,99", not 10.99. Convert with `csv-find-replace` in regex mode ((\d+),(\d+)$ to $1.$2) on the Cost values before merging.
Supplier renames a column between monthly feeds
Supplier schema changeLast month Acme used stock_level; this month quantity. Your saved rename map silently misses the new header, leaving an unmapped column. Mitigation: after Step 2, verify the canonical headers are all present before proceeding, and audit your rename maps quarterly.
Same SKU, genuinely different products across small suppliers
Genuine ambiguityTwo small suppliers using sequential numbering may both ship WIDGET-001 for different products. Dedup collapses them on the key match regardless of product name. Mitigation: namespace the identifiers before merge (acme:WIDGET-001 vs bestbrand:WIDGET-001) so they do not collide.
I forked it expecting a cron schedule
Manual trigger by defaultThis blueprint ships with scheduleCron: null — it runs when you drop files or enqueue it, not on a timer. The orchestrator does support cron (the enqueue endpoint accepts source: cron, and other blueprints carry cron expressions), but you must add a schedule to your forked copy yourself for monthly automation.
Frequently asked questions
Why does the merge run before the rename here?
Because `csv-merger` is the only node that accepts the multi-file drop — it is wired first by design so you select every supplier CSV at once and it unions them into one file. The rename, dedupe, and clean steps then post-process that single merged CSV. If you renamed per-file first you would have to run the rename N times manually; merge-first keeps it to one drop.
Why can't I just use csv-merger directly on raw supplier feeds?
You can run the merger, but the output is column-soup: suppliers' different header spellings (SKU, product_sku, Part Number) survive as separate columns because the merger only folds case/whitespace duplicates, not different names. Step 2's `csv-header-rename` relabels those variants toward your canonical schema afterward.
Does the rename step actually merge two columns into one?
No. csv-header-rename relabels header cells by position; it does not coalesce data. If the merged file has both sku and product_sku, mapping both to SKU yields two columns titled SKU. To get a single populated column, coalesce them with `csv-column-merger` before dedup. This is the most common surprise for first-time forkers.
When duplicate SKUs exist, which supplier's row is kept?
The first one. `csv-deduplicator` keeps the first matching row (case-insensitive) and drops the rest. The winner is therefore whichever supplier file you merged first in Step 1 — order your inputs deliberately, preferred supplier first.
The node has a keep-last option — does it work?
The node config UI shows a Strategy select with keep-first and keep-last, but the orchestrator runtime applies keep-first. Treat keep-first as the behavior you get. To make a non-first row win (e.g. cheapest), reorder the rows first — add a `csv-sorter` step so the row you want is first, then keep-first keeps it.
How do I keep the lowest-cost supplier per SKU?
Insert a `csv-sorter` step on Cost ascending with numeric: true between rename and dedupe. After sorting, the cheapest row for each SKU sits first, so the keep-first dedup keeps it. See the lowest-cost cookbook example.
Should I dedupe on SKU or UPC?
SKU is your internal identifier; UPC/EAN is the manufacturer's barcode. For supplier merging, SKU is usually correct — one row per logical product in your catalogue. Whichever you pick, make it the first column so the dedup keys on it reliably (the runtime reads the first column when the key resolution is ambiguous).
Will my wholesale cost data be uploaded to JAD?
No. Every tool in this chain is a CSV tool that executes client-side — in your browser, or on your paired @jadapps/runner for queued/scheduled runs. Wholesale costs, supplier identifiers, and SKU lists never reach a JAD server. Only orchestration metadata (fork, run-triggered events) is recorded, not your file contents.
Do I need a paid plan to fork or run this?
No. Forking requires only that you are signed in, and every node here is a free-tier CSV tool with no minimum tier, so the tier precheck passes for everyone. Paid tiers gate media/security tools that this all-CSV workflow does not use.
Can I run this on a monthly schedule?
Yes, but you have to set it up — this blueprint ships unscheduled (scheduleCron: null). After forking, add a cron expression to your copy; the orchestrator's enqueue path supports source: cron. Or trigger it manually each month from the Run Center, which enqueues a run and records a run_triggered audit event.
What if a supplier sends XLSX instead of CSV?
Convert it to CSV first (File, Save As, CSV UTF-8). Critical: set any barcode / UPC / EAN columns to Text before saving, otherwise Excel coerces 13-digit codes to scientific notation. Better still, ask the supplier to send CSV directly so the codes never pass through a spreadsheet.
How does forking actually copy the workflow?
The from-blueprint fork endpoint builds the graph by placing each blueprint node in a horizontal chain and auto-wiring consecutive nodes via the first compatible out-to-in ports, copies each node's config verbatim, creates a private workflow row you own, snapshots it as version 1, and writes a forked audit event tagged with this blueprint's slug. You then edit the rename map and run it.
Local-first by design
This workflow executes entirely on your jadapps-runner. API keys, database credentials, and OAuth tokens are stored in an AES-GCM-encrypted vault on your device — they are never uploaded to JAD Apps' servers. The server only stores the workflow graph (the recipe), not the secrets.