
Fermin Perdomo
Full Stack Developer

Store and Process CSV Files in the Background Using Laravel Queues
Handling CSV file uploads is a common feature in many web applications, especially dashboards and admin panels. However, processing large CSV files during the request lifecycle can slow down your app. Instead, we can offload the heavy lifting to a background job using Laravel Queues.
In this article, you'll learn how to:
Upload and store a CSV file
Dispatch a job to process the file asynchronously
Save the data into a database
🛠 Prerequisites
Laravel 10+
Queue configured (e.g.
database
,redis
,sqs
)A simple database table to insert records
1️⃣ Set Up the Database
Let’s assume you’re importing users. Create a migration:
php artisan make:migration create_imported_users_table
// database/migrations/xxxx_xx_xx_create_imported_users_table.php
Schema::create('imported_users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
});
Run it:
php artisan migrate
2️⃣ File Upload Form
Create a simple blade file:
<!-- resources/views/upload.blade.php -->
<form action="{{ route('csv.upload') }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="csv" accept=".csv" required>
<button type="submit">Upload</button>
</form>
3️⃣ Routes and Controller
Create a controller to handle upload:
php artisan make:controller CsvUploadController
<?php
// app/Http/Controllers/CsvUploadController.php
use Illuminate\Http\Request;
use App\Jobs\ProcessCsv;
class CsvUploadController extends Controller
{
public function showForm()
{
return view('upload');
}
public function upload(Request $request)
{
$request->validate([
'csv' => 'required|file|mimes:csv,txt',
]);
$path = $request->file('csv')->store('csv_uploads');
ProcessCsv::dispatch($path); // Dispatch background job
return back()->with('message', 'CSV uploaded! Processing in background.');
}
}
Define the routes:
<?php
// routes/web.php
Route::get('/upload', [CsvUploadController::class, 'showForm']);
Route::post('/upload', [CsvUploadController::class, 'upload'])->name('csv.upload');
4️⃣ Create the Job to Process CSV:
php artisan make:job ProcessCsv
<?php
// app/Jobs/ProcessCsv.php
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use App\Models\ImportedUser;
class ProcessCsv implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
protected $path;
public function __construct($path)
{
$this->path = $path;
}
public function handle()
{
$file = Storage::get($this->path);
$lines = explode(PHP_EOL, $file);
$header = null;
foreach ($lines as $line) {
if (empty(trim($line))) continue;
$data = str_getcsv($line);
if (!$header) {
$header = $data;
continue;
}
$userData = array_combine($header, $data);
// Basic validation can be added here
ImportedUser::create([
'name' => $userData['name'] ?? '',
'email' => $userData['email'] ?? '',
]);
}
}
}
5️⃣ Set Up Queue
If using database
driver, configure .env
:
QUEUE_CONNECTION=database
Create the queue tables:
php artisan queue:table
php artisan migrate
Then run the queue worker:
php artisan queue:work
✅ Example CSV Format
Make sure your CSV is in this format:
name,email
Alice,[email protected]
Bob,[email protected]
You can follow the next steps to improve it by:
Adding
try/catch
in the jobLogging errors
Using Laravel’s
failed()
method on the job classAdding progress tracking in the database
🔁 Step 6: Add Progress Tracking
Create a new table to track each CSV upload's progress:
php artisan make:model CsvImport -m
<?php
// database/migrations/xxxx_xx_xx_create_csv_imports_table.php
Schema::create('csv_imports', function (Blueprint $table) {
$table->id();
$table->string('file_path');
$table->enum('status', ['pending', 'processing', 'completed', 'failed'])->default('pending');
$table->text('error_message')->nullable();
$table->timestamps();
});
Run the migration:
php artisan migrate
Update the upload method to create a tracking record:
<?php
// CsvUploadController.php
use App\Models\CsvImport;
public function upload(Request $request)
{
$request->validate([
'csv' => 'required|file|mimes:csv,txt',
]);
$path = $request->file('csv')->store('csv_uploads');
$import = CsvImport::create([
'file_path' => $path,
]);
ProcessCsv::dispatch($import->id);
return back()->with('message', 'CSV uploaded! Processing in background.');
}
🔄 Step 7: Update the Job for Safe Execution
Update ProcessCsv
to handle errors and update progress.
<?php
// app/Jobs/ProcessCsv.php
use App\Models\CsvImport;
use Illuminate\Support\Facades\Log;
class ProcessCsv implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
protected $importId;
public function __construct($importId)
{
$this->importId = $importId;
}
public function handle()
{
$import = CsvImport::findOrFail($this->importId);
$import->update(['status' => 'processing']);
try {
$file = Storage::get($import->file_path);
$lines = explode(PHP_EOL, $file);
$header = null;
foreach ($lines as $line) {
if (empty(trim($line))) continue;
$data = str_getcsv($line);
if (!$header) {
$header = $data;
continue;
}
$userData = array_combine($header, $data);
ImportedUser::create([
'name' => $userData['name'] ?? '',
'email' => $userData['email'] ?? '',
]);
}
$import->update(['status' => 'completed']);
} catch (\Exception $e) {
Log::error('CSV Processing Failed: ' . $e->getMessage());
$import->update([
'status' => 'failed',
'error_message' => $e->getMessage(),
]);
throw $e; // This will trigger the failed() method
}
}
public function failed(\Throwable $exception)
{
$import = CsvImport::find($this->importId);
if ($import) {
$import->update([
'status' => 'failed',
'error_message' => $exception->getMessage(),
]);
}
Log::critical('CSV Job failed: ' . $exception->getMessage());
}
}
🚀 Conclusion
Processing CSV files in the background using Laravel Queues ensures your application stays responsive and scalable. This technique is perfect for bulk imports and large data operations.