ferdiunal/laravel-nova-media-field
A Laravel Nova media field for uploading, previewing, and managing images and files directly within your resources. Adds a media picker to Nova forms, streamlining media attachment and file handling in the admin panel without custom frontend work.
Before touching the field, make sure the substrate is in place — the field is a thin Nova UI over Spatie’s package, not a replacement for it.
composer require spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate
composer require ferdiunal/laravel-nova-media-field
Then publish assets if the package ships them:
php artisan vendor:publish --tag=laravel-nova-media-field-assets
php artisan nova:publish
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Post extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('cover')->singleFile();
$this->addMediaCollection('gallery');
}
}
use Ferdiunal\LaravelNovaMediaField\MediaField;
public function fields(Request $request)
{
return [
MediaField::make('Cover', 'cover')
->collection('cover')
->singleFile(),
MediaField::make('Gallery', 'gallery')
->collection('gallery')
->multiple(),
];
}
Where to look first: open vendor/ferdiunal/laravel-nova-media-field/README.md and, more importantly, src/ — with 12 stars and a single 2024-09 release, the source is the documentation. Skim the field’s src/MediaField.php for the actual builder methods before assuming an API; the collection/preview method names are the first thing to confirm.
First real use case: a single cover image on a resource. Get that working end to end (upload in Nova → row appears in media table → URL renders on the frontend) before attempting multiple collections or conversions.
Single vs. multiple collections. Mirror Spatie semantics in the field: call ->singleFile() in registerMediaCollections() and ->multiple() on the Nova field for galleries. Mismatched cardinality is the most common cause of “upload succeeds but field shows nothing” — Spatie silently keeps only the newest file for a singleFile() collection while the UI expects a list.
Preview via conversions, not originals. Register a small conversion in the model and point the field’s preview at it:
$this->addMediaConversion('thumb')->width(300)->height(300)->nonQueued();
Queued conversions produce broken thumbnails in Nova until the queue worker runs — use nonQueued() for thumbnails, keep heavy conversions queued.
Validation belongs in rules(), not Spatie settings.
MediaField::make('Gallery', 'gallery')
->collection('gallery')
->multiple()
->rules('array', 'max:5')
->rules(['gallery.*' => 'image|max:5120']);
Spatie’s acceptsMimeTypes is a storage constraint, not a user-facing validation error; the two should agree, but the Nova rules() array is what actually rejects the upload.
Read side stays pure Spatie. Don’t reach for package helpers in your app code:
$url = $post->getFirstMediaUrl('cover', 'thumb');
$all = $post->getMedia('gallery');
$srcset = $post->getFirstMedia('cover')?->getSrcset('thumb');
This keeps you portable if the field is ever swapped out — the media library is the durable layer.
Conditional display. Standard Nova field modifiers work: ->onlyOnDetail(), ->readonly(), ->hideWhenCreating(). Use ->readonly() in combination with the field for admin-only management of media that editors shouldn’t overwrite.
Disk and directory control. Set the disk in the model’s collection config ($this->addMediaCollection('gallery')->useDisk('s3')), not on the field. Mixing the two produces uploads that write to the field’s disk but generate URLs against the collection’s disk.
Resource form layout. Media fields are tall; place them at the end of the form or in a Panel with ->columns(1) so the file grid doesn’t crush neighbouring inputs on narrow viewports.
composer.json ("ferdiunal/laravel-nova-media-field": "1.0.*") and add a test that renders the resource form so a Nova upgrade breaks CI rather than production.composer.json require block against your Nova major before upgrading. If it caps at Nova 4, you’re frozen there.composer update, php artisan nova:publish and npm run build (or the vendor publish tag) is the first fix, not a code change.deletePreservingMedia() or soft-delete, orphaned files accumulate in storage. Schedule a cleanup command.HasMedia is easy to forget on related models. A field rendering on a BelongsTo-adjacent resource (e.g. editing a Profile from User) will fail silently if the underlying model doesn’t implement the interface.storage/logs/laravel.log for Spatie exceptions (FileDoesNotExist, DiskDoesNotExist). Most failures are disk misconfiguration (FILESYSTEM_DISK vs. the collection’s useDisk()) rather than the field itself.php artisan storage:link must be run in every environment (including containers/CI) or preview URLs 404 while uploads "work" — a classic false-positive.registerMediaCollections() / registerMediaConversions() on the model and a custom MediaPathGenerator for directory layouts. Overriding the field’s Vue component is possible via field.meta but undocumented — avoid it.How can I help you explore Laravel packages today?