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 Nova Csv Import Laravel Package

simonhamp/laravel-nova-csv-import

A Laravel Nova package that adds CSV import support to your resources. Upload a CSV, map columns to model fields, validate rows, and import records directly from Nova with progress feedback and customizable import logic.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install (Nova 4/5, PHP 8+)

composer require simonhamp/laravel-nova-csv-import
php artisan vendor:publish --provider="SimonHamp\LaravelNovaCsvImport\CsvImportServiceProvider"

The publish step drops config/csv-import.php. Read that file first — it is the only knob you get for disk/location/delimiter behaviour, and everything else is configured per-action.

2. Register the action on the resource you want to import into

// app/Nova/User.php
use SimonHamp\LaravelNovaCsvImport\CsvImport;

public function actions(NovaRequest $request): array
{
    return array_merge(parent::actions($request), [
        (new CsvImport())
            ->canSee(fn ($request) => $request->user()->isAdmin())
            ->canRun(fn ($request, $model) => $request->user()->isAdmin()),
    ]);
}

3. First use case

Go to the resource index → tick any row (Nova needs a selection to show the action menu) → Import CSV → upload a file → map CSV headers to Nova fields → run. The first row of the file is treated as the header row, so keep column names human-readable (Email, Company ID) and do the mapping in the modal rather than renaming headers to match DB columns.

Where to look next: the CsvImport action class itself (it is small — fields(), handle(), and the import loop), the compiled Vue components under resources/js, and the config. Because there are effectively zero downstream dependents, reading the source is cheaper than searching for answers. Note that from v0.8.0 the package ships a Workbench harness (PR #77) — if you want to contribute, run the package's own sandbox via composer test/Workbench rather than wiring a throwaway Nova app.

Implementation Patterns

Scope the action, don't ship a global one. Attaching CsvImport to every resource means a support ticket every time someone imports a users.csv into Invoices. Either register it only on the resources that legitimately need bulk creation, or put it behind canSee plus a config flag so it can be turned off in production.

Map to Nova fields, not to DB columns. The package builds its mapping UI from the target resource's fields(). That means computed/onlyOnDetail fields, HasMany, and file fields are noise in the mapping dropdown. Trim the mapping surface by returning a reduced field set for the import action's target resource, or subclass the action and override the field list used for mapping.

Relationships: resolve keys before the import. BelongsTo columns are matched on the related model's key, not its display label. In practice you pre-process the CSV:

// users.csv has "Company" but invoices need company_id
$rows = collect($rows)->map(fn ($row) => [
    ...$row,
    'Company' => Company::where('name', $row['Company'])->value('id'),
])->all();

If you do this a lot, wrap it in a subclassed action so the transformation lives in one place rather than in a spreadsheet macro that someone forgets to run.

Subclass for repeatable imports. The highest-leverage pattern for day-to-day work is a thin subclass per import shape:

class ImportCustomers extends CsvImport
{
    public function name() { return 'Import Customers'; }

    public function handle(ActionFields $fields, Collection $models)
    {
        // custom validation, then delegate
        return parent::handle($fields, $models);
    }
}

This is also where you set defaults the package can't know about — created_by, tenant IDs from $request->user(), a status column, or normalising $1,234.56 → 1234.56, dates from d/m/Y, and Yes/No → 1/0.

Attribute ownership explicitly. Actions run with the authenticated user's context but the importer will not stamp audit columns for you. Anything like team_id, created_by, or source = 'csv' has to come from the action's fields() or a fillUsing hook on the Nova field.

Chunk large files. The default path is synchronous inside the action request. Files over a few thousand rows will hit max_execution_time and leave a half-imported table. Either validate row counts up front and reject oversized files, or move the parsing into a queued job and keep the Nova action as the file-upload/triage step.

Tests belong at the importer layer, not the Nova layer. Keep fixture CSVs in tests/Fixtures, call the import logic directly, and assert on the database. Asserting through the Nova action modal is brittle and slow.

Gotchas and Tips

  • BOM and encoding. CSVs exported from Excel are frequently UTF-8-with-BOM or Windows-1252. The BOM gets glued onto your first header (\uFEFFEmail) and the mapping silently fails. Strip it on ingest: ltrim($firstLine, "\xEF\xBB\xBF").
  • Delimiter mismatch. European Excel defaults to ;. Check config/csv-import.php for the delimiter setting before debugging "column count mismatch" errors.
  • Nova validation rules are not a guarantee. Don't assume the rules() from your resource fields are enforced row-by-row. Add explicit validation in the action (or a FormRequest-style check per row) and fail fast with a readable line number.
  • Unique constraints explode mid-run. A duplicate email on row 900 aborts the import with a raw QueryException and no rollback. Wrap the import in a transaction, or pre-validate the whole file against unique rules and reject it before writing anything.
  • Empty strings vs. null. Excel blanks arrive as ''. This breaks nullable unique indexes and date casts. Normalise to null in the action before the model is filled.
  • Storage and cleanup. Uploaded files land on the configured disk. Verify write permissions in non-local environments, and prune the directory on a schedule — otherwise /storage/app/csv-import grows forever with personally identifiable data in it.
  • Nova version drift. The package ships compiled assets and a Vue component tied to Nova's modal internals. v0.8.0 is the release that officially adds Nova 5 compatibility (PR #86) via the Workbench harness — upgrading from v0.7.x on a Nova 5 install is the change you actually want, but treat the modal as the risk surface on every Nova bump. Pin the compatible line in composer.json ("simonhamp/laravel-nova-csv-import": "0.8.*") and read the changelog on bump — with near-zero dependents, you are the QA.
  • canSee is not canRun. The action menu is client-side; set both. Importing is a write operation on arbitrary volume and deserves the same authorisation strictness as a mass-assignment endpoint.
  • Debugging tip: when the mapping dropdown is empty or wrong, it's almost always the target resource's fields() returning something unexpected for the current request — dump the field set in the action before chasing the Vue side.
  • Extension points worth knowing: override fields() to add import options (skip-first-row, update-existing, dry-run), override handle() to wrap in a transaction and custom validation, and override name() so multiple importers on one resource are distinguishable in the modal.
  • Contributing/testing: with Workbench in place (v0.8.0+), reproduce package bugs inside the package repo rather than a downstream Nova app — that's the fastest way to isolate whether a failure is the importer or your resource's fields().
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin