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

Media Bundle Laravel Package

softspring/media-bundle

Softspring MediaBundle is a Symfony bundle for managing uploaded media files and images, with Doctrine-backed media entities, configurable storage and upload handling, plus helpers for integrating and rendering media assets in your applications.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a Symfony bundle (softspring/media-bundle) rather than a Laravel package, so day-to-day leverage assumes a Symfony app (or a Laravel app that has deliberately bridged Symfony's DI/HttpKernel). Treat the install as a Symfony bundle first; Laravel-side usage means manually wiring the services and config.

Minimal steps:

  1. composer require softspring/media-bundle
  2. Register the bundle in config/bundles.php:
    Softspring\MediaBundle\SfsMediaBundle::class => ['all' => true],
    
  3. Import routing if you want the built-in admin/CRUD endpoints, or use the Twig helpers only and manage media yourself:
    sfs_media:
        resource: '@SfsMediaBundle/config/routing.php'
    
  4. Configure at minimum a storage filesystem and image driver (Flysystem + liip/imagine-bundle are the usual companions; local storage for dev, S3 for prod).
  5. Look here first, in this order:
    • SfsMediaBundle extension/config tree (config/config.php) — the media types and image filters you must define.
    • MediaFilterInterface / the built-in image filter services — this is where your transformation rules live.
    • The Twig extension — this is the surface you will actually touch 90% of the time.

First real use case: define one media type (e.g. post_image) with one thumbnail and one large version, upload through the admin controller (or programmatically via the media manager), then render it in a template:

{{ sfs_media_render(post.image, 'large') }}

That single flow — type definition → upload → version generation → render — is the whole package in miniature.

Implementation Patterns

Define media types in config, not in code. Each type declares its allowed versions. Keep version names semantic (thumb, card, hero) rather than pixel sizes; sizes belong to the filter, not the type.

Treat MediaVersion as the unit of storage. A Media is the logical asset; each MediaVersion is a concrete generated file. This split is what makes v5.6.0's unique index meaningful — one row per (media, version name) pair — and it is the mental model you want when debugging "why is my image stale".

Render via Twig helpers, resolve URLs server-side. Use sfs_media_url(...) when you need the raw path (e.g. into a srcset you build yourself) and the render helper when you want the bundle to emit the markup. Avoid constructing storage paths by hand; URLs change shape when you swap Flysystem adapters.

Leverage the new public-URL version filter (v5.6.0). This release adds a version public URL filter — the intended pattern is to derive a version's public URL in a filter chain rather than calling the storage layer directly. Practically: use it wherever you were previously concatenating a base URL + version path, and prefer it in Twig so the URL logic stays in one place. If you have custom filters that emit absolute URLs, audit them — this filter likely supersedes that code.

Programmatic uploads follow a two-step shape. Create/persist the Media, then let the bundle generate versions (either eagerly on flush or via a queued/console step depending on your config). For bulk imports, disable eager generation and run the version-generation command afterwards — otherwise an import of 10k assets will thrash your image driver.

Integrate at the boundary, not the middle. The cleanest Laravel/Symfony integration is: your domain entity holds a Media relation or a media id; a thin service wraps the bundle; controllers and templates never call bundle internals. When the bundle changes (as it did here), only the wrapper moves.

Filter chains are composable. Register small filter services and chain them (crop → resize → optimize) rather than one monolithic filter per version. It keeps cache keys stable and makes A/B-ing a new crop cheap.

Cache invalidation is your responsibility. On version regeneration, tag your cache entries by media id so a re-upload actually invalidates rendered pages. The bundle will not do this for you.

Gotchas and Tips

v5.6.0 adds a unique index on MediaVersion — migrate before you upgrade loudly. If your existing database already contains duplicate version rows (easy to accumulate if any past code path re-generated versions without cleaning up), the migration will fail on the index creation. Before deploying:

SELECT media_id, version, COUNT(*) c
FROM sfs_media_version
GROUP BY media_id, version
HAVING c > 1;

Deduplicate (keep the newest, repoint any references) and only then run the migration. There is now also protection against future duplications, so the new code path is safer than the old one — but the upgrade itself is the risky moment. Plan it as a data migration, not a schema migration.

Beware the "no prior assessment" trap on the version table. Because the unique index lands in a release that also adds a feature, it is tempting to lump them together. Roll them out separately: index first (with dedupe), then the URL filter adoption.

Version regeneration is not automatic on config change. Editing a filter's dimensions does not invalidate existing MediaVersion rows. You need an explicit regeneration pass; otherwise old files persist silently and your templates look inconsistent across assets.

Storage adapter swap changes URLs, not files. Moving from local to S3 leaves the DB untouched — paths are relative. If you rendered absolute URLs anywhere by hand, that's where breakage appears. The v5.6.0 public-URL filter is the fix; adopt it rather than patching strings.

Image driver memory limits. GD is easy but memory-hungry on large originals; Imagick is faster but slower to install. Whichever you pick, set a sane memory_limit on the CLI context — console-driven bulk generation fails with an opaque blank error when it runs out.

Routing name collisions. The bundle's admin routes use fairly generic names. If you mount them alongside your own media endpoints, prefix them or you'll get duplicate-route exceptions that point at the wrong file.

Extension points worth knowing: custom MediaFilterInterface implementations, the storage adapter (any Flysystem filesystem can be injected), and the Twig extension itself (safe to decorate for project-specific helpers rather than editing templates everywhere).

Debugging checklist when a version won't render: (1) is the version name declared in the media type config? (2) does the MediaVersion row exist in the DB? (3) does the file exist on the configured filesystem? (4) is the filter registered in the container? The package fails quietly between steps 1 and 2, which is the usual source of "it just shows nothing".

Don't chase Laravel parity. There is no ServiceProvider, no facade, no config/media.php. Any php artisan vendor:publish tutorial you find for this package is fabricated. If you're in Laravel, budget time for a bridge layer and keep it isolated.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons