aditkuma / laravel-bulk-upload
Queue-backed bulk CSV/Excel upload pipeline for Laravel - any columns, any row count - with a live processed/failed/in-progress/remaining progress dashboard (WebSocket broadcast + polling fallback).
Requires
- php: ^8.1
- illuminate/broadcasting: ^9.0
- illuminate/database: ^9.0
- illuminate/http: ^9.0
- illuminate/queue: ^9.0
- illuminate/support: ^9.0
- phpoffice/phpspreadsheet: ^1.29 || ^2.0 || ^5.0
- pusher/pusher-php-server: ^6.0 || ^7.0
README
Queue-backed bulk CSV/Excel upload pipeline for Laravel 9 - any columns, any
row count - with a live processed/failed/in-progress/remaining progress
dashboard. Pairs with the
Angular component
(@aditkuma/ngx-bulk-upload), but the four REST endpoints work with any
frontend.
How it works
POST /api/uploadsstores the file and dispatchesAnalyzeUploadJob, returning auuidimmediately - the HTTP request never waits on parsing.AnalyzeUploadJobstreams just the header row and a row count (PhpSpreadsheet'slistWorksheetInfo+ a read-filteredload()), then splits the data rows into fixed-size chunks and dispatches them as aBus::batchofProcessUploadChunkJob.- Each
ProcessUploadChunkJobreads only its own row window (bounded memory regardless of file size), validates rows, and atomically updates the parentUpload's counters - safe under many concurrent queue workers. FinalizeUploadJobruns once, right after the batch finishes, setting the final status.- Every chunk (and the final job) broadcasts on the public channel
upload.{uuid}- if you don't configure a broadcaster, this fails silently (seeSafeBroadcaster) and pollingGET /api/uploads/{uuid}still works.
Row-level validation is behind the RowProcessor interface (default:
GenericRowProcessor, which only rejects entirely-blank rows, plus a
CSV/TXT-only ragged-column check inside ProcessUploadChunkJob - a sparsely
filled Excel row is normal and is never flagged). Bind your own
RowProcessor to add domain-specific processing (e.g. inserting into your
own tables) without touching the upload pipeline itself:
$this->app->bind( \Aditkuma\LaravelBulkUpload\Contracts\RowProcessor::class, \App\Uploads\MyRowProcessor::class );
Install
composer require aditkuma/laravel-bulk-upload
The service provider is auto-discovered. Then:
php artisan vendor:publish --tag=bulk-upload-config # optional, to customize
php artisan migrate
Requirements
- Laravel 9, PHP 8.1+
- A queue connection that supports
Bus::batch()(thedatabasedriver works out of the box - just make sure you've runphp artisan queue:table,php artisan queue:failed-table, andphp artisan queue:batches-tableat some point, since batching needs thejob_batchestable) - A running queue worker (
php artisan queue:work) - nothing processes without one - (Optional) A Pusher-protocol broadcaster (e.g. self-hosted Soketi) for realtime updates - omit entirely and the frontend's polling fallback still works
Configuration
config/bulk-upload.php (env vars in parens):
| Key | Default | Purpose |
|---|---|---|
chunk_size (BULK_UPLOAD_CHUNK_SIZE) |
500 |
Rows per queued job |
max_file_size_kb (BULK_UPLOAD_MAX_FILE_SIZE_KB) |
512000 |
Server-side upload size limit |
allowed_extensions |
csv,txt,xls,xlsx |
Accepted file types |
disk (BULK_UPLOAD_DISK) |
local |
Filesystem disk (from config/filesystems.php) for stored source files |
register_routes (BULK_UPLOAD_REGISTER_ROUTES) |
true |
Set false to wire up UploadController yourself |
route_prefix (BULK_UPLOAD_ROUTE_PREFIX) |
api |
Prefix for the package's routes |
Routes
POST {prefix}/uploads- multipart upload, returns{ uuid }GET {prefix}/uploads/{uuid}- progress snapshot (also the polling endpoint)GET {prefix}/uploads/{uuid}/errors?page=&per_page=- paginated failed rowsGET {prefix}/uploads/{uuid}/errors/export- CSV download of all failures
Broadcasting
If BROADCAST_DRIVER=pusher is configured (pointed at Soketi or Pusher
Cloud), UploadProgressUpdated broadcasts on public channel upload.{uuid}
with event name progress.updated, payload matching the polling endpoint's
JSON shape.
Development
git clone https://github.com/aditkuma/laravel-bulk-upload.git
cd laravel-bulk-upload
composer install
There's no bundled test Laravel app in this repo - point a real app's
composer.json at this directory with a local path repository to iterate
against it.