Skip to content

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=s3

The 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.

DiskFiles shown to people
Visibility is public, file is sharedA direct URL from the disk.
Visibility is public, file is Only meA signed link to the library's serve route. It expires.
Any other diskA 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.webp

The 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:work

Without 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=7

Trash stays on disk until it is pruned. See Commands for the full list.

Commercial licence. One licence per production project. Terms · Privacy · Refunds