File Uploads
Livewire offers powerful support for uploading files within your components.
First, add the WithFileUploads
trait to your component. Once this trait has been added to your component, you can use wire:model
on file inputs as if they were any other input type and Livewire will take care of the rest.
Here's an example of a simple component that handles uploading a photo:
<?php namespace App\Livewire; use Livewire\Component;use Livewire\WithFileUploads;use Livewire\Attributes\Validate; class UploadPhoto extends Component{ use WithFileUploads; #[Validate('image|max:1024')] // 1MB Max public $photo; public function save() { $this->photo->store(path: 'photos'); }}
<form wire:submit="save"> <input type="file" wire:model="photo"> @error('photo') <span class="error">{{ $message }}</span> @enderror <button type="submit">Save photo</button></form>
Notice the above example uses a "save" method instead of an "upload" method. This is a common "gotcha". The term "upload" is reserved by Livewire. You cannot use it as a method or property name in your component.
From the developer's perspective, handling file inputs is no different than handling any other input type: Add wire:model
to the <input>
tag and everything else is taken care of for you.
However, more is happening under the hood to make file uploads work in Livewire. Here's a glimpse at what goes on when a user selects a file to upload:
- When a new file is selected, Livewire's JavaScript makes an initial request to the component on the server to get a temporary "signed" upload URL.
- Once the URL is received, JavaScript does the actual "upload" to the signed URL, storing the upload in a temporary directory designated by Livewire and returning the new temporary file's unique hash ID.
- Once the file is uploaded and the unique hash ID is generated, Livewire's JavaScript makes a final request to the component on the server, telling it to "set" the desired public property to the new temporary file.
- Now, the public property (in this case,
$photo
) is set to the temporary file upload and is ready to be stored or validated at any point.
Storing uploaded files
The previous example demonstrates the most basic storage scenario: moving the temporarily uploaded file to the "photos" directory on the application's default filesystem disk.
However, you may want to customize the file name of the stored file or even specify a specific storage "disk" to keep the file on (such as S3).
You can access the original file name of a temporary upload, by calling its ->getClientOriginalName()
method.
Livewire honors the same APIs Laravel uses for storing uploaded files, so feel free to consult Laravel's file upload documentation. However, below are a few common storage scenarios and examples:
public function save(){ // Store the file in the "photos" directory of the default filesystem disk $this->photo->store(path: 'photos'); // Store the file in the "photos" directory in a configured "s3" disk $this->photo->store(path: 'photos', 's3'); // Store the file in the "photos" directory with the filename "avatar.png" $this->photo->storeAs(path: 'photos', name: 'avatar'); // Store the file in the "photos" directory in a configured "s3" disk with the filename "avatar.png" $this->photo->storeAs(path: 'photos', name: 'avatar', 's3'); // Store the file in the "photos" directory, with "public" visibility in a configured "s3" disk $this->photo->storePublicly(path: 'photos', 's3'); // Store the file in the "photos" directory, with the name "avatar.png", with "public" visibility in a configured "s3" disk $this->photo->storePubliclyAs(path: 'photos', name: 'avatar', 's3');}
Handling multiple files
Livewire automatically handles multiple file uploads by detecting the multiple
attribute on the <input>
tag.
For example, below is a component with an array property named $photos
. By adding multiple
to the form's file input, Livewire will automatically append new files to this array:
use Livewire\Component;use Livewire\WithFileUploads;use Livewire\Attributes\Validate; class UploadPhotos extends Component{ use WithFileUploads; #[Validate(['photos.*' => 'image|max:1024'])] public $photos = []; public function save() { foreach ($this->photos as $photo) { $photo->store(path: 'photos'); } }}
<form wire:submit="save"> <input type="file" wire:model="photos" multiple> @error('photos.*') <span class="error">{{ $message }}</span> @enderror <button type="submit">Save photo</button></form>
File validation
Like we've discussed, validating file uploads with Livewire is the same as handling file uploads from a standard Laravel controller.
Many of the validation rules relating to files require access to the file. When uploading directly to S3, these validation rules will fail if the S3 file object is not publicly accessible.
For more information on file validation, consult Laravel's file validation documentation.
Temporary preview URLs
After a user chooses a file, you should typically show them a preview of that file before they submit the form and store the file.
Livewire makes this trivial by using the ->temporaryUrl()
method on uploaded files.
For security reasons, temporary preview URLs are only supported on files with image MIME types.
Let's explore an example of a file upload with an image preview:
use Livewire\Component;use Livewire\WithFileUploads;use Livewire\Attributes\Validate; class UploadPhoto extends Component{ use WithFileUploads; #[Validate('image|max:1024')] public $photo; // ...}
<form wire:submit="save"> @if ($photo) <img src="{{ $photo->temporaryUrl() }}"> @endif <input type="file" wire:model="photo"> @error('photo') <span class="error">{{ $message }}</span> @enderror <button type="submit">Save photo</button></form>
As previously discussed, Livewire stores temporary files in a non-public directory; therefore, typically there's no simple way to expose a temporary, public URL to your users for image previewing.
However, Livewire solves this issue by providing a temporary, signed URL that pretends to be the uploaded image so your page can show an image preview to your users.
This URL is protected against showing files in directories above the temporary directory. And, because it's signed, users can't abuse this URL to preview other files on your system.
If you've configured Livewire to use S3 for temporary file storage, calling ->temporaryUrl()
will generate a temporary, signed URL to S3 directly so that image previews aren't loaded from your Laravel application server.
Testing file uploads
You can use Laravel's existing file upload testing helpers to test file uploads.
Below is a complete example of testing the UploadPhoto
component with Livewire:
<?php namespace Tests\Feature\Livewire; use Illuminate\Http\UploadedFile;use Illuminate\Support\Facades\Storage;use App\Livewire\UploadPhoto;use Livewire\Livewire;use Tests\TestCase; class UploadPhotoTest extends TestCase{ /** @test */ public function can_upload_photo() { Storage::fake('avatars'); $file = UploadedFile::fake()->image('avatar.png'); Livewire::test(UploadPhoto::class) ->set('photo', $file) ->call('upload', 'uploaded-avatar.png'); Storage::disk('avatars')->assertExists('uploaded-avatar.png'); }}
Below is an example of the UploadPhoto
component required to make the previous test pass:
use Livewire\Component;use Livewire\WithFileUploads; class UploadPhoto extends Component{ use WithFileUploads; public $photo; public function upload($name) { $this->photo->storeAs('/', $name, disk: 'avatars'); } // ...}
For more information on testing file uploads, please consult Laravel's file upload testing documentation.
Uploading directly to Amazon S3
As previously discussed, Livewire stores all file uploads in a temporary directory until the developer permanently stores the file.
By default, Livewire uses the default filesystem disk configuration (usually local
) and stores the files within a livewire-tmp/
directory.
Consequently, file uploads are always utilizing your application server, even if you choose to store the uploaded files in an S3 bucket later.
If you wish to bypass your application server and instead store Livewire's temporary uploads in an S3 bucket, you can configure that behavior in your application's config/livewire.php
configuration file. First, set livewire.temporary_file_upload.disk
to s3
(or another custom disk that uses the s3
driver):
return [ // ... 'temporary_file_upload' => [ 'disk' => 's3', // ... ],];
Now, when a user uploads a file, the file will never actually be stored on your server. Instead, it will be uploaded directly to your S3 bucket within the livewire-tmp/
sub-directory.
Before customizing the file upload disk, you must first publish Livewire's configuration file to your application's /config
directory by running the following command:
php artisan livewire:publish --config
Configuring automatic file cleanup
Livewire's temporary upload directory will fill up with files quickly; therefore, it's essential to configure S3 to clean up files older than 24 hours.
To configure this behavior, run the following Artisan command from the environment that is utilizing an S3 bucket for file uploads:
php artisan livewire:configure-s3-upload-cleanup
Now, any temporary files older than 24 hours will be cleaned up by S3 automatically.
If you are not using S3 for file storage, Livewire will handle file cleanup automatically and there is no need to run the command above.
Loading indicators
Although wire:model
for file uploads works differently than other wire:model
input types under the hood, the interface for showing loading indicators remains the same.
You can display a loading indicator scoped to the file upload like so:
<input type="file" wire:model="photo"> <div wire:loading wire:target="photo">Uploading...</div>
Now, while the file is uploading, the "Uploading..." message will be shown and then hidden when the upload is finished.
For more information on loading states, check out our comprehensive loading state documentation.
Progress indicators
Every Livewire file upload operation dispatches JavaScript events on the corresponding <input>
element, allowing custom JavaScript to intercept the events:
Event | Description |
---|---|
livewire-upload-start |
Dispatched when the upload starts |
livewire-upload-finish |
Dispatched if the upload is successfully finished |
livewire-upload-cancel |
Dispatched if the upload was cancelled prematurely |
livewire-upload-error |
Dispatched if the upload fails |
livewire-upload-progress |
An event containing the upload progress percentage as the upload progresses |
Below is an example of wrapping a Livewire file upload in an Alpine component to display an upload progress bar:
<form wire:submit="save"> <div x-data="{ uploading: false, progress: 0 }" x-on:livewire-upload-start="uploading = true" x-on:livewire-upload-finish="uploading = false" x-on:livewire-upload-cancel="uploading = false" x-on:livewire-upload-error="uploading = false" x-on:livewire-upload-progress="progress = $event.detail.progress" > <!-- File Input --> <input type="file" wire:model="photo"> <!-- Progress Bar --> <div x-show="uploading"> <progress max="100" x-bind:value="progress"></progress> </div> </div> <!-- ... --></form>
Cancelling an upload
If an upload is taking a long time, a user may want to cancel it. You can provide this functionality with Livewire's $cancelUpload()
function in JavaScript.
Here's an example of creating a "Cancel Upload" button in a Livewire component using wire:click
to handle the click event:
<form wire:submit="save"> <!-- File Input --> <input type="file" wire:model="photo"> <!-- Cancel upload button --> <button type="button" wire:click="$cancelUpload('photo')">Cancel Upload</button> <!-- ... --></form>
When "Cancel upload" is pressed, the file upload will request will be aborted and the file input will be cleared. The user can now attempt another upload with a different file.
Alternatively, you can call cancelUpload(...)
from Alpine like so:
<button type="button" x-on:click="$wire.cancelUpload('photo')">Cancel Upload</button>
JavaScript upload API
Integrating with third-party file-uploading libraries often requires more control than a simple <input type="file" wire:model="...">
element.
For these scenarios, Livewire exposes dedicated JavaScript functions.
These functions exist on a JavaScript component object, which can be accessed using Livewire's convenient $wire
object from within your Livewire component's template:
@script<script> let file = $wire.el.querySelector('input[type="file"]').files[0] // Upload a file... $wire.upload('photo', file, (uploadedFilename) => { // Success callback... }, () => { // Error callback... }, (event) => { // Progress callback... // event.detail.progress contains a number between 1 and 100 as the upload progresses }, () => { // Cancelled callback... }) // Upload multiple files... $wire.uploadMultiple('photos', [file], successCallback, errorCallback, progressCallback, cancelledCallback) // Remove single file from multiple uploaded files... $wire.removeUpload('photos', uploadedFilename, successCallback) // Cancel an upload... $wire.cancelUpload('photos')</script>@endscript
Configuration
Because Livewire stores all file uploads temporarily before the developer can validate or store them, it assumes some default handling behavior for all file uploads.
Global validation
By default, Livewire will validate all temporary file uploads with the following rules: file|max:12288
(Must be a file less than 12MB).
If you wish to customize these rules, you can do so inside your application's config/livewire.php
file:
'temporary_file_upload' => [ // ... 'rules' => 'file|mimes:png,jpg,pdf|max:102400', // (100MB max, and only accept PNGs, JPEGs, and PDFs)],
Global middleware
The temporary file upload endpoint is assigned a throttling middleware by default. You can customize exactly what middleware this endpoint uses via the following configuration option:
'temporary_file_upload' => [ // ... 'middleware' => 'throttle:5,1', // Only allow 5 uploads per user per minute],
Temporary upload directory
Temporary files are uploaded to the specified disk's livewire-tmp/
directory. You can customize this directory via the following configuration option:
'temporary_file_upload' => [ // ... 'directory' => 'tmp',],