Skip to content

File upload

The file upload field stores uploaded files on a filesystem disk and submits their paths. It supports single or multiple files, image-only mode, size and count limits, and direct-to-storage signed uploads. Create one with FileUpload::make().

FileUpload::make('avatar', 'Avatar')
->image()
->maxSize(2048)

->disk() chooses the filesystem disk the field reads and writes. Without it the field uses the lattice.files.disk config value (defaulting to public).

FileUpload::make('document')->disk('s3');

->image() restricts the picker to images and renders an image-aware preview. It also defaults the accepted types to image/* unless you set them explicitly.

FileUpload::make('avatar')->image();

->acceptedFileTypes() takes a list of MIME types or extensions for the file picker’s accept attribute. ->maxSize() caps each file’s size in kilobytes.

FileUpload::make('attachment')
->acceptedFileTypes(['application/pdf', 'image/png'])
->maxSize(5120);

->multiple() lets the field accept more than one file; it then submits an array of paths. ->maxFiles() caps how many the form accepts.

FileUpload::make('gallery', 'Gallery')
->image()
->multiple()
->maxFiles(8);

By default files are uploaded through your form endpoint. ->signedUpload() switches to direct uploads: the client asks the form endpoint for a short-lived signed URL, uploads straight to the disk (for example S3), and submits the resulting object key. This keeps large files off your PHP process. The disk must support temporary upload URLs.

FileUpload::make('document')
->disk('s3')
->signedUpload();

A submitted file arrives as an UploadedFile (or an array of them for ->multiple()); store it however you like:

public function handle(Request $request): Response
{
$data = $this->validate($request);
if (($data['avatar'] ?? null) instanceof UploadedFile) {
$data['avatar'] = $data['avatar']->store('avatars', 'public');
}
// persist $data['avatar'] ...
}

A signed upload arrives as a temporary object key under lattice.files.temp_prefix, not the final path — temporary objects are cleaned up, so you must promote the key out of the temp prefix before persisting it. Call finalizeSignedUploads() on the same field, passing the submitted keys and a closure that returns each file’s destination path. It moves the object and returns the finalized disk, path, name, mime_type, and size for each file:

$finalized = FileUpload::make('document')->disk('s3')->signedUpload()
->finalizeSignedUploads(
(array) $data['document'],
fn (string $key, array $meta): string => "documents/{$request->user()->id}.{$meta['extension']}",
);
foreach ($finalized as $upload) {
// $upload['disk'], $upload['path'], $upload['name'], $upload['mime_type'], $upload['size']
}

When a form is bound to a record, stored paths render as existing files the user can remove. Each existing file carries a sealed token, and the client posts the tokens of removed files under {name}__removed[]. Resolve them server-side with the static FileUpload::removed() helper, which unseals each token (skipping any forged, expired, or mismatched one) and returns the trusted disk paths to delete:

foreach (FileUpload::removed($request, 'avatar') as $path) {
Storage::disk('public')->delete($path);
}

FileUpload shares label, required, disabled, read-only, and visibility options with every field — see Fields. For validation and conditional behavior, see Validation and Conditional fields.