> ## Documentation Index
> Fetch the complete documentation index at: https://subtext.fullstory.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Metabase and Subtext: From a Signal to the Session

> Format the Subtext session URL as a clickable link in Metabase, then jump from any dashboard tile, question, or alert straight into the user's real session.

Metabase shows you *that* a number moved: a funnel dropped, an error-rate tile climbed, a cohort in a table looks wrong. It reads your warehouse and charts the shape of the problem, but it can't show you the moment one specific person hit it. Subtext can. Point Metabase at a `subtext_url` column your collector already landed and every dashboard row becomes a jumping-off point — your agent reads the `subtext_url`, opens that person's real session, and walks the decisive moments with screenshots, the component tree, network, and console. Metabase detects; Subtext diagnoses.

<Note>
  This guide assumes the [Subtext capture snippet](/docs/install/overview) is installed and capturing sessions, and that Metabase is connected to a warehouse table with a `subtext_url` column that a collector (Snowplow, Hightouch, Segment, or direct inserts) already landed.
</Note>

## Attach the session URL

Metabase doesn't capture anything — it reads a `subtext_url` column your collector already landed in the warehouse. Attaching the link is configuration, not code: return the column in a question, then format it as a link so every row opens the session. The examples assume ClickHouse, but the steps are identical on any warehouse Metabase connects to.

### Return the column in a question

Write a question that selects `subtext_url` alongside whatever you are analyzing:

```sql theme={null}
SELECT user_id, event, event_time, subtext_url
FROM events
WHERE event = 'checkout_error'
  AND subtext_url != ''
ORDER BY event_time DESC
LIMIT 50
```

### Format the column as a link

In the question's visualization settings, open the `subtext_url` column and set **Display As → Link**. The column already holds a full URL, so the link template is just the value itself. Store the full `subtext_url` as captured — the signed URL originates in the browser via `FS('getSession')` and can't be reconstructed from a bare session id, so a link template can't rebuild it:

<CodeGroup>
  ```text Full URL column theme={null}
  {{subtext_url}}
  ```
</CodeGroup>

Set **Link text** to `Open session` (or leave it blank to show the URL). Now every row renders an **Open session** link that opens the replay in a new tab.

### Make it stick with a model

Set the formatting once on a Metabase **model** and every question and dashboard built on that model inherits the clickable link automatically. Add the `subtext_url` column to the model, set its display to **Link** in the model's metadata, and downstream questions render it without re-configuring.

<Warning>
  Don't put a question that exposes `subtext_url` into a **public link** or **static embed** — the signed URL grants replay access, so keep these questions and dashboards to authenticated, internal users. Don't wrap the raw URL in `LowCardinality` upstream or truncate it in the query; the full signed string is required for the link to resolve. And don't rely on the link for rows written before the collector attached `subtext_url` — filter `subtext_url != ''` so empty rows don't render dead links.
</Warning>

## From signal to session

However you first hear about a problem in Metabase, the path is the same: click the **Open session** link, grab the `subtext_url`, and hand it to your agent for a Detect-vs-Diagnose read — the dashboard says these users errored or dropped; the session shows what they actually experienced.

<AccordionGroup>
  <Accordion title="A dashboard tile flags a funnel drop">
    The analyst or growth PM reviewing the weekly funnel dashboard sees a funnel or error-rate tile below trend. The linked question lists the affected users, each row with an **Open session** link.

    Click into the question behind the tile and copy a few **Open session** links (or right-click → copy link). Then hand them to your agent:

    ```text theme={null}
    Our Metabase funnel dashboard shows checkout conversion below trend. Here are session URLs for users
    who started but didn't complete:
    <PASTE 3-4 subtext_urls>

    Review them and find the common failure point. Detect-vs-Diagnose: the data says they dropped — what
    does each session actually show at the drop step? One synthesized hypothesis at the end.
    ```
  </Accordion>

  <Accordion title="An analyst filters a question to a bad cohort">
    The data analyst exploring ad-hoc slices a question and one segment looks wrong — high error rate, low completion. Each row already carries an **Open session** link.

    ```text theme={null}
    This segment in my Metabase question has an unusually high error rate. Here are a few of their
    sessions:
    <PASTE 2-3 subtext_urls>

    Open them and tell me what's breaking for this segment specifically. Is it one bug, or a few
    different failures that happen to cluster here?
    ```
  </Accordion>

  <Accordion title="A scheduled alert fires">
    A Metabase alert on a question crosses its threshold (error count spikes, conversion drops) and emails anyone subscribed to it. Open the question from the alert email; the offending rows carry **Open session** links.

    ```text theme={null}
    A Metabase alert fired: `checkout_error` crossed its threshold this hour. Here's a session from the
    alerting query: <PASTE subtext_url>

    Open it around the error and tell me whether this is a new regression or the same known issue. Show
    me the UI and console at that moment.
    ```
  </Accordion>
