---
url: /reference/troubleshooting.md
description: >-
  Fixes for the problems you are most likely to hit: 404 images, missing
  conversions, S3 CORS, upload limits, standalone 403, empty tenants and assets.
---

# Troubleshooting

## Images return 404 after upload

The files are on the `public` disk but the web server cannot reach them.

1. Run `php artisan storage:link`. `media-library:install` does this when it can.
2. Check `APP_URL`. The `public` disk builds URLs from it, so `http://localhost` in production produces broken links.
3. Check `MEDIA_LIBRARY_DISK` (or `disk` in [the config](/reference/configuration#disk)) points at the disk you expect.

## Thumbnails work, larger sizes are missing

The `thumb` conversion runs during the upload. `preview` and any conversion with `'queued' => true` run on the queue. Start a worker:

```bash
php artisan queue:work
```

With `QUEUE_CONNECTION=sync` they run inline. To rebuild after a fix, run `php artisan media-library:regenerate --missing`. See [Commands](/reference/commands#media-libraryregenerate).

## The image editor will not open on S3

The editor reads the original pixels in the browser, so the bucket must allow your panel's origin. Add a CORS rule:

```json
[{ "AllowedOrigins": ["https://admin.example.com"], "AllowedMethods": ["GET"], "AllowedHeaders": ["*"] }]
```

The editor shows a CORS message when the browser blocks the request. The same applies to a CDN in front of the bucket. See [Storage & S3](/guide/storage).

## Private or S3 links return 403

Temporary URLs and signed links expire after `temporary_url_minutes` (30 by default), and the library caches each URL for half of that. A 403 on a page that has been open for a long time means the link expired: reload.

If every link fails straight away on S3, check the bucket credentials and region in `config/filesystems.php`, and that the server clock is correct, because S3 signatures include a timestamp. On a local private disk, links go through a signed route, so a changed `APP_KEY` or `APP_URL` invalidates them.

## Uploads fail on large files

Files are sent in chunks of `upload.chunk_size` (5 MB by default), so a 500 MB file is many small requests. Three limits matter:

| Limit | Where | Effect |
|---|---|---|
| `upload.max_file_size` | [Config](/reference/configuration#upload) or `maxFileSize()` | The whole file, in kilobytes. Over it, the upload stops with "This file is larger than the limit". |
| `post_max_size` | `php.ini` | Must be larger than `chunk_size` plus a little overhead. |
| `upload_max_filesize` | `php.ini` | Each chunk is a multipart file upload, so this must also be larger than `chunk_size`. PHP's default is 2 MB. |

If you cannot raise the PHP limits, lower `upload.chunk_size`. The minimum the server accepts is 256 KB. A reverse proxy in front of PHP also needs room: nginx's `client_max_body_size` must exceed the chunk size.

An error saying "The upload was interrupted" means a chunk was rejected or out of order. One person can have at most 20 unfinished uploads. Run `media-library:prune` to clear abandoned ones.

## "Not enough storage left"

A [`quota`](/reference/configuration#quota) is set and the tenant, or the whole app, is full. Raise the quota or delete unused files. Trashed files still count until they are pruned.

## 403 or 404 on the library outside a panel

Pages outside a Filament panel are off by default. Set this in `config/filament-media-library.php`:

```php
'standalone' => ['enabled' => true],
```

While it is off, the standalone upload and download routes return 404, and `<livewire:media-library>` and `<livewire:media-library-picker>` abort with 403. Before you turn it on, register a policy that fits your site. See [Outside Filament panels](/guide/outside-panels).

## The library is empty for a tenant

With tenancy on, strict mode shows nothing when no tenant resolves. That is by design, so one tenant never sees another's files.

* In a panel with `->tenant(...)`, make sure you opened the library under a tenant URL.
* Outside panels, `tenancy.enabled` needs a working `tenancy.resolver`. Check that it returns the current tenant in the request that renders the component.
* Files uploaded before you turned tenancy on have no tenant and do not match the scope.
* Set `tenancy.strict` to `false` only if you want unresolved requests to see everything.

See [Multi-tenancy](/guide/tenancy).

## Livewire "Cannot update locked property"

The picker and browser lock their configuration (`multiple`, `maxItems`, `acceptedFileTypes`, `folderId`, `layout` and more) so a visitor cannot change them from the browser. The error means something tried to change one after render: a `wire:model` bound to one of those properties, or a `$wire.set()` call. Pass options as component attributes when you render it instead:

```blade
<livewire:media-library-picker wire:model="coverId" accept="image/*" />
```

Bind only to `value`, which is what `wire:model` does by default. Clear your browser cache after an upgrade so old JavaScript does not send stale state.

## Styles or scripts do not load

Run:

```bash
php artisan filament:assets
```

Do it on every deploy, because the assets are copied to `public/`. The library's CSS loads on demand on pages that use it, and you do not need to edit your Filament theme. If you cache views or assets behind a CDN, clear them after upgrading the package.

## Still stuck

Search the error text in this page first. If you open a support request, include the package version, Filament and Livewire versions, the disk driver and the browser console output.
