Adding a Progress Bar to Livewire File Uploads

February 22nd, 2024

Uploading files in Livewire is super simple — but for larger files, adding a progress bar is a great idea.

In this article, we'll cover the basics of listening to Livewire upload events and rendering a simple progress bar. We'll also handle cancelling uploads as a bonus.

If you'd like to learn how to handle chunked, pausable and resumable uploads in Livewire, we have a course for that just here!

Let's make progress.

If you've not already done so, create a simple Upload component. This is where we'll be able to choose a file and have it start uploading.

php artisan livewire:make Upload

Update the Upload component to allow for file uploads with the WithFileUploads trait, and create a method to eventually decide what you'll do with the file when the form is submitted. I've added some example validation rules here, but you'll need to adjust it depending on your needs.

class Upload extends Component
{
    use WithFileUploads;

    public $file;

  	#[Validate('required', 'max:1024')]
    public function save()
    {
        //
    }

    // ...
}

In the upload.blade.php view, include the input and submit button, and wire up the input to the public property we added to the Upload component.

<form wire:submit="save">
    <div>
        <input type="file" name="file" id="file" wire:model="file">
        <button type="submit">Save</button>
    </div>
</form>

As soon as you choose a file to upload via the input, Livewire will begin uploading the file in the background and store it as a temporary file until it's ready to be moved. This is the event we want to hook into to display the progress.

We'll do this with Alpine.js.

<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; progress = 0;"
        x-on:livewire-upload-progress="progress = $event.detail.progress"
    >
        <input type="file" name="file" id="file" wire:model="file">
        <button type="submit">Save</button>

        <div x-show="uploading">
            <span x-text="progress"></span>
        </div>
    </div>
</form>

There's a lot happening here, so let's break it down.

First up, we need to store whether or not an upload is in progress and the percentage. Using the x-data object with Alpine.js, we store both these defaults.

<div x-data="{ uploading: false, progress: 0 }">

Next, we listen to the global events fired by Livewire for the upload, and set uploading to true or false depending on which event fired. Alongside that, we read the progress from the event's detail object and set that within our x-data object.

<div
    x-data="{ uploading: false, progress: 0 }"
    x-on:livewire-upload-start="uploading = true"
    x-on:livewire-upload-finish="uploading = false; progress = 0;"
    x-on:livewire-upload-progress="progress = $event.detail.progress"
>

Finally (at least for now), we show the progress within a span.

<div x-show="uploading">
    <span x-text="progress"></span>
</div>

If you go ahead and upload a file now, you'll see the progress appear, increment and then disapear once the upload is finished.

As a side note, there are a ton of issues you'll face when uploading huge files to test this out. Here's what you'll want to configure if you're stuck.

  1. Update your php.ini file and add/increase the upload_max_filesize and post_max_size items. I set these both to 2G (2GB) for testing.
  2. Publish the Livewire configuration using php artisan livewire:publish --config and change rules under temporary_file_upload to a larger value. Make sure you revert this to a sensible value before pushing to production.
'temporary_file_upload' => [
    // ...
    'rules' => ['max:1048576'], // Example: ['file', 'mimes:png,jpg']  | Default: ['required', 'file', 'max:12288'] (12MB)
    // ...
],
  1. You may need to increase or set the client_max_body_size in nginx. Here's an example.
server {
    listen 127.0.0.1:80 default_server;
    root /;
    charset utf-8;
    client_max_body_size 240000M; // Add or adjust this

    // ...
}

Ok, assuming you're able to upload huge files, let's continue by adding a real progress bar. I'm using Tailwind here, but vanilla CSS or another framework will work just as well.

<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; progress = 0;"
        x-on:livewire-upload-progress="progress = $event.detail.progress"
    >
        <input type="file" name="file" id="file" wire:model="file">
        <button type="submit">Save</button>

        <div x-show="uploading">
            <div class="w-full h-4 bg-slate-100 rounded-lg shadow-inner mt-3">
                <div class="bg-green-500 h-4 rounded-lg" :style="{ width: `${progress}%` }"></div>
            </div>
        </div>
    </div>
</form>

As you can see from this line, we manually bind the style in with Alpine.js and set the width to the inner bar to the progress we fetched earlier.

<div class="bg-green-500 h-4 rounded-lg" :style="{ width: `${progress}%` }"></div>

Perfect, you now have a working progress bar for your Livewire uploads!

If you'd like to have the ability to cancel an upload while it's in progress, this is really easy. Add the following line to the progress bar section.

<div x-show="uploading">
    <div class="w-full h-4 bg-slate-100 rounded-lg shadow-inner mt-3">
        <div class="bg-green-500 h-4 rounded-lg" :style="{ width: `${progress}%` }"></div>
    </div>

    <!-- Add this -->
    <button type="button" class="text-sm text-blue-500" wire:click="$cancelUpload('file')">Cancel</button>
</div>

Once this button is clicked, Livewire will cancel the upload.

However, your progress bar will remain stuck. No worries, just add another event to your Alpine.js component to reset everything once an upload gets cancelled.

<div
    x-data="{ uploading: false, progress: 0 }"
    x-on:livewire-upload-start="uploading = true"
    x-on:livewire-upload-finish="uploading = false; progress = 0;"
    x-on:livewire-upload-progress="progress = $event.detail.progress"
    x-on:livewire-upload-cancel="uploading = false; progress = 0;"
>

Nice, you're now able to cancel uploads.

There are a ton of things you can achieve with uploads in Livewire, so I'd recommend checking out the official documentation as always.

Happy uploading!

Thanks for reading! If you found this article helpful, you might enjoy our practical screencasts too.
Author
Alex Garrett-Smith
Share :

Comments

No comments, yet. Be the first to leave a comment.