Features
Uploads
When you bind a file input with nitro:model, the file uploads as soon as the visitor chooses it. The component holds a signed reference to it, the view can preview it, and a #[Server] method validates and stores it.
On this page
As soon as the visitor chooses a file, it is sent to the server, where it waits in a private directory.
The property becomes an Upload. The browser can show the file's name, its size and a preview,
and a #[Server] method validates and stores it like any uploaded file in Laravel.
The component
<?php
namespace App\Nitro\Components;
use Nitro\Attributes\Locked;
use Nitro\Attributes\Server;
use Nitro\Component;
use Nitro\Upload;
class PhotoCheck extends Component
{
public ?Upload $photo = null;
#[Locked]
public string $result = '';
#[Server]
public function check(): void
{
$this->validate(['photo' => 'required|image|max:2048']);
[$width, $height] = getimagesize($this->photo->getRealPath());
// A demo: the photo is checked, not kept. Your app would store it with $this->photo->store('photos', 'public').
$this->result = "{$this->photo->getClientOriginalName()}: {$width} × {$height} pixels, ".number_format($this->photo->getSize() / 1024, 1).' KB, checked on the server.';
$this->reset('photo');
}
}
<div class="w-full max-w-sm space-y-3 text-sm">
<label class="flex cursor-pointer items-center justify-center gap-2 rounded-xl border-2 border-dashed border-slate-300 px-4 py-5 text-slate-500 hover:border-blue-400 dark:border-white/15">
<input type="file" accept="image/*" nitro:model="photo" class="sr-only">
<span nitro:loading.remove nitro:target="photo">Choose a photo (2 MB at most)</span>
<span nitro:loading nitro:target="photo">Uploading…</span>
</label>
@error('photo') <p class="text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
@if ($photo && str_starts_with($photo->getMimeType(), 'image/'))
<div class="flex items-center gap-3">
<img src="{{ $photo->temporaryUrl() }}" alt="{{ $photo->getClientOriginalName() }}" class="size-16 rounded-lg object-cover ring-1 ring-slate-200 dark:ring-white/10">
<div class="min-w-0 flex-1">
<p class="truncate font-medium text-slate-900 dark:text-white">{{ $photo->getClientOriginalName() }}</p>
<p class="text-xs text-slate-500">{{ number_format($photo->getSize() / 1024, 1) }} KB · {{ $photo->getMimeType() }}</p>
</div>
<button type="button" nitro:click="check" nitro:loading.attr="disabled" class="rounded-lg bg-slate-900 px-3 py-2 font-semibold text-white disabled:opacity-50 dark:bg-white dark:text-slate-900">Check</button>
</div>
@endif
@if ($result !== '')
<p class="rounded-lg bg-emerald-50 px-3 py-2 text-emerald-700 ring-1 ring-emerald-200 dark:bg-emerald-500/10 dark:text-emerald-300 dark:ring-emerald-500/20">{{ $result }}</p>
@endif
</div>
Compiling…
Checked, not kept, for this demo
So that any visitor can try it, the example reads the photo's size on the server and keeps nothing. These docs accept images up to 2 MB. Your app would store the photo with $this->photo->store('photos', 'public').
Accepting one file or several
A ?Upload property takes one file. To take the files of an input with multiple,
use an array property marked #[Uploads]:
use Nitro\Attributes\Uploads;
use Nitro\Upload;
public ?Upload $photo = null; // One file, from <input type="file" nitro:model="photo">
#[Uploads]
public array $photos = []; // Several files, from <input type="file" multiple nitro:model="photos">
#[Server]
public function save(): void
{
$this->validate(['photos' => 'max:5', 'photos.*' => 'image|max:2048']);
foreach ($this->photos as $photo) {
$photo->store('gallery', 'public');
}
$this->reset('photos');
}
Upload is Laravel's UploadedFile, so validation rules such as
image, mimes and max, and store(), all work as usual. You
can use these methods in the view, on the server and in the browser:
| Method | Gives |
|---|---|
temporaryUrl()
|
A URL to preview the file, for images |
getClientOriginalName()
|
Its name on the visitor's computer |
getClientOriginalExtension()
|
Its extension |
getSize()
|
Its size, in bytes |
getMimeType()
|
Its type, such as
image/png
|
Showing progress while it uploads
nitro:loading nitro:target="photo" shows an element while the file is on its way, just as it
does for a #[Server] call. The input also fires events that report the progress:
const input = document.querySelector('#photo');
input.addEventListener('nitro-upload-start', () => bar.hidden = false);
input.addEventListener('nitro-upload-progress', (e) => bar.value = e.detail.progress);
input.addEventListener('nitro-upload-finish', () => bar.hidden = true);
input.addEventListener('nitro-upload-error', () => bar.hidden = true);
What Nitro checks
- As each file arrives, Nitro checks it against
nitro.uploads.rules. If a rule refuses the file, the property gets an$errorsmessage and the upload stops there. - In your method, you check your own rules with
$this->validate(). - The reference the browser holds is signed, and it only works in its own session. Nitro refuses a made-up or changed reference. Only
Uploadand#[Uploads]properties accept files. - Leftover uploads that no component stored are removed after a day.
// config/nitro.php
'uploads' => [
'directory' => storage_path('app/nitro-uploads'), // Where uploads wait until you store them.
'rules' => ['file', 'max:12288'], // Checked as each file arrives.
'max_files' => 20, // Files in one input at once.
'throttle' => '60,1', // Uploads a minute, per visitor.
],