</AccordionGroup>

<Tip>
  For a funnel investigation across several users, ask your agent to consolidate its findings into one proof document (`doc-create`) with a screenshot per dropped user and a single shareable link — then drop that back into the dashboard's description so the tile carries the human story behind it.
</Tip>

## For agents

An autonomous agent can run the whole loop against Metabase: run the analysis your team already trusts, extract the `subtext_url`, and hand each session to the Subtext MCP. Metabase ships an official first-party MCP server (Metabase 60+, available on all plans), built into your instance and served from the `/api/metabase-mcp` endpoint. Clients authenticate via OAuth 2.0 against Metabase's embedded OAuth server, with tokens scoped to the connecting user's Metabase permissions, and it requires AI features to be enabled — if AI features are off in **Admin > AI**, the MCP server stays off. Its read-only tools — `search`, `read_resource`, `construct_query`, `execute_query`, `query` — run queries and return rows with column metadata. The Metabase REST API remains an alternative path, and `POST /api/card/<card_id>/query/json` is still the single-call way to run a saved question: the MCP server has no run-card-by-id tool, so an agent reads the question via `read_resource` (`metabase://question/<id>`) and then constructs and executes its query. Metabase is a convenience layer over your warehouse, so you can also skip it entirely and query the underlying warehouse through its own MCP (for ClickHouse, the official `mcp-clickhouse` server). The official server also exposes write tools (`create_question`, `execute_sql`, and more), so connect as a Metabase user with read-only permissions — the OAuth token inherits that user's permissions — and note that an admin can disable `execute_sql` instance-wide via the `mcp-execute-sql-enabled` setting.

<Steps>
  <Step title="Discover who is affected">
    Run the saved question (card) that already encodes the funnel, error, or cohort analysis — a card contains the exact analysis your team trusts. Via the official Metabase MCP server or the REST API directly:

    ```text theme={null}
    POST https://<your-metabase>/api/card/<card_id>/query/json
    # Header: x-api-key: <metabase_api_key>
    # → array of row objects, each including the subtext_url field
    ```
  </Step>

  <Step title="Extract the session URL">
    Read `subtext_url` off the returned rows:

    ```text theme={null}
    rows = POST /api/card/<card_id>/query/json      # Metabase REST or the official Metabase MCP
    urls = [r.subtext_url for r in rows if r.subtext_url]
    ```

    To build the query fresh instead, run SQL against the warehouse MCP directly for the same rows.
  </Step>

  <Step title="Hand off to Subtext">
    For each URL, call `review-open(session_url=<subtext_url>)` on the Subtext MCP and step through the drop or error moment for a Detect-vs-Diagnose read.
  </Step>
</Steps>

<Note>
  The card must actually select `subtext_url` and filter out empty values, or the field won't be in the result — check the question's columns first. A card returns whatever row limit the question sets, so raise it or paginate for a full cohort. Use read-only credentials on whichever surface you pick: a review agent only reads. And `subtext_url` only round-trips if a collector attached it (see [Attach the session URL](#attach-the-session-url)).
</Note>

## Related

* [Session Review overview](/docs/session-review/overview) — what your agent does once a session is open.
* [Install the capture snippet](/docs/install/overview) — required before any session URL exists to attach.
