netgen/remote-media-bundle
Netgen Remote Media Bundle integrates remote media providers into Symfony/eZ Platform, adding a field type and browser so editors can search, preview, and embed externally hosted videos, images, and files without uploading them to the CMS.
First, the reality check: this is an eZ Publish / eZ Platform (Ibexa) bundle, not a Laravel package. There is no service provider, no publishable config, no Blade directives. You cannot composer require this into a Laravel app and get anything useful. If you are a Laravel dev, treat this as a reference implementation for modelling remote-media fields (provider registry + value object + render helpers) — or as work you only take on inside an Ibexa codebase. The concepts map cleanly (container, config tree, tagged services, Twig extensions), the plumbing does not.
Minimal path if you are on Ibexa/eZ:
composer require netgen/remote-media-bundle
Then register Netgen\Bundle\RemoteMediaBundle\NetgenRemoteMediaBundle in config/bundles.php (Symfony 4/5) or the legacy ezpublish/EzPublishKernel.php / app/AppKernel.php for eZ Publish.Where to look first, in this order:
DependencyInjection/Configuration.php — the authoritative config shape. Do not trust blog snippets; the provider config array changed across versions.Provider/ProviderInterface.php (and Provider/Cloudinary/CloudinaryProvider.php) — the contract you implement for a custom backend.eZ/FieldType/RemoteMedia/ (or FieldType/RemoteMedia/ in Ibexa-era versions) — the field type, Value, and Type classes. This tells you exactly what is persisted.Resources/config/services.yml — the service IDs and, critically, the provider tag name.Resources/views/ — the default Twig template, which is what you will override.First real use case: a "Video" or "Image" field on an article content type where editors paste a Cloudinary public ID or a YouTube/Vimeo URL, and the front end renders a provider-transformed asset rather than a locally stored binary. This is the bundle's whole reason to exist: no binary in your DB or var storage, provider-side transforms, one field type for many sources.
Provider registry, not hardcoded calls. Everything routes through a registry keyed by provider identifier. That means your templates and business logic ask for cloudinary or vimeo by name and never import a concrete client. Follow that discipline in your own code — never instantiate a provider directly.
Custom provider. Implement the provider interface, return the field's value object from getValue(), and tag the service. The tag name is the thing to verify in services.yml; it is roughly:
App\Media\VimeoProvider:
tags:
- { name: netgen.remote_media.provider, alias: vimeo }
Then register it under providers: in the bundle config so the admin UI and field type can resolve it. If your service is private (Symfony 4+ default), the registry lookup at runtime will fail — see Gotchas.
Rendering. Use the bundle's Twig helpers/extensions rather than reconstructing URLs by hand; the helper handles provider dispatch and transformation option formatting. Override the shipped field template by placing a template at the same path under your app's templates directory — this is the standard eZ/Ibexa override mechanism and is far less brittle than editing the vendor view.
Headless / API output. The stored value is small: provider identifier, resource ID, and any provider metadata (width/height, resource type, alt text depending on version). That is the right thing to serialize. Build the delivery URL in your API layer via the provider so transformations stay server-side and you never leak API secrets into JSON. If you need signed URLs, generate them per-request in the controller — never cache a signed URL beyond its expiry.
Responsive markup. Because the asset lives at the provider, do not fight the CMS's image variation system. Request srcset candidates as distinct provider transformations and emit a plain <picture>/<img srcset> block. Keep a single mapping of your breakpoints to transformation strings in one Twig macro or PHP class so it is not duplicated across templates.
Content migration / fixtures. When creating content programmatically, construct the field value through the field type's value class and let the field type's Type::toPersistenceValue() do the work. Do not write raw hashes into the attribute table. If you are importing a large set of Cloudinary assets, batch it and throttle — provider APIs rate-limit and a naive loop will burn your quota mid-import.
Testing. Write a fake provider that returns deterministic URLs, tag it, and use it in functional tests. Assert on the resolved URL and on the rendered markup, not on the provider SDK. This keeps CI free of network calls and credentials.
config/remote-media.php, no Blade component. Any tutorial that implies otherwise is wrong. Porting the idea to Laravel is a rewrite, not an install.public: true / a service alias / whether the registry uses a service locator. This is the single most common integration failure.Configuration.php for your installed version rather than trusting any snippet, including ones in this write-up.ngremotemedia), not the class name. A mismatch produces a silently empty field in the admin UI.eZ/FieldType/... vs FieldType/...) and different template override conventions. Confirm which branch you installed before copying code.w_600 means nothing to Vimeo. Do not try to build a provider-agnostic transformation DSL on top; expose per-provider options and let templates pass the right ones.How can I help you explore Laravel packages today?