Metabase Dashboard Embedding
Metabase supports two embedding modes: public links (no auth) and signed embedding (JWT-signed for authorized access). Second variant embeds personalized dashboards with user-level filtering.
Signed Embedding
Metabase generates iframe URL with JWT token signed by secret:
import jwt from 'jsonwebtoken';
function generateMetabaseEmbedUrl(
dashboardId: number,
params: Record<string, unknown> = {}
): string {
const METABASE_SITE_URL = process.env.METABASE_URL;
const METABASE_SECRET_KEY = process.env.METABASE_SECRET_KEY;
const payload = {
resource: { dashboard: dashboardId },
params, // filters: { user_id: 123, date_range: 'last30days' }
exp: Math.round(Date.now() / 1000) + 10 * 60 // 10 min token
};
const token = jwt.sign(payload, METABASE_SECRET_KEY);
return `${METABASE_SITE_URL}/embed/dashboard/${token}#bordered=true&titled=false`;
}
// API endpoint
app.get('/api/dashboard/embed-url', authenticate, (req, res) => {
const url = generateMetabaseEmbedUrl(
Number(process.env.METABASE_DASHBOARD_ID),
{ user_id: req.user.id, org_id: req.user.organizationId }
);
res.json({ url });
});
React Component
function MetabaseEmbed({ userId }: { userId: number }) {
const [iframeUrl, setIframeUrl] = useState('');
useEffect(() => {
fetch(`/api/dashboard/embed-url?userId=${userId}`)
.then(r => r.json())
.then(({ url }) => setIframeUrl(url));
}, [userId]);
return (
<iframe
src={iframeUrl}
width="100%"
height={600}
allowFullScreen
frameBorder="0"
/>
);
}
Security
- Secret key never exposed to frontend
- JWT tokens expire quickly (10 min recommended)
- Parameters sanitized on server
- Row-level security via filters
Timeline
Basic embedding—1–2 days. With parameter filtering and security—2–3 days.







