elipzis/nova-embed-card
Laravel Nova card that turns a URL attribute into a rich embedded link preview, pulling Open Graph and Twitter Card metadata to show title, description, and image inside your Nova resources.
composer.json for its Nova version constraint (laravel/nova: ^4.0|^5.0 or similar). A card built for Nova 4 will not register its Vue component correctly under Nova 5 if the mixin/boot pattern changed.dist/js/card.js in the package. Many small cards ship prebuilt assets; if not, you'll need to npm install && npm run prod inside the package directory, or register the source path in your app's webpack.mix.js / vite.config.js — otherwise the dashboard renders an empty card shell with a console error about an unknown component.app/Nova/Dashboards/Main.php → cards() (older setups use NovaServiceProvider::cards()):use Vendor\IframeCard\IframeCard;
public function cards(): array
{
return [
IframeCard::make()
->url('https://www.youtube.com/embed/dQw4w9WgXcQ')
->width('1/2'),
];
}
src/*.php) and its resources/js/card.js. The PHP class tells you which fluent setters exist (URL, height, title); the Vue component tells you which card.meta keys it actually reads. Any meta key the JS doesn't consume is silently ignored — no error, no render.First use case: drop a YouTube embed URL onto the main dashboard as an onboarding/"what's new" video, or embed a company status page. Start there, confirm the card renders and resizes, then move to internal embeds.
Wrap it in a project card. Don't scatter ->url('...') literals across dashboards. Subclass it once so URLs and heights come from config:
class HelpVideoCard extends IframeCard
{
public function __construct()
{
parent::__construct();
$this->url(config('help.video_embed_url'))
->height(360);
}
}
That gives you one place to change when the video or dashboard URL changes, and lets you ->canSee(fn ($request) => $request->user()->isAdmin()) in the dashboard.
Keep URLs in config/env, not in code. Embed targets change per environment (staging Grafana vs. production Metabase). A config/embeds.php keyed by environment avoids committing internal hostnames.
Use grid widths deliberately. ->width('1/3') alongside other cards works well; full-width iframes on a dashboard with two or three other cards tend to produce awkward scroll nesting. Pair a video card with a metrics card at 1/3 to keep the row balanced.
Conditional visibility over separate dashboards. canSee() on the card is cheaper than building a second dashboard for users who shouldn't see the content — but remember it's a UI-level filter only, not authorization for the embedded resource itself.
Signed/temporary URLs for internal embeds. If you're embedding Grafana, Metabase, or an internal wiki, generate a short-lived signed URL server-side rather than relying on the user's session cookie (third-party cookie restrictions in modern browsers will bite you on cross-domain iframes).
Testing. The card is just meta serialization, so it's cheap to cover:
$card = (new HelpVideoCard)->meta();
$this->assertStringStartsWith('https://www.youtube.com/embed/', $card['url']);
Add one browser-level check (Dusk or a manual smoke test) that the iframe actually renders — that's the failure mode unit tests can't catch.
X-Frame-Options / frame-ancestors wins. If the target site sends X-Frame-Options: DENY or a CSP frame-ancestors that excludes your Nova domain, no card configuration can override it. You get a blank card and a console refusal message. This is the single most common complaint — always verify the target allows framing before blaming the package.watch?v= will not embed; use /embed/{id}. Same class of mistake with Google Docs (/preview vs /edit) and Vimeo.http:// iframe inside an https:// Nova app is blocked outright. Enforce https in config and validate it.Content-Security-Policy, add your embed hosts to frame-src (or child-src for older browsers). Nova itself doesn't ship a CSP by default, so this usually only appears after a security hardening pass — and looks like a sudden regression.->url() / withMeta() is serialized into the page and readable in the DOM and network tab. No API keys, no long-lived tokens, no internal admin URLs you wouldn't show the user.aspect-ratio-style heights for video and generous fixed heights for dashboards.?theme=dark), otherwise accept the mismatch.sandbox and allow attributes; a rogue iframe can attempt top-level navigation or trigger downloads.frame-src; (4) iframe element present but empty → wrong embed URL or a 404 upstream. Checking the browser console and the iframe's network request distinguishes these in seconds.How can I help you explore Laravel packages today?