backpack/medialibrary-uploaders
Adds Backpack for Laravel admin panel uploaders for Spatie's Laravel Media Library, so you can attach images and files to your models through Backpack's familiar form fields instead of building custom upload logic.
Install both the package and its underlying dependency (Spatie Media Library), then publish Spatie's migration:
composer require backpack/medialibrary-uploaders spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate
php artisan storage:link
Next, make the Eloquent model media-aware. This is required before any uploader will work — the package writes through Spatie, not through a column:
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Product extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('photo')->singleFile();
$this->addMediaCollection('gallery');
}
}
Then swap the field in your CRUD controller. The package ships uploaders that mirror Backpack's built-in upload field types (image, upload, upload_multiple); confirm the exact field-type/uploader names in the README, since they are registered by the package's service provider and are the one thing you should verify against your Backpack major version:
$this->crud->addField([
'name' => 'photo', // must match the media collection name
'type' => 'image', // media-library-backed uploader
'disk' => 'public',
'aspect_ratio' => 1,
]);
Where to look first: the README for the registered field types, then src/Uploaders in the package (it is small — read the whole thing in ten minutes), then config/media-library.php for disk, conversions and queue settings.
First use case: take an existing avatar column on User, move it to a avatar media collection, and confirm the create/update flow writes a media row instead of a string path.
Single-file vs. multi-file collections. singleFile() is what you want for avatars, covers, and logos — re-uploading in Backpack replaces the previous media item automatically. Without it you accumulate versions silently, and getFirstMediaUrl() starts returning whatever sorted first.
Read through the model, not the field. The uploader is write-side only. Every consumer (API resources, Blade, Inertia props) should go through Spatie:
$product->getFirstMediaUrl('photo', 'thumb');
$product->getMedia('gallery')->map->getUrl();
If you have existing views reading $product->photo, add an accessor so you don't chase them all down:
public function getPhotoUrlAttribute(): ?string
{
return $this->getFirstMediaUrl('photo', 'thumb') ?: null;
}
Conversions for the admin preview. Backpack's image preview renders whatever URL the field resolves. Register a small thumb conversion and point the field's preview at it; otherwise the list view downloads full-resolution originals on every row.
$this->addMediaConversion('thumb')->width(200)->height(200)->nonQueued();
Use nonQueued() for admin thumbnails, or users will see broken previews until a worker picks the job up.
Multiple uploads in one field. For galleries and attachments, a multi-file collection plus the multiple-upload field type is the straightforward path. Inside a repeatable/repeater field, keep the collection name static and let Spatie handle ordering — do not try to derive collection names from repeater indices.
Migration path off a column. Do it in a one-off command: read the old column, addMediaFromDisk(...)->toMediaCollection('photo'), keep the column read-only for a release, then drop it. Don't do this inside a migration unless you're comfortable with storage side effects in schema changes.
Disk strategy. Put originals on S3 and set 'disk' => 's3' on the field plus the matching disk in config/media-library.php. Conversions land next to the original unless you set a separate conversion disk — worth doing, since thumbnails are served far more often than originals.
InvalidMediaCollection the moment an upload hits a collection you didn't declare in registerMediaCollections(). Register collections before wiring the field, and remember models using HasMedia via a trait in a parent class need the trait's method to be reachable.fillable, no casts, no orderBy. Any leftover $model->photo string logic (sorting, search, CSV exports) breaks. Audit before swapping the field.Model::where(...)->delete() and raw queries skip model events, so media rows and files are orphaned. Use php artisan medialibrary:clean or delete per-model. This is the recurring production bug with medialibrary.QUEUE_CONNECTION=sync everything looks fine; in production the thumbnails just never appear. Either run a worker or mark admin-critical conversions nonQueued().media table have a row? Is model_type the FQCN you expect (proxies/polymorphism bite here)? Is the file on the configured disk? Does storage:link exist for the public disk? That sequence resolves most issues in under a minute.vendor.accepted_mime_types/dimension rules explicitly; the uploader will happily accept whatever Spatie accepts, and 'mime_types' on the field alone won't cover the update path in every Backpack version.How can I help you explore Laravel packages today?