
September 07, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
File uploads look simple until your app stores client contracts, product galleries, PDF receipts, and profile photos on the same model. The Laravel Spatie Media Library Complete Guide you are reading walks through Spatie Laravel Media Library end to end — from Composer install on Laravel 13 through collections, conversions, cloud disks, and the production gotchas I hit on legal-tech portals and eCommerce builds. If you already ship Laravel apps and want one package to attach, transform, and serve files without reinventing upload logic, this is the reference page.
spatie/laravel-medialibrary, adds HasMedia and InteractsWithMedia to Eloquent models, defines named collections and image conversions, stores files on local or S3 disks, and serves URLs through Blade or API resources — all without bloating your main table with file columns.What Is Spatie Media Library and Why Use It in Laravel?
Spatie Laravel Media Library is a first-party-quality package that associates one or many files with any Eloquent model. Instead of scattering avatar_path, thumbnail, and document_url columns across tables, you keep file metadata in a dedicated media table and store binaries on a configured filesystem disk.
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 solves 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 upload logic close to the domain model. That aligns with how I structure modern Laravel architecture: fat models or dedicated actions, thin controllers, and filesystem config centralised in config/filesystems.php.
| 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; requires queue worker 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 version in my stack. Composer 2.10 handles the install. The package supports Laravel 11 through 13 — check the release notes if you are still on Laravel 12 (supported until February 2027).
Install the package and publish assets
- Require the package:
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 either GD or Imagick. On Ubuntu servers I maintain, Imagick is preferred for 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 is fine 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
For queued conversions — which you want on any upload larger than a thumbnail — set QUEUE_CONNECTION=database or Redis 8.10 and run a worker via Supervisor. I use the same pattern on Deployer-managed releases described in my GitLab CI pipeline for Laravel notes: migrate, symlink, reload PHP-FPM, restart queue workers.
How Do You Add File Uploads and Media Collections to Eloquent Models?
The heart of this Laravel Spatie Media Library complete guide 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 form request with validation:
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 API endpoints returning JSON, map media in an API resource rather than exposing raw disk paths. That mirrors patterns in Laravel API best practices and keeps 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 — a mistake I have seen when teams store everything under storage/app/public without thinking through authorisation.
How Do You Create Image Conversions and Responsive Variants?
Conversions are derived files generated 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 synchronously on upload — otherwise your HTTP request waits on Imagick.
For deeper image tuning, pair this package with the dedicated walkthrough on image optimization in Laravel with Spatie Media Library and front-end lazy loading 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 — something I treat as architecture, not a post-launch patch, when doing technical SEO work alongside development. Structured product images on florist eCommerce builds benefit directly 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 — the same operational 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 that appear the first time you deploy to AWS, DigitalOcean Spaces, or a client's 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 digital product platforms and 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, but 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 other 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 because 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; if you remain on Laravel 12, it is supported until February 2027 — plan upgrades before Laravel 11's EOL in March 2026 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, and 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 so Imagick does not spike CPU on a live web node.
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 Laravel Spatie Media Library Complete Guide: 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.

