jameslkingsley/nova-media-library
A Laravel Nova integration for spatie/laravel-medialibrary that lets you upload, manage, and attach media to Eloquent models directly from Nova, with file previews and support for multiple media collections.
The package is a single Nova field (Jameslkingsley\NovaMediaLibrary\MediaLibrary) that renders a Spatie Media Library collection as an upload/preview widget and attaches uploads to the parent model. It is unmaintained (last release January 2019), so treat version selection as step one, not an afterthought.
1. Pin the stack before installing. The field was built for Nova 1.x/2.x and medailibrary 7.x on PHP 7.x. In composer.json you generally need something like laravel/nova: ^2.0, spatie/laravel-medialibrary: ^7.0, and jameslkingsley/nova-media-library: ^1.0. Installing it against Nova 4 or medialibrary 10 will either fail to resolve or produce a field whose Vue 2 assets never mount.
2. Install and publish the media migrations if you have not already:
composer require jameslkingsley/nova-media-library
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag=migrations
php artisan migrate
php artisan storage:link
3. Prepare the model. The field does nothing useful unless the model implements Spatie\MediaLibrary\HasMedia and uses InteractsWithMedia, and the collection it writes to must be registered:
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Post extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('images');
}
}
4. Add the field to the resource.
use Jameslkingsley\NovaMediaLibrary\MediaLibrary;
public function fields(Request $request)
{
return [
MediaLibrary::make('Images'),
];
}
5. Look here first when something breaks. Because the README is thin, the source of truth is vendor/jameslkingsley/nova-media-library/src/MediaLibrary.php (available field methods, default collection/disk) plus its routes/ and resources/js/ directory (the upload endpoint and what payload the Vue component posts). Read those two before guessing at method names.
First use case to prove out: a cover single-image collection on one model — upload through the field, confirm a media row appears, confirm the file lands on the configured disk, then confirm the same image renders on the detail view. If that loop works, the rest of the package is variations on it.
Collection-per-field. The cleanest pattern is one media collection per field, declared explicitly rather than relying on the field's default collection name:
MediaLibrary::make('Gallery')->collection('gallery')
and a matching addMediaCollection('gallery') on the model. This keeps multiple media fields on the same resource from fighting over the same rows, and makes registerMediaConversions / registerMediaCollections readable.
Single vs. multiple. For a cover image, declare the collection as single-file on the model (->singleFile() in medialibrary 7+, or however the field exposes its single mode) so a second upload replaces the first instead of stacking. For galleries, keep it multi-file and add ->sortable() on the model's collection if you want drag-ordering in the admin. Mixing these up is the most common source of "why do I have twelve covers" bugs.
Conversions for the Nova index. Uploads are full-size originals. Define the sizes you actually render in the admin and in your front end, then use them everywhere instead of the original:
public function registerMediaConversions(Media $media = null): void
{
$this->addMediaConversion('thumb')->width(300)->height(300)->nonQueued();
}
Anything the Nova index or detail view displays should point at a thumb-style conversion; otherwise a 12MB phone photo gets shipped to every row of your resource table.
Bridging to plain Nova fields. The field handles the media itself, but you often still want a text column for alt text or credit. The usual pattern is a normal Text::make('Alt text') beside the media field, resolved on the model from the first media item's custom properties, and written back in the model's saving/saved hook. Don't try to make the media field carry non-media data.
Scoping and authorization. Media fields are expensive to render, so gate them with ->canSee() / ->canBeSeeable() where the role does not need them, and hide them on index/detail if you are not eager-loading. A common split is: full upload widget on forms, a read-only preview (resolveUsing on the first media URL, or a plain Image field) on index and detail.
Import and seed paths. Do not route bulk imports through the Nova field. Use Spatie's API directly ($model->addMediaFromUrl(...)->toMediaCollection('gallery')) in seeders, console commands, and imports; the field is a UI layer over the same API and adds nothing off-request.
Replacing it incrementally. If you need behaviour the archived field does not offer (video, ordering UI, per-item metadata), the low-risk route is to keep the model side on Spatie and swap only the Nova field for a maintained one (ebess/advanced-nova-media-library is the usual successor). Because the database schema and collections stay put, that swap is a resource-file change, not a migration.
composer why-not output rather than assuming.registerMediaCollections() does not declare that exact name, you get an exception or a file that vanishes from the UI. Log the collection name the field posts (browser network tab, the upload request payload) and match it.implements HasMedia / use InteractsWithMedia produces confusing 500s from the field's endpoint rather than a friendly validation error.config/media-library.php says, not where the field says, unless it exposes ->disk(). On S3, remember the field's preview URLs and any storage:link setup must agree; broken thumbnails in Nova are almost always a disk/URL mismatch, not a field bug.->withMedia('gallery')-style eager loading (or with('media')) in the resource's indexQuery; a 50-row list otherwise fires 50+ queries.curl/dd() in the package's controller — that isolates field-vs-model-vs-filesystem in one step.php artisan nova:publish and a hard browser cache clear fix most of these; if not, the majors are mismatched.resolve/fill or add your own defaults (disk, collection, accept list, max size), and keep that subclass in your app so an eventual replacement is a one-file swap. Everything model-side (conversions, custom properties, responsive images) should be done in Spatie directly, never through the field.php artisan medialibrary:regenerate and expect it to be slow on large libraries.How can I help you explore Laravel packages today?