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

Nova Embed Card Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the card via Composer, then check the package's 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.
  2. Confirm the compiled assets exist. Look for 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.
  3. Register it in a dashboard. In Nova 4/5 the entry point is 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'),
    ];
}
  1. Look first at the PHP card class (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.

Implementation Patterns

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.

Gotchas and Tips

  • 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.
  • Use the embed URL, not the watch URL. YouTube watch?v= will not embed; use /embed/{id}. Same class of mistake with Google Docs (/preview vs /edit) and Vimeo.
  • Mixed content. An http:// iframe inside an https:// Nova app is blocked outright. Enforce https in config and validate it.
  • App-level CSP. If you run a strict 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.
  • Never put secrets in the meta. Everything passed via ->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.
  • Fixed heights and inner scrollbars. Most cards take a pixel height; if the embedded page is taller you get a nested scroll area, which is awkward on tablet. Prefer aspect-ratio-style heights for video and generous fixed heights for dashboards.
  • Dark mode doesn't propagate. Nova's dark theme has no effect inside the iframe — most embeds will render with a white background. Pass the theme into the embed URL if the provider supports it (?theme=dark), otherwise accept the mismatch.
  • Sandbox untrusted content. For anything you don't fully control, prefer an embed that sets sandbox and allow attributes; a rogue iframe can attempt top-level navigation or trigger downloads.
  • Blank card checklist: (1) unknown Vue component name in the console → asset/publish problem; (2) refusal-to-frame message → target's headers; (3) CSP violation → 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.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings