Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Media Action Laravel Package

hugomyb/filament-media-action

Filament Media Action lets you display video, audio, PDFs, images and more directly in Filament via an action. It auto-detects file extensions and selects the right player, works in forms, tables and infolists, and supports options like autoplay.

View on GitHub
Deep Wiki
Context7

Getting Started

Install for your Filament version. The package follows Filament's major version. On Filament v3 pin the 3.x branch explicitly, on Filament v4 use the default install:

# Filament v3
composer require hugomyb/filament-media-action:^3.0

# Filament v4
composer require hugomyb/filament-media-action

There is nothing else to wire up: no service provider to register, no migrations, no config file. The package is a single Action class (Hugomyb\FilamentMediaAction\Actions\MediaAction) plus a Blade view. Optional publishes exist for the modal view and translations, but you should only publish them once you have a concrete customization need.

Where to look first. Read the README usage section and scan the seven example screenshots in docs/ — they show the range of placements (table row actions, infolist entries, prefix/suffix actions, chained modal footers). Then read the published view only if the built-in player markup doesn't fit. The changelog is unusually informative for a small package, so skim it before upgrading: nearly every release is a media-detection or mobile-playback fix.

First use case. The 80% case is a preview button on a table row, where the record stores either a URL or a disk path:

use Hugomyb\FilamentMediaAction\Actions\MediaAction;

MediaAction::make('preview')
    ->iconButton()
    ->icon('heroicon-o-video-camera')
    ->media(fn ($record) => $record->media_url)

Clicking the icon opens a modal; the package sniffs the extension (mp4, mp3, pdf, jpg, YouTube URL, …) and renders the appropriate <video>, <audio>, <img>, <iframe>, or YouTube embed. That's the whole feature. Treat it as a thin, pleasant wrapper over the Filament modal Action API rather than as a media management system.

Implementation Patterns

Use closures, never eager values. ->media() accepts a string or a closure receiving $record, $livewire, $data, $model, and $arguments. Always pass a closure when the URL is per-record:

MediaAction::make('listen')
    ->media(fn ($record) => $record->audio_path)
    ->modalHeading(fn ($record) => $record->title)

Leverage the standard Action API for polish. Because MediaAction extends Filament's action, everything you already know works: ->modalHeading(), ->modalDescription(), ->modalFooterActionsAlignment(Alignment::Center), ->modalWidth(), ->iconButton(), ->tooltip(), ->visible(fn ($record) => ...), ->authorize().

Chain media in the modal footer for galleries. extraModalFooterActions() can host further MediaActions, which is the package's intended way to do "next / previous" previews:

MediaAction::make('cover')
    ->media(fn ($record) => $record->cover_url)
    ->extraModalFooterActions([
        MediaAction::make('sample')
            ->media(fn ($record) => $record->sample_url),
        Tables\Actions\Action::make('open')
            ->url(fn ($record) => $record->cover_url)
            ->openUrlInNewTab()
            ->icon('heroicon-o-globe-alt'),
    ])

Mix plain Actions in alongside MediaActions so users still get an escape hatch to open the raw asset in a new tab.

Use autoplay() selectively. The method accepts a boolean or a closure receiving $mediaType ('youtube', 'audio', 'video', 'image', 'pdf'):

->media(fn ($record) => $record->url)
->autoplay(fn ($record, $mediaType) => $mediaType === 'video')

Prefer narrowing autoplay to video rather than firing it for every type — images and PDFs gain nothing and audio blaring from a table row is a UX smell.

Pair autoplay with ->preload(false). This is the documented fix for "Autoplay failed or was blocked" browser errors. In practice, ->autoplay()->preload(false) is the configuration most likely to actually play on first open.

Force the type when detection can't work. Extensionless URLs, signed storage URLs (S3 temporaryUrl() produces long query strings), and local .test hosts are the classic failure modes:

use Hugomyb\FilamentMediaAction\Actions\MediaAction;

