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).

Maintainers

Package info

github.com/aditkuma/laravel-bulk-upload

pkg:composer/aditkuma/laravel-bulk-upload

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-07-22 10:15 UTC

This package is auto-updated.

Last update: 2026-07-22 11:29:20 UTC


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

  1. POST /api/uploads stores the file and dispatches AnalyzeUploadJob, returning a uuid immediately - the HTTP request never waits on parsing.
  2. AnalyzeUploadJob streams just the header row and a row count (PhpSpreadsheet's listWorksheetInfo + a read-filtered load()), then splits the data rows into fixed-size chunks and dispatches them as a Bus::batch of ProcessUploadChunkJob.
  3. Each ProcessUploadChunkJob reads only its own row window (bounded memory regardless of file size), validates rows, and atomically updates the parent Upload's counters - safe under many concurrent queue workers.
  4. FinalizeUploadJob runs once, right after the batch finishes, setting the final status.
  5. Every chunk (and the final job) broadcasts on the public channel upload.{uuid} - if you don't configure a broadcaster, this fails silently (see SafeBroadcaster) and polling GET /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() (the database driver works out of the box - just make sure you've run php artisan queue:table, php artisan queue:failed-table, and php artisan queue:batches-table at some point, since batching needs the job_batches table)
  • 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 rows
  • GET {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.