In a Livewire component, how does temporaryUrl() show a preview of an uploaded file, and why can it throw for a PDF receipt?
answer
- signed URL to a private temp file
- local: livewire.preview-file route
- S3 or GCS: a presigned URL
- preview_mimes decides what is allowed
- FileNotPreviewableException otherwise
basics
~10 stemporaryUrl() returns a signed URL that serves the private temporary file for previewing; it only works for extensions listed in temporary_file_upload.preview_mimes, so a PDF throws FileNotPreviewableException unless you check isPreviewable() first.
solid answer
~40 sThe temporary directory is not public, so `$receipt->temporaryUrl()` generates a **signed** URL for previewing it before the claim is saved. On a local-style disk that is Livewire's `livewire.preview-file` route, signed for at least 30 minutes; on an S3 or GCS temporary disk it is the storage's own presigned URL, so previews do not pass through the app server. It only works for files whose detected extension is in `temporary_file_upload.preview_mimes`, which ships with image, audio and video types and **no PDF**. For anything else it throws `FileNotPreviewableException`, so a template should check `$receipt->isPreviewable()` and fall back to showing the file name, or add `pdf` to `preview_mimes` if serving PDFs inline is acceptable.
go deeper
Recall that temporaryUrl() gives a preview link for an uploaded image before saving.
Explain the signed route versus presigned cloud URLs, the preview_mimes allowlist and FileNotPreviewableException.
Decide which user file types may be served inline from your origin, and handle non-previewable receipts gracefully.
Set a policy for rendering user-supplied files, weighing preview convenience against content-injection risk.
## Why previews need special handling Livewire stores uploads in a **private** temporary directory, `livewire-tmp` on the configured disk, until the component stores them permanently. Nothing in that directory has a public URL. Yet showing the user a thumbnail of the receipt they just picked, before they submit the expense claim, is a basic expectation. `TemporaryUploadedFile::temporaryUrl()` bridges that gap with a short-lived **signed URL**. ## How the URL is produced | Temporary disk | URL returned | Lifetime | |---|---|---| | local-style disk | Livewire's `livewire.preview-file` route with a signature | at least 30 minutes, until the end of that hour | | S3 or GCS | the disk's presigned URL for the object | at least a day, until the end of that hour | For S3 and GCS the presigned URL asks the storage service to send the file with an `attachment` content disposition, and the image is loaded straight from the bucket, not through the app server. For the local route, the signature stops users from requesting other files, and the route serves files only from the temporary directory. ## The preview allowlist Before generating anything, `temporaryUrl()` calls `isPreviewable()`, which checks the file's **detected** extension against `config('livewire.temporary_file_upload.preview_mimes')`. The shipped list covers common images (`png`, `jpg`, `jpeg`, `gif`, `bmp`, `svg`, `webp`), audio (`mp3`, `wav`, `m4a`, `mpga`, `wma`) and video (`mp4`, `mov`, `avi`, `wmv`). If the extension is not listed, it throws `FileNotPreviewableException`. Receipts are very often PDFs, and `pdf` is not in the default list. So a template that blindly writes: ```html <img src="{{ $receipt->temporaryUrl() }}"> ``` works in a demo with a JPEG and fails with an exception the first time someone uploads a PDF. The robust version checks first: ```html @if ($receipt?->isPreviewable()) <img src="{{ $receipt->temporaryUrl() }}" alt="Receipt preview"> @elseif ($receipt) <span>{{ $receipt->getClientOriginalName() }}</span> @endif ``` ## Checking before rendering `isPreviewable()` returns a boolean instead of throwing for unsupported types, so it is the natural guard in the template. It uses the same detection as `temporaryUrl()`: the file's contents decide the extension, so renaming `receipt.pdf` to `receipt.png` does not make it previewable. ## Choices for PDFs 1. **Show the name only** until the claim is saved, then link to the stored copy. 2. **Add `pdf` to `preview_mimes`** and render it in an `<iframe>` or `<object>`, accepting that user-supplied PDFs are served from your domain (local disk) or the bucket. 3. **Generate a thumbnail** server-side after storing, if the product needs it. ## Security notes - The allowlist exists because serving arbitrary user files from your own origin is risky; `svg` in particular can carry script, which is why some teams remove it from `preview_mimes`. - Signed URLs expire, so a preview left open past the expiry stops loading; re-rendering the component generates a fresh one. - The docs describe previews as image-only, but the shipped config also allows audio and video; the config is what decides. ## Common mistakes - Calling `temporaryUrl()` without checking `isPreviewable()`. - Assuming the URL is public and permanent and saving it on the record. - Forgetting that after `store()` the preview should come from the stored file, not the temporary one. ## Putting it together on the expense form A practical receipt field shows a thumbnail for JPEG and PNG photos, the file name with an icon for PDFs, and nothing until the upload finishes. The template guards with `isPreviewable()`, the component validates type and size before `save()`, and after saving, the claim page links to the stored copy through the permanent disk's own URL or a download route that checks the user may see that claim.
- Why might a team remove svg from preview_mimes?SVG is XML that can contain script. Serving a user-uploaded SVG from the application's own domain, which the local preview route does, risks script running in your origin if the file is opened directly. Removing `svg` from the allowlist makes `temporaryUrl()` refuse it.
- Should you save the value of temporaryUrl() on the expense claim?No. It is a signed, expiring URL to a temporary file that cleanup will delete. Store the file with `store()` and save that path; generate URLs from the stored copy when you need them.
saying these in an interview costs you the question
- Assumes temporaryUrl() works for every file type.
- Saves the temporary URL on the record as the receipt's permanent link.
- Believes the livewire-tmp directory is publicly served.
- Thinks previews always stream through the app server, even on S3.