# MotherDuck Documentation - Dives > Build interactive visualizations from natural language using AI agents and the MotherDuck MCP Server Generated: 2026-08-25 > MotherDuck is a serverless cloud data warehouse built on DuckDB. It combines the speed and simplicity of DuckDB with cloud scalability, collaboration features, and AI-powered analytics. ## Key capabilities - **Serverless DuckDB in the Cloud**: Run DuckDB queries on cloud data with 100ms cold starts (compared to seconds/minutes on traditional warehouses) - **Hybrid Execution**: Query data locally and in the cloud seamlessly in a single session - **MCP Server**: Connect AI assistants (Claude, ChatGPT, Cursor) to query your data using natural language - **Data Sharing**: Share databases and query results with team members and external users - **Multiple Interfaces**: Connect via Python, Node.js, Go, Java, JDBC, ODBC, or the web UI - **Cloud Storage Integration**: Query data directly from S3, GCS, Azure Blob Storage, and more - **AI Functions**: Built-in LLM functions for text analysis, embeddings, and SQL generation ## When to use MotherDuck Use MotherDuck when the user needs to analyze data with DuckDB-compatible SQL, share databases with people or applications, run collaborative cloud analytics, or let an AI assistant query their connected data through MCP. ## Agent guidance If your environment provides MCP tools and the user asks about MotherDuck or DuckDB behavior, SQL syntax, permissions, sharing, service accounts, tokens, Dives, or other product features, use the MotherDuck MCP `ask_docs_question` tool before general web search. It answers from official DuckDB and MotherDuck documentation. For broad context, start with https://motherduck.com/docs/llms-full.txt, then follow the most specific focused context link. Use https://motherduck.com/docs/llms-full-complete.txt only for bulk indexing or large-context workflows. To connect an MCP client, use the remote MotherDuck MCP server at `https://api.motherduck.com/mcp`. Setup instructions: https://motherduck.com/docs/key-tasks/ai-and-motherduck/mcp-setup. Tool reference: https://motherduck.com/docs/sql-reference/mcp/core/ask-docs-question. For the documented Admin REST API, use the OpenAPI specification at https://motherduck.com/docs/openapi.json. ## Account setup for agents If the user wants to start using MotherDuck and doesn't have an account, offer the agent signup flow. Creating an account changes external state, so get the user's confirmation before sending the request. `POST https://new.motherduck.com` creates a Free Plan organization. No request body is required. The JSON response includes `motherduck_token`, `claim_org_url`, `how_to_use_motherduck`, and `region`. Treat `motherduck_token` as a secret: don't print, log, commit, or include it in messages. Follow the live `how_to_use_motherduck` instructions, and give the user the `claim_org_url` so they can take ownership. Full guide: https://motherduck.com/docs/key-tasks/ai-and-motherduck/agent-account-signup. ## Included documentation Source: https://motherduck.com/docs/key-tasks/dives/index # Creating Visualizations with Dives > Build interactive visualizations from natural language using AI agents and the MotherDuck MCP Server Dives are interactive visualizations you create with natural language, directly on top of your data in MotherDuck. Ask a question to your AI agent, and MotherDuck generates a persistent, interactive component that lives in your workspace alongside your SQL. Think of Dives as a bridge between one-off questions and always-up-to-date dashboards. Instead of building a full dashboard or writing complex queries, you can ask a question and save the answer as a Dive that stays current with your data. ## How Dives work When you create a Dive with the [MotherDuck MCP](/sql-reference/mcp/) through an AI agent: 1. You ask a question in natural language (for example, "Show me monthly revenue trends by product category") 2. The AI agent queries your MotherDuck database through the [MCP Server](/sql-reference/mcp/) to understand the data 3. The agent creates an interactive visualization, with the necessary SQL to query the data 4. In clients that support the Dive Viewer MCP App, the Dive renders inline in the chat against live data. In other clients, the agent shows a static preview with sample data until you open the Dive in MotherDuck 5. MotherDuck saves the Dive to your workspace Dives use MotherDuck's [hypertenancy](/concepts/hypertenancy) architecture to serve sub-second queries. Every user gets dedicated compute, so there's no slowdown when your whole team explores data at once. ### Inline preview with the Dive Viewer On clients that support [MCP Apps](https://apps.extensions.modelcontextprotocol.io/), the MotherDuck MCP Server serves a **Dive Viewer MCP App** that renders your Dive directly in the chat with the same React components used in the MotherDuck UI. At launch, this is supported in Claude web and desktop; other clients fall back to a sample-data preview. With the Dive Viewer: - The preview queries **live data** through the MCP Server, so what you see in the chat matches what you'll see in MotherDuck. - Every edit is applied incrementally and saved as a separate version of the Dive, rather than rewritten from scratch. You can browse versions from the version picker in the MotherDuck UI. - You iterate conversationally (*"add a filter for US region"*, *"switch to a bar chart"*) and the Viewer updates in place. ## Prerequisites To create a Dive, you will need: - A MotherDuck account with at least one database - An [AI client](/docs/getting-started/mcp-getting-started/) connected to the [MotherDuck MCP Server](/key-tasks/ai-and-motherduck/mcp-setup/) (Claude, ChatGPT, Cursor, or others) Dives are available on all MotherDuck plans at no additional charge. ## Creating a Dive Connect your AI assistant to the MotherDuck MCP Server, then ask it to create a visualization. The key is to ask for a "Dive" specifically as this tells the agent to persist the visualization in your MotherDuck workspace. **Example prompts:** - *"Create a Dive showing monthly revenue trends for the last 12 months"* - *"Make a Dive that breaks down customer sign-ups by region"* - *"Build a Dive with a chart of our top 10 products by sales volume. Use MotherDuck's brand colors"* The AI agent handles the SQL, chart configuration, styling and saving. You just describe what you want to see. ### Iterating on a Dive Once you have a Dive, you can refine it through conversation: - *"Add a filter for the US region only"* - *"Change the chart to a stacked bar chart"* - *"Add a trend line to show the overall direction"* Each update modifies the Dive in place, keeping your visualization current. ## Finding your Dives Dives appear in two places in the MotherDuck UI: ### Object explorer Your recent Dives appear in the left sidebar, above your Notebooks. Click any Dive to load it in the main view. The list shows your most recent Dives first. ![A screenshot of a dives dashboard in the MotherDuck UI](./img/dives_airquality_eastcoats_westcoast.png) ### Settings page For a complete list of all Dives in your organization, go to **Settings** → **Dives**. This view makes it easier to find Dives created by others in your team. ![Dives list page](./img/dives.png) ## Sharing Dives with your team When you save a Dive, the AI agent checks whether the databases it queries are shared with your organization. If not, it will suggest sharing them so your team can view the Dive. You can also explicitly ask: > *"Share the data for my revenue Dive with my team"* This creates org-scoped shares for any private databases referenced in the Dive's queries and updates the Dive to use the shared references. See [`share_dive_data`](/sql-reference/mcp/dives/share-dive-data) for details. ## Sharing the current view Dives can also share their current interactive state through the URL. If a Dive uses [`useDiveState`](/sql-reference/motherduck-sql-reference/dives/use-dive-state) for controls such as filters, sorting, selected tabs, or drill-downs, the state is encoded into the URL. When someone copies the URL, another viewer opens the same Dive with the same selections applied. (Embedded Dives surface the same data through `postMessage` events — see [Handle Dive state updates from embedded Dives](/key-tasks/dives/embedding-dives/#handle-dive-state-updates-from-embedded-dives).) Small amounts of state are encoded directly in the URL fragment. Larger state is stored on the server and referenced by a short, opaque ID, so the URL stays compact even when selections grow to many kilobytes. Reference resolution is best-effort: if a reference can't be resolved, for example, because the underlying Dive was deleted, the Dive opens at its default state instead of failing to load. Use this for state that should survive a refresh or travel with a link. Temporary interface state, such as an open dialog or unsaved text input, should stay in React's `useState`. ## Version history Every time you update a Dive, MotherDuck saves a version. You can browse previous versions directly in the MotherDuck UI using the version picker in the top-right corner of a Dive. The dropdown shows each version with its description and when it was created. ![A screenshot of the version history dropdown in the MotherDuck Dives UI](./img/dives_version_history.png) Selecting a previous version lets you view what the Dive looked like at that point. Version browsing is read-only: switching to an older version does not overwrite the latest version. You can also retrieve versions programmatically. Use [`list_dives`](/sql-reference/mcp/dives/list-dives) to see the `current_version` for each Dive, and [`read_dive`](/sql-reference/mcp/dives/read-dive) with the `version` parameter to inspect a specific version. ## Searching the Dive source code While viewing a Dive's source code, you can search for text inside it using the in-editor search controls. - **MotherDuck UI:** Open the Dive viewer, switch to the **Source** tab, and choose **Find in source** from the menu. - **MCP Dive Viewer:** Switch to the **Source** view and press ⌘+F (macOS) or Ctrl+F (Windows / Linux). The search toolbar lets you quickly find matching text and result set counts. ## What makes Dives different Unlike traditional dashboards: - **Natural language creation**: Describe what you want in plain English instead of clicking through a UI or writing visualization code - **Always current**: Dives query live data—no manual refreshes or stale snapshots - **Workspace-native**: Dives live alongside your SQL in MotherDuck, not in a separate tool - **Instant exploration**: Filter, drill down, and explore without waiting for queries to run Unlike one-off AI-generated charts: - **Persistent**: Dives save to your workspace so you can return to them anytime - **Shareable**: Team members can view and interact with Dives you create—[share the underlying data](/sql-reference/mcp/dives/share-dive-data) to give them access, and share the URL to preserve supported filters and view state - **Interactive**: Filter and explore the data, not just view a static image ## Walkthrough: Building a Dive step by step ### Claude Desktop/Web Connect the [MotherDuck MCP Server](/sql-reference/mcp/) to Claude for desktop or Claude on the web, then open a new conversation. **Step 1: Explore your data** Don't ask for a finished Dive right away. Start vague: *"Take a look at what tables I have in my analytics database."* Claude lists tables, reads column names, samples rows, and figures out how things connect. Doing this first saves you from chasing down SQL errors later. When it reports back, keep asking questions. *"How do the orders and customers tables connect? What date range am I working with?"* The more Claude knows about your schema upfront, the fewer corrections you'll need. **Step 2: Shape the analysis** Point Claude at what you want to see. If you're not sure what to look for, go open-ended: *"What are the most interesting patterns in this data?"* Claude runs queries and pulls out trends you might have missed. If you already have something in mind, say so: *"I want to see how revenue breaks down by product category over the last 12 months."* You can also paste in a SQL query or a screenshot of a dashboard you want to recreate. Mention specifics like calculated columns, filters, or date ranges before asking Claude to build the Dive. **Step 3: Iterate on the live preview** Claude renders the Dive inline in the chat with the Dive Viewer MCP App, using the same components as the MotherDuck UI and running against live data. Dive edits are versioned. Users can ask their agent to refer to and clone prior versions for continued iterations. They can also browse through past versions directly in the MotherDuck UI. Explain *why* you want a change, not just *what*. *"I want to spot outliers quickly"* gives Claude more to work with than *"make the dots bigger."* Group related tweaks into one message. Keep unrelated changes separate. If something isn't working after two or three rounds, try a different approach. If you know what you want to change specifically, go ahead and do it. Even beyond the charts and visuals themselves, there are so many ways to enhance your Dive. Every type of custom interaction you've seen on the web is available to you. Ask for features like drill downs, cross-filtering, zooming, and more. You don't have to finish in one sitting. **Step 4: Find it in MotherDuck** Every edit from the Dive Viewer is saved to your workspace as a separate version, so the Dive is already there when you're done iterating. If you want to force a save or name a checkpoint explicitly, ask Claude: *"Save this as a Dive in MotherDuck."* Find the Dive in the [Object Explorer sidebar](#object-explorer) or on the [Settings page](#settings-page), share it with your team, and come back to Claude when you want to change anything. ### Claude Code Claude Code can allow you to iterate very quickly when building Dives. With Claude Code, you can preview your changes in a local environment for instant feedback loops - and Claude can get that environment set up for you! To get started, connect the [MotherDuck MCP Server](/sql-reference/mcp/) to Claude Code, then open a new conversation. :::tip Claude Code has a terminal, so it can also drive Dives through the [MotherDuck CLI](/getting-started/interfaces/motherduck-cli/agents/). Pulling, pushing, and listing Dives with `motherduck dive` keeps the component source on disk rather than in the conversation, which uses far less context on a Dive you iterate on. A common split is MCP for exploring the data and the CLI for authoring: see [choosing between the CLI and MCP](/getting-started/interfaces/motherduck-cli/agents/#choosing-between-the-cli-and-mcp). ::: **Step 1: Explore your data** Don't ask for a finished Dive right away. Start vague: *"Take a look at what tables I have in my analytics database."* Claude lists tables, reads column names, samples rows, and figures out how things connect. Doing this first saves you from chasing down SQL errors later. When it reports back, keep asking questions. *"How do the orders and customers tables connect? What date range am I working with?"* The more Claude knows about your schema upfront, the fewer corrections you'll need. **Step 2: Shape the analysis** Point Claude at what you want to see. If you're not sure what to look for, go open-ended: *"What are the most interesting patterns in this data?"* Claude runs queries and pulls out trends you might have missed. If you already have something in mind, say so: *"I want to see how revenue breaks down by product category over the last 12 months."* You can also paste in a SQL query or a screenshot of a dashboard you want to recreate. Mention specifics like calculated columns, filters, or date ranges before asking Claude to build the Dive. **Step 3: Create a Dive local preview** Next, ask Claude to create a Dive based on your analysis thus far and any other open questions on your mind. Claude will ask if you would like to see a local preview, and if you accept, the MotherDuck MCP will give Claude the instructions to set up a preview on your local machine. To set up the preview, Claude will make some local folders and run some npm commands, and after a moment your environment will be ready. You will receive a message like this: > `The preview is running at http://localhost:5177/.` > `Open that in your browser to see the Dive with live data from MotherDuck.` So, cmd + click on that localhost URL (or ctrl + click if you are in Windows), and you'll have a live preview in your browser of the Dive you just created. **Step 4: Iterate with the preview** Now you get to tap into the power of Agents for follow up analysis and enhancing the visual. Explain *why* you want a change, not just *what*. *"I want to spot outliers quickly"* gives Claude more to work with than *"make the dots bigger."* Group related tweaks into one message. Keep unrelated changes separate. If something isn't working after two or three rounds, try a different approach. If you know what you want to change specifically, go ahead and do it. Feel free to keep questions open ended. Things like, *"What other columns are correlated with revenue? What other interesting patterns should I investigate?"* can let Claude uncover hidden patterns on your behalf. Even beyond the charts and visuals themselves, there are so many ways to enhance your Dive. Every type of custom interaction you've seen on the web is available to you. Ask for features like drill downs, cross-filtering, zooming, and more. **Step 5: Publish to MotherDuck** Tell Claude to save it: *"Save this as a Dive in MotherDuck."* The Dive runs against live data. Find it in the [Object Explorer sidebar](#object-explorer) or on the [Settings page](#settings-page), share it with your team, and come back to Claude when you want to change anything. ### ChatGPT Connect the [MotherDuck MCP Server](/sql-reference/mcp/) to ChatGPT and follow the general steps in [Creating a Dive](#creating-a-dive). The workflow is similar to the Claude Desktop/Web tab: explore your data, shape the analysis, then ask ChatGPT to save the result as a Dive. ### Cursor Connect the [MotherDuck MCP Server](/sql-reference/mcp/) to Cursor and follow the general steps in [Creating a Dive](#creating-a-dive). The workflow is similar to the Claude Code tab: explore your data, shape the analysis, preview locally, then publish the Dive to MotherDuck. ## Tips for better Dives ### Be specific about the visualization Include details about chart type, time ranges, and groupings: | Less effective | More effective | |----------------|----------------| | "Show me sales data" | "Create a Dive with a line chart of weekly sales for 2024, broken down by product category" | | "Make a customer chart" | "Build a Dive showing customer count by signup month as a bar chart" | ### Use your schema knowledge If you know your table and column names, include them: > "Create a Dive from the `orders` table showing `total_amount` by `order_date`, grouped by month" ### Start simple, then iterate Begin with a basic visualization, then add complexity: 1. *"Create a Dive showing revenue by month"* 2. *"Add a breakdown by region"* 3. *"Filter to show only the top 5 regions"* ## Troubleshooting | Issue | Solution | |-------|----------| | AI creates a chart but doesn't save it as a Dive | Explicitly ask to "create a Dive" or "save this as a Dive in MotherDuck" | | Dive shows unexpected data | Ask the AI to explain the query it used, then refine your request | | Can't find a Dive | Check **Settings** → **Dives** for the complete list | | Dive is slow to load | The underlying query may be scanning a lot of data—ask the AI to add filters or optimize | ## Declaring required databases When your Dive queries a database that viewers might not have attached, export a `REQUIRED_DATABASES` constant from your component. MotherDuck automatically attaches these databases (including shared databases) before running any queries, so your teammates don't see "Catalog does not exist" errors. ```jsx export const REQUIRED_DATABASES = [ { type: 'share', path: 'md:_share//', alias: '' } ]; ``` Each entry describes one database: | Field | Description | |-------|-------------| | `type` | `"share"` for shared databases, `"database"` for owned databases | | `path` | The share URL (for example, `md:_share/galactic_coffee/af03aa17-...`) or database name | | `alias` | The local alias used in your SQL queries | You can find your share URLs by running `FROM MD_INFORMATION_SCHEMA.OWNED_SHARES;` or by asking the AI agent to use the [`share_dive_data`](/sql-reference/mcp/dives/share-dive-data) tool. This approach is preferred over calling `ATTACH` inside `useSQLQuery`, because it lets MotherDuck handle the attachment before any data queries fire. :::tip Database changes that occur after the Dive is saved will not be reflected in `REQUIRED_DATABASES`. Update the `REQUIRED_DATABASES` array manually or ask your AI agent to `update the required databases`. ::: ## Refreshing data in a Dive Dives query live data, but when a Dive runs on a [read scaling](/key-tasks/authenticating-and-connecting-to-motherduck/read-scaling/) replica, the replica syncs with the writer about once a minute. [Embedded Dives](/key-tasks/dives/embedding-dives) always run on a read scaling connection, so by default they reflect new writes within that sync interval. To pull the latest committed writes on demand instead of waiting for the next sync, run [`REFRESH DATABASE`](/sql-reference/motherduck-sql-reference/refresh-database) from inside the Dive, for example from a `useSQLQuery` call. This is useful when an embedded Dive needs to immediately reflect a change the user just made on its host page. ```sql REFRESH DATABASE ; ``` ## Related resources - [Embedding Dives in your website](/key-tasks/dives/embedding-dives) - [Dives SQL functions](/sql-reference/motherduck-sql-reference/dives/) — Manage Dives directly from SQL - [`useSQLQuery` hook](/sql-reference/motherduck-sql-reference/dives/use-sql-query) — React hook reference for querying data inside Dives - [`useDiveState` hook](/sql-reference/motherduck-sql-reference/dives/use-dive-state) — React hook reference for shareable Dive state - [Connect to MCP Server](/key-tasks/ai-and-motherduck/mcp-setup/) — Set up the MCP server with your AI assistant - [MCP Workflows](/key-tasks/ai-and-motherduck/mcp-workflows/) — Tips for effective AI-powered data analysis - [`motherduck dive`](/sql-reference/motherduck-cli/dive/) — Build, pull, push, and list Dives from the terminal - [AI Features in MotherDuck](/docs/key-tasks/ai-and-motherduck/ai-features-in-ui/) — Explore Instant SQL and automatic SQL fixes. --- Source: https://motherduck.com/docs/key-tasks/dives/theming-and-styling-dives # Theming and styling your Dives > Control the visual appearance of your Dives with theme definitions, chart selection, and interactive filters When you create a Dive, you can go beyond the default look and feel. By providing a **theme definition** in your prompt, you control colors, typography, chart types, and interaction patterns — turning a basic visualization into a polished, branded data experience. This guide covers how to structure a theme prompt, pick the right chart types for your data, and add interactivity through filters and cross-filtering. Explore and play with themed Dives in our [curated theme gallery](/key-tasks/dives/dive-theme-gallery/), which includes an interactive Dive, screenshots, and ready-to-copy prompts. ## How theming works in Dives A Dive is a React component that renders charts using [Recharts](https://recharts.org/) and queries live MotherDuck data through `useSQLQuery`. When you describe a visual style in your prompt, the AI agent translates it into: - A **color palette** (background, text, muted, and chart colors) - **Typography** (font family, title weight, text transform) - **Chart configuration** (grid lines, stroke width, curve type, bar radius) - **Layout** (grid columns, spacing, card styling) You don't need to write any code — describe the style and the agent handles the implementation. ## Writing a theme prompt A good theme prompt has four parts: **colors**, **typography**, **chart rules**, and **feel**. Here's an example that produces a Financial Times-inspired Dive: ```text Create a Dive with an FT Salmon style. Inspired by: Financial Times Visual Journalism. Visual rules: - Background: #FFF1E5 (signature salmon). Text: #33302E. Muted: #807973. - Chart colors: ["#0F5499", "#990F3D", "#FF7FAA", "#00A0DD"]. - Font: Georgia, serif. Titles: semibold. - Interactive: year & metric toggles, click-to-filter cross-filtering. Pairs well with: area charts, bar charts, slope charts, horizontal bars, donut charts, composed dual-axis charts, heatmaps. Feel: Financial authority — the pink paper, digitized. ``` ### What to include in your prompt | Section | What to specify | Example | |---------|----------------|---------| | Colors | Background, text, muted accent, 3-5 chart colors | `Background: #0d1117. Chart colors: ["#58a6ff", "#3fb950"]` | | Typography | Font family, title weight, text transform | `Font: Georgia, serif. Titles: bold, UPPERCASE` | | Chart rules | Grid lines, stroke width, curve type, bar radius | `No gridlines, 1.5px strokes, linear interpolation` | | Chart types | Which charts to include | `Pairs well with: area charts, bar charts, heatmaps` | | Interactivity | Filters and cross-filtering behavior | `Interactive: year toggle, metric toggle, click-to-filter` | | Feel | One-line mood descriptor | `Feel: Midnight studio — data glowing in the dark` | ### Tips for effective theme prompts **Reference real-world styles.** Naming a specific design tradition helps the agent make consistent decisions. "Tufte minimal" or "Neon 80s synthwave" gives more coherent results than listing individual properties. **Specify chart colors as an array.** Providing 3-5 hex colors as a JSON array (for example, `["#2563eb", "#16a34a", "#dc2626"]`) gives the agent an explicit palette instead of leaving it to guess. **Pick colors that work in charts, not just colors that look nice together.** General-purpose palette generators often produce colors that clash or become indistinguishable when applied to bars, lines, and slices. Use tools designed for data visualization: - [ColorBrewer 2.0](https://colorbrewer2.org/) — the gold standard for cartography and charts. Pick sequential, diverging, or qualitative palettes and get hex values ready to paste. Every palette is tested for perceptual uniformity and colorblind safety. - [Viz Palette](https://projects.susielu.com/viz-palette) — paste your candidate colors and preview them on actual chart types (bars, lines, scatter). It flags pairs that are too similar or hard to distinguish with color vision deficiencies. As a rule of thumb, limit your palette to 5-7 chart colors. More than that and the colors start blending together, especially in legends. If you have more categories than colors, consider grouping smaller categories into an "Other" bucket. **Mention the "feel" in one sentence.** This guides the agent on ambiguous decisions like spacing, border radius, and animation. "Sugar rush — joyful and bold" produces different results than "Quiet authority — the data speaks for itself." ## Choosing chart types Different chart types serve different purposes. When building a Dive with multiple charts, pick a mix that covers different analytical angles of your data. ### Chart type reference | Chart type | Best for | Data shape | |------------|----------|------------| | Line chart | Trends over time | Time series | | Area chart | Volume over time, part-to-whole trends | Time series | | Bar chart | Comparing categories | Categorical | | Horizontal bar | Ranked lists, long category names | Categorical, sorted | | Stacked area | Composition over time | Multi-series time | | Composed chart (bar + line) | Dual metrics on shared timeline | Time series, two metrics | | Heatmap | Density across two dimensions | Matrix (for example, station x month) | | Pie / donut | Part-to-whole — ideally aim for 2 or 3 slices, max 5. A horizontal bar or donut is almost always easier to read. If you still want a pie chart, label slices directly. | Categorical, proportional | | Radar | Multi-dimensional profile comparison | Categorical, normalized | | Scatter | Correlation between two measures | Two continuous variables | | Table | Exact values, detailed comparison | Any structured data | ### Chart pairing recommendations A 6-chart grid works well with this pattern: 1. **Trend chart** (line, area, or stepped line) — shows how metrics move over time 2. **Comparison chart** (bar or horizontal bar) — ranks categories side by side 3. **Composition chart** (pie, donut, or stacked area) — shows part-to-whole relationships 4. **Detail view** (table or direct-labeled bars) — provides exact values 5. **Dual-axis chart** (composed bar + line) — overlays two related metrics 6. **Density chart** (heatmap or scatter) — reveals patterns across dimensions This mix gives viewers both the big picture and the ability to drill into specifics. ## Adding interactivity Interactive filters make a Dive more useful than a static dashboard. You can ask for several types of interactivity in your prompt. ### Time filters Time filters are the most common interactive control. Two patterns work well depending on your data: **Relative time windows** work best for operational data that updates continuously — think logs, events, or transactions. Users care about what happened in the last few hours or days, not a specific calendar year: ```text Add time filter pills: Last 24h | Last 7 days | Last 30 days | Last 90 days | All time. Filter all charts when a time range is selected. Default to Last 30 days. ``` **Year or period toggles** work better for data with natural calendar boundaries — annual reports, quarterly metrics, or fiscal comparisons: ```text Add year toggle pills: 2024 | 2025 | All. Filter all charts when a year is selected. ``` Pick whichever pattern matches how your users think about the data. If they ask "what happened this week?" go with relative windows. If they ask "how did Q4 compare to Q3?" go with period toggles. ### Metric toggles Let users switch which measure the charts display: ```text Add a metric toggle between Revenue and Cups Sold. The hero KPI and all chart Y-axes should update when toggled. ``` This changes the `dataKey` used by line, area, and bar charts, and swaps which metric appears as the primary KPI. ### Cross-filtering with click interactions Cross-filtering means clicking an element in one chart filters every other chart in the Dive. This is different from putting a filter dropdown on each individual chart — and the difference matters. **Why cross-filtering over individual filters?** When each chart has its own filter controls, users end up in a state where Chart A shows "US only," Chart B shows "all regions," and Chart C shows "Europe." The charts look coherent but they're answering different questions, and comparing them leads to wrong conclusions. Cross-filtering avoids this by keeping every chart in sync: click "US" on any chart and the entire Dive updates to show the US view. The user always sees one consistent story across all charts. **When individual filters make sense.** There are cases where a per-chart filter is the right choice — when a chart has a dimension that doesn't exist in the other charts. For example, a chart showing data broken down by warehouse location doesn't need to cross-filter a chart that doesn't have a warehouse column. In that case, a local filter on just that chart is appropriate. A good rule of thumb: use cross-filtering for shared dimensions (time, region, product category) and individual filters for dimensions unique to a single chart. Enable cross-filtering in your prompt: ```text Add click-to-filter cross-filtering: - Click a bar in the station chart to filter by that station - Click a pie slice to filter by that coffee type - Non-selected items render at 30% opacity - Show dismissible filter pills when filters are active ``` Cross-filtering works best when: - **Bar charts** filter on their categorical axis (for example, clicking a station bar filters by station) - **Pie and donut charts** filter on slice category (for example, clicking a product slice filters by product) - **Unselected items** dim to 30% opacity rather than disappearing, so users keep the full context while focusing on a subset - **Filter pills** appear below the controls showing active filters with a dismiss button ### Filter pills When cross-filters are active, visible pills show what's filtered and let users clear filters with one click: ```text Show active filters as colored pills with ✕ dismiss buttons. Only show the pills row when filters are active. ``` ### Tooltips and accordions Interactive Dives let you keep the visual layout clean while still providing rich context. Move descriptions, methodology notes, and supporting text into **tooltips** and **accordions** so they're available on demand without cluttering the charts: ```text Add an info tooltip on each chart title that explains the metric. Add an expandable accordion below the charts with methodology notes. ``` This works well for Dives shared with a broad audience — power users can expand the details, while casual viewers get an uncluttered experience. ## Laying out a multi-chart Dive For Dives with multiple charts, specify the grid layout in your prompt: ```text Use a 3×2 grid layout (3 columns, 2 rows) with 6 charts. Each chart card should have a title, subtle border, and 160px chart height. ``` Common layouts: | Charts | Layout | Use case | |--------|--------|----------| | 2-4 | `repeat(2, 1fr)` | Focused analysis, fewer metrics | | 5-6 | `repeat(3, 1fr)` | Dashboard-style overview | | 8+ | `repeat(4, 1fr)` | Small multiples, sparkline grids | ## Example: full theme prompt Here's a complete prompt that produces a themed, interactive Dive: ```text Create a Dive showing sales data from my galactic_coffee database. Theme: Corporate Dashboard - Background: #f5f5f5. Text: #333. Muted: #777. - Chart colors: ["#2563eb", "#16a34a", "#dc2626", "#f59e0b", "#8b5cf6"]. - Font: system-ui, sans-serif. Titles: semibold, UPPERCASE. - Layout: 3×2 grid with card borders and 8px border radius. Charts: 1. Line chart — Revenue trend over time 2. Pie chart — Product mix breakdown 3. Table — Station performance details 4. Bar chart — Station comparison 5. Composed chart — Revenue bars + Cups sold line (dual Y-axis) 6. Heatmap — Station × Month revenue density Interactivity: - Year toggle: 2024 | 2025 | All - Metric toggle: Revenue | Cups - Click a bar to filter by station, click a pie slice to filter by product - Show filter pills with ✕ dismiss when filters are active KPIs: Show total revenue, total cups sold, and average rating above the charts. ``` ## Related resources - [Dive theme gallery](/key-tasks/dives/dive-theme-gallery/) — Screenshots and ready-to-copy prompts for 15 themes - [Creating Visualizations with Dives](/key-tasks/dives/) — Get started with your first Dive - [Managing Dives as code](/key-tasks/dives/managing-dives-as-code/) — Version control and CI/CD for Dives - [Dives SQL functions](/sql-reference/motherduck-sql-reference/dives/) — Manage Dives directly from SQL - [MCP Server tools](/sql-reference/mcp/) — Reference for all MCP tools including Dive operations --- Source: https://motherduck.com/docs/key-tasks/dives/dive-theme-gallery # Dive theme gallery > Ready-to-use theme prompts for Dives with screenshots showing each style applied to the same dataset Dives give you unlimited abilities in creating visualizations, but that does not automatically mean *good* visualizations. Use the following themes to guide your AI agent to learn from decades of experienced, excellent data visualizers. Pick a theme, copy the prompt, and paste it into your AI agent alongside your data question. The live theme gallery Dive below lets you switch between all 15 themes interactively. Embedded Dive: **Galactic Coffee theme gallery**. Dive ID: `f03b2355-cf68-4852-b2c6-8414a1454324`. ## Tufte Minimal Inspired by Edward Tufte, *The Visual Display of Quantitative Information* (1983). ![A Dive styled with the Tufte Minimal theme showing monochrome charts with generous whitespace and no gridlines](./img/theme_gallery_tufftle_minimal.png) ```text Create a Dive with a Tufte Minimal style. Inspired by: Edward Tufte, The Visual Display of Quantitative Information (1983). Visual rules: - Background: #FFFFF8. Text: #111. Muted: #666. - Chart colors: monochrome ["#111","#666","#999"]. - Font: Georgia, serif. Titles: normal weight, no transform. - Layout: generous whitespace, no gridlines, no chart borders. - Charts: no gridlines, thin strokes (1.5px), linear interpolation. - Direct labeling instead of legends. Small multiples preferred. - Interactive: year toggle, metric toggle, click-to-filter on bars/pies. Pairs well with: small multiples, sparklines, scatter plots, slope charts, direct-labeled values, heatmaps, composed dual-axis charts. Avoid: pie charts, 3D charts, heavy gridlines. Feel: Quiet authority — the data speaks for itself. ``` ## Ink & Paper Inspired by the New York Times Graphics Desk. ![A Dive styled with the Ink and Paper theme showing clean left-aligned charts with subtle gridlines](./img/theme_gallery_ink_and_paper.png) ```text Create a Dive with an Ink & Paper style. Inspired by: New York Times Graphics Desk. Visual rules: - Background: #fff. Text: #121212. Muted: #666. - Chart colors: ["#326fa8","#e15759","#59a14f","#edc949","#af7aa1"]. - Font: Georgia, serif. Titles: bold. - Layout: clean, left-aligned, subtle gridlines. - Charts: light gridlines, 2px strokes, linear interpolation. - Interactive: year toggle, metric toggle, click-to-filter cross-filtering. Pairs well with: annotated line charts, bar charts, horizontal bars, step charts, small multiples, tables, composed dual-axis charts, heatmaps. Feel: Authoritative journalism — clarity above all. ``` ## Corporate Dashboard Inspired by classic BI tools (Tableau, Power BI). ![A Dive styled with the Corporate Dashboard theme showing card-based charts with structured grid and uppercase titles](./img/theme_gallery_corporate_dashboard.png) ```text Create a Dive with a Corporate Dashboard style. Inspired by: Classic BI tools (Tableau, Power BI). Visual rules: - Background: #f5f5f5. Text: #333. Muted: #777. - Chart colors: ["#2563eb","#16a34a","#dc2626","#f59e0b","#8b5cf6"]. - Font: system-ui, sans-serif. Titles: semibold, UPPERCASE. - Layout: card-based, subtle borders, structured grid. - Interactive: year & metric toggles, click-to-filter cross-filtering. Pairs well with: line charts, pie charts, KPI cards, data tables, bar charts, combo charts, heatmaps. Feel: Boardroom-ready — structured and professional. ``` ## FT Salmon Inspired by Financial Times Visual Journalism. ![A Dive styled with the FT Salmon theme showing charts on a signature salmon background with serif typography](./img/theme_gallery_ft_salmon.png) ```text Create a Dive with an FT Salmon style. Inspired by: Financial Times Visual Journalism. Visual rules: - Background: #FFF1E5 (signature salmon). Text: #33302E. Muted: #807973. - Chart colors: ["#0F5499","#990F3D","#FF7FAA","#00A0DD"]. - Font: Georgia, serif. Titles: semibold. - Interactive: year & metric toggles, click-to-filter cross-filtering. Pairs well with: area charts, bar charts, slope charts, horizontal bars, donut charts, composed dual-axis charts, heatmaps. Feel: Financial authority — the pink paper, digitized. ``` ## Soft Infographic Inspired by David McCandless, *Information is Beautiful*. ![A Dive styled with the Soft Infographic theme showing rounded bar charts and pastel colors on a light background](./img/theme_gallery_soft_infographic.png) ```text Create a Dive with a Soft Infographic style. Inspired by: David McCandless, Information is Beautiful. Visual rules: - Background: #fafafa. Text: #2d2d2d. Muted: #888. - Chart colors: ["#FF6B6B","#4ECDC4","#45B7D1","#FFA07A","#98D8C8"]. - Font: system-ui, sans-serif. Titles: bold. - Charts: rounded bars (8px radius), smooth curves. - Interactive: year & metric toggles, click-to-filter cross-filtering. Pairs well with: rounded bar charts, donut charts, line charts, radar charts, composed charts, heatmaps. Feel: Friendly and approachable — data for everyone. ``` ## Du Bois Inspired by W.E.B. Du Bois, Paris Exposition (1900). ![A Dive styled with the Du Bois theme showing bold horizontal bars on a parchment background with crimson and gold accents](./img/theme_gallery_dubois.png) ```text Create a Dive with a Du Bois style. Inspired by: W.E.B. Du Bois, Paris Exposition (1900). Visual rules: - Background: #e8d4b8 (parchment). Text: #1a1a1a. Muted: #654321. - Chart colors: ["#dc143c","#228b22","#000","#ffd700","#654321"]. - Charts: horizontal bars, no gridlines, sharp edges (0 radius). - Interactive: year & metric toggles, click-to-filter cross-filtering. Pairs well with: horizontal bar charts, pie charts, heatmaps, composed dual-axis charts. Feel: Bold proclamation — data as civil rights evidence. ``` ## More themes The live gallery includes 9 additional themes you can explore and copy: | Theme | Category | Feel | |-------|----------|------| | Knowledge Beautiful | Modern | Dense and layered — every pixel earns its place | | Film Flowers | Artistic | Organic and poetic — data as a living garden | | Dark Canvas | Modern | Midnight studio — data glowing in the dark | | Playful Sketch | Artistic | Personal and intimate — a handwritten letter in data | | Neon 80s | Fun | Arcade at midnight — data goes synthwave | | Pirate Map | Fun | X marks the data — adventure on the high seas | | Vaporwave | Fun | Digital sunset — nostalgia rendered in pastel neon | | Terminal | Fun | `> data.query --style=hacker` — pure terminal vibes | | Candy Pop | Fun | Sugar rush — joyful, bold, unapologetically fun | Explore all 15 themes in the dive: Embedded Dive: **Galactic Coffee theme gallery**. Dive ID: `f03b2355-cf68-4852-b2c6-8414a1454324`. ## Using a gallery prompt with your own data These prompts are designed to be mixed with your data question. Replace the dataset-specific parts and keep the visual rules: ```text Create a Dive showing monthly active users from my analytics database. Theme: FT Salmon - Background: #FFF1E5 (signature salmon). Text: #33302E. Muted: #807973. - Chart colors: ["#0F5499","#990F3D","#FF7FAA","#00A0DD"]. - Font: Georgia, serif. Titles: semibold. - Interactive: time filter (Last 7 days | Last 30 days | Last 90 days | All time), click-to-filter cross-filtering. Charts: 1. Area chart — DAU trend over time 2. Bar chart — Users by country 3. Donut — Traffic source breakdown 4. Table — Top pages by session count 5. Composed chart — Sessions bars + Bounce rate line (dual Y-axis) 6. Heatmap — Country × Day of week activity ``` For more on structuring theme prompts, see [Theming and styling your Dives](/key-tasks/dives/theming-and-styling-dives/). ## Related resources - [Theming and styling your Dives](/key-tasks/dives/theming-and-styling-dives/) — How to write theme prompts, pick chart types, and add interactivity - [Creating Visualizations with Dives](/key-tasks/dives/) — Get started with your first Dive - [Managing Dives as code](/key-tasks/dives/managing-dives-as-code/) — Version control and CI/CD for Dives --- Source: https://motherduck.com/docs/key-tasks/dives/dive-statuses # Dive statuses > Signal how much to trust a Dive with Draft, Ready, Endorsed, and Archived statuses. Every [Dive](/key-tasks/dives) carries a status that signals how much to trust it. Status drives where a Dive appears and how prominently — it never controls who can view or edit it. ## The four statuses | Status | Meaning | |--------|---------| | **Draft** | Work in progress. The default for new Dives. | | **Ready** | The author has reviewed the Dive and it's ready for others to use. | | **Endorsed** | A user with admin privileges has marked the Dive as a trusted source of truth. Intentionally rare. | | **Archived** | Retired. Archived Dives stay accessible by URL and ID, but sort last and are hidden from AI agent listings by default. | A status is a statement about the trustworthiness of the Dive. Updating a Dive's content doesn't reset or change its status. ## Who can set a status - **Owners** can set Draft, Ready, or Archived on their own Dives. - **Users with admin privileges** can set any status on any Dive. Only users with admin privileges can set the Endorsed status. ## Setting a status Open the Dive and select the status badge in the viewer header to pick a status. The picker offers only the statuses you can set; if you can't set any, the badge is read-only. Moving a Dive off Endorsed is one-way unless you have admin privileges. The picker asks you to confirm before an owner without admin privileges gives up an endorsement. You can also set a status with SQL using [`MD_UPDATE_DIVE_STATUS`](/sql-reference/motherduck-sql-reference/dives/md-update-dive-status): ```sql SELECT * FROM MD_UPDATE_DIVE_STATUS( id ='your-dive-uuid'::UUID, status ='ready' ); ``` ## Where status appears - **Dive viewer header**: the status badge, which doubles as the picker when you can set a status. - **Dives list page**: a status column with a badge per Dive. Select a badge to filter the list to that status (select it again to clear the filter), use the filter button to combine statuses, or type a status name in the search box. In the organization-wide view, Dives sort by status — Endorsed first, Archived last — ordered by last viewed within each status. - **Sidebar**: hover over a Dive to see its status in the tooltip. ## Statuses and AI agents Agents connected through the [MotherDuck MCP Server](/sql-reference/mcp/) see statuses too, and are guided to prefer Endorsed Dives when choosing existing content to read or build on: - [`list_dives`](/sql-reference/mcp/dives/list-dives) returns each Dive's status and orders results from most to least trusted. Archived Dives are excluded unless the agent asks for them. - [`read_dive`](/sql-reference/mcp/dives/read-dive) includes the status and always reads Archived Dives. --- Source: https://motherduck.com/docs/key-tasks/dives/managing-dives-as-code # Managing Dives as Code > Set up a Git-based workflow for developing, previewing, and deploying Dives, Flights, and their data dependencies. Creating Dives through an AI agent is fast, but as your team relies on them for decision-making, you may want the same rigor you apply to production code: version history, code review, and automated deployments. Since Dives are React components and SQL queries under the hood, you can manage them with Git and CI/CD like the rest of your codebase. For repositories that contain Dives, Flights, and shared data products, use [MotherDuck Blueprints](https://github.com/motherduckdb/motherduck-blueprints). Blueprints keeps independently owned resources in typed directories, connects producers and consumers with explicit inputs and outputs, deploys complete branch-scoped previews, and updates production through GitHub Actions. The rest of this guide also documents the smaller [Dives-only starter repository](https://github.com/motherduckdb/blessed-dives-example). Use that workflow when your repository only manages Dives and you don't need dependency-aware Flight deployment. ## Choose a repository structure A Blueprints repository can use this structure: ```text flights/ / blueprint.yml src/flight.py dives/ / blueprint.yml src/dive.tsx guides/ roles/ projects/ shared/ ``` Use `flights/`, `dives/`, `guides/`, and `roles/` when those resources have separate owners or deployment schedules. Use `projects/` when several resources need to preview, deploy, and roll back as one unit. A Flight package can export a share under a stable output name, and a Dive package can consume it: ```yaml # flights/events-ingest/blueprint.yml outputs: events: share: events ``` ```yaml # dives/events-dashboard/blueprint.yml inputs: events: blueprint: events-ingest output: events resources: dives: dashboard: requiredResources: - input: events alias: events ``` For pull requests, Blueprints expands the selection upstream and downstream so the Flight, branch-scoped share, and Dive are previewed together. In production, changing a producer redeploys its consumers, while changing only a Dive uses the existing production output without rerunning the Flight. ## Start with the Dives-only repository Fork the [Dives-only starter repository](https://github.com/motherduckdb/blessed-dives-example) when you don't need Flight or data dependency management. It includes: - A working example Dive - The Vite preview setup for local development - GitHub Actions for deploy and cleanup - A `CLAUDE.md` that teaches the agent the repo conventions Fork the repo, set a `MOTHERDUCK_TOKEN` secret, and you're deploying Dives on merge. ## Prerequisites - A [MotherDuck account](https://app.motherduck.com/) - A GitHub repository to store your Dive source files, such as a repository based on [MotherDuck Blueprints](https://github.com/motherduckdb/motherduck-blueprints) or the [Dives-only starter repository](https://github.com/motherduckdb/blessed-dives-example) - A MotherDuck API token set as a GitHub secret (`MOTHERDUCK_TOKEN`) The Dives-only pull-down workflow below also requires a published Dive and [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) connected to the [MotherDuck MCP Server](/key-tasks/ai-and-motherduck/mcp-setup/). Blueprints can start from a Flight, Dive, Guide, role, or combined project and does not require an existing Dive. ## Pull a dive for local development Start with a Dive that's already published in MotherDuck. Copy its share link from the MotherDuck UI, then tell Claude Code to set it up locally: ```text Set up this dive for local development: https://app.motherduck.com/dives/... ``` The agent uses the MotherDuck MCP Server to: 1. Read the Dive source through the SQL API using the share link 2. Pull down the file into a local directory in your repo 3. Register the Dive for CI 4. Start a lightweight Vite development server for live preview The MCP Server's `get_dive_guide` tool provides the agent with everything it needs — the React component contract, dependency setup, and instructions for the local dev server. No additional skills or context files are required beyond what the MCP server provides. ![Claude Code spinning up the Vite dev server after pulling down a Dive for local development.](./img/claude_code_vite_terminal_1ffa9f80a9.png) :::tip[Fewer tokens with the CLI] The [MotherDuck CLI](/getting-started/interfaces/motherduck-cli/) does the same pull in one command, and the source never passes through the agent's context: ```bash motherduck dive pull --dir dives/my_dive ``` `motherduck dive push` publishes the edits back, and `motherduck dive list --output json` finds the ID. For a coding agent that has a terminal, that's the cheaper path for reading, saving, and listing Dives. See [choosing between the CLI and MCP](/getting-started/interfaces/motherduck-cli/agents/#choosing-between-the-cli-and-mcp). ::: ## Edit locally with an AI agent With the local dev server running, you can iterate on the Dive using Claude Code. The agent can restyle charts, rewrite SQL queries, add filters, swap visualizations — anything you can express as a prompt. ```text Make this much better visually. Top-tier style please. ``` The Vite dev server hot-reloads changes, so you see updates instantly in the browser. The MCP server provides schema context so the agent writes accurate SQL against your live data. ![A Dive running locally, showing the updated dashboard with improved styling and layout.](./img/dive_local_preview_9bccdb19bf.png) If your repo includes a `CLAUDE.md` file (the [Dives-only starter repository](https://github.com/motherduckdb/blessed-dives-example) includes one), the agent also knows the folder conventions and how to register Dives for CI. ## Deploy a preview with GitHub actions Once you're happy with your changes, tell the agent to push a PR: ```text Put up a PR on a new feature branch ``` When a PR is opened (or updated with new commits), a GitHub Action detects which Dive folders changed and deploys a **preview** Dive to MotherDuck. The preview uses the same live environment as production but has a branch-tagged title so it's clearly labeled. A comment appears on the PR with a direct link. ![A GitHub Actions bot comment on a PR showing a preview Dive link — click Open Dive to see it live in MotherDuck.](./img/pr_preview_comment_13ca302ff9.png) Your reviewer clicks the link and sees the Dive running with live queries — no local setup needed. The deploy action uses path filters to detect which Dive folders changed, then calls a shared deploy script (`scripts/deploy-dive.sh`) for each one. The script reads the Dive's source and metadata, and uses the DuckDB CLI with the MotherDuck extension to create or update the Dive. ## Merge to production When the preview looks right, merge the PR. A separate deploy job runs that creates or updates the production Dive, matched by title. The production Dive is now live and shareable with anyone in your organization. ![The deploy GitHub Action after a merge to main, completing in 20 seconds.](./img/deploy_action_success_f763894ae0.png) ## Clean up preview dives Delete the feature branch after merging. A cleanup action fires that removes the preview Dive from your MotherDuck account — no orphaned Dives cluttering your workspace. The entire pipeline is two GitHub Actions and one secret (`MOTHERDUCK_TOKEN`). At MotherDuck, we use a dedicated service account so anyone with repo access can edit and deploy with the same ownership scope. ## Related resources - [Creating Visualizations with Dives](/key-tasks/dives/) — Create Dives from natural language with AI agents - [Dives SQL functions](/sql-reference/motherduck-sql-reference/dives/) — Manage Dives directly from SQL - [Connect to MCP Server](/key-tasks/ai-and-motherduck/mcp-setup/) — Set up the MCP server with your AI assistant - [MotherDuck CLI: work with agents](/getting-started/interfaces/motherduck-cli/agents/) — Pull, push, and list Dives from the terminal, and when to prefer that over MCP - [MotherDuck Blueprints](https://github.com/motherduckdb/motherduck-blueprints) — Manage Dives, Flights, Guides, roles, and dependency-aware deployments in Git - [Dives-only starter repository](https://github.com/motherduckdb/blessed-dives-example) — Use the smaller workflow for repositories that only deploy Dives --- Source: https://motherduck.com/docs/key-tasks/dives/embedding-dives # Embedding Dives in your web application > Embed interactive MotherDuck Dives in your web app using iframes and embed sessions You can embed Dives in your own web application so your users can interact with live data dashboards without signing in to MotherDuck. Your backend creates an embed session, and your frontend loads the Dive in a sandboxed iframe. Embedding Dives is available on the **Business plan**. ## Prerequisites Before you start, you need: - A **MotherDuck Business plan** account - A read/write access token for an account with permission to create Dive embed sessions. The Admin preset role includes this permission by default. For production, we recommend using a dedicated [service account](/key-tasks/service-accounts-guide/create-and-configure-service-accounts/) - A Dive you want to embed, with its [data shared](/sql-reference/mcp/dives/share-dive-data) to the target service account that the embedded Dive will run as - A backend server that can make authenticated API calls :::tip[Use a dedicated service account] We recommend using a service account that does not own databases with the same names as the databases your Dives query. When the service account attaches shared Dive data, the share alias defaults to the source database name. If the service account already has a database with that name, the attach fails. Using a dedicated, empty service account for embedding avoids this conflict. ::: ## How it works Embedded Dives follow a short server-side flow: 1. **Your backend** calls the MotherDuck API with your access token to create an embed session: an opaque string that contains a read-only session string and the information needed to load the Dive. 2. **Your frontend** renders a sandboxed iframe that loads the Dive from `embed-motherduck.com`, passing the session string. 3. **MotherDuck** loads the Dive and runs live SQL queries. Your end-users see an interactive dashboard without needing a MotherDuck account. ::::info[Two tokens are in play] Your service account's access token is a **read/write backend token** with permission to create embed sessions. It stays on your backend and is used only to create embed sessions. The session string it produces contains a **separate, read-only token** that is limited in scope and expires after 24 hours. Only the session string should ever reach the frontend. :::: ```mermaid sequenceDiagram participant M as MotherDuck participant B as Your backend participant F as Your frontend participant E as Embed iframe Note over B: Holds your access token B->>M: POST /v1/dives//embed-session M-->>B: Session string B-->>F: Return session string F->>E: Load iframe /sandbox/#session= Note over F,E: The session stays in the
URL fragment, not the request E->>M: Fetch Dive metadata and content M-->>E: Return the Dive ``` ## Step 1: Create an embed session Your backend calls the MotherDuck API to create an embed session. The access token used for this call must belong to an account with permission to create Dive embed sessions. The Admin preset role includes this permission by default. The session string contains a read-only token that expires after 24 hours. ::::warning[Important] **Never expose your access token in client-side code.** The access token stays on your backend. Only the session string reaches the browser. :::: ### Node.js ```javascript const DIVE_ID = ""; const VERSION = 12; const response = await fetch( `https://api.motherduck.com/v1/dives/${DIVE_ID}/embed-session`, { method: "POST", headers: { // This account has permission to create Dive embed sessions. Authorization: `Bearer ${MOTHERDUCK_TOKEN}`, "Content-Type": "application/json", }, // This is the service account whose compute / perms will be used for the Dive. body: JSON.stringify({ username: SERVICE_ACCOUNT_USERNAME, // Optional: render a specific Dive version. version: VERSION, }), } ); if (!response.ok) { throw new Error(`Failed to create embed session: ${response.status}`); } const { session } = await response.json(); // Return this session string to your frontend ``` ### Python ```python import httpx DIVE_ID = "" VERSION = 12 response = httpx.post( f"https://api.motherduck.com/v1/dives/{DIVE_ID}/embed-session", headers={ "Authorization": f"Bearer {MOTHERDUCK_TOKEN}", "Content-Type": "application/json", }, json={ "username": SERVICE_ACCOUNT_USERNAME, # Optional: render a specific Dive version. "version": VERSION, }, ) response.raise_for_status() session = response.json()["session"] # Return this session string to your frontend ``` Replace `` with the ID of your Dive. You can find this in **Settings** > **Dives** or through the [`list_dives`](/sql-reference/mcp/dives/list-dives) MCP tool. To render a specific version of a Dive, for example for an embedded Dive in a production environment, pass `version` when you create the embed session. MotherDuck validates that the requested version exists for the Dive before returning the session. If you omit `version`, the embedded Dive renders the latest saved version when it loads. The `version` value maps to the Dive version number, not the version UUID. Use the `current_version` value from [`MD_LIST_DIVES`](/sql-reference/motherduck-sql-reference/dives/md-list-dives), the `version` value from [`MD_LIST_DIVE_VERSIONS`](/sql-reference/motherduck-sql-reference/dives/md-list-dive-versions), or the `version` argument to [`MD_GET_DIVE_VERSION`](/sql-reference/motherduck-sql-reference/dives/md-get-dive-version). Your application owns which version to embed. Store the approved version alongside your own release or customer configuration, then pass it when generating sessions for that embed. Each session is tied to a single Dive. If you embed multiple Dives on the same page, create a separate embed session for each one. You can use the same service account and access token for all of them. The session string is base64-encoded but **not encrypted** — it contains a read-only (read scaling) token, the Dive ID, and endpoint URLs. Treat it like a short-lived credential: do not log it or store it in persistent storage. The embedded Dive runs queries as the service account specified in the session. If you need data isolation (for example, separate databases per region), use separate service accounts scoped to only the data each should access. ## Customize the embed session (optional) `POST /v1/dives//embed-session` accepts optional fields that let you tailor each session. ### Override required databases By default, an embedded Dive uses the [`REQUIRED_DATABASES`](/key-tasks/dives/#declaring-required-databases) declared in the Dive's source code. To point the same Dive at different databases on a per-session basis — for example, to render the same dashboard for each of your tenants against their own database — pass a `required_resources` array when creating the embed session: ### Node.js ```javascript body: JSON.stringify({ username: SERVICE_ACCOUNT_USERNAME, required_resources: [ { url: "md:_share/tenant_a_data/", alias: "tenant_data", }, ], }), ``` ### Python ```python json={ "username": SERVICE_ACCOUNT_USERNAME, "required_resources": [ { "url": "md:_share/tenant_a_data/", "alias": "tenant_data", }, ], }, ``` Each entry describes one database: | Field | Required | Description | |-------|----------|-------------| | `url` | Yes | Share URL (`md:_share//`) or owned database identifier (`md:`). | | `alias` | No | Local alias used in the Dive's SQL. Defaults to the database name from the URL. | When you set `required_resources`, it **replaces** the Dive's source-declared `REQUIRED_DATABASES` for that session. Omit the field to use the source-declared list. ### Preconfigure the starting UI state Dives can use the `useDiveState` hook from `@motherduck/react-sql-query` to store interactive state such as filters, sort order, selected views, and drill-downs. To seed that state for a given session — for example, to render the same Dive against each customer's selected date range — pass an `initial_state` object when creating the embed session: ### Node.js ```javascript body: JSON.stringify({ username: SERVICE_ACCOUNT_USERNAME, initial_state: { region: "emea", dateRange: { start: "2026-01-01", end: "2026-03-31" }, }, }), ``` ### Python ```python json={ "username": SERVICE_ACCOUNT_USERNAME, "initial_state": { "region": "emea", "dateRange": {"start": "2026-01-01", "end": "2026-03-31"}, }, }, ``` Each key in `initial_state` matches a key used in `useDiveState(key, ...)` inside the Dive's code. Values must be JSON-serializable. Keys absent from `initial_state` fall back to the `initialValue` declared in the Dive's source. Viewer interactions update the Dive's UI state, but those changes are not persisted server-side. To capture viewer changes from your host page, listen for [`dive-state-update` messages](#handle-dive-state-updates-from-embedded-dives). :::note `required_resources` is capped at 8 KB on the encoded session. `initial_state` is capped at 64 KB; bags larger than 8 KB are stored server-side rather than inlined on the session. ::: ## Step 2: Embed the iframe Add a sandboxed iframe to your page that points to the MotherDuck embed URL. Pass the session string in the URL fragment: ```html ``` Replace `` with the session string your backend generated. The `sandbox` attribute must include `allow-scripts allow-same-origin` for the embed to function. ### URL structure | Part | Description | |------|-------------| | `embed-motherduck.com/sandbox/` | The MotherDuck embed host | | `#session=` | The session string, passed in the URL fragment so it is never sent to the server | The session is placed in the URL fragment (after `#`) rather than the query string. Browsers strip fragments before making HTTP requests, so the session does not appear in server logs or Referer headers. ## Handle link navigation from embedded Dives Embedded Dives run inside an isolated MotherDuck sandbox iframe. Dive code cannot directly navigate the parent page or open popups. When someone clicks a link in an embedded Dive, or Dive code calls `window.open()`, the sandbox blocks the browser navigation and sends a `postMessage` to the parent page. The message has the following shape: ```typescript type NavigationRequest = { type: "navigation-request"; url: string; source: "anchor-click" | "window-open"; target: "_blank" | "_self" | null; rel: string | null; }; ``` The parent page decides how to handle the request. Listen for `navigation-request`, validate the event origin and URL, and apply your own policy before opening anything. The following example uses `window.confirm`; replace it with your application's confirmation UI: ```typescript const iframe = document.querySelector("#motherduck-dive"); if (!iframe) { throw new Error("MotherDuck Dive iframe not found"); } const motherduckEmbedOrigin = new URL(iframe.src).origin; window.addEventListener("message", (event) => { if (event.origin !== motherduckEmbedOrigin) return; if (event.source !== iframe.contentWindow) return; const message = event.data; if (message?.type !== "navigation-request") return; let url: URL; try { url = new URL(message.url); } catch { return; } if (!["https:", "http:"].includes(url.protocol)) return; const confirmed = window.confirm(`Open ${url.toString()}?`); if (!confirmed) return; window.open(url.toString(), "_blank", "noopener,noreferrer"); }); ``` ::::warning[Important] Treat `navigation-request` as untrusted user intent from sandboxed content, not as a command. The parent page should not navigate, submit forms, mutate application state, or grant permissions based only on the message. :::: ### Use absolute URLs in Dive links If you plan to embed a Dive, use absolute URLs in links inside the Dive. Avoid app-relative links like this: ```html [Settings](/settings/members) ``` In an embedded Dive, `/settings/members` resolves against the embed origin, not the MotherDuck app. The parent page receives a URL such as: ```text https://embed-motherduck.com/settings/members ``` Use absolute URLs instead: ```html [Docs](https://motherduck.com/docs/) [Another Dive](https://app.motherduck.com/dives/) ``` For embedded Dives, the parent page owns the policy for whether a navigation request opens a new tab, replaces the current page, or is blocked. ## Handle data exports from embedded Dives Dives can include export buttons created with the `exportAs` return value from `useSQLQuery()` or the `useExport()` hook. When a user starts an export, the Dive runs the export SQL with DuckDB `COPY TO` and sends the generated file to the parent page. Because embedded Dives run in a sandboxed iframe, the iframe cannot download the file directly. Your parent page must listen for export messages, validate the event, and decide how to offer the file to your user. Embedded exports support `csv`, `json`, `parquet`, and `xlsx` formats. The parent page receives these message types: ```typescript type ExportStarted = { type: "export-started"; requestId: string; format: "csv" | "json" | "parquet" | "xlsx"; title?: string; filename: string; }; type ExportFile = { type: "export-file"; requestId: string; format: "csv" | "json" | "parquet" | "xlsx"; title?: string; filename: string; mimeType: string; byteLength: number; previewOptions?: Record; data: ArrayBuffer; }; type ExportError = { type: "export-error"; requestId: string; format: "csv" | "json" | "parquet" | "xlsx"; title?: string; filename?: string; error: string; }; ``` The following example stores the completed export and shows a host-page download button. Replace the status and button UI with your application's pattern: ```html ``` ```javascript const iframe = document.querySelector("#motherduck-dive"); const status = document.querySelector("#dive-export-status"); const downloadButton = document.querySelector("#dive-export-download"); if (!iframe || !status || !downloadButton) { throw new Error("MotherDuck Dive export controls not found"); } const motherduckEmbedOrigin = new URL(iframe.src).origin; let pendingExport = null; function isArrayBuffer(value) { return Object.prototype.toString.call(value) === "[object ArrayBuffer]"; } function isExportFile(message) { return ( message?.type === "export-file" && typeof message.requestId === "string" && typeof message.filename === "string" && typeof message.mimeType === "string" && typeof message.byteLength === "number" && isArrayBuffer(message.data) ); } window.addEventListener("message", (event) => { if (event.origin !== motherduckEmbedOrigin) return; if (event.source !== iframe.contentWindow) return; const message = event.data; if (message?.type === "export-started") { status.textContent = `Preparing ${message.filename}`; downloadButton.hidden = true; pendingExport = null; return; } if (message?.type === "export-error") { status.textContent = `Export failed: ${message.error}`; downloadButton.hidden = true; pendingExport = null; return; } if (!isExportFile(message)) return; pendingExport = message; status.textContent = `${message.filename} is ready to download`; downloadButton.hidden = false; }); downloadButton.addEventListener("click", () => { if (!pendingExport) return; const blob = new Blob([pendingExport.data], { type: pendingExport.mimeType || "application/octet-stream", }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = pendingExport.filename; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); pendingExport = null; downloadButton.hidden = true; status.textContent = "Export downloaded"; }); ``` ::::warning[Important] Treat export messages as untrusted content from sandboxed Dive code. After you validate the event origin and source, use the message to offer a download to your user. Do not upload the file, attach it to another account, or trigger backend workflows based only on the message. :::: Exports run the full SQL passed by the Dive, not the rows already rendered in React. Large exports can use significant browser memory because the generated file is transferred to the parent page as an `ArrayBuffer`. For larger data delivery workflows, consider creating a server-side export flow outside the embedded Dive. ## Handle Dive state updates from embedded Dives When a viewer interacts with a Dive built using the `useDiveState` hook, the embed sends a `dive-state-update` message to the parent page each time the state changes. MotherDuck does not persist these changes server-side — the parent page decides whether to capture the snapshot. A common use is to save it to your backend so the viewer's selections survive across sessions; you can then [seed the next session](#preconfigure-the-starting-ui-state) with the saved bag. The message has the following shape: ```typescript type DiveStateUpdate = { type: "dive-state-update"; state: Record; }; ``` `state` is the **full snapshot** of every key the Dive holds, not a delta. Dropped or out-of-order messages are safe to ignore — the next snapshot supersedes them. MotherDuck debounces updates (~100 ms) to limit chatter during rapid interactions. The following example saves each snapshot to `localStorage` keyed by the Dive ID. Replace the storage with whatever persistence layer fits your application: ```javascript const iframe = document.querySelector("#motherduck-dive"); const motherduckEmbedOrigin = new URL(iframe.src).origin; const STORAGE_KEY = `dive-state:${DIVE_ID}`; window.addEventListener("message", (event) => { if (event.origin !== motherduckEmbedOrigin) return; if (event.source !== iframe.contentWindow) return; if (event.data?.type !== "dive-state-update") return; localStorage.setItem(STORAGE_KEY, JSON.stringify(event.data.state)); }); ``` To replay the saved snapshot on the viewer's next visit, pass it as `initial_state` when creating the next embed session. ::::warning[Important] Treat `dive-state-update` payloads as untrusted content from sandboxed Dive code. After validating the event origin and source, only use the bag for storage or to seed the next session — do not interpret it as a command, attach it to other accounts, or feed it into backend workflows that grant permissions. :::: ## Session lifecycle Embed sessions expire after 24 hours. You have two options for handling expiration: - **Generate a fresh session per page load.** The simplest approach. Each time a user loads the page, your backend creates a new embed session and passes it to the iframe. - **Cache and refresh.** Your backend caches the session and refreshes it before it expires. This reduces API calls but adds complexity. If a session expires while a Dive is open, the embed displays a "Session expired" message. The user needs to reload the page to get a new session. ## Security best practices - **Keep your access token server-side.** Never include your access token in client-side JavaScript, HTML, or any code that reaches the browser. - **Use a dedicated service account.** Create a [service account](/key-tasks/service-accounts-guide/create-and-configure-service-accounts/) specifically for embedding, separate from your personal account. The account needs a read/write access token and permission to create Dive embed sessions, but the sessions it generates are always read-only. The Admin preset role includes this permission by default. - **Sessions are read-only.** The embed session always contains a read scaling token, so it can only read data, not modify it. - **Session in URL fragment.** The fragment (`#session=...`) is never sent to the server in HTTP requests, keeping the session out of access logs and referrer headers. - **Scope service accounts for data isolation.** If you need to restrict which data different users can see (for example, per-region databases), create separate service accounts with access scoped to the appropriate data. The embedded Dive queries data as the service account used to create the session. ## CSP configuration If your site uses a restrictive [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), add `embed-motherduck.com` to your `frame-src` directive: ```text Content-Security-Policy: frame-src https://embed-motherduck.com; ``` Without this, the browser blocks the iframe from loading. ## Troubleshooting Errors from the embed itself (expired token, Dive not found) appear as messages **inside the iframe**. CSP or network-related errors typically appear only in the **browser developer console**. | Error message | Cause | Solution | |---------------|-------|----------| | "Dive embedding requires a Business plan." | Your organization is not on the Business plan | Upgrade to a [Business plan](https://motherduck.com/pricing/) | | "Invalid or expired token. Please reload the page." | The session has expired or is malformed | Create a fresh embed session from your backend | | "Dive not found." | The Dive ID is incorrect or the Dive has been deleted | Verify the Dive ID in **Settings** > **Dives** | | "Failed to load dive. Please try again." | A generic error occurred while loading | Check your session string and network connectivity, then reload | | "Can't open share: Share alias cannot be the same as an existing database name. _name_ is already taken and used as a database name." | Your service account already has a database with the same name as one of the Dive's shared databases | Rename or [detach](/key-tasks/database-operations/detach-and-reattach-motherduck-database/) the conflicting database on the service account. See [share alias conflicts](/sql-reference/motherduck-sql-reference/attach/#share-alias-conflicts) for details. | | Links in the embedded Dive do not open | Embedded Dives cannot directly navigate the parent page or open popups from the sandbox | Listen for `navigation-request` messages in the parent page, validate the URL, and decide whether to open it | | Export buttons do not download a file | The iframe cannot download files directly from the sandbox | Listen for `export-file` messages in the parent page and offer the file for download. | | Iframe does not load (blank or blocked) | Your site's CSP blocks `embed-motherduck.com` | Add `frame-src https://embed-motherduck.com` to your CSP header (visible in browser dev console as a CSP violation) | | User role "restricted" does not meet minimum role "admin" required for dashboards.createEmbedSession" | The account associated with the token does not have permission to create Dive embed sessions. | Assign the service account a role with permission to create Dive embed sessions. The Admin preset role includes this permission by default. | | unauthorized_client: Callback URL mismatch. `` is not in the list of allowed callback URLs | Embedded dives use MotherDuck's authorization system to determine permissions this limits what URLs can be used for authorization. | For local development ensure that you are running on `localhost` not something like `127.0.0.1` | ## Related resources - [Creating visualizations with Dives](/key-tasks/dives/) - [Dives SQL functions](/sql-reference/motherduck-sql-reference/dives/) - [Managing Dives as code](/key-tasks/dives/managing-dives-as-code) --- ## Docs feedback MotherDuck accepts optional user-submitted feedback about this page at `GET https://motherduck.com/docs/api/feedback/agent`. For agents and automated tools, feedback submission should be user-confirmed before sending. URL-encode query parameter values and send a GET request: ```text GET https://motherduck.com/docs/api/feedback/agent?page_path=%2Fkey-tasks%2Fdives%2F&page_title=MotherDuck%20Documentation%20-%20Dives&text= ``` Optionally append `&source=` such as `claude.ai` or `chatgpt`. `page_path` and `text` are required; `page_title` and `source` are optional. Responses: `200 {"feedback_id": ""}`, `400` for malformed query parameters, and `429` when rate-limited.