Back to Course |
Livewire 3 From Scratch: Practical Course

File Uploads in Livewire

In this lesson, let's see how easy it is to upload a file with Livewire.


DB Structure and Upload Input

First, we need a field in the DB where to store images' path.

database/migrations/xxxx_add_photo_to_products_table.php:

Schema::table('products', function (Blueprint $table) {
$table->string('photo')->nullable();
});

app/Models/Product.php:

class Product extends Model
{
protected $fillable = [
'name',
'description',
'category_id',
'color',
'in_stock',
'photo',
];
 
// ...
}

And we need a file input in the form.

resources/views/livewire/products-create.blade.php:

<form method="POST" wire:submit="save">
// ...
 
<div class="mt-4">
<label for="photo" class="block font-medium text-sm text-gray-700">Photo</label>
<input wire:model="form.image" type="file" id="photo" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm">
@error('form.image')
<span class="mt-2 text-sm text-red-600">{{ $message }}</span>
@enderror
</div>
 
<button class="mt-4 px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700">
Save Product
</button>
</form>

photo image field

We binded photo input to a form.image public property. Don't forget all properties are in the ProductsForm Form Object.

app/Livewire/Forms/ProductsForm.php:

class ProductsForm extends Form
{
// ...
#[Validate('image')]
public $image;
 
// ...
}

And here's how we will show image in the table.

resources/views/livewire/products.blade.php:

// ...
<table class="min-w-full divide-y divide-gray-200 border">
<thead>
<tr>
<th class="px-6 py-3 bg-gray-50 text-left">
</th>
<th class="px-6 py-3 bg-gray-50 text-left">
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Name</span>
</th>
<th class="px-6 py-3 bg-gray-50 text-left">
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Category</span>
</th>
<th class="px-6 py-3 bg-gray-50 text-left">
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Description</span>
</th>
<th class="px-6 py-3 bg-gray-50 text-left">
</th>
</tr>
</thead>
 
<tbody class="bg-white divide-y divide-gray-200 divide-solid">
@forelse($products as $product)
<tr class="bg-white">
<td class="px-6 py-4">
@if($product->photo)
<img src="/storage/{{ $product->photo }}" class="w-20 h-20" />
@endif
</td>
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{{ $product->name }}
</td>
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
@foreach($product->categories as $category)
<div>{{ $category->name }}</div>
@endforeach
</td>
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{{ $product->description }}
</td>
<td>
<a href="{{ route('products.edit', $product) }}" class="inline-flex items-center px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest">
Edit </a>
<a wire:click="deleteProduct({{ $product->id }})" onclick="return confirm('Are you sure?') || event.stopImmediatePropagation()" href="#" class="inline-flex items-center px-4 py-2 bg-red-600 rounded-md font-semibold text-xs text-white uppercase tracking-widest">
Delete </a>
</td>
</tr>
@empty
<tr>
<td class="px-6 py-4 text-sm" colspan="3">
No products were found.
</td>
</tr>
@endforelse
</tbody>
</table>
// ...

table with image


Uploading Image

First, we must add a WithFileUploads trait to the Livewire component.

app/Livewire/ProductsCreate.php:

use Livewire\WithFileUploads;
 
class ProductsCreate extends Component
{
use WithFileUploads;
 
// ...
}

Now we can upload an image and save its filename in the DB.

app/Livewire/Forms/ProductsForm.php:

class ProductsForm extends Form
{
// ...
 
public function save(): void
{
$this->validate();
 
$filename = $this->image->store('products', 'public');
 
$product = Product::create($this->all());
$product = Product::create($this->all() + ['photo' => $filename]);
$product->categories()->sync($this->productCategories);
}
 
public function update(): void
{
$this->validate();
 
$filename = $this->image->store('products', 'public');
 
$this->product->update($this->all());
$this->product->update($this->all() + ['photo' => $filename]);
$this->product->categories()->sync($this->productCategories);
}
}

Upload files there are many ways, with or without a package. In this simple example, we upload an image in the products directory of a public disk.


Sometimes, you should allow users to cancel file uploading. For example, if the upload takes a long time. You can add a button and call the $cancelUpload() function.

resources/views/livewire/products-create.blade.php:

<form method="POST" wire:submit="save">
// ...
 
<div class="mt-4">
<label for="photo" class="block font-medium text-sm text-gray-700">Photo</label>
<input wire:model="form.image" type="file" id="photo" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm">
@error('form.image')
<span class="mt-2 text-sm text-red-600">{{ $message }}</span>
@enderror
<button type=button" wire:click="$cancelUpload('form.image')" class="mt-4 px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700">
Cancel upload
</button>
</div>
 
<button class="mt-4 px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700">
Save Product
</button>
</form>

When "Cancel upload" is pressed, the file upload request will be aborted, and the file input will be cleared.

For more, read the official documentation.

If you want to learn more about file uploading, I have a course File Uploads in Laravel, or you can check more content about file upload here.