Modular embedding SDK - AI chat

Embedded 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:

  1. Click the grid icon in the upper right.
  2. Select Admin.
  3. Click the AI tab.
  4. 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 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.

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

AI chat 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

AI chat dedicated chart

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: useMetabot returns null until the SDK bundle has loaded and <MetabaseProvider> has mounted. Always guard before use. If you don’t guard it, the first render will throw Cannot read properties of null when you reach for metabot.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: MetabotQuestion renders agent text messages internally, including markdown formatting, transcript scrolling, and input styling. The useMetabot hook 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 render message.message as 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.

Was this helpful?

Thanks for your feedback!
Want to improve these docs? Propose a change.