
September 07, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
File uploads break down the moment one model needs a hero image, a PDF gallery, and a private client contract. The spatie media library package solves that in Laravel by storing file metadata in a dedicated table and binaries on any configured disk. This guide covers install on Laravel 13, collections, conversions, cloud storage, and the production traps I hit on legal-tech portals and eCommerce builds. If you are evaluating Spatie packages, start with the essential Laravel Spatie plugins overview — Media Library is usually the first one I wire in.
HasMedia and InteractsWithMedia to Eloquent models, stores files in named collections with optional image conversions, and serves URLs through Blade or API resources — without bloating your main table with path columns.What Is Spatie Media Library and Why Use It in Laravel?
Spatie Laravel Media Library associates one or many files with any Eloquent model. Instead of scattering avatar_path, thumbnail, and document_url columns, you keep metadata in a media table and store binaries on local, public, or S3 disks.
On real client projects — client portals with document sharing, florist shops with hundreds of product images, booking systems with itinerary PDFs — I reach for this package because it handles recurring problems in one place:
- Multiple files per record — galleries, attachments, versioned uploads.
- Named collections — separate
avatarfromdocumentswith different rules. - Image conversions — thumbnails, WebP variants, watermarks via queued jobs.
- Custom properties — JSON metadata (alt text, uploaded-by, expiry) without schema migrations.
- Disk abstraction — swap local storage for S3 without rewriting controllers.
Compared with rolling your own upload handler, Spatie Media Library keeps file logic close to the domain model. That matches how I structure modern Laravel architecture: fat models or dedicated actions, thin controllers, and filesystem config in config/filesystems.php.
Spatie also ships a paid Media Library Pro add-on with drop-zone UI components. The open-source package documented at the official Spatie site covers everything most backends need. Pro helps when you want pre-built Livewire or Vue upload widgets without writing front-end glue.
| Approach | Pros | Cons |
|---|---|---|
Path columns on model (photo_path) | Fast to prototype | Breaks with multiple files, no conversions, disk logic in controllers |
Polymorphic attachments table (DIY) | Flexible | You rebuild conversions, ordering, responsive images |
| Spatie media library | Collections, conversions, queues, responsive images built in | Learning curve; queue worker required for async conversions |
| Cloud-only (S3 direct SDK) | Scales storage | No Eloquent integration; metadata sync is manual |
How Do You Install and Configure Spatie Media Library in Laravel 13?
Laravel 13 requires PHP 8.3 or higher. PHP 8.5 is the current anchor in my stack. Composer 2.10 handles the install. The package supports Laravel 11 through 13. Laravel 12 remains supported until February 2027.
Install the package and publish assets
- Require the package with Composer.
- Publish and run the migration.
- Optionally publish config for disk and queue tuning.
composer require spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate
Optional config publish:
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-config"
Filesystem and queue prerequisites
Image conversions need GD or Imagick. On Ubuntu servers I maintain, Imagick gives better quality and memory behaviour:
sudo apt install php8.5-imagick
sudo systemctl reload php8.5-fpm
Configure your default disk in .env. Local public storage works for development. Production apps usually move to S3-compatible storage:
FILESYSTEM_DISK=public
MEDIA_DISK=public
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=ap-south-1
AWS_BUCKET=
AWS_URL=
Run the standard Laravel storage link once per server:
php artisan storage:link
Queued conversions need a worker. Set QUEUE_CONNECTION=database or Redis 8.10 and run Supervisor. I use the same Deployer pattern from my GitLab CI pipeline for Laravel notes: migrate, symlink, reload PHP-FPM, restart queue workers after each release.
How Do You Add File Uploads and Media Collections to Eloquent Models?
The core of any laravel spatie media library setup is wiring models correctly. Any model that accepts uploads implements Spatie\MediaLibrary\HasMedia and uses InteractsWithMedia.
Basic model setup
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Post extends Model implements HasMedia
{
use InteractsWithMedia;
protected $fillable = ['title', 'body'];
}
Register collections in registerMediaCollections()
Collections are named buckets with rules: single vs multiple files, accepted MIME types, disk name.
public function registerMediaCollections(): void
{
$this->addMediaCollection('featured')
->singleFile()
->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp']);
$this->addMediaCollection('gallery')
->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp']);
$this->addMediaCollection('documents')
->acceptsMimeTypes(['application/pdf'])
->useDisk('s3');
}
Controller upload patterns
From a validated form request:
public function store(StorePostRequest $request)
{
$post = Post::create($request->validated());
if ($request->hasFile('featured')) {
$post->addMediaFromRequest('featured')
->usingFileName($request->file('featured')->hashName())
->withCustomProperties(['alt' => $request->input('alt_text')])
->toMediaCollection('featured');
}
foreach ($request->file('gallery', []) as $image) {
$post->addMedia($image)->toMediaCollection('gallery');
}
return redirect()->route('posts.show', $post);
}
Validation belongs in a Form Request. Never trust client MIME checks alone:
public function rules(): array
{
return [
'featured' => ['nullable', 'image', 'max:5120'],
'gallery.*' => ['image', 'max:5120'],
'documents.*' => ['file', 'mimes:pdf', 'max:10240'],
];
}
For JSON endpoints, map media in an API resource. That mirrors Laravel API best practices. Keep authorisation at the policy layer — see Laravel policies and gates for gating who may upload or delete files on shared records.
Retrieving and displaying media
In Blade:
@if($post->getFirstMediaUrl('featured'))
<img src="{{ $post->getFirstMediaUrl('featured', 'thumb') }}"
alt="{{ $post->getFirstMedia('featured')?->getCustomProperty('alt') }}"
class="img-fluid">
@endif
@foreach($post->getMedia('gallery') as $media)
<img src="{{ $media->getUrl('webp') }}" alt="" loading="lazy">
@endforeach
Common retrieval helpers:
getFirstMedia('collection')— returns a Media model instance or null.getFirstMediaUrl('collection', 'conversion')— URL string for templates.getMedia('collection')— ordered collection of all items.hasMedia('collection')— boolean guard before rendering.
On a legal-tech portal I built, separating documents (private disk, policy-gated download route) from public-assets (public disk) prevented accidental exposure of client PDFs. I have seen teams store everything under storage/app/public without thinking through authorisation.
How Do You Create Image Conversions and Responsive Variants?
Conversions are derived files from an original upload: thumbnails, cropped squares, WebP copies, greyscale previews. Register them in registerMediaConversions().
Define conversions on the model
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\Image\Enums\Fit;
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb')
->width(300)
->height(300)
->fit(Fit::Crop, 300, 300)
->nonQueued();
$this->addMediaConversion('webp')
->format('webp')
->quality(82)
->performOnCollections('featured', 'gallery');
$this->addMediaConversion('preview')
->width(1200)
->performOnCollections('gallery')
->queued();
}
By default, conversions run queued. Use ->nonQueued() only for small thumbnails needed on upload. Otherwise your HTTP request waits on Imagick.
For deeper image tuning, pair this package with the walkthrough on image optimization in Laravel with Spatie Media Library. Front-end lazy loading lives in your Vite bundle — see Vite config for Laravel projects for asset pipeline setup.
Responsive images
Spatie can generate responsive image markup with multiple srcset widths:
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('responsive')
->width(1600)
->withResponsiveImages();
}
In Blade, output the responsive bundle:
{{ $post->getFirstMedia('featured')?->img('', ['class' => 'img-fluid']) }}
That helps Core Web Vitals on content-heavy sites. I treat it as architecture, not a post-launch patch, when doing technical SEO work alongside development. Product images on florist eCommerce builds benefit from WebP conversions plus responsive srcsets.
Ordering, replacing, and deleting
For ordered galleries:
$mediaItem->move($post, 'gallery', $newOrder);
Replace a single-file collection by uploading again. Spatie removes the previous file when singleFile() is set. Explicit delete:
$post->clearMediaCollection('featured');
$post->delete(); // cascades media rows; files removed via model event
When deleting models in bulk, watch disk I/O. For large cleanups I batch deletes and monitor queue backlog. That is the same discipline I apply during ongoing Laravel maintenance contracts.
How Do You Handle Production Storage, Queues, and Security?
Development on a local public disk hides problems. They surface the first time you deploy to AWS, DigitalOcean Spaces, or a shared host with limited inode counts.
S3 and CDN configuration
Point a collection at S3:
$this->addMediaCollection('gallery')
->useDisk('s3');
In config/filesystems.php, define the S3 disk per Laravel filesystem documentation. Set AWS_URL or a CloudFront domain so generated URLs point at your CDN, not raw bucket endpoints.
On document-heavy portals like client portals with file sharing, S3 with private ACL plus signed temporary URLs is the pattern. The media row exists in the database. The download route checks a policy before calling $media->getTemporaryUrl(now()->addMinutes(15)).
Security checklist
- Validate file type and size server-side on every upload endpoint.
- Never expose private collection files via public disk or predictable URLs.
- Authorise upload, view, and delete with Laravel policies tied to model ownership.
- Scan PDFs and office documents if users upload them — Spatie stores bytes; it does not antivirus-scan.
- Store original filenames only for display; use hashed storage names to reduce path-guessing attacks.
- Log failed conversion jobs — silent Imagick failures produce broken thumbnails in admin UIs.
Database and migration hygiene
The published migration creates a media table. Treat it like any core table: back it up, index foreign keys, and plan growth. On high-volume catalogues, the media table can grow faster than the parent model table. Pair with sensible retention rules and occasional orphaned-file audits.
If you version your schema carefully — as outlined in database migrations and seeding best practices — adding custom properties rarely needs new columns. Spatie stores them in JSON.
Real-world collection patterns
Patterns I reuse across custom Laravel applications:
| Domain | Collections | Conversions | Disk |
|---|---|---|---|
| eCommerce product | images, manuals | thumb, webp, large | S3 public + private |
| Law firm portal | client-documents, avatars | thumb for avatars only | private S3 |
| Booking / trek agency | itinerary-pdf, gallery | webp, responsive | mixed |
| User profile | avatar (singleFile) | thumb 150px | public |
For JSON debugging of API payloads that include media metadata, a quick pass through the on-site JSON formatter saves time when mapping nested custom properties.
When building admin panels, Filament has community plugins that integrate Spatie Media Library. Useful if your team already standardised on Filament for back-office CRUD. The core lesson stays the same: collections and conversions live on the model, not scattered in admin form callbacks.
Key Takeaways
- Implement
HasMediaandInteractsWithMedia, then define collections inregisterMediaCollections()before writing upload controllers. - Use named collections with MIME restrictions and
singleFile()where only one asset belongs on a record. - Queue image conversions in production; reserve
nonQueued()for tiny thumbnails only. - Match disk visibility to data sensitivity — public product images on CDN, client documents on private S3 with policy-gated signed URLs.
- Pair Spatie with Form Request validation, Laravel policies, and responsive images for SEO-friendly, secure file handling.
- Monitor the
mediatable size and failed queue jobs — conversions fail quietly if Imagick is missing on the worker server.
People Also Ask
Does Spatie Media Library work with Laravel 13?
Yes. Install via Composer on Laravel 13 with PHP 8.3 or higher, publish migrations, and run php artisan migrate. The package tracks current Laravel releases. Laravel 12 is supported until February 2027. Laravel 11 reached EOL in March 2026 — upgrade if you still run that line.
Can Spatie Media Library store PDFs and documents, not just images?
Absolutely. Collections accept any MIME type you allow. Image conversions apply only to raster images. PDFs and spreadsheets store as-is. Use custom properties for titles or document types. Serve downloads through authorised controller routes rather than public URLs.
How do you regenerate image conversions after changing conversion settings?
Run php artisan media-library:regenerate, optionally filtered by model type or collection. On large catalogues, run it during a maintenance window or chunk models in a custom Artisan command. Imagick can spike CPU on a live web node if you regenerate everything at once.
Is Spatie Media Library better than storing files directly on the model?
For any model with more than one file type or any need for thumbnails and responsive images, yes. Path columns work for a single avatar on a hobby project. They become unmaintainable when you add galleries, document uploads, ordering, and cloud storage — exactly the problems this package was written to solve.
Ship File Uploads That Scale With Your Laravel App
You now have a production-oriented reference for spatie media library: install, model wiring, collections, conversions, cloud disks, and the security boundaries that separate public product photos from private client documents. On projects I deliver through web development in Nepal and remote engagements, this package is my default whenever uploads are more than a single column — especially on eCommerce builds and enterprise portals where files are operational data, not decoration.
If you want Spatie Media Library integrated into a new feature or an existing codebase upgraded to Laravel 13 with proper storage architecture, review the portfolio of shipped Laravel work and get in touch — include your current disk setup, expected upload volume, and whether files are public or confidential so the collection design starts in the right place.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