MediaAction::make('clip')
    ->media(fn ($record) => Storage::disk('s3')->temporaryUrl($record->path, now()->addMinutes(10)))
    ->mediaType(MediaAction::TYPE_VIDEO)

Use the constants (TYPE_VIDEO, TYPE_AUDIO, TYPE_IMAGE, TYPE_PDF, TYPE_YOUTUBE) rather than string literals; they are the stable API going forward.

Restrict player controls for protected content. disableDownload(), disableFullscreen(), disableRemotePlayback(), and controlsList() all accept booleans or closures:

->disableDownload(fn ($record) => $record->is_protected)

Treat these as cosmetic controls only — see the Gotchas section.

Customize the view as the extension point. Publishing the view gives you $media and $mediaType variables and full control over markup (wrapping the player, adding metadata under it, swapping in your own <video> attributes). Do this once per project and keep the published file small; every package upgrade asks you to diff it.

Gotchas and Tips

disableDownload() is not access control. The controlsList attribute is honoured only in Chromium-based browsers (Chrome, Edge) and can be bypassed trivially — the URL is still in the DOM. For genuinely protected media, issue short-lived signed URLs server-side, put the action behind ->authorize(), and accept that anyone who can view can also save.

Detection is extension-then-Content-Type, and both can fail. The package looks at the URL path extension, falls back to an HTTP Content-Type probe, and has a history of fixes here: query parameters broke detection early (Fix.md), missing extensions were patched in v3.1.0.5, and header casing for some hosts in v3.1.1.5. If you see "Unsupported media type", (a) confirm the URL resolves, (b) check the case of the extension (.MOV is a known offender — force TYPE_VIDEO), (c) force ->mediaType() to isolate whether detection or playback is the problem. The Content-Type fallback also means a slow or non-public endpoint can stall the action.

Pin and upgrade deliberately. There is a hard split at Filament v3 vs v4. Accidentally installing 3.x on a v4 app (or the reverse) produces confusing class-level errors rather than a clean message. Once installed, follow the changelog: the iOS audio spinner (v3.1.1.0), iPhone video playback (v3.1.1.2), and mediaElement.load is not a function (v3.1.1.3) fixes are exactly the kind of bugs you will hit on mobile without knowing the package caused them.

Mobile and autoplay are the recurring weak spot. iOS Safari refuses autoplay more aggressively than desktop Chrome, and the loading-state logic has been reworked repeatedly (v3.1.1.0, v3.1.1.4). Budget a manual test pass on an actual iPhone for any media action with autoplay() enabled. The 200ms artificial loading delay added in v3.1.1.4 is intentional — don't "fix" it in a published view.

PDFs are rendered in an iframe and inherit remote-host behaviour. Cross-origin PDFs need permissive headers; PDFs behind your own session auth will render fine, but PDFs on a signed S3 URL may be blocked by browser PDF viewers that dislike long query strings. The changelog shows the maintainer oscillating between Google Viewer, <embed>, and <iframe> — the current iframe approach depends on the browser's native PDF support, so test in Safari and Firefox, not just Chrome.

Null and missing media fail silently or loudly depending on placement. A closure returning null yields an empty modal rather than a helpful error. Guard with ->visible(fn ($record) => filled($record->url)) or a ->media() closure that falls back to a placeholder. Similarly, keep an eye on relation loading: fn ($record) => $record->media->url will issue N+1 queries if media isn't eager-loaded in your table query.

Debugging checklist, in order: dump the exact URL being returned by your closure (including query string); curl -I it to see the Content-Type; temporarily force ->mediaType() to each of the five constants; open the modal and inspect the DOM for the rendered tag; then publish the view if you need to instrument it. The action's detection logic is in the Action class, so a ray()/dd() in your closure is usually faster than reading the source.

Keep scope realistic. At 56 stars and zero dependents, this is a small, single-maintainer package — well-suited to internal admin panels, less so to anything where a breaking change in Filament's modal internals would be costly. The MIT license and the tiny surface area (one Action class, one view, translations) make it easy to vendor or fork if the maintainer stalls; the published view is your insurance policy for exactly that scenario.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log