stepanenko3/nova-media-field
Laravel Nova field for adding media uploads and management to Nova resources. Attach and preview images or files directly in the admin panel, with a simple, customizable field that streamlines media handling in Laravel apps.
Prerequisites. Laravel Nova (verify against your installed major version — the package's last release was March 2024) and spatie/laravel-medialibrary already installed and migrated on the target model. The field is only a UI shell over medialibrary; nothing works until the model is a proper media host.
composer require stepanenko3/nova-media-field
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Post extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('cover')
->singleFile()
->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp']);
}
}
use Stepanenko3\NovaMediaField\MediaField;
public function fields(Request $request)
{
return [
MediaField::make('Cover')
->collection('cover')
->disk('public')
->accept('image/*')
->rules('max:4096'),
];
}
Method names (
collection,disk,accept,single,maxFiles,preview) are the ones this package family exposes; confirm exact casing against the README before you build a form around them.
php artisan storage:link
First use case to ship: a single cover/avatar image on one resource. It exercises the full loop — upload → attach to collection → render preview in index/detail → read back with $model->getFirstMediaUrl('cover'). Once that path is green, multi-file galleries and conversions are incremental.
Where to look first: the README's usage example, then the field's PHP class (src/) to confirm which fluent setters actually exist, then your model's registerMediaCollections(). Bugs here are almost always a mismatch between #2 and #3, not the package.
One field per collection. Don't try to drive multiple collections from a single field. Build a gallery resource with cover (single) and gallery (multi) as two separate fields and two separate addMediaCollection calls.
Separate form and display concerns. Uploads belong on forms; previews belong on index/detail. Compose the field twice rather than one field doing both:
MediaField::make('Cover')->collection('cover')->disk('public')
->onlyOnForms(),
MediaField::make('Cover')->collection('cover')
->preview(fn ($value, $resource) => $resource->getFirstMediaUrl('cover', 'thumb'))
->exceptOnForms(),
Pair with conversions, not originals. Register a thumb conversion on the model and reference it in the index preview. Listing 20 rows that each pull a 4MB original is the classic Nova slowdown:
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb')->width(200)->height(200)->nonQueued();
}
Let medialibrary own the read path. The Nova field is an admin input, not an API. Everywhere else — Blade, JSON resources, sitemaps — read via getFirstMediaUrl('cover', 'thumb') or getMedia('gallery'). This keeps your front end working if you later swap or drop the field.
Multi-file galleries. Use a multi-file field on a non-single collection, and add ordering support on the model side (order_column) if the UI exposes drag-to-reorder. Treat the media order as data: if order matters publicly, expose it and assert it in a test.
Validation belongs on the field and the collection. ->accept() is a picker hint; ->rules('mimes:jpeg,png,webp', 'max:4096') is the enforcement, and acceptsMimeTypes() on the collection is the backstop for programmatic uploads. Set all three consistently or you'll get 500s from medialibrary on files the browser happily allowed.
Testing strategy. Test the media layer, not the Vue component. Use Storage::fake('public') and UploadedFile::fake()->image('cover.jpg'), attach via $post->addMedia(...)->toMediaCollection('cover'), assert getFirstMediaUrl and that the conversion exists. Exercise the Nova field itself with a single manual or Dusk pass per release; field-level feature tests have poor payoff-to-effort here.
cover when the model registers covers renders empty with no error. If the field is blank on a record that has media, diff the strings first.storage:link or wrong disk. disk('public') on the field must match the collection's useDisk()/filesystem config. Mismatch produces broken previews while the upload "succeeds."nonQueued() the small thumbnails or ship a placeholder fallback in preview().media table for records whose model_id no longer resolves before you assume deletion is complete.->readonly() via canSee/dependsOn), remember attachments won't be written — a common "it works for admins but not editors" bug.composer.json, and do a real form save on upgrade — asset compilation and field-registration changes usually surface there, not in tests.vendor/. Keep that subclass in app/Nova/Fields/ so composer update can't eat it.media table row exists → conversion exists → file exists at the generated URL. That sequence finds it in under two minutes every time.How can I help you explore Laravel packages today?