ahmed-abdelrhman/filament-media-gallery
| Install | |
|---|---|
composer require ahmed-abdelrhman/filament-media-gallery |
|
| Latest Version: | v2.0 |
| PHP: | ^8.2 |
| Laravel: | 12 · 13 |
| License: | MIT |
| Last Updated: | Mar 16, 2026 |
| Links: | GitHub · Packagist |
Filament Media Gallery Plugin
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.
Related Packages
Manage your media files using spatie media library with easy to use GUI for Fila...
Manage your media files using spatie media library with easy to use GUI for Fila...
Tool and field that will let you managing files and add them to the posts
Opinionated prebuilt stat widgets to quickly add to your Filament dashboards.
Version History
| Version | Released | PHP | License |
|---|---|---|---|
| v2.0 | ^8.2 | MIT | |
| v1.0.3 | ^8.2 | MIT | |
| v1.0.2 | ^8.2 | MIT | |
| v1.0.1 | ^8.2 | MIT | |
| v1.0.0 | ^8.2 | MIT |