File Uploads
- Basic Upload
- Handling Multiple Files
- File Validation
- Temporary Preview Urls
- Testing File Uploads
- Uploading Directly To Amazon S3
- Loading Indicators
- Progress Indicators (And All JavaScript Events)
- JavaScript Upload API
- Configuration
Basic File Upload
Note: Your Livewire version must be >= 1.2.0 to use this feature.
Livewire makes uploading and storing files extremely easy.
First, add the WithFileUploads
trait to your component. Now 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:
1use Livewire\WithFileUploads; 2 3class UploadPhoto extends Component 4{ 5 use WithFileUploads; 6 7 public $photo; 8 9 public function save()10 {11 $this->validate([12 'photo' => 'image|max:1024', // 1MB Max13 ]);14 15 $this->photo->store('photos');16 }17}
1<form wire:submit.prevent="save">2 <input type="file" wire:model="photo">3 4 @error('photo') <span class="error">{{ $message }}</span> @enderror5 6 <button type="submit">Save Photo</button>7</form>
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, there is more 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 then 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 app'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 store the file on (maybe in an S3 bucket for example).
Livewire honors the same API's Laravel uses for storing uploaded files, so feel free to browse Laravel's documentation. However, here are a few common storage scenarios for you:
1// Store the uploaded file in the "photos" directory of the default filesystem disk. 2$this->photo->store('photos'); 3 4// Store in the "photos" directory in a configured "s3" bucket. 5$this->photo->store('photos', 's3'); 6 7// Store in the "photos" directory with the filename "avatar.png". 8$this->photo->storeAs('photos', 'avatar'); 9 10// Store in the "photos" directory in a configured "s3" bucket with the filename "avatar.png".11$this->photo->storeAs('photos', 'avatar', 's3');12 13// Store in the "photos" directory, with "public" visibility in a configured "s3" bucket.14$this->photo->storePublicly('photos', 's3');15 16// Store in the "photos" directory, with the name "avatar.png", with "public" visibility in a configured "s3" bucket.17$this->photo->storePubliclyAs('photos', 'avatar', 's3');
The methods above should provide enough flexibility for storing the uploaded files exactly how you want to.
Handling Multiple Files
Livewire handles multiple file uploads automatically by detecting the multiple
attribute on the <input>
tag.
Here's an example of a file upload that handles multiple uploads:
1use Livewire\WithFileUploads; 2 3class UploadPhotos extends Component 4{ 5 use WithFileUploads; 6 7 public $photos = []; 8 9 public function save()10 {11 $this->validate([12 'photos.*' => 'image|max:1024', // 1MB Max13 ]);14 15 foreach ($this->photos as $photo) {16 $photo->store('photos');17 }18 }19}
1<form wire:submit.prevent="save">2 <input type="file" wire:model="photos" multiple>3 4 @error('photos.*') <span class="error">{{ $message }}</span> @enderror5 6 <button type="submit">Save Photo</button>7</form>
File Validation
Like you've seen in previous examples, validating file uploads with Livewire is exactly the same as handling file uploads from a standard Laravel controller.
Note: Many of the Laravel validation rules relating to files require access to the file. If you are uploading directly to S3 these validation rules will fail if the object is not publicly accessible.
For more information on Laravel's File Validation utilities, visit the documentation.
Real-time Validation
It's possible to validate a user's upload in real-time, BEFORE they press "submit".
Again, you can accomplish this like you would any other input type in Livewire:
1use Livewire\WithFileUploads; 2 3class UploadPhoto extends Component 4{ 5 use WithFileUploads; 6 7 public $photo; 8 9 public function updatedPhoto()10 {11 $this->validate([12 'photo' => 'image|max:1024', // 1MB Max13 ]);14 }15 16 public function save()17 {18 // ...19 }20}
1<form wire:submit.prevent="save">2 <input type="file" wire:model="photo">3 4 @error('photo') <span class="error">{{ $message }}</span> @enderror5 6 <button type="submit">Save Photo</button>7</form>
Now, when user selects a file (After Livewire uploads the file to a temporary directory) the file will be validated and the user will receive an error BEFORE they submit the form.
Temporary Preview Urls
After a user chooses a file, you may want to show them a preview of that file BEFORE they submit the form and actually store the file.
Livewire makes this trivial with the ->temporaryUrl()
method on uploaded files.
Note: for security reasons, temporary urls are only supported for image uploads.
Here's an example of a file upload with an image preview:
1use Livewire\WithFileUploads; 2 3class UploadPhotoWithPreview extends Component 4{ 5 use WithFileUploads; 6 7 public $photo; 8 9 public function updatedPhoto()10 {11 $this->validate([12 'photo' => 'image|max:1024',13 ]);14 }15 16 public function save()17 {18 // ...19 }20}
1<form wire:submit.prevent="save"> 2 @if ($photo) 3 Photo Preview: 4 <img src="{{ $photo->temporaryUrl() }}"> 5 @endif 6 7 <input type="file" wire:model="photo"> 8 9 @error('photo') <span class="error">{{ $message }}</span> @enderror10 11 <button type="submit">Save Photo</button>12</form>
Livewire stores temporary files in a non-public directory as previously mentioned, therefore, there's no simple way to expose a temporary, public URL to your users for image previewing.
Livewire takes care of this complexity, by providing a temporary, signed URL that pretends to be the uploaded image so that your page can show something to your users.
This URL is protected against showing files in directories above the temporary directory of course and because it's signed temporarily, users can't abuse this URL to preview other files on your system.
->temporaryUrl()
will generate a temporary, signed URL from S3 directly so that you don't hit your Laravel app server for this preview at all.
Testing File Uploads
Testing file uploads in Livewire is simple with Laravel's file upload testing helpers.
Here's a complete example of testing the "UploadPhoto" component with Livewire.
1/** @test */ 2public function can_upload_photo() 3{ 4 Storage::fake('avatars'); 5 6 $file = UploadedFile::fake()->image('avatar.png'); 7 8 Livewire::test(UploadPhoto::class) 9 ->set('photo', $file)10 ->call('upload', 'uploaded-avatar.png');11 12 Storage::disk('avatars')->assertExists('uploaded-avatar.png');13}
Here's a snippet of the "UploadPhoto" component required to make the previous test pass:
1class UploadPhoto extends Component 2{ 3 use WithFileUploads; 4 5 public $photo; 6 7 // ... 8 9 public function upload($name)10 {11 $this->photo->storeAs('/', $name, $disk = 'avatars');12 }13}
For more specifics on testing file uploads, reference Laravel's file upload testing documentation.
Uploading Directly To Amazon S3
As previously mentioned, Livewire stores all file uploads in a temporary directory until the developer chooses to store the file permanently.
By default, Livewire uses the default filesystem disk configuration (usually local
), and stores the files under a folder called livewire-tmp/
.
This means that file uploads are always hitting your server; even if you choose to store them in an S3 bucket later.
If you wish to bypass this system and instead store Livewire's temporary uploads in an S3 bucket, you can configure that behavior easily:
In your config/livewire.php
file, set livewire.temporary_file_upload.disk
to s3
(or another custom disk that uses the s3
driver):
1return [2 ...3 'temporary_file_upload' => [4 'disk' => 's3',5 ...6 ],7];
Now, when a user uploads a file, the file will never actually hit your server. It will be uploaded directly to your S3 bucket, under the sub-directory: livewire-tmp/
.
Configuring Automatic File Cleanup
This temporary directory will fill up with files quickly, therefore, it's important to configure S3 to cleanup files older than 24 hours.
To configure this behavior, simply run the following artisan command from the environment that has the S3 bucket configured.
1php artisan livewire:configure-s3-upload-cleanup
Now, any temporary files older than 24 hours will be cleaned up by S3 automatically.
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:
1<input type="file" wire:model="photo">2 3<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.
This works with the entire Livewire Loading States API.
Progress Indicators (And All JavaScript Events)
Every file upload in Livewire dispatches JavaScript events on the <input>
element for custom JavaScript to listen to.
Here are the dispatched events:
Event | Description |
---|---|
livewire-upload-start |
Dispatched when the upload starts |
livewire-upload-finish |
Dispatches if the upload is successfully finished |
livewire-upload-error |
Dispatches if the upload fails in some way |
livewire-upload-progress |
Dispatches an event containing the upload progress percentage as the upload progresses |
Here is an example of wrapping a Livewire file upload in an AlpineJS component to display a progress bar:
1<div 2 x-data="{ isUploading: false, progress: 0 }" 3 x-on:livewire-upload-start="isUploading = true" 4 x-on:livewire-upload-finish="isUploading = false" 5 x-on:livewire-upload-error="isUploading = false" 6 x-on:livewire-upload-progress="progress = $event.detail.progress" 7> 8 <!-- File Input --> 9 <input type="file" wire:model="photo">10 11 <!-- Progress Bar -->12 <div x-show="isUploading">13 <progress max="100" x-bind:value="progress"></progress>14 </div>15</div>
JavaScript Upload API
Integrating with 3rd-party file-uploading libraries often requires finer-tuned control than a simple <input type="file">
tag.
For these cases, Livewire exposes dedicated JavaScript functions.
The functions exist on the JavaScript component object, which can be accessed using the convenience Blade directive: @this
. If you haven't seen @this
before, you can read more about it here.
1<script> 2 let file = document.querySelector('input[type="file"]').files[0] 3 4 // Upload a file: 5 @this.upload('photo', file, (uploadedFilename) => { 6 // Success callback. 7 }, () => { 8 // Error callback. 9 }, (event) => {10 // Progress callback.11 // event.detail.progress contains a number between 1 and 100 as the upload progresses.12 })13 14 // Upload multiple files:15 @this.uploadMultiple('photos', [file], successCallback, errorCallback, progressCallback)16 17 // Remove single file from multiple uploaded files18 @this.removeUpload('photos', uploadedFilename, successCallback)19</script>
Configuration
Because Livewire stores all file uploads temporarily before the developer has a chance to validate or store them, Livewire assumes some default handling of 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 this, you can configure exactly what validate rules should run on all temporary file uploads inside config/livewire.php
:
1return [2 ...3 'temporary_file_upload' => [4 ...5 'rules' => 'file|mimes:png,jpg,pdf|max:102400', // (100MB max, and only pngs, jpegs, and pdfs.)6 ...7 ],8];
Global Middleware
The temporary file upload endpoint has throttling middleware by default. You can customize exactly what middleware this endpoint uses with the following configuration variable:
1return [2 ...3 'temporary_file_upload' => [4 ...5 'middleware' => 'throttle:5,1', // Only allow 5 uploads per user per minute.6 ],7];
Temporary Upload Directory
Temporary files are uploaded to the livewire-tmp/
directory on the specified disk. You can customize this with the following configuration key:
1return [2 ...3 'temporary_file_upload' => [4 ...5 'directory' => 'tmp',6 ],7];
Maximum Upload Time
File uploads are automatically invalidated if they take longer than 5 minutes. You can customize this with the following configuration key:
1return [2 ...3 'temporary_file_upload' => [4 ...5 'max_upload_time' => 5,6 ],7];