Relation manager with spatie media library
In this article we will view how to add spatie medialibrary as a relation.
To get started install new Laravel project called filamentmedia
laravel new filamentmedia
New install filament use this command
cd filamentmediacomposer require filament/filament
Filament recommend adding this to your composer.json
's post-update-cmd
:
"post-update-cmd": [ // ... "@php artisan filament:upgrade"],
php artisan make:model Adventure -m
Edit your migration for adventure table
<?php use Illuminate\Database\Migrations\Migration;use Illuminate\Database\Schema\Blueprint;use Illuminate\Support\Facades\Schema; return new class extends Migration{ /** * Run the migrations. */ public function up(): void { Schema::create('adventures', function (Blueprint $table) { $table->id(); $table->string('name'); $table->longText('description')->nullable(); $table->integer('price')->nullable(); $table->boolean('published')->default(1); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('adventures'); }};
Now install filament/spatie-laravel-media-library-plugin
composer require filament/spatie-laravel-media-library-plugin
Now publish the migration to create the media table.
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="migrations"
Migrate the table
php artisan migrate
Prepare your Adventure model for attaching media also add attributes to fillable for mass assignment.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Database\Eloquent\Model;use Spatie\MediaLibrary\HasMedia;use Spatie\MediaLibrary\InteractsWithMedia; class Adventure extends Model implements HasMedia{ use HasFactory; use InteractsWithMedia; /** * The attributes that are mass assignable. * * @var array<string> */ protected $fillable = ['name', 'description', 'price', 'published'];}
Create Adventure Resource and Relation Manager for media
php artisan make:filament-resource AdventureResource Adventurephp artisan make:filament-relation-manager AdventureResource media name
Navigate to app\Filament\Resources\AdventureResource.php
file. Define form and table.
public static function form(Form $form): Form{ return $form ->schema([ Forms\Components\Card::make() ->schema([ Forms\Components\TextInput::make('name') ->required() ->maxLength(255), Forms\Components\TextInput::make('price'), Forms\Components\RichEditor::make('description') ->columnSpan('full'), Forms\Components\Toggle::make('published') ->default(true) ->columnSpan('full'), Forms\Components\SpatieMediaLibraryFileUpload::make('photos') ->collection('adventures') ->multiple() ->image(), ]) ->columns(2), ]);} public static function table(Table $table): Table{ return $table ->columns([ Tables\Columns\TextColumn::make('name') ->searchable(), Tables\Columns\TextColumn::make('price'), Tables\Columns\IconColumn::make('published') ->boolean(), Tables\Columns\TextColumn::make('created_at') ->date(), ]) ->filters([ // ]) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]);}
You must register the new media relation manager in your resource's getRelations()
method:
public static function getRelations(): array{ return [ RelationManagers\MediaRelationManager::class ];}
resources\views\filament\forms\components\image-preview.blade.php
<x-dynamic-component :component="$getFieldWrapperView()" :id="$getId()" :label="$getLabel()" :label-sr-only="$isLabelHidden()" :helper-text="$getHelperText()" :hint="$getHint()" :hint-action="$getHintAction()" :hint-color="$getHintColor()" :hint-icon="$getHintIcon()" :required="$isRequired()" :state-path="$getStatePath()"> <div x-data="{ state: $wire.entangle('{{ $getStatePath() }}').defer }"> <img src="{{ $getState() }}" style="height: 185px;" class="object-cover" /> </div></x-dynamic-component>
Navigate to app\Filament\Resources\AdventureResource\RelationManagers\MediaRelationManager.php
file.
public static function form(Form $form): Form{ return $form ->schema([ Forms\Components\Hidden::make('id'), Forms\Components\ViewField::make('file_name') ->label('Image Preview') ->disabled() ->view('filament.forms.components.image-preview') ->afterStateHydrated(function ($component, $get, $state) { $component->state(Storage::url($get('id') . '/' . $state)); }) ->dehydrated(false), Forms\Components\TextInput::make('name') ->required() ->maxLength(255) ->columnSpan('full'), ]);}
Change Title and Label
protected static ?string $title = 'Images'; protected static ?string $modelLabel = 'Images';
Disable CreateAction and add DeleteAction
public static function table(Table $table): Table{ return $table ->columns([ Tables\Columns\ImageColumn::make('file_name') ->label('Image') ->getStateUsing(function ($record) { return $record->getFullUrl(); }), Tables\Columns\TextColumn::make('name'), ]) ->filters([ // ]) ->headerActions([ // Tables\Actions\CreateAction::make(), ]) ->actions([ Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]) ->reorderable('order_column') ->defaultSort('order_column');}
Disable Image preview on edit form. To do this you add this to SpatieMediaLibraryFileUpload
Forms\Components\SpatieMediaLibraryFileUpload::make('photos') ->loadStateFromRelationshipsUsing(fn ($component) => $component->state([]))
This will not load the previously added media to file upload.
To prevent automatic deletion of the previous added file.
Forms\Components\SpatieMediaLibraryFileUpload::make('photos') ->saveRelationshipsUsing(fn ($component) => $component->saveUploadedFiles())
Now the Form Component will look like this
Forms\Components\SpatieMediaLibraryFileUpload::make('photos') ->collection('adventures') ->multiple() ->image() ->loadStateFromRelationshipsUsing(fn ($component) => $component->state([])) ->saveRelationshipsUsing(fn ($component) => $component->saveUploadedFiles()),
To refresh the page after the submission navigate to app\Filament\Resources\AdventureResource\Pages\EditAdventure.php
file.
<?php namespace App\Filament\Resources\AdventureResource\Pages; use App\Filament\Resources\AdventureResource;use Filament\Pages\Actions;use Filament\Resources\Pages\EditRecord; class EditAdventure extends EditRecord{ protected static string $resource = AdventureResource::class; protected function getActions(): array { return [ Actions\DeleteAction::make(), ]; } protected function getRedirectUrl(): string { return $this->getResource()::getUrl('edit', ['record' => $this->record]); }}
Hi! I'm trying to add an action and a bulkAction to the table to download files. When it downloads a file, it works correctly, but when it downloads selected files using the bulkAction, it doesn't display the actual file name inside the ZIP, but rather the name under which it's stored. Is there a way to create the ZIP with the real file names?