Skip to content

Overview ​

The FileAction class extends Filament\Actions\Action, creating a custom Filament action that allows users to upload, manage, and delete file attachments associated with a model. This action is particularly useful for chat or task management systems where users need to attach files to messages, tasks, or other entities.

Key Features of FileAction ​

  1. File Uploading

    • Supports multiple file uploads.
    • Files are stored in the chats-attachments directory.
    • Users can preview, download, reorder, and delete files.
  2. File Type Restrictions

    • Accepts only specific file types such as images, PDFs, Word documents, Excel sheets, and plain text files.
    • Files are limited to a maximum size of 10 MB (10,240 KB).
  3. Notifications

    • Success notifications when files are uploaded or deleted.
    • Warning notifications if there are no new files to upload.
    • Error notifications in case of failures.
  4. Database Integration

    • Uses an attachments relationship to fetch existing attachments.
    • Prevents duplicate file uploads by checking already uploaded files.
    • Deletes files both from the database and storage when removed.
  5. Filament UI Customizations

    • Uses a modal window for file management.
    • Provides a badge counter showing the number of existing attachments.
    • Uses an outlined gray button with a tooltip for better UX.

Breakdown of FileAction Implementation ​

Defining the Action Name ​

php
public static function getDefaultName(): ?string
{
    return 'file.action';
}
  • This sets a unique action name (file.action) that can be referenced when using this action within Filament resources.

Setting Up the Action ​

The setUp() method configures how this action behaves.

php
protected function setUp(): void
{
    parent::setUp();

    // Rest of code
}
  • Calls the parent setUp() method to ensure that Filament's default action setup runs.

UI Customization ​

php
$this
    ->color('gray')
    ->outlined()
    ->tooltip(__('chatter::filament/resources/actions/chatter/file-action.setup.tooltip'))
  • The action button appears in gray color and outlined for subtle appearance.
  • A tooltip provides additional guidance on what this action does.

Showing Attachment Count ​

php
->badge(fn ($record) => $record->attachments()->count())
  • Displays a badge counter on the action button, indicating the number of attachments.

File Upload Component ​

The schema contains a FileUpload component:

php
use Filament\Forms\Components\FileUpload;

->schema([
    FileUpload::make('files')
        ->hiddenLabel()
        ->multiple()
        ->directory('chats-attachments')
        ->disk('public')
        ->visibility('public')
        ->preserveFilenames()
        ->downloadable()
        ->openable()
        ->reorderable()
        ->previewable(true)
        ->deletable()
  • Allows multiple file uploads.
  • Stores files in the chats-attachments directory on the public disk, preserving original filenames.
  • Users can download, preview, reorder, and delete files.

File Validation ​

php
->acceptedFileTypes([
    'image/*',
    'application/pdf',
    'application/msword',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.ms-excel',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'text/plain',
])
->maxSize(10240) // 10 MB
  • Restricts files to images, PDFs, Word, Excel, and text files.
  • Limits file size to 10 MB per file.

Deleting Uploaded Files ​

php
->deleteUploadedFileUsing(function ($file, ?Model $record) {
    if (! $record) {
        return;
    }

    if (method_exists($record, 'removeAttachment')) {
        $attachment = $record->attachments()->where('file_path', $file)->first();

        if ($attachment) {
            $record->removeAttachment($attachment->id);
        } else {
            Storage::disk('public')->delete($file);
        }
    } else {
        $attachment = $record->attachments()->where('file_path', $file)->first();

        if ($attachment) {
            $attachment->delete();
        }

        Storage::disk('public')->delete($file);
    }

    Notification::make()
        ->success()
        ->title(__('chatter::filament/resources/actions/chatter/file-action.setup.form.fields.actions.delete.title'))
        ->body(__('chatter::filament/resources/actions/chatter/file-action.setup.form.fields.actions.delete.body'))
        ->send();
})
  • Prefers the record's removeAttachment() method (from the HasChatter trait), which verifies the attachment belongs to the record and removes both the database entry and the stored file.
  • Falls back to deleting the attachment row and the file from the public disk directly.
  • Sends a success notification when deletion is successful.

Setting Default File List (Preloaded Files) ​

php
->default(function (?Model $record) {
    if (! $record) {
        return [];
    }

    return $record->attachments()
        ->latest()
        ->get()
        ->pluck('file_path')
        ->toArray() ?? [];
}),
  • Preloads existing file paths when the action is opened.

Handling the Action (Uploading Files to Model) ​

php
->action(function (FileAction $action, array $data, ?Model $record): void {
    try {
        $existingFiles = $record->attachments()
            ->latest()
            ->get()
            ->pluck('file_path')
            ->toArray();

        $newFiles = array_filter($data['files'] ?? [], function ($file) use ($existingFiles) {
            return ! in_array($file, $existingFiles);
        });

        if (! empty($newFiles)) {
            $record->addAttachments($newFiles);

            Notification::make()
                ->success()
                ->title(__('chatter::filament/resources/actions/chatter/file-action.setup.actions.notification.success.title'))
                ->body(__('chatter::filament/resources/actions/chatter/file-action.setup.actions.notification.success.body'))
                ->send();
        } else {
            Notification::make()
                ->info()
                ->title(__('chatter::filament/resources/actions/chatter/file-action.setup.actions.notification.warning.title'))
                ->body(__('chatter::filament/resources/actions/chatter/file-action.setup.actions.notification.warning.body'))
                ->send();
        }
    } catch (Exception $e) {
        Notification::make()
            ->danger()
            ->title(__('chatter::filament/resources/actions/chatter/file-action.setup.actions.notification.error.title'))
            ->body(__('chatter::filament/resources/actions/chatter/file-action.setup.actions.notification.error.body'))
            ->send();

        report($e);
    }

    $action->resetFormData();
})
  • Filters out duplicate files (prevents re-uploading existing files).
  • Calls $record->addAttachments($newFiles) to attach new files to the model.
  • Sends success, warning, or error notifications based on the outcome.
  • Handles exceptions to prevent crashes.
php
use Filament\Support\Enums\IconPosition;
use Filament\Support\Enums\Width;

->modalHeading(__('chatter::filament/resources/actions/chatter/file-action.setup.title'))
->icon('heroicon-o-paper-clip')
->modalIcon('heroicon-o-paper-clip')
->iconPosition(IconPosition::Before)
->modalSubmitAction(
    fn ($action) => $action
        ->label(__('chatter::filament/resources/actions/chatter/file-action.setup.modal-submit-action-label'))
        ->icon('heroicon-m-paper-airplane')
)
->modalWidth(Width::TwoExtraLarge)
->slideOver(false);

After the action completes, a chatter.refresh Livewire event is dispatched so the chatter panel reloads the attachment list.

Released under the MIT License.