Modular embedding SDK - AI chat

Modular embedding SDK is only available on Pro and Enterprise plans (both self-hosted and on Metabase Cloud).
You can embed an AI chat in your application similar to Metabot in Metabase.
Embedded Metabot is a more focused version of Metabot designed to work well in an embedded context. Embedded Metabot can only display ad-hoc questions and metrics; it doesn’t know about dashboards.
To help embedded Metabot more easily find and focus on the data you care about most, select the collection containing the models and metrics it should be able to use to create queries.
If you’re embedding the Metabot component in an app, you can specify a different collection that embedded Metabot is allowed to use for creating queries.
Chat preview
You can check out a demo of the AI chat component on our Shoppy demo site.
Example
import React from "react";
import {
MetabotQuestion,
MetabaseProvider,
defineMetabaseAuthConfig,
} from "@metabase/embedding-sdk-react";
const authConfig = defineMetabaseAuthConfig({
metabaseInstanceUrl: "https://your-metabase.example.com",
});
export default function App() {
return (
<MetabaseProvider authConfig={authConfig}>
<MetabotQuestion />
</MetabaseProvider>
);
}
Props
| Property | Type | Description |
|---|---|---|
className? |
string |
A custom class name to be added to the root element. |
height? |
Height<string | number> |
A number or string specifying a CSS size value that specifies the height of the component |
isSaveEnabled? |
boolean |
Whether to show the save button. |
layout? |
"auto" | "sidebar" | "stacked" |
Layout for the MetabotQuestion component. - auto (default): Metabot uses the stacked layout on mobile screens, and a sidebar layout on larger screens. - stacked: the question visualization stacks on top of the chat interface. - sidebar: the question visualization appears to the left of the chat interface, which is on a sidebar on the right. |
style? |
CSSProperties |
A custom style object to be added to the root element. |
targetCollection? |
SdkCollectionId |
The collection to save the question to. This will hide the collection picker from the save modal. |
width? |
Width<string | number> |
A number or string specifying a CSS size value that specifies the width of the component |
API reference
Setting up AI chat
To configure your embedded AI chat in your Metabase:
- Click the grid icon in the upper right.
- Select Admin.
- Click the AI tab.
- In the left sidebar, click Embedded Metabot.
When embedding the Metabot component in your app, you should specify a collection that embedded Metabot is allowed to use for creating queries. Embedded Metabot will only have access to that collection.
For tips and more, see Metabot settings.
Layout
Use the layout prop to specify which layout to use for the Metabot component:
auto(default): Metabot uses thestackedlayout on mobile screens, and asidebarlayout on larger screens.stacked: the question visualization stacks on top of the chat interface.sidebar: the question visualization appears to the left of the chat interface, which is on a sidebar on the right.
Building custom AI chat UIs with useMetabot
If MetabotQuestion’s built-in layouts don’t fit your app, use the useMetabot hook to read Metabot’s conversation state directly and render your own UI. The hook gives you the messages, the chart the agent most recently produced, processing and error state, and actions to submit, cancel, retry, or reset the conversation.
AI chat with inline charts

When an agent responds, the message can contain a Chart component. You can walk the agent’s messages and render charts inline alongside the chat transcript:
import React, { useState } from "react";
import {
MetabaseProvider,
defineMetabaseAuthConfig,
useMetabot,
} from "@metabase/embedding-sdk-react";
const authConfig = defineMetabaseAuthConfig({
metabaseInstanceUrl: "https://your-metabase.example.com",
});
function MetabotChat() {
const metabot = useMetabot();
const [prompt, setPrompt] = useState("");
// useMetabot returns null until the SDK bundle has loaded
// and <MetabaseProvider> has mounted. Always guard before use.
if (!metabot) {
return <div>Loading…</div>;
}
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
if (!prompt.trim()) {
return;
}
metabot.submitMessage(prompt);
setPrompt("");
};
return (
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{metabot.messages.map((message) => {
if (message.role === "user") {
return (
<div key={message.id} style={{ alignSelf: "flex-end" }}>
{message.message}
</div>
);
}
if (message.type === "text") {
// message.message is markdown: links, bold, lists, code.
// Wrap in a markdown renderer (react-markdown, markdown-to-jsx,
// etc.) for production use; rendered as plain text here for brevity.
return <div key={message.id}>{message.message}</div>;
}
// Agent chart message — render its bound Chart inline.
const { Chart } = message;
return (
<div key={message.id} style={{ height: 400 }}>
<Chart drills height="100%" />
</div>
);
})}
</div>
<form onSubmit={handleSubmit} style={{ display: "flex", gap: 8 }}>
<input
value={prompt}
onChange={(event) => setPrompt(event.target.value)}
placeholder="Ask Metabot…"
disabled={metabot.isProcessing}
style={{ flex: 1 }}
/>
<button type="submit" disabled={metabot.isProcessing}>
Send
</button>
</form>
</div>
);
}
export default function App() {
return (
<MetabaseProvider authConfig={authConfig}>
<MetabotChat />
</MetabaseProvider>
);
}
AI chat with dedicated chart panel

