ericlagarda/nova-embed
A Laravel Nova package that adds an embed field for displaying videos, iframes, and other external content inside your admin panel. Configure embed URLs in resource forms and detail views to preview media without leaving Nova.
Install the field with Composer from the root of a Nova-enabled Laravel app:
composer require ericlagarda/nova-embed
If the package ships a service provider, confirm it is registered (Nova 2/3 projects typically need the provider added to config/app.php; Nova 4+ relies on package discovery). Fields that ship compiled JS also need the provider to call Nova::script() / Nova::style() — check src/ and the package composer.json extra.laravel.providers entry before assuming discovery works.
Attach the field to a resource in the usual Nova way. The package exposes an embed field; the exact class name and fluent methods should be verified against src/ (there is no upstream documentation beyond the README), but the typical shape is:
use EricLagarda\NovaEmbed\Embed; // confirm namespace/class name from src/
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title'),
Embed::make('Video URL', 'video_url'),
];
}
Where to look first
src/ — the field class and any service provider. This tells you the real API (make(), any ->width(), ->height(), ->provider() style methods, and the underlying column name).resources/js/ — the Vue component. It defines how the stored value is transformed into markup and whether it does any client-side URL parsing (YouTube/Vimeo regex) or just renders a server-provided string.README.md — the only documentation; expect it to be thin and possibly stale.First use case: rendering a stored YouTube/Vimeo/media URL as an <iframe> on the resource detail page. Start with a single Embed::make('Video') on a detail view, confirm the iframe renders, then move to the form view.
Detail-view embed (the safe, common pattern). Store the raw URL in a plain text column and let the field render it read-only:
Embed::make('Trailer', 'trailer_url')
->onlyOnDetail(),
Keep the column a normal string in the migration. The field should not be your source of truth — it is a presenter. This avoids surprises if the package is later abandoned or the Nova field breaks on upgrade.
Form view with live preview. If the field supports form display, the stored value is the URL and the Vue component previews it as the user types. Pair it with validation on the model or request so only trusted hosts are accepted:
'trailer_url' => ['nullable', 'url', 'regex:/^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be|vimeo\.com)\//i'],
Because the embed markup is derived from user input, constrain the domain list. This is the single most important integration decision — everything else is cosmetic.
Pairing with a media library. A robust pattern is to keep spatie/laravel-medialibrary (or similar) as the canonical store and use the embed field only for external hosted video:
Embed::make('External Video')->hideFromIndex(),
File::make('Uploaded Video')->disk('s3'),
Document for your team which one wins when both are filled; a resource-level accessor or resolveUsing helps keep the display consistent.
Index/detail thumbnails. If the component exposes thumbnails, ->onlyOnIndex() is useful for content tables; otherwise keep the field off the index — iframes in table rows are heavy and hurt Tinker/CLI-adjacent tooling and Nova search performance.
Wrapping for reuse. Because the API surface is unverified and the package is stale, wrap it:
class EmbedField
{
public static function make(string $name, ?string $attribute = null): Embed
{
return Embed::make($name, $attribute)
->help('Paste a YouTube or Vimeo URL');
}
}
Then a future swap to a maintained field (or a custom Vue component) is a one-file change across all resources.
Server-side normalization. Add a mutator to canonicalize URLs before storage (https://, strip tracking params). The Vue preview will then match what the detail view renders, and you avoid two different URL forms in the DB.
Maintenance risk is the headline. Last release was 2020-10-28 and the package has effectively zero dependents. Nova 4 and 5 changed field/asset registration and Vue versions significantly. Verify it loads under your Nova version before building a workflow around it; if the compiled JS is Vue 2-only it will silently fail or throw in the browser console on Nova 4+.
oEmbed/network calls. If the field resolves embed data server-side (via an oEmbed provider or oscarotero/embed-style dependency), every detail view render may hit the network. Wrap resolution in Cache::remember() with a TTL, and always handle timeouts and non-200 responses — an unreachable provider should degrade to a plain link, not a 500 on the resource page.
XSS via embed HTML. Never echo provider HTML with {!! !!} without sanitizing. Prefer constructing the iframe yourself from a validated URL over trusting a remote html payload. If the package returns raw HTML, sandbox the iframe (sandbox="allow-scripts allow-same-origin") and set referrerpolicy.
Content Security Policy. Nova's default CSP and any app-level CSP will block third-party iframes. You will need frame-src https://www.youtube.com https://player.vimeo.com in your policy. This is the most common "the field renders nothing" report — check the browser console, not the Laravel log.
Config quirks. There is unlikely to be a published config file, but if there is one, publish with php artisan vendor:publish --tag=nova-embed and commit it. Field defaults (dimensions, provider list) often live in the Vue component, not PHP, so they cannot be changed without republishing assets.
Debugging workflow. When the field is blank:
Nova::script() registration shows as a 404 or an unregistered component warning.nova_get_components / the Nova service provider boot for the component name.Testing. Since embedding is network-dependent, fake it. Bind a stub resolver in your test case, or test only that the field is present in the resolved Nova fields array:
$this->actingAs($user)
->get("/nova-api/users/{$user->id}")
->assertOk();
Do not write tests that assert on remote iframe markup.
Extension points. The two practical extension seams are (a) a custom accessor/resolveUsing on the field to transform the stored URL, and (b) replacing the Vue component entirely with your own Nova::script()-registered component if the bundled one is incompatible. The latter is usually less work than patching the package inline — fork it, pin your fork in composer.json via a VCS repository, and keep the diff minimal so you can migrate off when a maintained alternative appears.
How can I help you explore Laravel packages today?