Search by

ahmed-abdelrhman / filament-media-gallery

ahmedabd2003

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

Statistics

Installs: 3 063

Dependents: 0

Suggesters: 0

Stars: 17

Open Issues: 0

v2.1.1 2026-10-30 19:02 UTC

This package is auto-updated.

Last update: 2026-10-31 14:30:10 UTC


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:

  1. Images are not clickable — no built-in lightbox or fullscreen view.
  2. 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
PDF 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-image
  • Video — requires ffmpeg + php-ffmpeg/php-ffmpeg

This package adds two optional, dependency-light generators you can enable instead:

Generator Handles Server requirement
PdfThumbnail PDF 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.

OfficeThumbnail has 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's response()->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 with Content-Disposition: inline (the default for Spatie's local and S3 disks). If your disk forces attachment, 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 → applies rounded-full (circle for images/videos, pill shape for file cards)
  • false → applies rounded-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:

  1. thumbnail conversion (fastest, smallest)
  2. preview conversion (medium quality)
  3. 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.