bfinlay/laravel-excel-seeder
Seed Laravel databases directly from Excel spreadsheets. Integrates with Laravel Excel to import rows into Eloquent models, making it easy to populate tables from .xlsx or .csv files during development, testing, or setup.
bfinlay/laravel-excel-seeder lets Laravel's seeder system read .xlsx, .xls and .csv files and insert the rows into tables — no hand-written DB::table()->insert() arrays. It's the pragmatic bridge for teams where the "source of truth" for reference data (countries, tax rates, product catalogues, chart of accounts) lives in a spreadsheet maintained by non-developers.
Minimal steps:
composer require bfinlay/laravel-excel-seeder — this pulls in PhpSpreadsheet for reading files and doctrine/dbal for schema/column introspection.php artisan vendor:publish --tag=spreadsheet-seeder (or the provider's config tag) to get config/spreadsheet-seeder.php. Read this file first — it is the real documentation: paths, file extensions, sheet-to-table naming, chunk size and per-column options all live here.snake_case, as they appear in the migration).use Bfinlay\SpreadsheetSeeder\SpreadsheetSeeder;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call(SpreadsheetSeeder::class);
}
}
php artisan migrate:fresh --seed.First use case to try: a single small lookup table seeded end-to-end. Confirm the column-name mapping and type coercion on that one table before pointing it at a directory of workbooks — that's where 90% of the surprises surface.
Directory-as-dataset. The natural workflow is one seeder call over a folder of workbooks, each sheet mapping to a table. This keeps seeding declarative and lets a business analyst add a new reference table by adding a sheet, provided the migration already exists. Wrap the call so migrate:fresh --seed produces a fully populated environment.
Ordering. Parent tables must load before children or FK constraints trip. Either prefix filenames with a numeric ordering hint, or split into multiple seeder classes (ReferenceSeeder, CatalogueSeeder) and $this->call([...]) them in dependency order — the explicit seeder-class approach is easier to reason about than filename conventions.
Two-tier seeding. Use Excel for low-volume, human-maintained reference data and factories for high-volume synthetic data. Mixing both in one DatabaseSeeder gives realistic demos without forcing analysts to generate 10k rows by hand.
One-off import command. For operational loads, don't route through db:seed; write a thin artisan command that accepts a path, runs the seeder against that specific file, and logs row counts. This is the pattern that survives contact with production: ad-hoc db:seed against a live database is a foot-gun.
Transactions and dry runs. Wrap bulk imports in DB::transaction() where the table size permits it, so a malformed row on line 4,000 doesn't leave a half-populated table. For large files, prefer an explicit row-count assertion and a --dry-run flag that parses and reports without writing.
Test fixtures. A small workbook committed to tests/Fixtures makes an effective fixture for integration tests of reporting/export features — you get a realistic dataset without a wall of array literals.
Doctrine DBAL 4 (v3.4.3). This release only widens the doctrine/dbal constraint — no code changes expected on your side. Two things to check when you bump:
doctrine/dbal ^3, Composer will keep you on dbal 3; you're not forced onto 4, you're merely allowed onto it. Nothing breaks by staying put.->change() support leaned on dbal 3, so a project mid-upgrade on older Laravel should confirm resolution before assuming compatibility.Pin ^3.4.3 once your test suite passes; the risk here is dependency-graph conflict, not behaviour change.
Silent column mismatches. If a header in the sheet doesn't match a real column, you get missing data or an SQL error rather than a helpful "column 'Foo Bar' not found in sheet X" message. Standardise headers to snake_case and add a check that dumps array_diff($headers, Schema::getColumnListing($table)) when onboarding a new workbook.
Sheet naming. Table lookup is by sheet name — trailing spaces, mixed case, or Products (final) will not resolve. Rename the tab, not the code.
Excel type coercion. This is the single biggest source of "the data is wrong but no error was thrown": leading zeros stripped from postcodes and account numbers, dates arriving as serial integers, 1 vs 1.0 vs "1". Format the columns as Text in the workbook and declare casts in the config; don't rely on one alone.
Phantom rows. Excel files routinely carry formatting out to row 10,000 and column ZZ. Empty-but-styled cells can produce rows of nulls or fail not-null constraints. Trim the used range before committing the file.
Memory. PhpSpreadsheet loads the sheet into memory. A 50MB xlsx can exceed a 512MB limit. Use the chunk-size setting, prefer CSV for large datasets (it streams far better), and raise memory_limit in the seeder invocation rather than globally.
Production safety. There is nothing stopping a sheet from truncating or duplicating a live table. Gate any run behind an environment check and default to insert-or-fail rather than truncate outside local/staging.
Extension points. Rather than editing vendor behaviour, subclass the package seeder to add pre/post hooks (audit logging, cache invalidation, updated_at stamping) and override config keys per-invocation. That keeps you upgrade-safe — relevant right now, since this release proves the package is still absorbing ecosystem churn.
How can I help you explore Laravel packages today?