Remove duplicate rows with equality as a stated choice: case, spaces, accents and numbers each their own switch, and the rule in force shown
Initializing in your browser…
Format and validate against RFC 4180 by section, with output verified by reading it back
Diff two CSV files with the matching rule stated: a key that may repeat, or a real Myers diff, with every row accounted for
View, sort and edit a CSV, with an undo stack that goes back exactly one step and a parse report that tells a broken quote from a ragged row
A mailing list merged from several sources has the same address written three ways.
Input
Ann@Example.com · ann@example.com · "ann@example.com " bob@example.com · carl@example.com · bob@example.com
Under three rules
identical only 1 group, 1 row removed, 5 kept ignoring case 2 groups, 2 rows removed, 4 kept ignoring case+spaces 2 groups, 3 rows removed, 3 kept
The same six rows give three different answers, which is why the rule has to be stated rather than buried in a mode name. The tool writes the one in force out in sentences: which normalisations apply, that the first row of each group survives, and that a shorter row is compared as if its missing fields were empty.
Find and remove duplicate rows, having first said what "duplicate" means. Case, surrounding spaces, repeated spaces inside a value, accents written two ways, and 1 against 1.0 are five separate decisions, and this tool makes each one a switch rather than burying them in a mode name.
The entire job of a duplicate remover is one question nobody states: what counts as the same. Measured on 2026-09-01 against a corpus of 20 row pairs, each with the answer recorded under eight rule sets and cross-checked against a second implementation, the previous version of this tool was right on 54 of 60. Three of the six failures came from a single structural fault.
The key for a row was the compared fields joined with a pipe character. That is not a key, it is a hash with obvious collisions: the row ["a|b", "c"] and the row ["a", "b|c"] both join to "a|b|c", so two genuinely different rows were reported as duplicates of each other and one of them was deleted. Any separator has the same problem with a value that contains it. The key is a structure now, so the field boundaries survive and no separator can be smuggled in.
The mode called "trimmed" also folded case. Its normaliser lower-cased, trimmed, and collapsed whitespace all at once, so choosing "trimmed" silently merged Alice with alice. Those are now three independent switches, each saying what it does with an example.
Case folding used toLowerCase, which is a case MAPPING and not the Unicode operation for caseless matching. The difference that turns up in real data is the German sharp s: STRASSE lower-cases to strasse and the sharp s does not, so the two never matched. Folding handles it, along with the ligatures and the Greek final sigma.
Nothing normalised Unicode. The word cafe with an acute accent can be written with one code point or with an e followed by a combining accent; they are the same word to a reader and different strings to a computer, macOS writes the second form and almost everything else writes the first, so a file that has been through both has visually identical rows that no amount of case folding will match. NFC normalisation is now a switch.
And nothing compared numbers as numbers, so 1 and 1.0 were never duplicates. That is a reasonable default and it was neither stated nor changeable; it is now a switch, and turning it on also makes 007 and 7 the same value, which is exactly why it has to be a choice rather than a default.
Whatever you choose, the tool writes out the rule in force in plain sentences: which normalisations apply, which row of each group survives, and that a row shorter than the others is compared as if its missing fields were empty. The scan is one pass over the rows, so 50,000 rows take about 27 milliseconds.
Turn on case folding and space trimming so "Ann@Example.com" and "ann@example.com " are one person, and see exactly which rule made them match.
Turn on accent normalisation to catch rows that look identical and are not, because one system wrote a precomposed letter and another wrote a combining accent.
Turn on numeric comparison so 1 and 1.0 are the same reference, and note that 007 and 7 then are too, which is why it is a choice.
Preview every group with the rule that produced it written out, and exclude individual rows from removal before committing.
Whatever you say. By default two rows are duplicates only when the compared fields are character for character identical. Each of the five switches relaxes that in one specific way, and the tool writes the resulting rule out in sentences so it is never a guess.
Because they are different questions, and combining them is how the previous version merged Alice with alice for someone who had only asked to ignore trailing spaces. Every switch does one thing.
Most likely the accents. A letter with an accent can be written as one code point or as the plain letter followed by a combining accent, and the two are different strings. Turn on accent normalisation. If the values contain the German sharp s, case folding handles that too.
Only if you turn on numeric comparison. It is off by default because it also makes 007 and 7 the same value, and a zero-padded code is usually meant to be distinct.
The first of each group by default, in file order. You can keep the last instead, or remove every row in a group when the presence of a duplicate means neither copy is trustworthy. The choice is stated alongside the results.
Its missing fields are compared as empty, which the tool says explicitly. A row of ["Alice"] and a row of ["Alice", ""] are therefore the same row.
Rows and columns are parsed and transformed in memory in your browser. No record ever reaches a server.