Columns appear once you paste a CSV.
Paste a messy CSV and tidy it without opening a spreadsheet: trim stray spaces, drop blank and duplicate rows, filter, sort and keep only the columns you need. Every step is a checkbox, the output updates as you click, and the stats show how many rows each step removed. Semicolon, tab and pipe files work too, and the output keeps whichever delimiter the input used.
Columns appear once you paste a CSV.
email,name,plan,seats,signed_up
ada@example.com ,Ada Lovelace,pro,12,2024-03-02
grace@example.com,Grace Hopper,team,40,2023-11-19
,,,,
bob@example.com,Bob Tables,free,1,2024-06-30
grace@example.com,Grace Hopper,team,40,2023-11-19
alan@example.com, Alan Turing,pro,3,2022-01-15
"lin@example.com","Lin, Mei",team,9,2024-02-08
ada@example.com,Ada Lovelace,pro,12,2024-03-02 email,name,seats
grace@example.com,Grace Hopper,40
ada@example.com,Ada Lovelace,12
lin@example.com,"Lin, Mei",9
alan@example.com,Alan Turing,3
Settings used: trim on, empty rows removed, filter
plan excluding free, duplicates
removed by whole row, sort by seats descending,
keep email, name and
seats. Eight rows in, four out: one empty,
one filtered, two duplicates, and the untrimmed
ada@example.com only matches its twin because
trimming ran first.
Trim strips leading and trailing whitespace from every cell, header included. A row counts as empty when every cell is blank after that, so ,,,, goes too.
A plain substring match, case-insensitive unless you tick Match case. Pick Any column to search the whole row. Exclude matches flips it into a "drop rows containing" filter. No regex, no wildcards.
By whole row, two rows must match cell for cell. By a key column, only that column is compared and the first row with each value wins. Comparison is exact, so Bob and bob are different.
Numbers sort as numbers (9 before 10, 1,200 read as 1200). Text uses a natural, case-insensitive order so item2 comes before item10. Blank cells go last either way, and ties keep their original order.
Values are never reformatted. Leading zeros, long IDs and values that look like dates come out exactly as they went in, which is the main reason to clean a CSV outside a spreadsheet. Quoting in the output is minimal: a field gets quotes only if it contains the delimiter, a double quote or a line break. The delimiter and line endings of the input are kept.
Dates are sorted as text. That works for ISO dates like
2024-03-02, but 03/02/2024
sorts by month or day before year, so the order
comes out wrong. Everything runs in your browser, so
the practical limit is memory: a file of a few hundred
thousand rows is fine on a laptop, a multi-gigabyte dump is a
job for csvkit or a database.
// rows: array of arrays from any CSV parser (csv-parse, papaparse, the one above)
function clean(rows, { key = null, sortBy = null, desc = false, keep = null } = {}) {
const [header, ...body] = rows;
let out = body
.map((r) => r.map((c) => c.trim()))
.filter((r) => r.some((c) => c !== ""));
const seen = new Set();
out = out.filter((r) => {
const k = key === null ? JSON.stringify(r) : r[key];
return seen.has(k) ? false : (seen.add(k), true);
});
if (sortBy !== null) {
const coll = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
out.sort((a, b) => coll.compare(a[sortBy], b[sortBy]) * (desc ? -1 : 1));
}
const pick = (r) => (keep ? keep.map((i) => r[i] ?? "") : r);
return [pick(header), ...out.map(pick)];
}
const rows = [
["email", "name", "seats"],
[" ada@example.com ", "Ada", "12"],
["grace@example.com", "Grace", "40"],
["", "", ""],
["ada@example.com", "Ada", "12"],
["bob@example.com", "Bob", "9"],
];
console.log(clean(rows, { sortBy: 2, desc: true, keep: [0, 2] })); Intl.Collator with numeric: true sorts 9 before 10 and item2 before item10 without a separate number check.
import csv
with open("users.csv", newline="", encoding="utf-8") as f:
header, *rows = csv.reader(f)
rows = [[c.strip() for c in r] for r in rows] # trim
rows = [r for r in rows if any(r)] # drop empty rows
rows = [r for r in rows if "free" not in r[2].lower()] # filter on column 3
seen, unique = set(), []
for r in rows: # dedupe on email
if r[0] not in seen:
seen.add(r[0])
unique.append(r)
unique.sort(key=lambda r: float(r[3] or 0), reverse=True) # numeric sort
keep = [0, 1, 3] # email, name, seats
with open("clean.csv", "w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow([header[i] for i in keep])
w.writerows([[r[i] for i in keep] for r in unique]) float(r[3] or 0) treats blank cells as 0; use a tuple key like (r[3] == "", float(r[3] or 0)) to push blanks to the end instead.
Steps run in a fixed order: trim, remove empty rows, filter, remove duplicates, sort, keep columns. Order matters for deduplication: with trimming on, " ada@example.com " and "ada@example.com" count as the same value. Sorting is stable, so rows with equal values keep their original order.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
Paste or upload the CSV and keep Remove duplicate rows ticked. Choose By whole row to drop exact copies, or pick a key column such as email to keep only the first row for each value.
Yes. Pick the column under Sort. Values that are numbers are compared as numbers, so 9 comes before 10, and blank cells always go last.
Untick the columns you do not want under Keep columns. The header and every row are cut down to the ticked columns, in their original order.
No. Apart from trimming whitespace when that step is on, values are copied through unchanged. Leading zeros, long IDs and date-like strings stay as text.
No. All the cleaning runs in your browser. Upload CSV reads the file locally, and nothing is sent to a server.
Excel saves CSV with semicolons in locales where the comma is the decimal separator, such as Germany, France and the Netherlands, because 3,5 would otherwise split into two cells. Auto-detect normally spots this, and the Detected label above the input shows the choice. If it guesses wrong, pick Semicolon (;) in the Delimiter menu. The cleaned output keeps the semicolons so the same Excel opens it correctly.
Yes. Pick Pipe (|) under Delimiter, or leave Auto-detect on, which checks the first twenty lines for the character that appears the same number of times on each one. Pipes are common in database exports because addresses and names rarely contain them. Quoted fields are handled the same way as in a comma file, and the output is written with pipes again.
Pick the column under 5. Sort and leave the direction at Ascending (A to Z, 1 to 9). Descending (Z to A, 9 to 1) flips the order. Letters compare case-insensitively, so apple and Banana sort as you would expect, and blank cells go last in both directions. The header stays on top as long as First row is header is ticked.
Preview them in CSV to Table, load them with CSV to SQL or turn them into JSON. To change the delimiter, use the TSV to CSV Converter.