Appearance
Storage
Media Library Pro stores files on any Laravel filesystem disk. This page covers choosing a disk, how URLs are made, S3 setup, and the jobs that keep storage tidy.
Choose a disk
Set the disk in .env:
bash
MEDIA_LIBRARY_DISK=s3The config reads MEDIA_LIBRARY_DISK, then MEDIA_DISK, then falls back to public.
php
'disk' => env('MEDIA_LIBRARY_DISK', env('MEDIA_DISK', 'public')),
'conversions_disk' => null,Set conversions_disk to keep thumbnails and other conversions on a different disk from the originals. Leave it null to store them together.
Each file remembers the disk it was stored on. Changing disk later affects new uploads only. Existing files keep working from where they are.
Public and private disks
The library treats a disk as public when its entry in config/filesystems.php has 'visibility' => 'public'. Laravel's default public disk has it. The default local and s3 disks do not.
| Disk | Files shown to people |
|---|---|
Visibility is public, file is shared | A direct URL from the disk. |
Visibility is public, file is Only me | A signed link to the library's serve route. It expires. |
| Any other disk | A temporary URL from the disk (S3 and compatible), or a signed link to the serve route when the disk cannot create temporary URLs. It expires. |
Links expire after temporary_url_minutes, which defaults to 30 and cannot go below 2. The library caches each link for half that time, so a page does not sign the same file again and again.
php
'temporary_url_minutes' => 30,The serve route is GET /media-library/serve/{media}/{conversion?}. It only answers valid signed links, refuses trashed files, and sends the file with X-Content-Type-Options: nosniff and a locked-down Content-Security-Policy.
WARNING
On a public disk, a file marked Only me is hidden in the library and given only expiring links. The file itself still sits in a web-readable folder, and anyone with its exact URL can open it. When files must stay confidential, use a private disk: S3 with private visibility, or a local disk outside public/. See Private Files.
Rich text cannot use expiring links. For files on a non-public disk, the rich editor plugin inserts signed links that never expire.
S3 and S3-compatible storage
Configure the disk in config/filesystems.php as usual. For MinIO, Cloudflare R2 or DigitalOcean Spaces, set an endpoint:
php
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
],Keep the bucket private and the disk without a visibility setting. The library then serves every file through temporary URLs, and nothing in the bucket is public.
To serve public files straight from a bucket or CDN, set 'visibility' => 'public' and a url on the disk. Laravel then applies public visibility when it writes files, so your bucket must allow it.
CORS for the image editor
The image editor reads the original pixels in the browser. When files live on another domain, such as S3 or a CDN, the bucket must allow your panel origin. Without it, the editor shows an error instead of opening the image.
json
[{ "AllowedOrigins": ["https://admin.example.com"], "AllowedMethods": ["GET"], "AllowedHeaders": ["*"] }]Replace the origin with the address of your panel. Browsing, uploads and the picker do not need this rule. Only the editor does.
File paths
Spatie stores files under the media's numeric id by default. The library stores them under a random UUID folder instead, so URLs cannot be enumerated from sequential ids:
text
5b0e3f0c-.../photo.webp
5b0e3f0c-.../conversions/photo-thumb.webpThe library registers its path generator for the item model in Spatie's media-library.custom_path_generators. If you map the item model there yourself, the library leaves your entry alone. The prefix from Spatie's media-library config is honoured.
File names are slugged by default. Set upload.preserve_file_names to true to keep letters, numbers, spaces and dashes from the original name.
Conversions and the queue
Thumbnails are generated while the upload finishes, so new files appear at once. Larger conversions are queued. The default preview conversion runs on the queue, and so does rebuilding conversions after a focal point changes. Keep a worker running:
bash
php artisan queue:workWithout a worker, the original still shows, but queued conversions never appear. Presets and their queued flag are covered in Models & Blade. To rebuild existing conversions, see Commands.
Upload limits and chunks
Uploads arrive in chunks of upload.chunk_size bytes, 5 MB by default. Values below 256 KB are raised to 256 KB. Keep your PHP upload_max_filesize and post_max_size above the chunk size, not above your largest file.
upload.max_file_size is in kilobytes. The default, 512 * 1024, is 512 MB. Set a quota to cap total storage.
Chunks are stored under storage/app/media-library-chunks on the application server, not on the media disk. Change the folder name with upload.chunk_directory. If you run several servers, send all chunks of one upload to the same server, or share that directory.
Scheduled cleanup
Schedule the prune command so old trash and abandoned chunks are removed:
php
use Illuminate\Support\Facades\Schedule;
Schedule::command('media-library:prune')->daily();It does two things:
- Permanently deletes files that have been in the trash longer than
trash.prune_after_days(30 by default). Pass--days=to override once. - Deletes upload chunk folders untouched for 24 hours.
bash
php artisan media-library:prune --days=7Trash stays on disk until it is pruned. See Commands for the full list.