Split a CSV into smaller uploads without breaking records
Repeat the header, preserve multiline fields and reconcile every record when a destination limits rows per upload.
In this guide
An upload form accepts only a limited number of records at a time. You have one CSV export and need several files with the same columns. Cutting the file after a fixed number of text lines can break a quoted note across two files. Copying visible rows through a spreadsheet can also change identifiers before the files are saved. This walkthrough splits complete CSV records and checks that joining the results would recover the original values in their original order.
Establish the destination's actual limit
Ask whether the limit counts data records, includes the header, or measures file size. The helper in this guide limits data records per part. Each part also has one header record. If an importer permits 501 records including its header, use 500 data records per part. A record can contain more than one physical text line.
A row limit is different from a byte limit. Three short records may be smaller than one record containing a long description. If the destination limits bytes, check the sizes of the generated parts separately. This helper does not promise a maximum output size and will not divide an individual record. It is a small-file utility, limited to 5 MB per input and at most 1,000 parts, rather than a tool for multi-gigabyte exports.
Practice with eight records
Download split-input.csv and expected part boundaries. All records are fictional. The three columns are record_id, description and quantity. There are eight data records and the quantity total is 18.
| Part at a limit of 3 | Data records | First identifier | Last identifier |
|---|---|---|---|
| part-001.csv | 3 | 001 | 003 |
| part-002.csv | 3 | 004 | 006 |
| part-003.csv | 2 | 007 | 008 |
Record 003 contains First line, an embedded CRLF, and Second line in one description. That entire value belongs at the end of the first part. Record 004 contains quotation marks. Other cases include a comma, Café, a blank description, a literal zero quantity and two consecutive spaces. Splitting should preserve all of them, not clean them.
If you are unsure how a quoted newline differs from a new record, first read CSV commas, quotes and line breaks. The file's byte encoding is UTF-8 with a BOM, its delimiter is a comma, and its quote character is a double quote.
Generate the parts locally
Download and extract the CSV delivery practice pack. Keep all its files together. It contains the examples, expected results, readable script and test runner. Python 3.9 or newer is required, with no additional packages. The script does not upload files or make network requests. A colleague with Python can run the pack if you do not use a terminal yourself.
Open a terminal in the extracted folder and run:
python3 csv_delivery.py split split-input.csv split-output --rows 3
On Windows with the Python launcher installed, replace python3 with py -3. The Windows command has not been tested for this release. If you run the command from a different folder, supply the correct paths and put paths containing spaces in quotes.
The destination must not exist, and its parent folder must exist. A successful run creates three CSV files and summary.json inside split-output. It prints a report containing input_records: 8, output_records: 8, and part counts of 3, 3 and 2. To repeat the exercise, choose a new destination such as split-output-2. The script refuses to reuse an existing folder, so it will not overwrite an earlier result.
See where a four-line cut actually ends
Open split-input.csv in a text editor with line wrapping off. Copy the header and the next three physical lines into cut.csv, ending immediately after the line containing 003,"First line. Leave the original file intact. Try the truncated copy:
python3 csv_delivery.py split cut.csv broken --rows 3
Our run returned STOP: unexpected end of data. Record 003 opened a quoted description on the last copied line. Its closing quote and quantity are on the next line in the source. Adding a quote by hand would still leave part of the description and the quantity missing. Recover the complete input and rerun the split into a fresh folder.
| Boundary to inspect | Physical-line cut | Record-aware split at 3 |
|---|---|---|
| End of first part | Incomplete description for 003 | 003 ends with Second line, quantity 1 |
| Start of second part | Remainder of 003 would lead the next slice | First data record is 004, description He said "ready", quantity 3 |
| Recombined result | Parsing stopped | All eight records matched the input in order |
The successful run produced 3, 3 and 2 data records. Reading the last description in part-001.csv returned both text lines with their original CRLF between them. The terminal record and decoded boundary values show both runs. When delivering the parts, record the receiver's outcome beside each filename so a timeout can be checked before a retry.
Verify before uploading
The script reads each saved part again and compares its header and decoded fields with the corresponding input records. It then checks the combined record sequence. It writes a completed summary only after these comparisons pass. Check that summary.json says PASS before using the files. If a run is interrupted or reports an error, treat any partial output folder as incomplete and rerun into a fresh folder after resolving the cause.
Use the expected-boundaries file to check the first and last identifier in each part. Keep 001 as text when viewing it in a spreadsheet. Check that both lines of record 003 remain together. A text editor may show more physical lines than the part's reported record count, which is normal for this example.
Each part repeats the original header. When reconstructing the full table, retain that header only once. Do not concatenate every part as raw text and import the result without removing repeated headers. Preserve part order, and avoid accidentally uploading a part twice. A small delivery log with filename, record count and upload result is useful when the destination does not prevent duplicate submissions.
Know what is preserved
The helper preserves decoded field strings and row order. It writes UTF-8 with a BOM and CRLF record endings. Quoting may be rewritten, so the combined output is not promised to match the input byte for byte. The summary includes SHA-256 hashes for identifying the exact source and output files. Those hashes help distinguish file versions; they do not certify that the data or destination is correct.
The reader accepts UTF-8 with or without a BOM, comma delimiters and double quotes. It rejects inconsistent field counts and blank, duplicate or edge-spaced headers. It does not deduplicate records, repair encoding, sanitize spreadsheet formulas or validate business totals. Fix the actual source problem rather than deleting characters until a parser accepts the file.
Test record and next steps
On October 29, 2026, the downloadable test suite was run with Python 3.14.9 on macOS. It checked the 3/3/2 split, an exact 4/4 split, a limit larger than the input, record order, embedded CRLF, identifiers, Unicode, empty values, output protection and malformed-input rejection. The complete results and input hashes are in the dated test record. Run python3 test_csv_delivery.py from the extracted folder to repeat the tests. These are file-level checks. No new Excel application test or external upload test is claimed.
If your source consists of several differently ordered exports, map the headers before combining, then split the checked result. Before sending the parts, review the CSV delivery checks.
The implementation uses the reader and writer conventions documented in Python's csv module, including explicit newline handling. Hashes use Python's hashlib module.