slimani/filament-media-manager
Filament Media Manager for Filament v4/v5: folder-based media library with smooth browser UI, hierarchical tree navigation, tagging, search/filtering, and multi-disk support. Integrates with Filament forms, tables, and actions.
A comprehensive media manager plugin for Filament v4 and v5.
filament-select-tree.








You can install the package via composer:
composer require slimani/filament-media-manager
# Publish Media Manager migrations
php artisan vendor:publish --tag="media-manager-migrations"
# Publish Spatie MediaLibrary migrations (if not already published)
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
# Run migrations
php artisan migrate
You can publish the config file with:
php artisan vendor:publish --tag="media-manager-config"
If you are using a Custom Filament Theme, you must include both the plugin's pre-defined styles and allow Tailwind to scan your components for utility classes.
Add the following lines to your theme.css file:
@import '../../../../vendor/slimani/filament-media-manager/resources/css/media-manager.css';
@source '../../../../vendor/slimani/filament-media-manager/resources/**/*';
p-4, flex, etc.) that are used in the UI.A normal click selects one file or folder. To select multiple items, use the check icon on desktop or long-press an item on touch devices. This keeps normal browsing simple while still supporting bulk actions.
We are keeping this behavior under review. If you have feedback, please join the discussion in #18.
Register the plugin in your Panel Provider:
use Slimani\MediaManager\MediaManagerPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(MediaManagerPlugin::make());
}
To use the media manager with your models, add the InteractsWithMediaFiles trait. This is required for relationships and picker functionality.
avatar_id or cv_id), you need to add the foreign key columns to your model's migration:Schema::table('users', function (Blueprint $table) {
$table->foreignId('avatar_id')->nullable()->constrained('media_files')->nullOnDelete();
$table->foreignId('cv_id')->nullable()->constrained('media_files')->nullOnDelete();
});
$this->mediaFiles()), no migration is required. These relationships utilize the built-in media_attachments table included in the package migrations.Add the trait and define your specific relationships. Don't forget to add the foreign keys to your $fillable array.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Slimani\MediaManager\Concerns\InteractsWithMediaFiles;
class User extends Model
{
use InteractsWithMediaFiles;
protected $fillable = [
'name',
'email',
'avatar_id',
'cv_id',
];
public function avatar(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->mediaFile('avatar_id');
}
public function cv(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->mediaFile('cv_id');
}
/**
* Optional: Define custom collection relationships
*/
public function documents(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->mediaFiles('documents');
}
}
Use the MediaPicker field in your Filament forms. It uses the "Prepare Model" setup to handle file selection via a modal.
use Slimani\MediaManager\Form\MediaPicker;
use Filament\Forms\Form;
public static function form(Form $form): Form
{
return $form
->schema([
// Single file selection with specific names (requires InteractsWithMediaFiles in Model)
MediaPicker::make('avatar_id')
->relationship('avatar')
->label('User Avatar')
->required(),
MediaPicker::make('cv_id')
->relationship('cv')
->label('User CV'),
// Multiple file selection (requires MorphToMany relationship in Model)
MediaPicker::make('documents')
->relationship('documents')
->multiple()
->label('User Documents'),
]);
}
Display media in your Filament tables using the MediaColumn:
use Slimani\MediaManager\Tables\Columns\MediaColumn;
MediaColumn::make('avatar')
->circular()
->stacked()
The package provides two types of infolist entries: MediaImageEntry for image-specific features and MediaFileEntry for general file types with preview actions.
Use MediaImageEntry when you want to display image thumbnails with native Filament ImageEntry features (circular, stacked, etc.) and media manager conversion support.
use Slimani\MediaManager\Infolists\Components\MediaImageEntry;
MediaImageEntry::make('avatar')
->label('User Avatar')
->conversion('thumb') // Optional, defaults to 'thumb'
->circular() // Native Filament feature
->width(100)
Use MediaFileEntry for a generic media display that includes an automatic "Open Preview" action, ideal for documents, videos, and mixed media.
use Slimani\MediaManager\Infolists\Components\MediaFileEntry;
MediaFileEntry::make('cv')
->label('User CV')
Both components support the media preview action that opens files in a slide-over.
File::getUrl() returns clean URLs for disks with visibility => 'public', and temporary signed URLs for other disks when supported. For conversions, it also respects conversions_disk when set. See docs/url-generation.md for configuration details and examples.
The Media Manager integrates deeply with Filament's RichEditor, allowing you to insert images directly from your media library while keeping generated media URLs aligned with your disk visibility settings.
Implement the HasRichContent interface and use the InteractsWithRichContent trait. Then, use setUpRichContent to register the media manager configuration for your field.
use Filament\Forms\Components\RichEditor\Models\Concerns\InteractsWithRichContent;
use Filament\Forms\Components\RichEditor\Models\Contracts\HasRichContent;
use Slimani\MediaManager\Form\RichEditor\MediaManagerRichContentPlugin;
class User extends Authenticatable implements HasRichContent
{
use InteractsWithRichContent;
public function setUpRichContent(): void
{
$this->registerRichContent('resume')
->plugins([
MediaManagerRichContentPlugin::make()
->collection('preview')
->directory('User/Resumes'),
]);
}
}
Note: Implementing
HasFileAttachmentProviderin the plugin means that Filament automatically resolves the correct provider. You no longer need to callfileAttachmentProvider()manually on the attribute or renderer.
Add the MediaManagerRichContentPlugin to your RichEditor component. This adds a "Media Library" button to the toolbar.
use Filament\Forms\Components\RichEditor;
use Slimani\MediaManager\Form\RichEditor\MediaManagerRichContentPlugin;
RichEditor::make('resume')
->plugins([
MediaManagerRichContentPlugin::make()
->acceptedFileTypes(["image/*"]), // Optional: Limit to images only
])
To ensure that images in your rich text always use the correct URLs at render time, use the renderRichContent method in your Infolist. This is especially important when your media uses private disks or temporary signed URLs.
use Filament\Infolists\Components\TextEntry;
TextEntry::make('resume')
->html()
->state(fn ($record) => $record->renderRichContent('resume'))
This method parses the stored HTML and resolves each image ID into a fresh URL on the fly, solving the "expired link" problem entirely.
You can customize the Media Manager directly in your Panel Provider:
use Slimani\MediaManager\MediaManagerPlugin;
MediaManagerPlugin::make()
->navigationGroup('System')
->navigationLabel('Assets')
->navigationIcon('heroicon-o-folder')
->navigationSort(5)
->shouldRegisterNavigation(fn () => auth()->user()->isAdmin())
->headerWidgets([
MyCustomWidget::class,
])
->footerWidgets([
AnotherWidget::class,
])
->header(view('custom.header'))
->footer(view('custom.footer'))
->withVideoThumbnails() // Optional: Enable video thumbnails (requires FFMPEG)
Tenant isolation is opt-in and works with Filament's active tenant by default. Publish the additional tenancy migration, run it, and enable tenancy on the plugin:
php artisan vendor:publish --tag="media-manager-tenancy-migration"
php artisan migrate
use Slimani\MediaManager\MediaManagerPlugin;
MediaManagerPlugin::make()
->tenantAware()
The migration adds an indexed tenant_id string column to media files, folders, and tags.
This supports integer, UUID, and ULID tenant keys without coupling the package to a specific
tenant model. Existing records must be assigned to a tenant before enabling tenant awareness.
By default, the tenant key is resolved from Filament::getTenant(). A custom resolver and
column name may be supplied for applications using another tenancy context:
MediaManagerPlugin::make()
->tenantAware(
tenantResolver: fn () => auth()->user()?->team_id,
tenantColumn: 'team_id',
)
If you change the column name, update the published migration before running it. When tenancy is enabled and no tenant can be resolved, reads return no media records and writes throw an exception. Files, folders, tags, attachments, pickers, rich-editor integrations, and recursive folder statistics are all scoped to the resolved tenant.
For private tenant assets, use a private storage disk. Tenant database scoping prevents records from being selected across tenants, but a public disk URL remains publicly accessible to anyone who knows that URL.
The Media Manager is fully compatible with database table prefixes and allows you to customize the underlying models for File, Folder, Tag, and MediaAttachment.
All raw SQL queries within the package (such as recursive folder calculations) use dynamic table names retrieved from your models. This ensures that if you define a prefix in your database.php configuration, the package will automatically respect it.
If you need to add custom logic, relationships, or traits to the internal models, you can swap them out in the plugin registration:
use App\Models\CustomFile;
use App\Models\CustomFolder;
use Slimani\MediaManager\MediaManagerPlugin;
MediaManagerPlugin::make()
->fileModel(CustomFile::class)
->folderModel(CustomFolder::class)
->tagModel(CustomTag::class)
->attachmentModel(CustomMediaAttachment::class)
Note: Your custom models should typically extend the base models provided by the package to ensure all core functionality remains intact.
Thumbnail generation for videos is handled natively by the underlying Spatie MediaLibrary when FFMPEG and FFProbe are installed on your server.
To use this feature, you must install the FFMPEG PHP dependency:
composer require php-ffmpeg/php-ffmpeg
For more information on video processing requirements, please refer to the Spatie MediaLibrary Documentation.
You can customize or add media conversions from your application's AppServiceProvider or a dedicated Service Provider. Default conversions (thumb and preview) are registered first, and your callback can override them or add new ones.
use Slimani\MediaManager\Models\File;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
public function boot(): void
{
File::registerMediaConversionsUsing(function (File $file, ?Media $media = null) {
// Override default 'thumb' (300x300)
$file->addMediaConversion('thumb')
->width(150)
->height(150)
->nonQueued();
// Add a new custom conversion
$file->addMediaConversion('square')
->width(500)
->height(500)
->nonQueued();
// Default 'preview' (800x800) is kept if not overridden
});
}
After defining your conversions, you can specify which one to use in your components using the conversion() method. This controls which conversion is used for the table thumbnail, infolist image, or form picker preview.
use Slimani\MediaManager\Tables\Columns\MediaColumn;
use Slimani\MediaManager\Infolists\Components\MediaImageEntry;
use Slimani\MediaManager\Form\MediaPicker;
// In Tables
MediaColumn::make('avatar')
->conversion('thumb')
// In Infolists
MediaImageEntry::make('avatar')
->conversion('preview')
MediaFileEntry::make('cv')
->conversion('thumb')
// In Forms (controls the picker's file preview)
MediaPicker::make('avatar_id')
->conversion('thumb')
All components default to the thumb conversion if none is specified.
To populate your media manager with initial data, you can use a seeder. Here is an example of how to set up folders and files:
use Slimani\MediaManager\Models\Folder;
use Slimani\MediaManager\Models\File;
public function run(): void
{
$images = Folder::create(['name' => 'Project Images']);
$docs = Folder::create(['name' => 'Documents']);
// here should be the code if you have real files
// for now we will use dummy data
$banner = File::create([
'name' => 'Main Banner',
'folder_id' => $images->id,
'size' => 1024,
'extension' => 'jpg',
'mime_type' => 'image/jpeg',
'uploaded_by_user_id' => 1,
]);
// Attach actual media using Spatie MediaLibrary
$banner->addMediaFromUrl('https://picsum.photos/1200/800')
->toMediaCollection('default');
}
composer test
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.
How can I help you explore Laravel packages today?