Getting started
Advanced file upload
The most important component in the package is the AdvancedFileUpload component. This component is a form component that you can use to replace any FileUpload, with minimal changes to your code. Every time a file is uploaded, it is first uploaded (in chunks or using S3 Multipart) to the temporary file upload disk. Then, when you save the form, the files are moved to the disk you specify.
use RalphJSmit\Filament\Upload\Filament\Forms\Components\AdvancedFileUpload;
AdvancedFileUpload::make('profile_path')
->temporaryFileUploadDisk('s3') // Default to `config('livewire.temporary_file_upload.disk')` or `config('filesystems.default')`...
->disk('s3') // Default to `config('filament.default_filesystem_disk')`
As the files get moved between disks, it is therefore optimal if you use the same disk for temporary file uploads as the permanent disk, but it is not a requirement. You can also use scoped disks, which the package will handle automatically.
Make sure to have an
arrayorcollectioncast on your Eloquent model to contain an array of file paths.
Setting a directory
To place and read uploaded files from a certain directory on your disk, use the ->directory() method:
AdvancedFileUpload::make('banner_images')
->disk('s3')
->directory('banners')
->multiple()
When you set a directory, the directory is not prefixed to the dehydrated state. So if you upload a file in the above example, the dehydrated state will be an array of purely the filenames without starting with
banners/, as this is done automatically for convenience and flexibility. However, the native FilamentFileUploaddoes prefix the directory to the dehydrated state. If you would like to have the same behavior, you can passshouldDehydrateDirectory: trueas second parameter to the->directory()method.
If you upload an image, it will get placed automatically in the banners directory. Only the filename inside the banners directory will be returned, so if you upload a file banners/banner.jpg, only banner.jpg will be returned.
Accepted file types & preserving filenames (security)
You can specify which file types are allowed to be uploaded using the ->acceptedFileTypes() method. This method accepts an array of mimetypes or file extensions:
AdvancedFileUpload::make('brochure_path')
->acceptedFileTypes(['application/pdf'])
To accept images, use ->image(), which is an alias for the image/* mimetype group.
By default, the original filenames of the uploaded files are not preserved, but assigned a random name on the server with the neutral extension .upload. When the file is fully uploaded, the package will first retrieve the mimetype of the file content, and validate it against the accepted file types. Only if the mimetype (based on the actual file-content) is valid and accepted, the file will be renamed to the extension that belongs to this mimetype. This way, the package never trusts the file-extension provided the user (which can be easily manipulated), and is only looked at the contents of the file.
If you want to preserve the original filenames, you can use the ->preserveFilenames(). This method is safe to use for S3-based disks, as there no risk of malicious code execution is present. However, you should never use this for the public disk (or even local).
AdvancedFileUpload::make('brochure_path')
->acceptedFileTypes(['application/pdf'])
->preserveFilenames()
Another aspect to be aware of is that if the filename can be determined by the user, then this can lead to overwrites of existing files if not properly validated.
Storing filenames in independent column
As using ->preserveFilenames() is not optimal, you can also choose to let uploads be assigned a random generated name, but store the original filenames in a separate column. This way you can still show users the original filename, whilst ensuring security and file uniqueness on your actual disk. To enable this, use the ->storeFileNamesIn() method:
AdvancedFileUpload::make('brochure_path')
->acceptedFileTypes(['application/pdf'])
->storeFileNamesIn('brochure_filename')
It also works for multiple files:
AdvancedFileUpload::make('brochure_paths')
->acceptedFileTypes(['application/pdf'])
->storeFileNamesIn('brochure_filenames')
->multiple()
This method can be enabled safely even if you already have files, as the AdvancedFileUpload will look at the second column to see if any alias is defined, and if not, it will automatically fall back to the actual filename as on disk. Make sure that for single upload the filename column is a string, whereas for multiple uploads the filename column is a JSON column with an array or collection cast.
Fetching file information
When rendering existing files in the upload component, you have the choice to prevent the package from getting the file type, size and whether it exists by using the ->fetchFileInformation(false) method:
AdvancedFileUpload::make('banner_images')
->fetchFileInformation(false)
One downside of this is that the package cannot render an appropriate preview, and neither can it remove non-existing file paths on mount.
The file size and mimetype are getting cached, so for performance you don't necessarily have to disable this.
Spatie MediaLibrary support
The package supports an automatic integration with Spatie MediaLibrary. To enable the automatic loading of media files, use the ->spatieMediaLibrary() method:
AdvancedFileUpload::make('media')
->spatieMediaLibrary()
->multiple() // In case of single-support, only the first image will be loaded.
When enabled, this will take the current $record and load the media files from the MediaLibrary into the upload. Users can then view, edit, download, replace and delete these files. When a user saves the form, the media collection is updated.
To use a different collection, provide a collection: $collection parameter:
AdvancedFileUpload::make('media')
->spatieMediaLibrary(collection: 'banners')
->multiple()
Providing custom properties
You may pass in custom properties when uploading files using the ->customProperties() method:
AdvancedFileUpload::make('banners')
->spatieMediaLibrary(collection: 'banners')
->multiple()
->customProperties(['zip_filename_prefix' => 'folder/subfolder/'])
Providing custom headers
You may pass in custom headers when uploading files using the ->customHeaders() method:
AdvancedFileUpload::make('banners')
->spatieMediaLibrary(collection: 'banners')
->multiple()
->customHeaders(['CacheControl' => 'max-age=86400'])
Filtering media
It's possible to target a file upload component to only handle a certain subset of media in a collection. To do that, you can filter the media collection using the filterMediaUsing() method. This method accepts a function that receives the $media collection and manipulates it. You can use any collection method to filter it.
For example, you could scope the field to only handle media that has certain custom properties:
AdvancedFileUpload::make('banners')
->spatieMediaLibrary(collection: 'banners')
->customProperties(fn (Get $get): array => [
'gallery_id' => $get('gallery_id'),
])
->filterMediaUsing(function (Collection $media, Get $get): Collection {
return $media->where('custom_properties.gallery_id', $get('gallery_id'));
})
Multiple uploads
To allow multiple uploads, use the ->multiple() method:
AdvancedFileUpload::make('banner_images')
->disk('s3')
->multiple()
Minimum and maximum file count
You can specify a minimum and maximum number of files that can be uploaded using the ->minFiles() and ->maxFiles() methods:
AdvancedFileUpload::make('banner_images')
->multiple()
->minFiles(1)
->maxFiles(5)
Minimum and maximum file size
You can specify a minimum and maximum file size that can be uploaded using the ->minFileSize() and ->maxFileSize() methods. The file size is specified in bytes:
AdvancedFileUpload::make('banner_images')
->multiple()
->minFileSize(1024) // 1 KB
->maxFileSize(1024 * 1024) // 10 MB
This rule is only applied to newly uploaded files, as applying this to already existing files on the server would not be a great UX in case you ever must change this validation, and because it would make every form save very slow.
Reordering files
If you have multiple file-upload, you can allow users to reorder files using the ->reorderable() method. This will modify the order in which the file paths are dehydrated:
AdvancedFileUpload::make('banner_images')
->multiple()
->reorderable()
Reordering is also supported when using the Spatie MediaLibrary-integration.
Custom upload logic
By default, when a user uploads a file, the upload is first stored on the ->temporaryFileUploadDisk(), within the ->temporaryFileUploadDirectory() (usually livewire-tmp), and then within a nested directory. When a user submits the form, the file is moved (if the temporary disk is the same as the permanent disk) or copied to its permanent location.
If you want, you can provide a custom logic to replace this process, so that you can modify the file or determine the permanent file location yourself. To do use, use the ->saveUploadedFileUsing() method:
AdvancedFileUpload::make('banner_images')
->multiple()
->saveUploadedFileUsing(function (AdvancedFileUpload $component, string $temporaryFileUploadPath, string $temporaryFileUploadFilename) {
// Temporary disk...
$temporaryFileUploadDisk = $component->getTemporaryFileUploadDisk();
$temporaryFileUploadDirectory = $component->getTemporaryFileUploadDirectory();
// Permanent disk...
$disk = $component->getDisk();
$directory = $component->getDirectory();
$diskData = $component->getDiskData();
// Copy/move the file to permanent location...
$disk->put(
path: $diskData->generatePath($directory, $temporaryFileUploadFilename),
contents: $temporaryFileUploadDisk->readStream($temporaryFileUploadPath),
);
// Always return the _filename_ that the file was moved to (excluding the `$directory` prefix).
return $temporaryFileUploadFilename;
});
This hook can be called multiple times in the same request for different files if a user uploads multiple new files at once.
Uploading message
When a file is uploading, the "submit" button of the form will be temporarily disabled with the following text: 'Uploading file...'. To modify this message, use the ->uploadingMessage() method:
AdvancedFileUpload::make('banner_images')
->multiple()
->uploadingMessage('Uploading banner...')
It is recommended to keep the message short.
Layout options
The AdvancedFileUpload component is a column-based component, meaning that it exists of two child-groups: the dropzone and the files-section. For each, you can specify how many columns it should take up. For example, for a vertical layout, you can configure it like this:
AdvancedFileUpload::make('profile_path')
->columns(5) // 5 columns total
->dropzoneColumnSpan(2) // Dropzone section should span 2 columns
->filesColumnSpan(3) // Files section should span 3 columns
->dropzoneAfterFiles() // Dropzone should come after (or before: default) the files
Dropzone area
The default dropzone heading and description can be modified using the following methods:
AdvancedFileUpload::make('profile_path')
->dropzoneHeading('Drag & drop your profile image here')
->dropzoneDescription('or, click to select an image')
Above the heading and description three icons are displayed. You can modify these using the ->dropzonePreview{}Icon() methods, to better match the type of files you are expecting:
use Filament\Support\Icons\Heroicon;
AdvancedFileUpload::make('profile_path')
->dropzonePreviewLeftIcon(Heroicon::OutlinedDocument)
->dropzonePreviewCenterIcon(Heroicon::OutlinedPhoto)
->dropzonePreviewRightIcon(Heroicon::OutlinedPaperClip)
Instead of icons, you can also provide three images as previews. To do so, use the ->dropzonePreviewImages() method:
AdvancedFileUpload::make('profile_path')
->dropzonePreviewImages(
leftImageUrl: asset('preview-a-small.png'),
centerImageUrl: asset('preview-b-small.png'),
rightImageUrl: asset('preview-c-small.png')
)
Files
The AdvancedFileUpload component provides several actions that can be performed on files. Each action can be customized or disabled using the following methods:
Add file action
The add file action allows users to select files from their device to upload. You can customize this action using the ->addAction() method:
use Filament\Support\Icons\Heroicon;
AdvancedFileUpload::make('banner_images')
->addAction(function (Forms\Components\Actions\Action $action) {
return $action
->label('Upload banners')
->icon(Heroicon::OutlinedPhoto);
})
Delete file action
The delete file action allows users to remove individual files. You can control whether files can be deleted using the ->deletable() method, and customize the action using the ->deleteAction() method:
use Filament\Support\Icons\Heroicon;
AdvancedFileUpload::make('banner_images')
->deleteAction(function (Forms\Components\Actions\Action $action) {
return $action
->label('Remove banner')
->icon(Heroicon::OutlinedTrash);
})
To disable the action, use:
AdvancedFileUpload::make('banner_images')
->deletable(false)
Delete all files action
The delete all files action allows users to remove all files at once. You can control whether all files can be deleted using the ->deleteAll() method, and customize the action using the ->deleteAllAction() method:
use Filament\Support\Icons\Heroicon;
AdvancedFileUpload::make('banner_images')
->deleteAllAction(function (Forms\Components\Actions\Action $action) {
return $action
->label('Clear all banners')
->icon(Heroicon::OutlinedTrash);
})
To disable the action, use:
AdvancedFileUpload::make('banner_images')
->deleteAll(false)
Download file action
The download file action allows users to download individual files. You can control whether files can be downloaded using the ->downloadable() method, and customize the action using the ->downloadAction() method:
use Filament\Support\Icons\Heroicon;
AdvancedFileUpload::make('banner_images')
->downloadAction(function (Forms\Components\Actions\Action $action) {
return $action
->label('Download banner')
->icon(Heroicon::OutlinedArrowDownTray);
})
To disable the action, use:
AdvancedFileUpload::make('banner_images')
->downloadable(false)
Edit file action
The edit file action allows users to edit image files using the built-in image editor. You can control whether files can be edited using the ->editable() method, and customize the action using the ->editAction() method:
use Filament\Support\Icons\Heroicon;
AdvancedFileUpload::make('banner_images')
->editAction(function (Forms\Components\Actions\Action $action) {
return $action
->label('Edit banner')
->icon(Heroicon::OutlinedPencilSquare);
})
To disable the action, use:
AdvancedFileUpload::make('banner_images')
->editable(false)
Preview file action
The preview file action allows users to view files in a modal. You can control whether files can be previewed using the ->previewable() method, and customize the action using the ->previewAction() method:
use Filament\Support\Icons\Heroicon;
AdvancedFileUpload::make('banner_images')
->previewAction(function (Forms\Components\Actions\Action $action) {
return $action
->label('View banner')
->icon(Heroicon::OutlinedEye);
})
To disable the action, use:
AdvancedFileUpload::make('banner_images')
->previewable(false)
Replace file action
The replace file action allows users to replace an existing file with a new one. You can control whether files can be replaced using the ->replaceable() method, and customize the action using the ->replaceAction() method:
use Filament\Support\Icons\Heroicon;
AdvancedFileUpload::make('banner_images')
->replaceAction(function (Forms\Components\Actions\Action $action) {
return $action
->label('Replace banner')
->icon(Heroicon::OutlinedArrowPathRoundedSquare);
})
To disable the action, use:
AdvancedFileUpload::make('banner_images')
->replaceable(false)
Custom file actions
You can add custom actions to each file by using the ->extraItemActions() method. Actions are inserted just before the "delete"-action, as that is usually the most logical order. Inside the ->action() method, you can retrieve the disk name and the path to the file that the action was triggered on. This allows you to perform any action that has a relation to an individual file.
use Filament\Support\Icons\Heroicon;
AdvancedFileUpload::make('banner_images')
->extraItemActions([
fn (AdvancedFileUpload $component) => Forms\Components\Actions\Action::make('optimize')
->icon(Heroicon::Sparkles)
->iconButton()
->color('primary')
->action(function (array $arguments) use ($component) {
[$diskName, $path] = $component->getDiskNameAndPath($arguments['id']);
// Retrieve file...
$contents = Storage::disk($diskName)->get($path);
// Modify file based on `$diskName` and `$path`. Note: do *not* move
// the file as that will cause the component to end up invalid.
// The `$path` is the filename + the provided `->directory()` if specified.
}),
])
Note: when using custom actions, you should always use the
$component->getDiskNameAndPath()method to retrieve the disk name and path of the file that the action was triggered on. This ensures that you are working with the correct file. Furthermore, do not modify the file name or its location, as then the component ends up with an invalid state.