emiliogrv/nova-batch-load
A Laravel Nova tool for batch-loading records into your admin panel. Upload data, map fields, validate entries, and process imports in chunks with progress feedback, making bulk data entry faster and safer.
emiliogrv/nova-batch-load is a small Nova add-on that gives you a UI-driven XLS/CSV importer you can attach to any resource. Instead of building a bespoke upload screen, you get a Nova action that opens a wizard: pick a file → map spreadsheet columns to model attributes → import.
Minimal steps:
Install the package:
composer require emiliogrv/nova-batch-load
Nova packages are auto-discovered; if you are on an older Nova you may need to register the service provider manually. Check the composer.json of the package for extra.laravel.providers.
Publish any assets/config if the package ships them:
php artisan vendor:publish --provider="Emiliogrv\NovaBatchLoad\NovaBatchLoadServiceProvider"
(Confirm the exact provider name from the package source — it may differ.)
Attach the action to the resource you want to import into:
use Emiliogrv\NovaBatchLoad\BatchLoad;
public function actions(Request $request)
{
return [
(new BatchLoad),
];
}
Visit the resource index, select the action, upload a small test file, and run the mapping step.
First use case: a "Contacts" resource where ops teams need to bulk-create rows from a spreadsheet once a week. You do not want to hand-build a parser + mapping + validation UI for that — attach the action, map email/name/phone, and you're done.
Where to look first:
README.md for the current action class name and any options (it has changed across versions).vendor/emiliogrv/nova-batch-load/src/ — the importer/action classes are where the mapping and persistence logic lives.vendor/emiliogrv/nova-batch-load/resources/js/ — the Vue wizard, if you need to understand the mapping UI behavior.maatwebsite/excel (or similar) — that's what actually reads the file and drives date/number formatting quirks.Attach per-resource, not globally. The importer is resource-scoped because it maps columns onto a specific Eloquent model. Put it in the actions() array of the target resource so it does not leak into unrelated areas.
Standard CRUD import pattern. Typical setup:
public function actions(Request $request)
{
return [
(new BatchLoad)->canSee(function ($request) {
return $request->user()->hasRole('admin');
}),
];
}
Use canSee/canRun to restrict it — bulk imports are destructive-ish operations and deserve the same gating as forceDelete.
Mapping strategy. The wizard lets end users map spreadsheet headers to model columns. Keep the spreadsheet headers close to your column names (first_name, last_name) so mapping is obvious and the same template can be reused. For fields that can't be mapped (computeds, relations), post-process in a model observer or in an Import subclass — see extension points below.
Validate before you persist. Because the package generally hands you rows to persist, don't trust the spreadsheet. Add model-level $rules or a saving hook, or wrap imports in your own Import class that validates each row and rejects the whole batch on failure. Fail-fast on the header row is much cheaper than discovering a bad column after 5,000 inserts have run.
Large files: expect synchronous behavior. Small packages like this usually parse in-request. For anything over a few hundred rows, either:
handle/import step with a queued job that re-reads the uploaded file and dispatches row batches.Given the 16-star maturity level, do not assume chunking or queueing exists. Verify in the source.
Idempotency. For repeat imports (e.g. nightly sync files), use updateOrCreate in your persistence layer keyed on a natural column (email, SKU) rather than blind create. The package likely calls create; override this by subclassing the importer or by hooking Model::saving.
Integration tip — use it as a bootstrap, not a black box. The most valuable pattern with a package this small is to vendor it into a thin wrapper action of your own that extends its importer and adds domain rules (tenant scoping, audit logging, notifications). That keeps the mapping UI but gives you control over persistence.
Header/mapping fragility. Column mapping is usually string-matched against spreadsheet headers. BOM-prefixed CSVs (Excel's default "CSV UTF-8") will break the first header — strip the BOM before import or ask users to save as UTF-8 without BOM. Trailing spaces and case differences also bite; normalize headers in the mapping step.
Date and number locale. Underlying Excel libraries parse dates lazily. A column that looks like 2024-01-05 in Excel may arrive as a serial number or as 01/05/2024 depending on the user's locale. Don't rely on the library — cast dates in your importer with an explicit format, and consider asking users to format date columns as ISO 8601 in the template.
Memory. A 10MB XLSX can balloon to hundreds of MB when fully loaded, and a synchronous Nova request will hit memory_limit or max_execution_time with no useful error in the UI. Test with a realistic file early. If you can't queue it, at least bump limits in config/laravel-excel.php or your importer and log the row count.
Silent failures / error reporting. Small importers often catch row-level exceptions and continue. The result is a "success" toast with half the rows missing. Add explicit logging in your importer, surface per-row failures to the user (e.g. flash a summary, write a failed_imports log), and make the first run on production be a test file.
Transactions. Wrap the import in DB::transaction() so a failed batch doesn't leave the table in a half-imported state. But be aware that this can hold locks on large imports — for big files, prefer batched transactions with an "import run" record you can roll back by ID.
No file validation by default. Verify MIME/extension/size in your action before delegating. Users upload .xlsm, .html-disguised-as-.xls, and files named .xlsx that are actually CSVs — lock the accepted types down.
Nova version drift. With few stars, this package is likely behind on major Nova releases. Before committing, check the package's composer.json against your Nova version, and confirm the action still renders under Nova 4/5. The Vue wizard is the most likely thing to break on upgrade.
Extension points worth checking in source: whether the action exposes an importClass/importer override, whether it supports updateOrCreate via a lookup column, whether there's a config file for the mapping presets, and whether the importer dispatches events (you can hook RowImporting/RowImported style events to add validation or audit logging without forking).
When not to use it. If the import is one-off, scripted, or requires complex relations (nested hasMany, polymorphics), a short artisan command using maatwebsite/excel directly is simpler and easier to test than bending this Nova action. Use nova-batch-load when the mapping UI is the value — i.e. non-developers doing recurring, column-simple imports.
How can I help you explore Laravel packages today?