The CurrentChart component is bound to the latest chart the agent produced. Render CurrentChart once, and it will swap in new charts as the agent creates them. You’ll want to filter chart messages out of the transcript so they don’t render twice:
import React, { useState } from "react";
import {
MetabaseProvider,
defineMetabaseAuthConfig,
useMetabot,
} from "@metabase/embedding-sdk-react";
const authConfig = defineMetabaseAuthConfig({
metabaseInstanceUrl: "https://your-metabase.example.com",
});
function MetabotChat() {
const metabot = useMetabot();
const [prompt, setPrompt] = useState("");
// useMetabot returns null until the SDK bundle has loaded
// and <MetabaseProvider> has mounted. Always guard before use.
if (!metabot) {
return <div>Loading…</div>;
}
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
if (!prompt.trim()) {
return;
}
metabot.submitMessage(prompt);
setPrompt("");
};
const { CurrentChart } = metabot;
return (
<div style={{ display: "flex", gap: 16, height: 600 }}>
<div style={{ flex: 1 }}>
{CurrentChart ? (
<CurrentChart drills height="100%" />
) : (
<div
style={{ display: "grid", placeItems: "center", height: "100%" }}
>
Ask Metabot to generate a chart
</div>
)}
</div>
<div
style={{
width: 380,
display: "flex",
flexDirection: "column",
gap: 12,
}}
>
<div
style={{
flex: 1,
overflowY: "auto",
display: "flex",
flexDirection: "column",
gap: 4,
}}
>
{metabot.messages.map((message) => {
// Chart messages render in the dedicated panel on the left,
// so you should filter them out in the chat transcript
// so they don't render twice.
if (message.role === "agent" && message.type === "chart") {
return null;
}
// Agent text (message.role === "agent") is markdown: links,
// bold, lists, code, etc. Wrap in a markdown renderer (react-markdown,
// markdown-to-jsx, etc.). Rendered as plain
// text here for brevity. User text (message.role === "user") is
// raw, so no markdown rendering needed.
return (
<div
key={message.id}
style={{
alignSelf:
message.role === "user" ? "flex-end" : "flex-start",
}}
>
{message.message}
</div>
);
})}
</div>
<form onSubmit={handleSubmit} style={{ display: "flex", gap: 8 }}>
<input
value={prompt}
onChange={(event) => setPrompt(event.target.value)}
placeholder="Ask Metabot…"
disabled={metabot.isProcessing}
style={{ flex: 1 }}
/>
<button type="submit" disabled={metabot.isProcessing}>
Send
</button>
</form>
</div>
</div>
);
}
export default function App() {
return (
<MetabaseProvider authConfig={authConfig}>
<MetabotChat />
</MetabaseProvider>
);
}
Notes on useMetabot
- Guard against null while waiting for the SDK bundle:
useMetabotreturnsnulluntil the SDK bundle has loaded and<MetabaseProvider>has mounted. Always guard before use. If you don’t guard it, the first render will throwCannot read properties of nullwhen you reach formetabot.messages,metabot.submitMessage, etc., because the SDK ships its Metabot internals via a code-split chunk that isn’t available synchronously. - Bring your own Markdown renderer:
MetabotQuestionrenders agent text messages internally, including markdown formatting, transcript scrolling, and input styling. TheuseMetabothook hands you the raw conversation state, which means you own the rendering. In particular, agent text messages (message.type === 'text') contain markdown: links, bold, lists, inline code. The snippets above rendermessage.messageas plain text for brevity, but production usage should pass the text through a markdown renderer (react-markdown,markdown-to-jsx, or your own) so links and formatting display correctly. - Strip links returned by the agent: the agent text may include links pointing back to the host Metabase (like a link to a chart it created). Those links require an authenticated Metabase session, so people won’t be able to view the links.
Read docs for other versions of Metabase.