moonshine/import-export
Import/export companion for MoonShine admin panels: add CSV and Excel import/export actions to Laravel resources, map columns, validate rows, and handle large files in background queues with minimal configuration.
Confirm your runtime before requiring the package. As of 2.1.0 the package requires PHP 8.3+ — PHP 8.2 was dropped as a hard breaking change, and PHP 8.5 is now supported. Check your app's composer.json require.php constraint, your local/Herd binary, and your CI matrix (including any shivammathur/setup-php pins) before bumping:
php -v
composer require moonshine/import-export:^2.1
If you're still on 8.2, either upgrade PHP first or stay pinned to the last 2.0.x release; Composer will refuse to install 2.1.0 on an 8.2 platform.
Require the package alongside MoonShine v3 (the package targets the v3 handler pipeline — confirm the exact MoonShine and PHP constraints in its composer.json before installing):
composer require moonshine/import-export
Service provider should auto-register; no publish step is usually required, but check the README for any config/vendor:publish tag.
Read the source first, not the README. With a small, single-maintainer package, the README lags the code. Start at src/ and look for the handler classes (typically ImportHandler / ExportHandler extending MoonShine's Handler/ResourceHandler base). Those constructors tell you the real API — field list, disk, filename, format.
First use case — export a resource from the admin UI. Register the export handler on the resource:
// app/MoonShine/Resources/UserResource.php
public function handlers(): array
{
return [
...parent::handlers(),
new ExportHandler(),
];
}
You should now see an export action on the resource index. Verify it produces a file on the configured disk before you customize anything.
Then do the import path symmetrically — add ImportHandler to the same resource and upload a CSV exported in step 3. Round-tripping your own export is the fastest smoke test that column mapping, types, and encodings line up.
Per-resource, declarative handlers. The dominant pattern is registering handlers on a MoonShineResource, which gives you an admin-facing button with no extra controller code. Keep import and export handlers on the same resource so the column schema lives in one place.
Column/field mapping. Most likely the handler takes an array of field identifiers (or Field instances). Define one mapping used by both import and export so the formats never drift:
$fields = ['id', 'name', 'email', 'created_at'];
new ExportHandler(fields: $fields, disk: 'local', filename: 'users.xlsx');
new ImportHandler(fields: $fields, disk: 'local');
Custom value resolution for relationships / computed columns. For belongs-to relations you’ll typically need a closure or column alias that resolves the relation to a scalar on export and back to a foreign key on import (email → user_id). If the package exposes callbacks, wire them per-field; otherwise subclass the handler and override the row transform.
Round-trip as the contract. Treat the export output as the import schema. Add a header row, freeze column order, and pin date formats. This lets support staff export, edit in Excel, re-import — the single most common day-to-day workflow for admin panels.
Job-backed for large datasets. Do not run imports on the HTTP request once you cross a few thousand rows. Wrap the import in a queued job that instantiates the same handler and points at the uploaded file path on disk, so the logic stays in one place and only the transport moves to the queue.
Cross-resource handler reuse. Because a handler is just a class, instantiate it with different field arrays on different resources (e.g. a shared ExportHandler for users, orders, invoices) rather than writing per-resource subclasses unless you need custom transforms.
Integrate with MoonShine notifications. After import, surface success/failure counts through MoonShine’s MoonShine::notification() / flash layer so admins get row-level feedback instead of a silent redirect.
config.platform.php override in composer.json (a stale platform pin will happily install 2.1.0 into an app that actually runs 8.2 and then fatal at runtime).max_execution_time / memory_limit in the job, not the web request.DB::transaction only if the file is small; for large files use chunked inserts with per-chunk validation and collect failed rows, otherwise one bad cell rolls back thousands of good ones.;.2025-12-29 into a serial number and strips leading zeros from IDs and phone numbers. Either force text columns in the export or parse defensively on import.finally block after import and put a TTL/cleanup on exported files; admin disks fill up quickly if exports are left behind.local disk that isn’t the public one is the usual “export produced nothing” cause.handlers(); append with ...parent::handlers() so you don’t silently disable authorization or other resource handlers."moonshine/import-export": "^2.1") and read the changelog on every bump — the maintainer ships requirement-level breaking changes (as 2.1.0 did for PHP) without a major version bump. Skim internal handler signatures too: those are the likeliest thing to break on a MoonShine major upgrade.getFields() method rather than forking the package — it keeps you on upstream bug fixes and makes the next PHP-version bump someone else's problem.How can I help you explore Laravel packages today?