tapp/filament-library
Tapp Filament Library gives your Filament admin a centralized asset library for uploading, organizing, searching, and reusing files and images. Browse and select media directly from forms and resources through a clean, native interface, keeping content consistent across your Laravel app.
The package is a Filament plugin that adds a shared media/file library (folders, uploads, picker field) to a panel. Treat it as infrastructure, not a resource you write yourself — most of your work is wiring an existing model to it.
Install and publish.
composer require tappnetwork/filament-library
php artisan vendor:publish --tag="filament-library-migrations"
php artisan vendor:publish --tag="filament-library-config"
php artisan migrate
Publish config even if you change nothing — you will almost certainly need to change the storage disk.
Register the plugin in your PanelProvider, alongside Filament's own plugins:
->plugins([
FilamentLibraryPlugin::make(),
])
Look first at: the published config file (disk, folder settings, model bindings), then the plugin's Media/library model and its resource. Those two files answer 90% of "where do I change X" questions. Read the plugin source under vendor/tappnetwork/filament-library/src before filing an issue — it is small and readable.
First use case: give an existing resource a file attachment. Add the relation/migration, include the library's picker field in the resource form, and render the stored file in the table. Once that loop works you understand the whole package: upload into the library → reference the media record from your model → resolve a URL for display.
Start the smoke test on a non-production panel — the library writes to a real disk and creates real rows.
Attach the library to a domain model. The common shape is a polymorphic relation so one model can own many library assets, plus an accessor for the "primary" file. Keep the relation name consistent across models; you will reference it in forms, tables, and URL resolution.
Pick files in forms, don't re-implement uploads. Embed the library's picker field in your resource form and let the plugin handle upload UX, folder navigation, and validation. This is the single biggest win — resist building a second FileUpload beside it.
Store the reference, not the path. Persist the media/library ID (or the pivot) and resolve URLs at render time via the media model's url()/getUrl(). Storing absolute URLs breaks the moment you switch disk, add a CDN, or move to S3.
Scope per panel/tenant. If you run multiple panels, register the plugin only where the library belongs. For multi-tenant apps, add a tenant key to the media table and a global scope — the library page and picker must not leak files across tenants.
Customise resources by extension, not by editing vendor. Subclass the plugin's resource/page classes (or bind your own via config) when you need extra columns, filters, or a custom table. This keeps upgrades cheap.
Version discipline around Filament minors. v1.4.1 is a compatibility fix for Filament 5.7.8 — a column searchable() callback on the User column was being misread by Filament as isSearchable. That tells you the package tracks Filament internals closely. Pin the plugin to a patch range, and after any Filament minor bump run a quick smoke test on the library table (especially search and relationship columns) before shipping.
Test the boundary. Feature-test your own resource (form saves the media ID, table renders the URL). Livewire-test the library table's search/filter only if you have customised it — that's the surface most sensitive to Filament upgrades.
searchable() on computed/relationship columns. Filament's searchable() signature puts the boolean $condition first; a closure passed positionally is treated as "is this column searchable" rather than "how to search". Always pass custom search logic as the named argument:
TextColumn::make('user.name')
->searchable(query: fn (Builder $query, string $search) => $query
->whereHas('user', fn ($q) => $q->where('name', 'like', "%{$search}%"))),
This is exactly what v1.4.1 fixed upstream. If you maintain overrides of the library's table, apply the same pattern or your customisation will silently break on Filament 5.7.8+.
Storage disk surprises. Default is usually public; on S3/private disks url() may return a temporary signed URL — fine for display, useless if you cache rendered HTML. Check config/filesystems.php and remember php artisan storage:link for the public disk.
Migrations after upgrade. New package versions can add migrations. vendor:publish with --force will overwrite your edits; publish by tag and diff instead. Keep your own migrations for the tenant/owner columns you added.
Orphaned files. Deleting a model does not necessarily delete the underlying file. Add a deleting hook or a scheduled prune, and be deliberate about whether library media is shared (referenced by several models) before cascading deletes.
N+1 on the library table. Relationship columns (user.name, folder name) need ->modifyQueryUsing(fn ($q) => $q->with(...)) or $with on the resource. Noticeable once the library passes a few hundred rows.
Authorization. The plugin exposes its pages via standard Filament policies/Gates. If you gate the library page but forget the picker field, users can still attach files they shouldn't browse. Audit both entry points.
Extension points worth knowing: config-based model/resource bindings (swap the media model for your own subclass), the plugin's page classes for menu/label overrides, and macros/configureUsing on the picker field to set defaults (accept types, max size, default folder) globally rather than per-form.
Debugging tip. When something renders blank in the library table, check storage/logs/laravel.log for a Column/searchable type error first — most failures here are Filament API mismatches from a version skew, not your data.
How can I help you explore Laravel packages today?