Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Csv Seeder Laravel Package

jeroenzwart/laravel-csv-seeder

Laravel CSV Seeder is a lightweight package for populating database tables from CSV files. Create a seeder, point it at a CSV, and import rows with header handling and configurable delimiters. It’s a simple way to load fixtures, demo data, or bulk seed records.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm fit

composer require jeroenzwart/laravel-csv-seeder --dev

Drop --dev only if you seed reference data during production deploys. The last tagged release was 2023-09, so verify the package's composer.json constraints against your Laravel/PHP major before you build anything on it (if you're on Laravel 11/12, check that it installs without --ignore-platform-reqs).

Create a seeder

Generate a normal seeder and change the base class:

// database/seeders/CountrySeeder.php
namespace Database\Seeders;

use Jeroenzwart\LaravelCsvSeeder\CsvSeeder;

class CountrySeeder extends CsvSeeder
{
    public function __construct()
    {
        $this->table    = 'countries';
        $this->filename = database_path('seeders/data/countries.csv');
        $this->header   = true;        // first row is column names
        $this->delimiter = ',';
        $this->enclosure = '"';
        $this->escape    = '\\';
        $this->chunk     = 500;
        $this->timestamps = true;      // fills created_at/updated_at
    }
}

Run it

php artisan db:seed --class=CountrySeeder

Because it extends Laravel's Seeder, it composes normally in DatabaseSeeder::run() and works with db:seed, migrate:fresh --seed, and test-suite setup. That compatibility — not the CSV parsing — is the reason to use it: you get CSV ingestion without inventing a bespoke import command.

First use case to target: static/reference tables (countries, currencies, tax rates, permission catalogs) and local/CI fixtures. That's where the package's shape fits — a file in the repo, a seeder class, no runtime input.

Exact property names have been stable across 1.x but confirm against the README of the version you install; the package configures itself through public properties on the seeder rather than a published config file.

Implementation Patterns

Header-driven column mapping (the default pattern). With header = true, the first CSV row supplies column names and the rest are mapped positionally to them. Keep the header row identical to your schema column names — this is the contract that makes the package worth using, because column reordering in the file no longer breaks the seeder. Use header = false only for generated files where positional mapping is guaranteed by the producer.

Idempotent local resets. For dev/CI seeders, set $this->truncate = true so re-running gives a deterministic state. Pair it with php artisan migrate:fresh --seed rather than calling it ad hoc. Keep truncation out of any seeder you'd run against staging or production.

Hashed credentials from CSV. For fixture users, list the sensitive columns:

$this->hashable = ['password'];

The package hashes those cell values per row before insert, so your CSV can hold plaintext fixtures without shipping a factory that hashes them. Don't reuse this for real credentials — fixture CSVs are committed to the repo.

Chunked large imports. Set $this->chunk to something that keeps the insert statement under your driver's bind-parameter ceiling. For a 10-column table, 500 rows = 5,000 placeholders, comfortably under MySQL's 65,535 and Postgres' 32,767. The default is usually fine; tune only against a measured problem.

Composition in DatabaseSeeder. Order matters — parent tables before children, because the package does plain inserts and will trip foreign keys:

public function run(): void
{
    $this->call([
        CountrySeeder::class,
        UserSeeder::class,      // depends on countries
        OrderSeeder::class,
    ]);
}

