ahmed-abdelrhman / filament-media-gallery
A Filament infolist entry that renders Spatie Media Library collections with image lightbox and PDF card support.
Package info
github.com/ahmedabdelrhman2003/filament-media-gallery
Language:Blade
pkg:composer/ahmed-abdelrhman/filament-media-gallery
Requires
- php: ^8.2
- filament/filament: ^3.0 || ^4.0 || ^5.0
- spatie/laravel-medialibrary: ^10.0|^11.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A custom Filament infolist entry that renders Spatie Media Library collections as an interactive media grid — with a fullscreen Alpine.js lightbox for images and videos, and type-aware cards for PDF, Word, Excel, PowerPoint and other files that open in a new tab. No JavaScript build step in your app; the plugin ships its own compiled stylesheet and registers it as a Filament asset.
Problem It Solves
Out of the box, Filament's SpatieMediaLibraryImageEntry has two issues:
- Images are not clickable — no built-in lightbox or fullscreen view.
- PDFs render as broken images — Filament tries to display every media item as an
<img>, which shows a broken icon for PDF files with no way to open them.
This package replaces that entry with a smart, type-aware gallery component.
Requirements
| Dependency | Version | Notes |
|---|---|---|
| Laravel | 10+ | |
| Filament | 3, 4, 5 | Alpine.js + Tailwind bundled |
| Spatie Media Library | 10+ | Models must use HasMedia + InteractsWithMedia |
| PHP | 8.2+ |
No additional npm packages or Composer packages required.
Supported file types
| Type | Detection | Card | Click |
|---|---|---|---|
| Image | image/* |
Thumbnail (thumbnail → preview → original) |
Opens lightbox |
| Video | video/* |
Thumbnail conversion if one exists, else muted first-frame preview; play icon | Opens lightbox with a native <video controls> player |
application/pdf / .pdf |
First-page preview if a thumbnail conversion exists, else red document icon | Opens lightbox with an inline, scrollable PDF viewer (plus an open-in-new-tab button) | |
| Word | .doc .docx .odt .rtf or Word MIME |
First-page preview if a thumbnail conversion exists, else blue document icon | New tab |
| Excel | .xls .xlsx .ods .csv or Excel MIME |
First-page preview if a thumbnail conversion exists, else green table icon | New tab |
| PowerPoint | .ppt .pptx .odp or PowerPoint MIME |
First-page preview if a thumbnail conversion exists, else indigo presentation icon | New tab |
| Anything else | — | Grey document icon with extension badge | New tab |
Images, videos and PDFs share one lightbox sequence, so prev/next and the thumbnail strip move across all three.
File thumbnails (PDF, Office, video)
Cards show a first-page / first-frame preview for any file that has a thumbnail (or preview)
conversion generated by Spatie Media Library. For images that just works. For other types Spatie needs
an image generator that can turn the file into a picture, and it ships with:
Pdf— requires the Imagick PHP extension +spatie/pdf-to-imageVideo— requires ffmpeg +php-ffmpeg/php-ffmpeg
This package adds two optional, dependency-light generators you can enable instead:
| Generator | Handles | Server requirement |
|---|---|---|
PdfThumbnail |
pdftoppm (poppler-utils) or Ghostscript gs. No Imagick needed. |
|
OfficeThumbnail |
doc/docx, xls/xlsx, ppt/pptx, odt/ods/odp, rtf | LibreOffice (soffice) plus the PdfThumbnail requirement |
Enable them in config/media-library.php (publish it first if you haven't), before Spatie's own Pdf entry:
'image_generators' => [ AhmedAbdelrhman\FilamentMediaGallery\ImageGenerators\PdfThumbnail::class, AhmedAbdelrhman\FilamentMediaGallery\ImageGenerators\OfficeThumbnail::class, Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class, // ...the rest of Spatie's defaults ],
and make sure your model defines the conversion for the collection:
public function registerMediaConversions(?Media $media = null): void { $this->addMediaConversion('thumbnail') ->width(300)->height(300) ->performOnCollections('gallery'); }
Generators whose binaries are missing are skipped silently and the card falls back to the type icon.
Already-uploaded files get thumbnails after php artisan media-library:regenerate.
OfficeThumbnailhas not yet been exercised on a machine with LibreOffice installed; please report issues.
Video seeking note. Dragging the seek bar to an unbuffered position needs the file to be served with HTTP Range support (
206 Partial Content). nginx, Apache, S3 and Laravel'sresponse()->file()all do this; PHP's built-in server (php artisan serve) does not, so during local development the scrubber may snap back. This is a server limitation, not a plugin bug.
PDF preview notes. The inline viewer is the browser's native one inside an
<iframe>. It needs the file to be served withContent-Disposition: inline(the default for Spatie's local and S3 disks). If your disk forcesattachment, the browser downloads instead of previewing. iOS Safari only renders the first page inside an iframe; the open-in-new-tab button covers that case.
Installation
composer require ahmed-abdelrhman/filament-media-gallery
The service provider is auto-discovered by Laravel. No manual registration needed.
Then publish the plugin's stylesheet (run this again after every update):
php artisan filament:assets
The plugin uses Tailwind utility classes that Filament's own stylesheet does not include, so this step is required — without it the grid and the lightbox will render unstyled.
Using a custom Filament theme?
If your panel has a custom theme, you can skip
filament:assets and instead let your theme build the classes. Add the vendor path to your theme:
/* Filament 4 / 5 — resources/css/filament/admin/theme.css */ @source '../../../../vendor/ahmed-abdelrhman/filament-media-gallery/resources/views/**/*.blade.php';
// Filament 3 — resources/css/filament/admin/tailwind.config.js content: [ // ... './vendor/ahmed-abdelrhman/filament-media-gallery/resources/views/**/*.blade.php', ],
then rebuild your theme. Having both the plugin stylesheet and the theme classes is harmless.
Optionally, publish the views to customize them:
php artisan vendor:publish --tag=media-gallery-views
Optionally, register the plugin in your Filament panel for explicit panel-level registration:
// app/Providers/Filament/AdminPanelProvider.php ->plugins([ \AhmedAbdelrhman\FilamentMediaGallery\FilamentMediaGalleryPlugin::make(), ])
Usage
Basic usage
use AhmedAbdelrhman\FilamentMediaGallery\Infolists\Components\MediaGalleryEntry; MediaGalleryEntry::make('gallery') ->collection('coach_gallery') ->label('Gallery Images')
With a fixed card size
MediaGalleryEntry::make('certificates') ->collection('coach_certificates') ->size(300) // 300×300 px cards ->label('Certificates')
With circular cards
MediaGalleryEntry::make('profile') ->collection('profile_picture') ->size(120) ->rounded() // rounded-full ->label('Profile Photo')
Media on a related model
Use ->fromRelation() when the infolist is bound to a parent model but the media
belongs to a child relation:
// Infolist record = User, but media lives on User->coachProfile (CoachProfile model) MediaGalleryEntry::make('gallery') ->collection('coach_gallery') ->fromRelation('coachProfile') ->label('Gallery Images')
Full example in a Filament Resource infolist
use AhmedAbdelrhman\FilamentMediaGallery\Infolists\Components\MediaGalleryEntry; public static function infolist(Infolist $infolist): Infolist { return $infolist->schema([ Infolists\Components\Section::make('Gallery') ->schema([ MediaGalleryEntry::make('gallery') ->label('Gallery Images') ->collection('coach_gallery') ->fromRelation('coachProfile') ->visible(fn ($record) => $record->coachProfile?->getMedia( 'coach_gallery' )->isNotEmpty()), ]) ->collapsible() ->collapsed(), Infolists\Components\Section::make('Certificates') ->schema([ MediaGalleryEntry::make('certificates') ->label('Certificates') ->collection('coach_certificates') ->fromRelation('coachProfile') ->size(250) ->visible(fn ($record) => $record->coachProfile?->getMedia( 'coach_certificates' )->isNotEmpty()), ]) ->collapsible() ->collapsed(), ]); }
API Reference — MediaGalleryEntry
::make(string $name)
Standard Filament Entry factory. $name is the entry identifier (used internally by Filament).
->collection(string $collection): static
Required. Sets the Spatie Media Library collection name to load.
| Param | Type | Description |
|---|---|---|
$collection |
string |
Must match a collection name registered in the model's registerMediaCollections() |
->fromRelation(string $relation): static
Load media from a related model instead of the infolist's root record.
Use this when the infolist record (e.g. User) is not the model that owns the media
collection — the media belongs to a related model (e.g. CoachProfile via $user->coachProfile).
| Param | Type | Description |
|---|---|---|
$relation |
string |
The relation method name on the infolist record |
Omit this method entirely when the media belongs directly to the infolist record.
->size(?int $pixels): static
Set a fixed card size (width = height) in pixels.
| Param | Type | Default | Description |
|---|---|---|---|
$pixels |
int|null |
250 |
Card dimension in pixels. Pass null for a responsive grid |
With ->size(null), cards fill a responsive CSS grid (auto-fill, min 10rem) and stay square.
->rounded(bool $condition = true): static
Toggle fully rounded (circular) card corners.
true→ appliesrounded-full(circle for images/videos, pill shape for file cards)false→ appliesrounded-lg(standard rounded rectangle, this is the default)
Blade Component — standalone usage
The underlying component can also be used standalone (outside of infolists), for example in custom Filament pages or Livewire components:
<x-media-gallery-viewer :media="$model->getMedia('gallery')" :size="200" :rounded="true" />
Props
| Prop | Type | Default | Description |
|---|---|---|---|
media |
Collection |
collect() |
Spatie Media items to display |
size |
int|null |
null |
Fixed card size in px. Null = aspect-square |
rounded |
bool |
false |
Use rounded-full instead of rounded-lg |
Lightbox Behaviour
The lightbox is powered entirely by Alpine.js (no external libraries).
| Interaction | Effect |
|---|---|
| Click image, video or PDF card | Opens fullscreen lightbox |
← / → |
Previous / next item |
| Click thumbnail in strip | Jump to that item |
| Click dark backdrop | Closes lightbox |
Press ESC |
Closes lightbox |
| Click close button | Closes lightbox |
| Click prev/next arrows | Navigate between images (hidden when only 1 image) |
| Counter badge | Shows current / total (hidden when only 1 image) |
PDF files are never added to the lightbox. They open directly in a new browser tab.
Image Conversion Priority
When rendering image thumbnails in the grid, the component tries conversions in this order:
thumbnailconversion (fastest, smallest)previewconversion (medium quality)- Full original URL (fallback)
The lightbox always loads the full original URL for maximum quality.
To register these conversions on your model:
public function registerMediaConversions(?Media $media = null): void { $this->addMediaConversion('thumbnail') ->width(150)->height(150)->nonQueued(); $this->addMediaConversion('preview') ->width(400)->height(400)->nonQueued(); }
Dark Mode
All colours use Tailwind's dark: variants. The component respects Filament's dark mode
toggle automatically — no additional configuration needed.
Contributing — rebuilding the stylesheet
If you change any class in resources/views, rebuild resources/dist/media-gallery.css and commit it:
npm install npm run build
License
MIT — see LICENSE.