How to merge two excel workbooks with a left or inner join on a key column
- Step 1Open the Sheet Joiner — Go to the Multi-Sheet Joiner tool. It shows two drop zones because a join needs two tables — File A and File B.
- Step 2Drop File A (the left table) — Add the table whose rows you want to keep — your main dataset. Only its first sheet is read, so put the table on sheet 1 of the workbook.
- Step 3Drop File B (the right table) — Add the lookup or reference table. Again only its first sheet is parsed; xlsx and csv are both accepted.
- Step 4Name the key column in each file — Set
leftKeyto File A's key header andrightKeyto File B's key header (both default toid). They need not share a name — only matching values. - Step 5Choose the join type — Pick
leftto keep all File-A rows (unmatched ones get blank cells under File-B columns) orinnerto keep only rows with a match in both files. - Step 6Run and download joined-data.xlsx — Process the join. The result panel reports input rows (File A), output rows, and duration; download the merged
.xlsx(sheetJoined).
Option contract
The three controls the Sheet Joiner actually exposes, with real defaults from the tool schema. There are no presets, no fuzzy matching, and no composite-key field here — single key per side.
| Option | Control | Default | What it does |
|---|---|---|---|
leftKey | Text input | id | Header of the key column in File A. Read literally — must match the File-A header exactly (case- and space-sensitive). |
rightKey | Text input | id | Header of the key column in File B. Independent of leftKey; values are what must match, not the header text. |
joinType | Dropdown | left | left keeps every File-A row; inner keeps only rows whose key exists in both files. No right or outer option exists. |
Join-type behaviour
How each join type treats matched and unmatched rows. 'Fan-out' means one left row producing several output rows because File B has several matches for its key.
| Situation | Left join (default) | Inner join |
|---|---|---|
| File-A row, one File-B match | 1 merged row | 1 merged row |
| File-A row, 3 File-B matches | 3 rows (fan-out, one per match) | 3 rows (fan-out) |
| File-A row, no File-B match | Kept; File-B columns are blank | Dropped entirely |
| File-B row with no File-A match | Discarded (right-only rows never appear) | Discarded |
| Same column name in both files | File-B value overwrites File-A value | File-B value overwrites File-A value |
Tier limits for joins
From the Excel tier family. Because a join requires two files, the Free tier (1 file) cannot run it — the Sheet Joiner is Pro-minimum.
| Tier | Max file size | Max rows / file | Files at once |
|---|---|---|---|
| Free | 5 MB | 10,000 | 1 (cannot join — needs 2) |
| Pro | 50 MB | 100,000 | 5 |
| Pro-media | 200 MB | 500,000 | 20 |
| Developer | 500 MB | Unlimited | Unlimited |
Cookbook
Before/after fragments from real two-file joins. Keys are matched as strings, so type mismatches that break VLOOKUP do not break this join.
Basic left join on a shared key
File A is a product list, File B is a price table. leftKey and rightKey both = sku. Every product row gets the price column appended; the right key column (sku) is not duplicated.
File A (products, first sheet): sku,name A-1,Widget A-2,Gadget A-9,Sprocket File B (prices, first sheet): sku,price A-1,9.99 A-2,14.50 Left join output (joined-data.xlsx, sheet "Joined"): sku,name,price A-1,Widget,9.99 A-2,Gadget,14.50 A-9,Sprocket, <- no match: price left blank
Inner join drops the unmatched row
Same files, switch joinType to inner. Sprocket has no price in File B, so it is removed instead of kept with a blank.
joinType: inner Output: sku,name,price A-1,Widget,9.99 A-2,Gadget,14.50 (A-9 Sprocket is gone — inner keeps only keys present in both files.)
Number key matches text key
File A stores the order id as a number, File B as text. VLOOKUP returns #N/A here; the joiner coerces both with String() so they match.
File A: File B: order,total order,carrier 1001,42.00 "1001",FedEx 1002,18.50 "1002",UPS Left join (leftKey=order, rightKey=order): order,total,carrier 1001,42.00,FedEx 1002,18.50,UPS
One-to-many fan-out
File B has multiple rows per key. A left join emits one output row per File-B match, so File-A rows multiply. Output rows exceed File-A's input rows — confirm against the metrics panel.
File A (1 row): File B (2 rows for K1):
key,owner key,line_item
K1,Acme K1,Bolts
K1,Nuts
Left join output (2 rows):
key,owner,line_item
K1,Acme,Bolts
K1,Acme,NutsColumn-name collision — right wins
Both files have a status column. The join copies File B's status over File A's, since each right column except the key is written into the merged row. Rename one column first if you need both.
File A: File B: id,status id,status 7,Pending 7,Shipped Left join output: id,status 7,Shipped <- File B overwrote 'Pending'
Edge cases and what actually happens
Free tier tries to join
Run rejectedThe Sheet Joiner is Pro-minimum and the join itself needs two files, which exceeds the Free tier's 1-file allowance. The tool blocks the run with a 'Sheet Joiner requires Pro tier.' message. Upgrade to Pro (50 MB / 100,000 rows) to run joins.
Only one file dropped
RejectedA join needs both slots filled. With File B missing, the processor throws 'Two files are required for sheet joining.' Add the second file before running.
Key column header typed wrong
Invalid keyKeys are looked up by the exact header you type into leftKey / rightKey. A missing or misspelled header makes row[key] undefined, which String() turns into "" for every row — so on a left join all rows fall through unmatched (blank right columns), and on an inner join you get zero output rows. Check the headers exactly, including trailing spaces.
Blank keys collide
By designEmpty, null, or missing key cells all coerce to the same empty string "". Every File-A row with a blank key matches every File-B row with a blank key, which can produce a large accidental cross-product. Clean blank keys out first if your data has them.
Right-only rows never appear
By designThis is a left/inner join, not a full outer join. Rows that exist only in File B (no matching File-A key) are always discarded — there is no option to surface them. If you need them, swap which file is A and B and run again.
Key values differ by case
No matchString comparison is case-sensitive: ACME and acme are different keys and will not join. Normalise case in both files first with the case converter so the keys line up.
Only the first sheet is read
Sheet 1 onlyEach file contributes its first worksheet only. A workbook whose join table lives on sheet 2 will be read as its sheet 1 instead. Move the table to the first tab, or save it as its own file, before joining.
Dates as keys
PreservedCells parse with cellDates on and format-on-read, so a date key becomes a formatted string before comparison. Two files must render the date identically (same display format) for the keys to match; standardise them first with the date standardizer.
Composite (multi-column) key needed
Not supportedThe joiner matches on a single key column per side. Despite older copy mentioning composite keys, the actual contract has one leftKey and one rightKey. To emulate a composite key, concatenate the parts into one helper column in each file first, then join on that.
Output bigger than expected
ExpectedOne-to-many matches fan out: a File-A row with N File-B matches becomes N output rows. The metrics panel shows output rows vs input rows so you can see the multiplication — it is not a bug, it is how a relational join works.
Frequently asked questions
Is this a within-workbook sheet join or a two-file join?
It is a two-file join. You drop File A and File B into separate slots, and only the first sheet of each file is read. There is no in-app sheet picker — to join two sheets that live in one workbook, save each sheet as its own file (or move the second one to the first tab of a new file) and drop them as File A and File B.
What happens to columns with the same name in both files?
File B's key column is dropped to avoid a duplicate key column. Any other same-named column from File B overwrites File A's value, because each right column (except the key) is written into the merged row. Rename one side first with the header rename tool if you need to keep both.
Does it support joining on multiple key columns?
No. The real option contract is a single leftKey and a single rightKey. To match on a composite key, build one helper column in each file by concatenating the parts (e.g. region|sku), then join on that single helper column.
What is the difference between left join and inner join?
Left join keeps all File-A rows; rows with no File-B match are kept with blank cells under File-B's columns. Inner join keeps only rows whose key exists in both files — unmatched File-A rows are dropped. Default is left.
Is there a right join or full outer join?
No. Only left and inner are available. Rows that exist only in File B are always discarded. To approximate a right join, swap which file you load as A and B.
Will a number key match a text key?
Yes. Both sides are coerced with String() before comparison, so 1001 (number) matches "1001" (text). This is the opposite of VLOOKUP, which would return #N/A for the same mismatch.
Is key matching case-sensitive?
Yes. Acme and acme are treated as different keys. Normalise case on both files first with the case converter if your keys vary in capitalisation.
What format is the output?
A binary .xlsx workbook with a single sheet named Joined, downloaded as joined-data.xlsx. It holds values only — there are no VLOOKUP or Power Query formulas in it, so it never needs a refresh.
Are my files uploaded anywhere?
No. Parsing and joining run entirely in your browser via SheetJS. Nothing leaves the tab, which is why it works on locked-down or offline machines once the page has loaded.
Why are there more output rows than I started with?
Because of one-to-many fan-out: a File-A row matched by several File-B rows produces one output row per match. The metrics panel reports input vs output row counts so the multiplication is visible.
What tier and limits apply?
Pro minimum, since the join needs two files (above the Free tier's 1-file cap). Pro allows 50 MB and 100,000 rows per file; Pro-media 200 MB / 500,000; Developer 500 MB / unlimited.
What if a key column is empty in some rows?
Blank, null, and missing keys all become the empty string "" and match each other. That can create a large accidental cross-product between all blank-key rows. Strip blank keys first — the empty-row remover and a quick filter help — before joining.
Privacy first
Every JAD Excel tool runs entirely in your browser using SheetJS and ExcelJS. Your spreadsheets, formulas, and data never leave your device — verified by zero outbound network requests during processing.