Background Import Platform
Build a platform in Laravel for importing large CSV files (hundreds of thousands of rows, such as a product catalog or customer list) in the background. Users upload a file, see a preview with column mapping, and start the import; it runs in queued jobs that process the file in chunks, validate every row, save good rows and record bad ones with the exact reason, and report progress live. If a worker crashes or the server restarts, the import resumes from the last finished chunk instead of starting over, and running an import twice never creates duplicates. At the end, users can download a file of rejected rows to fix and re-upload. You'll learn queues and job batching, chunked processing, validation at scale, resumable jobs, idempotent upserts, and testing background work.
- Estimate
- ~15h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
Laravel · Composer
Project roadmap
- 01
Design the import system
~2hWrite DESIGN.md: an import has a file, a column mapping, a status (uploaded, mapped, processing, completed, failed or cancelled), counters (rows processed, imported and rejected) and the last completed chunk. Each chunk is a job processing a range of rows; rejected rows are stored with their row number and errors. Imported records are matched by a natural key (such as SKU or email) and upserted. Describe what happens when a job fails or the server restarts. Done when: DESIGN.md covers states, chunks, errors and recovery.
- 02
Upload and map columns
~2hBuild an upload page accepting CSV files up to a size limit, stored with a random name. Read the header and the first 10 rows to show a preview, and let the user map file columns to fields (suggesting matches by name). Check that required fields are mapped before starting. Done when: a large CSV can be uploaded, previewed and mapped.
- 03
Process in chunks
~3hWhen the import starts, count the rows and dispatch a batch of jobs (Bus::batch), one per chunk of 1,000 rows. Each job reads only its rows (streaming the file with SplFileObject or League CSV), validates each row with Laravel's Validator, upserts valid rows by the natural key in one query (Model::upsert) inside a transaction, stores rejected rows with their errors, and updates the counters. Done when: a 200,000-row file imports completely in the background.
- 04
Make imports resumable
~2.5hRecord each finished chunk in a table, and make jobs skip chunks already finished, so retrying or re-dispatching a batch never processes rows twice. Configure job retries with backoff and a timeout. Add a command that finds imports stuck in processing (for example after a server restart) and dispatches their unfinished chunks. Allow cancelling an import. Done when: killing the queue worker mid-import and restarting it finishes the import with correct counts and no duplicates.
- 05
Show progress and results
~2hBuild an import page that shows the status, a progress bar and the counters, updating every few seconds (by polling, or with broadcasting). When finished, show a summary with the most common errors and a button to download the rejected rows as a CSV with an extra errors column, ready to fix and re-upload. Done when: progress updates live, and the rejected rows download works.
- 06
Add limits and cleanup
~1hLimit how many imports each user can run at once and the queue's concurrency, stop an import when the rejection rate is very high (for example the wrong file), and delete uploaded files and rejected rows after a set number of days with a scheduled command. Done when: limits apply, and old files are cleaned up.
- 07
Test it
~2.5hWrite feature tests with the queue running synchronously and small test files: column mapping, validation errors recorded with row numbers, upserts not duplicating on a second import, resuming after a simulated failure in the middle of a chunk, cancelling, and the rejected rows download. Done when: php artisan test passes.
Resources
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack