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 Library Laravel Package

codedor/filament-media-library

Filament Media Library gives your admin panels a full media manager: upload, browse, search, and organize files, then attach them to your models via Spatie Media Library. Includes a reusable picker, image conversions, and a clean, native Filament UI.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal setup

composer require wotzebra/filament-media-library
php artisan vendor:publish --tag="filament-media-library-config"
php artisan vendor:publish --tag="filament-media-library-migrations"
php artisan migrate

Register the plugin on the panel you want the media browser available in:

// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(MediaLibraryPlugin::make());
}

Attach the media contract to the model you want to store files against (the package builds on Spatie's media library conventions):

use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

class Post extends Model implements HasMedia
{
    use InteractsWithMedia;
}

Where to look first

  1. The published config/filament-media-library.php — this is where the disk, accepted mime types, and (as of v4.7.0) the attachment model live. Read it before touching anything else.
  2. The plugin class + service provider — confirms which Filament version you're on and which resources/pages get registered.
  3. The migrations — the package ships its own attachment table; knowing its columns saves you a debugging session later.
  4. resources/views — the grid markup from PR #89 lives here, so any visual override happens at this level rather than in CSS hacks.

First use case

The fastest win is a media picker on a Resource form: drop the field into your schema, upload through the modal, and the selection is persisted to the model's media collection. Once that loop works end-to-end, replace the field in one table column and confirm the grid renders the thumbnail — that validates the whole chain (storage, conversions, grid) in about ten minutes.

Implementation Patterns

Form field inside a Resource

The package's value is that the media picker behaves like any other Filament field. Keep it in the schema, not in a custom Blade view, so validation, reactive state, and ->required() keep working:

use Wotzebra\FilamentMediaLibrary\Forms\Components\MediaPicker;

MediaPicker::make('hero')
    ->label('Hero image')
    ->collection('hero')
    ->acceptedFileTypes(['image/jpeg', 'image/png', 'image/webp'])
    ->maxFiles(1)
    ->required(),

Table column / read-only rendering

Use the same component family in table columns or infolists so the admin sees the thumbnail fallback from #89 rather than a broken image icon when no conversion exists yet.

Custom attachment model (v4.7.0, PR #88)

This is the headline change for anyone with non-default requirements. Before v4.7.0 the attachment model was fixed, which meant the only route to custom behaviour (extra columns, global scopes, tenant relations) was a fork or a container binding that could break on patch releases. Now:

  1. Create your model extending the package's base attachment model.
  2. Point the config key (check the published config for the exact name — typically attachment_model) at your class.
  3. Publish and adapt the migration if you added columns; keep the table name and morph alias identical to the package default so existing rows resolve.
// config/filament-media-library.php
'attachment_model' => App\Models\MediaAttachment::class,
class MediaAttachment extends \Wotzebra\FilamentMediaLibrary\Models\Attachment
{
    protected static function booted(): void
    {
        static::addGlobalScope(new TenantAttachmentScope);
    }

    public function registerMediaConversions(?Media $media = null): void
    {
        $this->addMediaConversion('thumb')->width(400)->height(400)->nonQueued();
    }
}

Multi-tenant / per-panel wiring

Because the model is now swappable, the natural pattern is resolving the model per panel rather than globally: a TenantAttachment for the tenant panel, the default for the central admin. Register the plugin with the resolved class in each panel provider, or drive the config from the current panel in a service provider's boot().

Conversions and the thumbnail fallback

PR #89 added a fallback when a thumbnail isn't available. Treat that as a safety net, not a strategy — register an explicit thumb conversion so the grid never loads originals. A multi-MB original rendered into a 200px card is the single most common cause of a "slow media page" report.

Integration tips

  • Eager load the media relation on list pages (->with('media')) — the grid touches media per row and will otherwise N+1 silently.
  • Wrap uploads in a Storage::fake() in tests and assert against the collection name, not the file path.
  • If you render media on the front end too, keep the conversion names in one place (a small enum or config array) so the admin grid and the public site don't drift.

Gotchas and Tips

Config caching will bite you on the model swap. After changing attachment_model, run php artisan config:clear (and config:cache in deploy). A stale cached config is the most likely reason the new model "isn't being used" — the class resolves fine but you're still reading the old value.

Swapping the model doesn't migrate anything. PR #88 changes which class is instantiated; it does not alter the schema. If your custom model expects extra columns, you need your own migration. Conversely, if you keep the default table but introduce a morph alias, set Relation::enforceMorphMap() before any media exists, otherwise existing rows point at the FQCN and new ones at the alias — a subtle, data-dependent bug.

Don't assume the fallback hides missing conversions. The #89 fallback exists so a freshly uploaded file renders before conversions finish. If you queue conversions, expect a brief window of full-size images in the grid. For large collections, use nonQueued() on the thumb conversion or pre-generate on upload.

Breakpoint changes are layout-only. The "real breakpoints" work in #89 means the grid reflows at genuine Tailwind breakpoints rather than fixed column counts. If you had CSS targeting the old selectors or a hardcoded ->columns(4), check it in a panel with the sidebar expanded and collapsed — the content width differs and is where regressions show up.

Card titles come from the media name, not your model. Truncation and escaping are handled, but if you were relying on the filename being hidden, you now get a title line. Override the column/view rather than fighting it with CSS.

Deletion semantics. Filament's delete action on an attachment removes the DB row; the underlying Spatie media record and file may survive depending on how you wired the relation. Decide explicitly between soft deletes, force deletes, and a cleanup job — orphaned files on S3 accumulate quietly and cost real money.

Version the plugin against Filament, not just itself. This package is on 4.x while Filament's own major line has moved; a 4.7.0 plugin release is not a statement about Filament compatibility. Pin both in composer.json and read the package's composer.json constraints before a composer update on a Friday.

Extension points, in order of preference: config (model, disk, mime types) → subclassing the attachment model → publishing and overriding views → forking. Reaching for a fork should be a last resort now that #88 exists; most customisation requests that previously needed one are satisfied by the model swap.

Upgrade risk from 4.6.1 → 4.7.0: low. Both changes are additive (a new configurable seam and layout/CSS improvements). No deprecations or breaking changes are signalled in the release notes. The one thing to verify post-upgrade is any prior workaround you used to customise the attachment model — if you had a container binding or a class_alias shim in place, remove it and use the config key instead, or you'll get two competing resolutions and confusing behaviour.

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.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr