Split a CSV by rows, file count, column value or a real byte-size maximum, with every input row accounted for exactly once
Initializing in your browser…
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
Convert both ways, converting a type only when it reads back the same, and naming every value that cannot
Format and validate against RFC 4180 by section, with output verified by reading it back
A 600 row file whose short rows come first and long rows last, split into files of at most 20 KB. And a twelve row file grouped by a region column holding A-B, A_B, A B, an empty value, the word empty, and cafe with an accent.
Two splits
1. By maximum size: 20 KB, on 300 rows of 5 characters followed by 300 of 400 2. By column value: region, on 12 rows across 6 distinct values
Result
By size: 7 files of 20,254 20,312 20,312 20,312 20,312 20,312 3,260 bytes
largest 20,312 against a 20,480 byte limit; 600 rows in, 600 out
By column: 6 files
sales_A-B.csv 2 rows sales_A_B.csv 2 rows
sales_A B.csv 2 rows sales_empty.csv 2 rows
sales_empty-value.csv 2 rows sales_café.csv 2 rows
12 rows in, 12 outBoth of these were wrong before, and both were wrong silently. The size estimate averaged the first 100 rows, which on this file are the short ones: 8.9 characters each, giving 2,301 rows per file and therefore ONE file, holding everything, when 20 KB was asked for. Sizes are the real encoded byte length now, so the limit is a limit. And the six group values used to collapse to four file names, because everything outside A to Z, a to z and 0 to 9 became an underscore: A-B, A_B and A B were one file, the empty value merged with the word empty, and cafe with an accent became caf_. A zip holds one entry per name, so the losing groups were gone from the download with nothing said. Both splits now show the rows that went in beside the rows that came out.
Split a CSV into smaller files by row count, file count, column value or maximum size. The sizes are the real encoded byte length of what will be written rather than an estimate, and the tool shows that every input row ended up in exactly one output file.
Measured on 2026-09-02 by downloading the archive and opening it with Python's zipfile, which is the only reference that settles the question: a "500 KB" file that is 380 KB is wrong by a number the file system already knows.
Splitting by size was an estimate, and the estimate was wrong in four independent ways. A row's size was taken as row.join(',').length + 1, averaged over the FIRST 100 ROWS ONLY. Measured on four files at a 100 KB target, the parts came out between 5.2 KB and 284.7 KB, and a 1.2 MB file whose long rows came last was written as ONE file of 1,226.5 KB, twelve times what was asked for. The four faults: String.length counts UTF-16 code units and a file is measured in bytes, so a column of CJK text was estimated at a third of its size; row.join(',') is not the CSV encoding of a row, since RFC 4180 section 2.6 quotes a field holding a comma or a quote and section 2.7 doubles each inner quote; a mean over the first 100 rows describes the first 100 rows; and neither the repeated header nor the two-byte CRLF separator was counted at all. Nothing here is estimated now. Each row's encoded length is computed once, the files are packed against those numbers, and across eight runs every predicted size equalled the file that was written, to the byte. The setting is a MAXIMUM rather than a target: no file exceeds it unless a single row is larger than the limit on its own, which is reported, because a row cannot be cut in half.
Splitting by column value lost rows. Group values were turned into file names by replacing every character outside A to Z, a to z and 0 to 9 with an underscore, so A-B, A_B and A B all became the same name, and a zip archive holds one entry per name: on a ten row file, four rows silently vanished and the download said nothing. Every non-Latin name collapsed the same way, cafe with an accent becoming caf_ and two different Japanese city names both becoming three underscores. File names keep their characters now, only what a file system actually refuses is replaced, and two files that would still share a name get a numbered suffix with the rename reported. The same ten row file now produces nine files holding all ten rows.
An empty value in the grouping column was turned into the literal string "empty", which merged it with rows whose value really is the word empty. The empty group is a flag now rather than a placeholder string, the same answer the pivot table gives, and the two stay apart.
Asking for more files than there are rows produced files with nothing in them: five files from a two row table gave three holding a header and no data. It writes as many files as there are rows and says why.
Every split now carries a partition check, the same one the merger, the comparator and the missing-data analyzer carry: the data rows across every output file are counted and shown against the data rows that went in. If those two numbers ever differ, the page says so rather than letting an archive quietly hold less than the file did.
Output files are written by RFC 4180: CRLF records per section 2.1, quoting as a minimum rather than a preference, and a UTF-8 byte order mark so a spreadsheet reads them correctly. The default naming pattern zero pads the part number, so part01 through part12 sort into the right order in a file listing. And a single-column CSV loads, where it used to be refused outright because papaparse reports UndetectableDelimiter for a file with no delimiter in it to detect.
Set the maximum to whatever the limit is and every file is under it, because the size is measured rather than estimated. The previous version could produce a file twelve times the number you typed.
Break a million-row export into chunks a spreadsheet can open, with the header repeated in each one.
One file per region, department or customer, with the file names keeping the values as they are written rather than flattening them into underscores.
The rows-in and rows-out counts sit next to each other above the file list, so a split that dropped something is visible before you download it.
A maximum. Files are packed against the real encoded byte length, so none exceeds the number you type. The single exception is a row that is larger than the limit on its own: it goes in a file of its own, that file is over the limit, and the page says so, because a row cannot be split in half.
Because rows are different lengths and a row cannot be divided. Each file takes as many rows as fit under the limit, so the last one is usually shorter and any file can end early if the next row is long.
By default yes, in every file. You can turn that off, in which case the header is written in the first file only, so the set of files holds it exactly once.
The second gets a numbered suffix and the rename is listed above the file list. Nothing is dropped. That case used to lose the rows outright: A-B, A_B and A B all became one file and only one of the three groups survived.
They go into their own file, kept apart from rows whose value is literally the word "empty". A row too short to have that column at all is grouped with the empty value and counted in a note.
You can ask, and you get one file per row rather than a set padded out with files holding nothing but a header. The page says how many were written and why.
The preview shows the data rows that went in and the data rows across every output file side by side. They should be equal, and the tool flags it in red if they are not.
CSV by RFC 4180: CRLF record separators, fields quoted where section 2.6 requires it, and a UTF-8 byte order mark so a spreadsheet reads accented and non-Latin text correctly. They are packaged in a single zip.
Rows and columns are parsed and transformed in memory in your browser. No record ever reaches a server.