The Metabase Embedding SDK provides event handlers to respond to user interactions and component lifecycle events.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/metabase/metabase/llms.txt
Use this file to discover all available pages before exploring further.
Global event handlers
Set up global event handlers in theMetabaseProvider:
import { MetabaseProvider } from '@metabase/embedding-sdk-react';
const eventHandlers = {
onDashboardLoad: (dashboard) => {
console.log('Dashboard loaded:', dashboard);
},
onDashboardLoadWithoutCards: (dashboard) => {
console.log('Dashboard structure loaded:', dashboard);
},
};
function App() {
return (
<MetabaseProvider
authConfig={authConfig}
eventHandlers={eventHandlers}
>
{/* Your app */}
</MetabaseProvider>
);
}
Dashboard events
onLoad
Triggered when a dashboard loads with all visible cards and their content:<InteractiveDashboard
dashboardId={1}
onLoad={(dashboard) => {
console.log('Dashboard name:', dashboard?.name);
console.log('Number of cards:', dashboard?.dashcards?.length);
// Track analytics
analytics.track('Dashboard Viewed', {
dashboardId: dashboard?.id,
name: dashboard?.name,
});
}}
/>
onLoadWithoutCards
Triggered after a dashboard loads, but without its cards (only the dashboard title, tabs, and cards grid are rendered):<InteractiveDashboard
dashboardId={1}
onLoadWithoutCards={(dashboard) => {
console.log('Dashboard structure loaded');
console.log('Dashboard has', dashboard?.tabs?.length || 0, 'tabs');
// Show loading indicator
setIsLoadingCards(false);
}}
/>
Complete dashboard example
import { InteractiveDashboard, MetabaseDashboard } from '@metabase/embedding-sdk-react';
import { useState } from 'react';
function Analytics() {
const [loadTime, setLoadTime] = useState<number | null>(null);
const [dashboard, setDashboard] = useState<MetabaseDashboard | null>(null);
const handleLoad = (loadedDashboard: MetabaseDashboard | null) => {
setDashboard(loadedDashboard);
const endTime = performance.now();
setLoadTime(endTime - startTime);
// Track performance
analytics.track('Dashboard Load Time', {
dashboardId: loadedDashboard?.id,
loadTime: endTime - startTime,
});
};
const startTime = performance.now();
return (
<div>
{loadTime && (
<div className="stats">
Loaded in {loadTime.toFixed(0)}ms
</div>
)}
<InteractiveDashboard
dashboardId={1}
onLoad={handleLoad}
onLoadWithoutCards={(dashboard) => {
console.log('Structure loaded for:', dashboard?.name);
}}
/>
</div>
);
}
Question events
onRun
Triggered when a question is executed:<InteractiveQuestion
questionId={1}
onRun={(question) => {
console.log('Question executed:', question);
console.log('Query:', question.query());
// Track query execution
analytics.track('Question Run', {
questionId: question.id(),
});
}}
/>
onSave
Triggered when a question is saved:<InteractiveQuestion
questionId={1}
isSaveEnabled={true}
onSave={(question) => {
console.log('Question saved:', question);
console.log('New question ID:', question.id());
// Show success message
toast.success('Question saved successfully!');
// Navigate to the saved question
navigate(`/question/${question.id()}`);
}}
/>
onBeforeSave
Triggered before a question is saved, allowing you to validate or modify the question:<InteractiveQuestion
questionId={1}
isSaveEnabled={true}
onBeforeSave={(question) => {
console.log('About to save:', question);
// Validate question name
const name = question.displayName();
if (!name || name.trim() === '') {
alert('Please provide a name for this question');
// Note: This won't prevent the save, just logs a warning
}
}}
/>
onNavigateBack
Triggered when the back button is clicked:import { useNavigate } from 'react-router-dom';
function QuestionView() {
const navigate = useNavigate();
return (
<InteractiveQuestion
questionId={1}
onNavigateBack={() => {
console.log('Back button clicked');
navigate('/dashboards');
}}
/>
);
}
Visualization events
onVisualizationChange
Triggered when the visualization type changes:<InteractiveQuestion
questionId={1}
onVisualizationChange={(question) => {
console.log('Visualization changed to:', question.display());
// Track visualization changes
analytics.track('Visualization Changed', {
questionId: question.id(),
newType: question.display(),
});
}}
/>
<InteractiveDashboard
dashboardId={1}
onVisualizationChange={(question) => {
console.log('Card visualization changed:', question.display());
}}
/>
Collection browser events
onClick
Triggered when an item is clicked:import { CollectionBrowser, MetabaseCollectionItem } from '@metabase/embedding-sdk-react';
import { useNavigate } from 'react-router-dom';
function Collections() {
const navigate = useNavigate();
const handleItemClick = (item: MetabaseCollectionItem) => {
console.log('Clicked:', item);
// Track clicks
analytics.track('Collection Item Clicked', {
itemId: item.id,
itemType: item.model,
itemName: item.name,
});
// Navigate based on type
switch (item.model) {
case 'dashboard':
navigate(`/dashboard/${item.id}`);
break;
case 'card':
case 'dataset':
navigate(`/question/${item.id}`);
break;
case 'collection':
// Navigation handled automatically
console.log('Navigating to collection:', item.name);
break;
}
};
return (
<CollectionBrowser
collectionId="root"
onClick={handleItemClick}
/>
);
}
Analytics integration
Google Analytics
import { InteractiveDashboard } from '@metabase/embedding-sdk-react';
import ReactGA from 'react-ga4';
function AnalyticsDashboard() {
return (
<InteractiveDashboard
dashboardId={1}
onLoad={(dashboard) => {
ReactGA.event({
category: 'Dashboard',
action: 'View',
label: dashboard?.name,
});
}}
/>
);
}
Segment
import { InteractiveQuestion } from '@metabase/embedding-sdk-react';
import { useAnalytics } from '@segment/analytics-react';
function QuestionView() {
const analytics = useAnalytics();
return (
<InteractiveQuestion
questionId={1}
onRun={(question) => {
analytics.track('Question Executed', {
questionId: question.id(),
questionName: question.displayName(),
});
}}
onSave={(question) => {
analytics.track('Question Saved', {
questionId: question.id(),
questionName: question.displayName(),
});
}}
/>
);
}
Mixpanel
import { InteractiveDashboard } from '@metabase/embedding-sdk-react';
import mixpanel from 'mixpanel-browser';
function Dashboard() {
return (
<InteractiveDashboard
dashboardId={1}
onLoad={(dashboard) => {
mixpanel.track('Dashboard Viewed', {
dashboard_id: dashboard?.id,
dashboard_name: dashboard?.name,
card_count: dashboard?.dashcards?.length,
});
}}
/>
);
}
Error handling
Handle errors using a custom error component:import { MetabaseProvider } from '@metabase/embedding-sdk-react';
const CustomError = ({ message }) => {
// Log error to monitoring service
console.error('SDK Error:', message);
// Track error
analytics.track('SDK Error', { message });
return (
<div className="error-container">
<h2>Something went wrong</h2>
<p>{message}</p>
<button onClick={() => window.location.reload()}>
Reload
</button>
</div>
);
};
function App() {
return (
<MetabaseProvider
authConfig={authConfig}
errorComponent={CustomError}
>
{/* Your app */}
</MetabaseProvider>
);
}
Performance monitoring
import { InteractiveDashboard, MetabaseDashboard } from '@metabase/embedding-sdk-react';
import { useEffect, useState } from 'react';
function PerformanceMonitoring() {
const [startTime] = useState(performance.now());
const [metrics, setMetrics] = useState<{
structureLoadTime?: number;
fullLoadTime?: number;
}>({});
return (
<>
{metrics.fullLoadTime && (
<div className="performance-metrics">
<p>Structure load: {metrics.structureLoadTime?.toFixed(0)}ms</p>
<p>Full load: {metrics.fullLoadTime?.toFixed(0)}ms</p>
</div>
)}
<InteractiveDashboard
dashboardId={1}
onLoadWithoutCards={(dashboard) => {
const loadTime = performance.now() - startTime;
setMetrics(prev => ({ ...prev, structureLoadTime: loadTime }));
// Send to monitoring service
monitoring.recordMetric('dashboard.structure.load', loadTime);
}}
onLoad={(dashboard) => {
const loadTime = performance.now() - startTime;
setMetrics(prev => ({ ...prev, fullLoadTime: loadTime }));
// Send to monitoring service
monitoring.recordMetric('dashboard.full.load', loadTime);
}}
/>
</>
);
}
Event handler types
import type {
MetabaseDashboard,
MetabaseQuestion,
MetabaseCollectionItem,
SdkEventHandlersConfig,
} from '@metabase/embedding-sdk-react';
// Global event handlers
const eventHandlers: SdkEventHandlersConfig = {
onDashboardLoad: (dashboard: MetabaseDashboard | null) => {
// Handle dashboard load
},
onDashboardLoadWithoutCards: (dashboard: MetabaseDashboard | null) => {
// Handle dashboard structure load
},
};
// Component-specific handlers
type OnLoad = (dashboard: MetabaseDashboard | null) => void;
type OnRun = (question: MetabaseQuestion) => void;
type OnSave = (question: MetabaseQuestion) => void;
type OnClick = (item: MetabaseCollectionItem) => void;
Best practices
Debounce frequent events
For events that fire frequently, consider debouncing:import { useCallback } from 'react';
import { debounce } from 'lodash';
function QuestionView() {
const debouncedRun = useCallback(
debounce((question) => {
console.log('Question run:', question);
analytics.track('Question Run', { questionId: question.id() });
}, 1000),
[]
);
return (
<InteractiveQuestion
questionId={1}
onRun={debouncedRun}
/>
);
}
Use error boundaries
import { Component, ReactNode } from 'react';
class ErrorBoundary extends Component<
{ children: ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught:', error, errorInfo);
analytics.track('Component Error', { error: error.message });
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<MetabaseProvider authConfig={authConfig}>
<InteractiveDashboard dashboardId={1} />
</MetabaseProvider>
</ErrorBoundary>
);
}
Track user journeys
import { useState } from 'react';
function Analytics() {
const [journey, setJourney] = useState<string[]>([]);
const addToJourney = (event: string) => {
setJourney(prev => [...prev, event]);
console.log('User journey:', [...journey, event]);
};
return (
<CollectionBrowser
onClick={(item) => {
addToJourney(`clicked_${item.model}_${item.id}`);
}}
/>
);
}
Related
- Configuration - SDK configuration
- TypeScript types - Type definitions