---
url: /guide/storage.md
description: >-
  Choose a disk for Media Library Pro: public and private disks, signed URLs, S3
  and S3-compatible storage, CORS, file paths, queues and cleanup.
---

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

| 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](/guide/private-files).
:::

Rich text cannot use expiring links. For files on a non-public disk, the [rich editor plugin](/guide/rich-editor) 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](/guide/models-and-blade). To rebuild existing conversions, see [Commands](/reference/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](/guide/tenancy) 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](/reference/commands) for the full list.
