outerweb/filament-image-library
Filament Image Library adds a reusable image library to your Filament panel, letting you upload, organize, and pick images from a central media pool in forms and tables. Browse, search, and reuse images instead of re-uploading them.
The package gives Filament a shared image pool: instead of every resource re-uploading a hero image, authors pick from a library that is managed once and reused across resources.
Minimal install:
composer require outerweb/filament-image-library
php artisan vendor:publish --tag="filament-image-library-migrations"
php artisan vendor:publish --tag="filament-image-library-config"
php artisan migrate
php artisan storage:link
Register the plugin on the panel you actually want the library page on:
// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
return $panel
->plugin(\Outerweb\FilamentImageLibraryPlugin::make())
// ...
}
Where to look first, in this order:
config/filament-image-library.php — disk, directory, and any model/view overrides. This is the file you will touch most.Image model (src/Models/) — decide early whether you will reference images by ID (relationship) or by path (string column), because it drives every migration you write afterwards.src/ — copy its namespace/name from the README rather than guessing; the API surface is small and stable, but the exact class name is the one thing people get wrong on day one.First use case to validate the install end to end: a Post resource with a single "cover image" field. Upload two images through the library page in the panel, then edit a post and confirm the modal picker lists them, that selecting one persists, and that the image renders on the front end via Storage::disk(config('filament-image-library.disk'))->url($post->cover).
Single image on a resource. Add the picker to the form schema next to your other fields, store the returned value in a dedicated column, and expose a URL accessor on the model so views never touch storage internals:
protected function coverUrl(): Attribute
{
return Attribute::get(fn () => $this->cover
? Storage::disk(config('filament-image-library.disk'))->url($this->cover)
: null);
}
Multiple images. For galleries, either a multiple() variant of the field backed by a JSON column, or a proper pivot/hasMany if images need ordering, captions, or per-usage alt text. Choose the pivot as soon as you need anything beyond "which images are attached" — JSON columns are a one-way door for ordering and translations.
Repeatable contexts. In Repeater and Builder blocks the picker works like any other field, but make sure the stored value is relative to the configured disk. Absolute URLs written into block data break the day you move to S3 or add a CDN.
Table previews. Pair it with Filament's ImageColumn pointed at the same disk/directory config, not at a hard-coded storage/ path. Keep disk and directory in config and read them in one place (a small ImageUrl helper or model accessor) so a disk change is one edit.
Managing the library itself. Treat the library page as an admin-only surface: gate it with a policy or canAccess() on the page and hide it from the navigation for roles that only consume images. Editors picking images and editors deleting images are usually different people.
Bridging existing uploads. If you already have FileUpload fields producing paths, a one-off command that walks those paths and inserts missing library records lets you migrate incrementally. Don't rewrite existing columns in the same release; add the library field, backfill, then deprecate the old one.
Seeding and tests. Use Storage::fake() in tests and create library records through the package model in factories rather than hitting the upload endpoint. Assert on the stored identifier, not the rendered URL.
migrate before vendor:publish --tag=...-migrations silently gives you no table and a confusing "relation does not exist" on the library page.storage:link is not optional with the default public disk, and APP_URL leaking into generated URLs is the usual cause of "images work in the panel, broken in email/API".nullOnDelete or a "detach/archive" flow, and add a withCount on the library table so admins see usage before deleting.getTableQuery(); the picker's convenience makes it easy to attach images to a dozen resources and then wonder why the index page got slow."outerweb/filament-image-library": "0.x.*" plus a review of the changelog on upgrade is cheap insurance, and the codebase is small enough to read in an afternoon.composer.json rather than assuming, and don't mix this plugin with a panel on a different major.php artisan config:clear locally and remember config:cache in deploy — published config that isn't cached is a classic "works on my machine".vendor/.How can I help you explore Laravel packages today?