Livewire File Uploads: Validate Before You Move to Permanent Storage
Decide when to validate Livewire uploads and how to move TemporaryUploadedFile to permanent storage without leaving orphaned temp files.
03 Aug 2025, 21:44 UTC

Decision: validate first, then move
A file uploaded through Livewire does not land directly in your public storage folder. It arrives as a TemporaryUploadedFile—a PHP uploaded file pointing to a short-lived copy under storage/app/livewire-tmp. If you move the file before validation, invalid files can remain on disk. If you never move it, the upload disappears when Livewire cleans temporary files.
Useful takeaway: validate the upload while it is still temporary, then move it to permanent storage only after validation passes. Schedule cleanup for temporary files left behind by failed requests.
Constraints and supported options
Before choosing an approach, confirm these constraints:
- Livewire version: v2 and v3 differ in temporary-file cleanup timing and client-side upload progress. Check
composer.jsonbefore copying examples from other versions. - PHP limits:
upload_max_filesizeandpost_max_sizecan reject a request before Livewire runs any validation. - File count: use a scalar property for one file or an array property with the
multipleattribute for several files. - Storage disk: decide whether files go to
public(accessible via URL) orlocal(private).
Option comparison
| Strategy | Validation timing | Move timing | Temporary-file risk | Best for |
|---|---|---|---|---|
Validate in save(), then move | Before the move, inside the save method | After validation passes | Low; invalid files never reach permanent storage | Most single-file uploads |
Validate on upload with an updated hook | As soon as the file arrives | Later, in save() | Medium; an invalid file may linger until cleanup | Immediate UI feedback |
| Move immediately, validate later | After the file is already stored | Immediately on upload | High; invalid files remain on disk until manually deleted | Not recommended |
Trade-offs
Validating inside save() keeps the temporary file isolated until you know the upload is acceptable. The downside is that the user waits until submission to learn about a bad file type or size. Validating in an updated hook gives faster feedback, but you must still re-run $this->validate() in save() because a user can bypass the UI. Moving before validation is the riskiest option: it couples storage to an untrusted input and creates orphaned files when validation fails.
Concrete implementation
This example uses Livewire's WithFileUploads trait. It binds <input type='file'> to a component property, validates the upload, then stores it on the public disk.
<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithFileUploads;
use Illuminate\Support\Facades\Storage;
class AvatarUpload extends Component
{
use WithFileUploads;
public $avatar;
protected $rules = [
'avatar' => 'required|image|max:1024',
];
public function save()
{
$validated = $this->validate();
$path = Storage::putFile('avatars', $validated['avatar']);
$this->avatar = null;
$this->dispatch('upload-complete', path: $path);
}
public function render()
{
return view('livewire.avatar-upload');
}
}
The Blade template:
<form wire:submit.prevent='save'>
<input type='file' wire:model='avatar' accept='image/*'>
@error('avatar')
<span>{{ $message }}</span>
@enderror
<button type='submit'>Save</button>
</form>
Storage::putFile accepts the TemporaryUploadedFile, moves it to storage/app/public/avatars, and returns the relative path. Because validation runs first, an invalid file is rejected before any permanent write occurs.
For multiple files, declare an array property and add multiple to the input:
public $avatars = [];
protected $rules = [
'avatars.*' => 'image|max:1024',
];
<input type='file' wire:model='avatars' multiple accept='image/*'>
Diagnostic checks
Run these in your project root. Replace example.com with your application path.
- Confirm the installed Livewire version:
cd /var/www/example.com
composer show livewire/livewire
Expected: a version line such as livewire/livewire v2.x or v3.x. Behavior differs between major versions, so adjust cleanup expectations accordingly.
- Verify temporary file creation:
ls -la storage/app/livewire-tmp
After submitting a file, you should see a newly created temporary file. If the directory is empty, check PHP upload limits and web-server write permissions on storage/app.
- Confirm validation blocks storage:
# Submit a non-image file, then check:
ls storage/app/public/avatars
The directory should remain empty for invalid uploads.
- Confirm permanent storage after success:
# After a successful save:
ls storage/app/public/avatars
You should see the moved file with a generated name.
Cleanup and rollback
Temporary files from failed validation can remain in storage/app/livewire-tmp. Schedule a cleanup command that removes files older than your chosen retention window, for example one hour:
$files = File::files(storage_path('app/livewire-tmp'));
foreach ($files as $file) {
if ($file->getMTime() < time() - 3600) {
$file->delete();
}
}
Because Storage::putFile changes disk state, include rollback if a later step fails:
try {
$path = Storage::putFile('avatars', $validated['avatar']);
// Create database record or dispatch jobs here.
$this->avatar = null;
} catch (\Throwable $e) {
if (isset($path)) {
Storage::disk('public')->delete($path);
}
throw $e;
}
Limitations
- PHP settings such as
upload_max_filesizeandpost_max_sizereject oversized uploads before Livewire validation runs. Check these inphp.inior your web-server configuration if large files fail silently. - Temporary-file cleanup timing differs between Livewire v2 and v3. Do not assume v2 behavior in a v3 application without testing.
- Never expose
storage/app/livewire-tmppaths directly. Validate and relocate files through Laravel's filesystem abstraction.
Practical result check
Submit a valid image through the component. The temporary file should appear under storage/app/livewire-tmp during the request, then the file should exist under storage/app/public/avatars after save() completes. Submit an invalid file type; the validation error should display and no file should appear under storage/app/public/avatars. If either check fails, review PHP upload limits, Livewire version, and filesystem permissions before deploying.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.