Navigation

Laravel

How to seed a database from a CSV file

Import CSV data into Laravel database in 2025 using seeders and League CSV. Handle large files, validation, and performance optimization for data imports.

Table Of Contents

Laravel's Built-in Solution: Custom Seeders

Got a CSV file with thousands of records to import? Laravel seeders combined with CSV parsing make this painless:

// database/seeders/UsersFromCsvSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use League\Csv\Reader;

class UsersFromCsvSeeder extends Seeder
{
    public function run()
    {
        // Read CSV file
        $csv = Reader::createFromPath(database_path('seeders/data/users.csv'), 'r');
        $csv->setHeaderOffset(0); // First row contains headers
        
        $records = $csv->getRecords();
        
        foreach ($records as $record) {
            DB::table('users')->insert([
                'name' => $record['name'],
                'email' => $record['email'],
                'email_verified_at' => now(),
                'password' => bcrypt($record['password'] ?? 'password'),
                'created_at' => now(),
                'updated_at' => now(),
            ]);
        }
    }
}

Install League CSV for better CSV handling:

composer require league/csv

Performance-Optimized CSV Import

For large CSV files, use chunking and batch inserts to avoid memory issues:

// High-performance CSV seeder
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use League\Csv\Reader;

class LargeDatasetSeeder extends Seeder
{
    public function run()
    {
        $csv = Reader::createFromPath(storage_path('app/import/products.csv'), 'r');
        $csv->setHeaderOffset(0);
        
        $batchSize = 1000;
        $batch = [];
        
        foreach ($csv->getRecords() as $record) {
            $batch[] = [
                'name' => $record['product_name'],
                'description' => $record['description'],
                'price' => (float) $record['price'],
                'category_id' => $this->getCategoryId($record['category']),
                'created_at' => now(),
                'updated_at' => now(),
            ];
            
            // Insert in batches for better performance
            if (count($batch) >= $batchSize) {
                DB::table('products')->insert($batch);
                $batch = []; // Reset batch
                
                $this->command->info("Imported batch of {$batchSize} records");
            }
        }
        
        // Insert remaining records
        if (!empty($batch)) {
            DB::table('products')->insert($batch);
            $this->command->info("Imported final batch of " . count($batch) . " records");
        }
    }
    
    private function getCategoryId($categoryName)
    {
        // Cache categories to avoid repeated queries
        static $categories = null;
        
        if ($categories === null) {
            $categories = DB::table('categories')->pluck('id', 'name')->toArray();
        }
        
        return $categories[$categoryName] ?? 1; // Default category
    }
}

Advanced CSV Import with Validation

Add data validation and error handling for production imports:

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use League\Csv\Reader;

class ValidatedCsvSeeder extends Seeder
{
    public function run()
    {
        $csv = Reader::createFromPath(database_path('seeders/customers.csv'), 'r');
        $csv->setHeaderOffset(0);
        
        $successCount = 0;
        $errorCount = 0;
        $errors = [];
        
        DB::transaction(function () use ($csv, &$successCount, &$errorCount, &$errors) {
            foreach ($csv->getRecords() as $offset => $record) {
                // Validate each record
                $validator = Validator::make($record, [
                    'name' => 'required|string|max:255',
                    'email' => 'required|email|unique:users,email',
                    'phone' => 'nullable|string|max:20',
                    'city' => 'required|string|max:100',
                ]);
                
                if ($validator->fails()) {
                    $errorCount++;
                    $errors[] = "Row " . ($offset + 2) . ": " . implode(', ', $validator->errors()->all());
                    continue;
                }
                
                try {
                    DB::table('users')->insert([
                        'name' => $record['name'],
                        'email' => strtolower(trim($record['email'])),
                        'phone' => $record['phone'] ?? null,
                        'city' => $record['city'],
                        'email_verified_at' => now(),
                        'password' => bcrypt('default_password'),
                        'created_at' => now(),
                        'updated_at' => now(),
                    ]);
                    
                    $successCount++;
                    
                } catch (\Exception $e) {
                    $errorCount++;
                    $errors[] = "Row " . ($offset + 2) . ": Database error - " . $e->getMessage();
                }
            }
        });
        
        // Report results
        $this->command->info("Import completed:");
        $this->command->info("✅ Successful: {$successCount} records");
        $this->command->info("❌ Failed: {$errorCount} records");
        
        if (!empty($errors)) {
            $this->command->error("Errors encountered:");
            foreach (array_slice($errors, 0, 10) as $error) { // Show first 10 errors
                $this->command->error($error);
            }
        }
    }
}

Run your CSV seeder:

# Run specific seeder
php artisan db:seed --class=UsersFromCsvSeeder

# Run all seeders
php artisan db:seed

# Fresh migration + seeding
php artisan migrate:fresh --seed

CSV structure example (database/seeders/data/users.csv):

name,email,phone,city
John Doe,john@example.com,+1234567890,New York
Jane Smith,jane@example.com,+0987654321,Los Angeles

For production imports, always use transactions, validate data, handle errors gracefully, and process large files in chunks. Consider using upsert() for handling duplicate records or Laravel Excel package for more complex spreadsheet imports.

Related: Laravel Collections: Beyond Basic Array Operations | Database Design: Fundamentals of Good Database Architecture | Database Design Patterns: Complete Guide 2025 | Laravel Factories and Seeders: Test Data the Right Way | Database Design: Fundamentals of Good Database Architecture

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Laravel