innopanda/laravel-asset-manager
A Livewire and Spatie Media Library asset manager for Laravel with a virtual folder tree, reusable picker modal, thumbnails, multi-select, file replacement, version history, and optional Filament form integration.
Install / upgrade
composer require innopanda/laravel-asset-manager
# or, for existing installs
composer update innopanda/laravel-asset-manager
Then, on a fresh install: publish the package's config/migrations/assets if the README calls for it (php artisan vendor:publish), run php artisan migrate, and clear caches (php artisan optimize:clear) after upgrading to v1.0.9 so the updated JS/CSS assets and Livewire component definitions are picked up.
Where to look first
AssetPicker field class — this is the public API you'll use in resource forms.MediaBrowser component — this is where selection state lives and where the picker hands off.First use case
Add a single-image field to an existing Filament resource, verify upload + selection end-to-end, then flip the same field to multiple selection for a gallery-style field. That two-step path exercises the whole flow (picker → browser → confirm → hydration) with minimal surface area, and it's the exact path v1.0.9 changed, so it doubles as your upgrade smoke test.
Single asset (the baseline)
Use AssetPicker as a form field in a Filament resource, backed by a nullable column. Persist the asset ID, not the URL:
AssetPicker::make('featured_image_id')
->label('Featured Image'),
Multiple assets
v1.0.9 makes the multi path first-class: persistent selection state, toggling, visual indication, and a payload of selected assets with IDs and URLs. Use it for galleries, repeaters of media, or "downloads" sections.
AssetPicker::make('gallery_ids')
->multiple() // confirm exact method name against the field class
->label('Gallery'),
Store the result in a JSON column ($casts = ['gallery_ids' => 'array']) or, for a normalized model, sync() the returned IDs into a pivot table.
Persist IDs, resolve URLs on render
The picker returns assets with both IDs and URLs — a convenience for client-side previews, not a storage format. Keep IDs in the database and resolve URLs at render time via your media model/disk. This survives disk changes, CDN migrations, and signed/temporary URL expiry.
Non-Filament Livewire usage
The MediaBrowser is a Livewire component, so you can mount it from any Livewire screen, not just Filament. The flow is: your component renders the picker trigger → the trigger dispatches an init/open event carrying the current selection → the browser manages the multi-select state → the browser dispatches a confirmation event with the chosen payload → your component listens with #[On('...')], updates its own public property, and closes the modal. Read the package's event names from the source rather than guessing; treat them as semi-public API.
Form hydration for edit screens
On edit, the picker must be seeded with existing selections before the browser opens. Hydrate from the model's stored IDs in the field's afterStateHydrated() / formatStateUsing() hook so the browser opens showing the current gallery checked — this is the most common "why is my gallery empty on edit" bug.
Testing
Livewire::test() the MediaBrowser directly: set the selection property, call the toggle method, assert the selection array matches, and assertDispatched() the confirmation event. For the Filament field, the cheapest reliable coverage is a feature test that saves a form with two assets and asserts both IDs land in the JSON column.
Logging while integrating
v1.0.9 adds detailed logging across the AssetPicker → MediaBrowser selection flow. Wire it up once while building out a new integration (tail the log during a click-through) and then dial the channel down for production — see Gotchas.
AssetPickers, scope the listener to a field identifier (or use #[On] with a unique event name per field instance) or the second browser will inherit the first's state.AssetPicker sits on top of Filament's form and Livewire; a Filament major upgrade can break the field before this package catches up. Pin innopanda/laravel-asset-manager in composer.json and test upgrades together.composer update alone won't refresh the served files. Check public/vendor/... (or your Vite build) and re-publish.Storage::disk(...)->url() on at render time must be the same, otherwise you'll get valid IDs with broken images. Centralize the disk in config and read it in one place.How can I help you explore Laravel packages today?