Guide
How to test file uploads properly
A practical checklist for testing upload forms and APIs — size limits, empty files, content-type validation, file names and interrupted transfers.
Most upload bugs don’t show up with the one test file a developer keeps on their desktop. They show up at the boundaries: a file one byte over the limit, a zero-byte file, a PNG renamed to .jpg, a name with an emoji in it. This checklist covers the cases worth testing, with a sample file for each.
1. Size limits: test both sides of the boundary
If your limit is 10 MB, test a file just under it and a file just over it. Be precise about units. Most servers treat “10 MB” as 10 × 1024 × 1024 = 10,485,760 bytes, but some product copy means 10,000,000 bytes, and the ~5% gap between those catches people out.
- 10 MB binary file — exactly 10,485,760 bytes
- 50 MB and 100 MB for the next common limits
- 20 MB JPG if your form only accepts images
Check that the limit is enforced before the whole file is uploaded. A client-side check gives fast feedback; the server must enforce it too (for example client_max_body_size in nginx, or your framework’s body limit). A 1 GB upload that fails at 99% is a bad experience.
2. Empty files
A zero-byte file is valid on disk but often breaks code that expects content: image decoders throw, hash functions return the empty-string digest, and some storage SDKs reject empty bodies. Your app should reject it with a clear message, not a 500 error.
- Zero-byte JPG, PDF and CSV
3. Validate content, not the extension
Extensions and the browser-supplied Content-Type are both controlled by the user. Check the file’s magic bytes on the server, and serve stored files with a fixed, safe Content-Type plus X-Content-Type-Options: nosniff.
- HTML file named .jpg — must never be served as HTML
- PNG named .jpg — harmless, but your thumbnailer should still cope
- Double extension
sample.php.jpg— catches validators that split on the first dot
4. File names
Never use the uploaded file name as a storage path. Generate your own key and keep the original name only as metadata. Then test how that name is displayed and sent back in Content-Disposition:
5. Progress and interruptions
Use a large file on a throttled connection (Chrome DevTools → Network → Slow 4G) to check that the progress bar moves smoothly, cancel works, and a dropped connection produces a retry or a clear error. The 100 MB and 500 MB files are random data, so HTTP compression can’t shrink them and the progress you see is real.
Quick reference
See the upload limits use case for all of these files on one page.