Mixed strategy: CSV for volume, factories for graphs. CSV is terrible at representing relationships (you can't express "each order gets 3 random line items"). Use CSV seeders for wide, flat reference data and Laravel factories for anything relational or randomized. Splitting this way keeps the CSV files reviewable.

Test fixtures. Put seeders under tests/ and call them from a base TestCase after RefreshDatabase, or just from DatabaseSeeder if the dataset is small. This gives integration tests a realistic dataset without a pile of Model::create() calls.

Environment gating. If a seeder is destructive or data-heavy, guard it:

public function run(): void
{
    if (app()->environment('production')) {
        $this->command->warn('Skipping CSV seed in production.');
        return;
    }
    parent::run();
}

What NOT to use it for. It is not a runtime import path. There's no per-row validation, no error reporting back to a user, no queue integration, no progress tracking, no resumability. If a user uploads a spreadsheet, write a job that parses and inserts with validation and per-row error collection — the seeder's all-or-nothing-per-row-model semantics won't give you the failure detail a user needs.

Gotchas and Tips

Empty strings, NULLs, and type coercion. Everything from fgetcsv is a string. '' is not null. On strict-mode MySQL, an empty cell destined for an INT or DATE column will error rather than insert NULL. Either emit explicit NULL markers in the CSV and normalize them in an overridden run(), or make those columns nullable with defaults. Similarly, booleans (0/1), enums, and JSON all depend on the value being castable — the model's $casts aren't in play here, you're going through the query builder.

Headers must match exactly. A trailing space (id ,name), a BOM at file start (\xEF\xBB\xBFid), or a case difference (ID vs id) produces either an "unknown column" SQL error or, worse, a silently missing column. Strip BOMs on export and assert the header row in a test.

timestamps = true requires the columns to exist. If the table has no created_at/updated_at, the insert fails. If your migration uses custom names, the flag won't help you — set the values in the CSV instead.

truncate = true bypasses foreign-key checks only if you disable them. TRUNCATE on a referenced table fails on MySQL/Postgres. Either truncate children first, or wrap with Schema::disableForeignKeyConstraints() / enableForeignKeyConstraints() around the call — and always re-enable, including on exception, or you'll leave the connection in a surprising state for the rest of the seed run.

Chunk boundaries are where bugs live. v1.6.3 is specifically a fix for "rows in chunks into the database." Two consequences: (a) pin the version — "jeroenzwart/laravel-csv-seeder": "1.6.3" or ^1.6.3, never a floating constraint on a bulk-write path; and (b) add a regression test that seeds exactly chunk - 1, chunk, chunk + 1, and 0 rows and asserts the persisted row count. If you have historically seeded through ≤1.6.2 with chunking enabled, spot-check those tables — silent under-writes are the failure mode, not exceptions.

No atomicity across chunks (verify this on your version). If chunk 4 of 10 fails, chunks 1–3 are already committed. Wrap the seed in DB::transaction() if you need all-or-nothing — but understand the memory trade-off on large files, and confirm the package doesn't already open its own transaction (nested transactions use savepoints, which behave subtly in some drivers).

Delimiter and locale. European Excel exports default to ; with , as decimal separator. If numbers arrive as 1,50, they'll insert into a DECIMAL column as 1 (or fail). Fix at export time to . decimals, or preprocess.

Encoding. fgetcsv on a Windows-1252 file yields mojibake in your database. Normalize to UTF-8 with iconv or mb_convert_encoding before seeding, and set the CSV source of truth to UTF-8 without BOM.

Large files and memory. Streaming via fgetcsv keeps file memory flat, but each chunk is built as an array of rows before insert — so peak memory is roughly chunk × row width × rows in flight. Watch this with wide tables or large TEXT/JSON columns; lower chunk rather than raising memory_limit.

Debugging a bad seed. The package doesn't log per-row failures. When a seed fails, the exception is usually a raw QueryException whose message points at the failing statement, not the CSV line. To find the offending row, temporarily set $this->chunk = 1 and print the row index in an overridden loop, or run the seed against a SQLite copy and bisect the file. Add DB::listen(fn($q) => logger($q->sql)) if you need to see exactly what's being built.

Production safety. db:seed prompts for confirmation in production; --force bypasses it, and nothing in this package re-checks. If you must seed in production (reference data on deploy), gate it in your deploy script and prefer insertOrIgnore-style semantics — the package does plain inserts and will throw on unique-constraint collisions, which is at least a safe failure but will fail a deploy mid-run.

Extension points. Because it's a class you extend, the honest escape hatch is overriding run() or the parsing/writing step. Keep such overrides thin and covered by a test — every override is a fork of behavior the next patch release may change, and this package's release notes are one line long (v1.6.3's entire changelog is "Fix for the rows in chunks into the database"), so read the diff, not the note, before upgrading.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource