gheith3/filament-file-view-entry
A Filament package that adds a file view entry for displaying uploaded files in infolists, with preview and download support for images, PDFs, and documents in Laravel admin panels.
Install the package with Composer (it requires PHP 8.2+, Laravel 11/12 and Filament 4 or 5 — verify your panel matches before pulling it into a project):
composer require gheith3/filament-file-view-entry
The plugin ships a service provider that registers the Blade view namespace file-view-entry-plugin. Register the plugin in your panel provider so that namespace is available, even though there is no configuration object to tweak:
use gheith3\FileViewEntryPlugin\FileViewEntryPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(FileViewEntryPlugin::make());
}
The only class you will touch day-to-day is gheith3\FileViewEntryPlugin\Infolists\Components\FileViewEntry. It is an Entry, so it belongs inside an Infolist schema — a ViewRecord page, a relation manager's infolist, or an action's modal schema. A minimal first use case is showing an uploaded document on a record view page:
use gheith3\FileViewEntryPlugin\Infolists\Components\FileViewEntry;
use Filament\Infolists\Infolist;
public function infolist(Infolist $infolist): Infolist
{
return $infolist->schema([
FileViewEntry::make('contract_path')
->showPreview()
->downloadable(),
]);
}
Where to look first when something is off: the published Blade view (resources/views/vendor/file-view-entry-plugin/infolists/components/file-view-entry.blade.php after publishing) is the real source of truth for markup, icons and the Alpine modal. The README and AGENTS.md in the repo cover the method surface; there is no separate docs site.
Single path vs. array vs. relationship. The entry inspects the state and handles all three shapes. A string is treated as a single file path; an array of arrays is rendered as a grid of cards; an Eloquent Collection from a relationship works out of the box because it is iterable. The most common real-world pattern is a hasMany attachments relation:
public function attachments(): HasMany
{
return $this->hasMany(Attachment::class);
}
// In the infolist
FileViewEntry::make('attachments')
->grid(3)
->titleKey('original_name')
->pathKey('storage_path')
->dateKey('created_at')
->showPreview()
->downloadable(),
Custom key mapping is the main integration knob. Arrays and models are read via titleKey() (default name), pathKey() (default file_path) and dateKey() (default null). Map these to whatever your schema actually stores. If you keep a raw storage path (attachments/invoices/2024-01.pdf) rather than a public URL, pass it as-is and use disk() to tell the entry which filesystem resolves it:
FileViewEntry::make('documents')
->grid(4)
->pathKey('path')
->disk('s3')
->showPreview()
->downloadable(),
Fixed grid vs. auto grid. grid() takes 1–6 and locks the column count; leaving it null keeps the responsive default (roughly 2 on mobile, 3 on tablet, 4–5 on desktop). For a document sidebar you usually want grid(2); for a media gallery grid(4) reads better. Pair it with ->columnSpanFull() when the entry sits in a two-column infolist and you want the grid to breathe.
Link mode for dense layouts. showAsLink() renders a compact text link instead of square cards. Use it in table-adjacent summaries, relation managers, or when a record has dozens of files and the card grid becomes a wall. You lose the icon preview affordance but keep preview/download behaviour.
Modal preview tuning. showPreview() is on by default; ->showPreview(false) disables it for types you don't want opening a modal. previewHeight() accepts either a string ('420px', '60vh') or an integer, and defaults to '300px'. Bump it for PDFs and video, drop it for audio.
Using it outside a ViewRecord page. Because it extends Entry, it composes into action modals and relation-manager infolists without extra work — a useful pattern for "view attachments" actions on a table row:
Tables\Actions\Action::make('files')
->infolist(fn (Record $record) => [
FileViewEntry::make('attachments')
->grid(3)
->dateKey('created_at')
->downloadable(),
])
dateKey() is null, not 'created_at'. If timestamps never appear, you simply haven't opted in — call ->dateKey('created_at') explicitly.Storage::disk()->temporaryUrl() with a ~5 minute expiry and fall back to url(). Long-lived pages, cached HTML, or users who leave a modal open will see broken previews/downloads. For public assets, put them on a public disk to skip the signed URL path entirely.disk() default is the filesystem config, not the entry's state. If your paths look correct but every file 404s, check config('filesystems.default') against where the files actually live — this is the single most common misconfiguration.pathKey must be a path, not a URL. If your column is already a fully-qualified URL, the entry will still try to resolve it through the disk. Store the relative path and let the component generate URLs.txt, md) open in the modal; everything else (.docx, .xlsx, .zip) falls back to opening a new tab. Don't promise clients inline Office previews — that's a browser/Office-Online problem, not a package one.grid() expects 1–6; passing 0 or 8 silently produces an unexpected layout rather than an exception. Validate at the call site if the value is dynamic.name, some without) renders cards with blank titles. Normalise in a getStateUsing() or accessor before the entry sees it.php artisan vendor:publish --tag=file-view-entry-plugin-views drops the template into resources/views/vendor/file-view-entry-plugin/. Override the Tailwind classes there (rounded-2xl, bg-gray-50 dark:bg-gray-800, w-12 h-12 icons) rather than fighting them with extraAttributes(). Note the hard-coded dark: variants — if you use a non-standard grey palette you'll want to rewrite them.composer.json, run vendor/bin/pest in the package itself if you vendor it for patching, and keep the published view in version control so an upstream change can't silently alter a live UI.file_path on a nested array needs the right make() key), and the filesystem URL actually resolves in a browser. Nine times out of ten it's the second one.How can I help you explore Laravel packages today?