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

Metabase Bundle Laravel Package

malefici/metabase-bundle

A Laravel/PHP bundle for integrating Metabase analytics into your application. It provides configuration and helpers to connect your app with Metabase, making it easier to embed dashboards and questions and surface analytics within Laravel projects.

View on GitHub
Deep Wiki
Context7

Getting Started

First, a fit check. This is a Symfony bundle (malefici/metabase-bundle), not a Laravel package. There is no service provider, no config/metabase.php, and no facade. Before investing time, decide which of two paths you're on:

  1. Port the idea, not the package. The core of Metabase embedding is a signed JWT (HS256) placed in an iframe URL. That's ~40 lines of code with firebase/php-jwt. Given 0 stars, 0 dependents, a single release (2024-03-17), and no ecosystem, this is the pragmatic choice for most Laravel apps.
  2. Consume the bundle in a Symfony service inside a mixed stack (e.g. a legacy Symfony admin alongside a Laravel app), or read its source as a reference implementation.

If you go with the package itself, minimal steps:

composer require malefici/metabase-bundle
  • Register the bundle in config/bundles.php (Symfony's registry, not Laravel's bootstrap/providers.php).
  • Publish/author the bundle config YAML — you'll need at minimum your Metabase site URL and the embedding secret key (Metabase Admin → Settings → Embedding → "Embedding secret key"). The exact key names are not well documented; read DependencyInjection/Configuration.php in vendor/malefici/metabase-bundle/ first. That file is the source of truth for every option.
  • Confirm what the bundle actually exposes: look for a service that signs tokens (something like MetabaseEmbedService / a signer), and any Twig extension. If there's no Twig function, you're calling the service directly from your controller.

First use case: render a single dashboard in an iframe, authenticated by a server-signed token. Controller builds the signed URL, view renders <iframe src="{{ $signedUrl }}">. That's the whole value proposition — everything else is parameterization.

Implementation Patterns

The core flow. Metabase signed embedding works like this: you take the dashboard/question ID, optional params, and an expiry, put them in a JWT payload, sign with your embedding secret, and append it as the token query param on /embed/dashboard/<token>. The bundle wraps the signing step. Your job is the surrounding plumbing.

Thin controller, thick service. Don't sign tokens in the controller. Bind a small wrapper as a singleton in a Laravel service provider (or use the bundle's service in Symfony) and inject it:

final class MetabaseEmbedder
{
    public function __construct(
        private string $siteUrl,
        private string $secret,
    ) {}

    public function dashboardUrl(int $id, array $params = [], int $ttl = 600): string
    {
        $payload = [
            'resource' => ['dashboard' => $id],
            'params'   => $params,          // e.g. ['tenant_id' => [1]]
            'exp'      => time() + $ttl,
        ];
        $token = JWT::encode($payload, $this->secret, 'HS256');
        return rtrim($this->siteUrl, '/') . '/embed/dashboard/' . $token . '#bordered=true&titled=true';
    }
}

Treat the bundle's signer as exactly this shape; if it doesn't expose a clean url() method, write yours and use the bundle only for config plumbing.

Parameterization is the real feature. A dashboard embedded for tenant A must never show tenant B's rows. Pass locked params when signing — the token is the authorization boundary, not the iframe. If a param must be user-adjustable (date range, category), exclude it from params and let Metabase's UI handle it; everything security-relevant gets baked into the token.

Expiry and caching. Signed tokens are cheap but not free, and they're short-lived (Metabase defaults to ~10 minutes). For a dashboard rendered on every page load, cache the signed URL keyed by a hash of (resource, params) with a TTL comfortably below exp — a few minutes. This avoids re-signing on every request and keeps clock skew from biting you.

Route + auth middleware. Expose GET /dashboards/{id} in your app, apply your normal auth middleware, resolve the user's tenant, then sign. The browser never talks to Metabase's API directly — the iframe loads a URL Metabase validates, and Metabase enforces the params you signed. Your middleware remains the only gate on who gets a token.

Blade component for reuse. Wrap the iframe in <x-metabase-dashboard :id="..." :params="..." /> so the iframe attributes (width, height, frameborder, allowtransparency, CSP-friendly loading="lazy") live in one place instead of scattered across views.

Gotchas and Tips

  • Symfony-in-Laravel mismatch is the top gotcha. You cannot composer require this and get a working provider. If you're on pure Laravel, budget the 30–60 minutes to implement the signer yourself and reuse this bundle's config/DI structure as a reference. You'll likely end up with less code and full control.
  • Vendor risk is real. Zero stars, zero dependents, one release in 2024, no visible maintenance. Pin the version in composer.json, and prefer wrapping it behind your own interface (MetabaseEmbedder) so you can swap to a hand-rolled signer without touching call sites.
  • Secret mismatch is the #1 runtime failure. The embedding secret must be enabled in Metabase admin and byte-for-byte identical to your config. A wrong secret yields a generic "Invalid token" page with no detail. Verify by decoding your generated token at jwt.io and comparing the signature against a known-good one.
  • Clock skew. If your PHP host's clock drifts, tokens will be rejected as expired or not-yet-valid. Keep TTLs short but not razor-thin (60s minimum), and prefer embedding the token in a page render over storing it.
  • URL encoding of params. Dashboard params with array values (['tenant_id' => [1,2]]) and special characters need care in the JWT payload. Metabase expects the JSON array form; a string "1,2" silently matches nothing.
  • Iframe blocked by headers. Metabase must be allowed to be framed by your domain. Check X-Frame-Options and Content-Security-Policy: frame-ancestors on the Metabase side, and add your origin. A blank iframe in devtools is almost always this, not the token.
  • Static vs interactive embedding. Metabase's newer interactive embedding (with full app-bar and drill-through) is a Pro/Enterprise feature. The signed-embed path covered here is the open-source option, with a simpler, more restricted iframe. Confirm which you're targeting before designing the UX — the bundle may only cover the OSS path.
  • Debugging loop: decode the token → check resource, params, exp → load the raw embed URL in a browser tab (not in your app) → read Metabase server logs for the rejection reason → then look at your app's CSP. Skip steps and you'll chase phantom auth bugs.
  • Extension points: if the bundle's signer is a concrete class, decorate it (Symfony decorates:) or wrap it rather than editing vendor code. If it ships a Twig function, don't reuse it — define your own so the view layer stays portable to Blade.
  • Read Configuration.php and the README before writing any config. The package's docs are sparse; the DI configuration node is the only reliable schema. Anything you guess about option names will be wrong.
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