How to build Medusa dashboards in Metabase
Medusa is the open-source, headless commerce platform built on Node.js, and it keeps every order, product, and customer in a PostgreSQL database you control. Metabase is where you turn that store data into shared, trustworthy dashboards. This guide covers two complementary paths: a lightweight MCP + CLI route that pulls live data with the Medusa MCP server and loads a CSV into Metabase with the Metabase CLI for quick analysis, and a durable direct-database route that connects Metabase straight to Medusa's Postgres so you can build dashboards anyone can read.
How do you connect Medusa to Metabase?
Most teams combine both routes: use the Medusa MCP server and Metabase CLI route to pull live data and stand up a quick analysis, and the direct-database route for the dashboards people depend on.
Live data in, quick analysis out
Pair Medusa's MCP server (to read live store data) with the Metabase CLI, whose upload command loads a CSV into Metabase as a ready-to-query table and model.
- Quick lookups like "how many orders did we ship this week?"
- Loading a Medusa CSV export into Metabase in seconds
- Spot-checks and one-off analyses without wiring up the database
- Great for exploration, not governed reporting
- Use read-only mode on the Medusa MCP to avoid accidental writes
- CSV uploads are snapshots — connect the database directly for live, ongoing analysis
Point Metabase at your Postgres
Medusa is a self-hosted, open-source commerce platform that stores everything in PostgreSQL. Metabase connects to Postgres natively, so you can query orders and products directly — no ETL required.
- Self-hosted stores where you control the database
- Getting to a first dashboard in minutes
- Live operational reporting on a read replica
- Query a read replica, never the live production writer
- Medusa v2's module tables differ from v1 — check your version
- Money is stored in minor units on some fields — normalize it
What can you analyze from Medusa data in Metabase?
- Sales — net revenue, orders, and AOV over time
- Repeat purchase — how many buyers come back, and how fast
- Product performance — top sellers, units, and return rate
- Regions & channels — revenue split across markets
- Fulfillment & payments — order status and capture rates
- Customer cohorts — revenue by acquisition month
Which Medusa dashboards should you build in Metabase?
Store overview
The daily pulse of sales and demand.
- Net revenue vs. prior period (number + trend)
- Orders per day (line)
- Average order value (number + trend)
- New vs. returning customer revenue (bar)
Product performance
What's selling across the catalog.
- Top products by net revenue (bar)
- Units sold by variant (table)
- Return rate by product (bar)
- Revenue by region / sales channel (bar)
Customers & repeat purchase
Are buyers coming back?
- Repeat-purchase rate by cohort (line)
- Orders per customer distribution (bar)
- Time between first and second order (histogram)
- Revenue by acquisition month (cohort table)
Fulfillment & payments
From cart to captured.
- Order status funnel (pending → completed) (bar)
- Payment capture vs. authorization (bar)
- Refunds by week (line)
- Cart-to-order conversion (number)
How do you connect the Medusa database directly?
In Metabase, add your Medusa database under Admin → Databases → Add database → PostgreSQL, using a read-only user against a replica. A few things to know:
- Version matters — Medusa v2 organizes data into module tables like
order,order_line_item, andproduct_variant; v1's layout differs. Confirm which you run. - Money units — some totals are stored in minor units (e.g. cents). Normalize by dividing where needed.
- Reserved words —
orderis a SQL keyword; quote it ("order") in queries.
How do you use the Medusa MCP server with the Metabase CLI?
Pair the Medusa MCP server with the Metabase CLI for fast, hands-on analysis. Medusa hosts an official Streamable-HTTP MCP server for its docs and developer workflows — useful for understanding the schema and drafting queries against live store data; the Metabase CLI's upload command loads a CSV into Metabase and creates a ready-to-query table and model. For analysis, connect Medusa in read-only mode.
Example workflow
- Ask the Medusa MCP how orders and line items relate, and which rows to pull this week.
- Export the orders or products you want to keep as a CSV.
- Run
mb upload csvto load it into Metabase as a table and model, then build questions and dashboards on top.
Be honest about the limits
- The Medusa MCP is docs/dev focused and great for live lookups — not for scheduled or audited reporting.
- A CSV upload is a point-in-time snapshot; refresh it with
mb upload replaceor connect the database directly for real history. - Use Medusa's read-only option so analysis can't trigger writes.
mb upload csvneeds an uploads database configured under Admin → Settings → Uploads.
How do you set up the Medusa MCP server and the Metabase CLI?
Medusa MCPofficial
- Endpoint
https://docs.medusajs.com/mcp- Transport
- Streamable HTTP
- Auth
Authorization: Bearer <token>- Scope
- Docs and developer workflows
Metabase CLIofficial
- Install
npm install -g @metabase/cli- Auth
mb auth login(browser OAuth on v62+, or an API key)- Load data
mb upload csv --file data.csv- Requires
- An uploads database (Admin → Settings → Uploads)
{
"mcpServers": {
"medusa": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://docs.medusajs.com/mcp",
"--header",
"Authorization: Bearer YOUR_TOKEN"
]
}
}
}# Install the Metabase CLI
npm install -g @metabase/cli
# Log in (opens your browser; requires Metabase v62+)
mb auth login --url https://your-metabase.example.com
# Load a Medusa CSV export — creates a table AND a model
mb upload csv --file medusa-orders.csv --collection root
# Refresh that same table later from a new export
mb upload replace <table-id> --file medusa-orders.csvCursor's OAuth flow can be flaky, so the Medusa docs recommend the URL plus a Bearer token. Replace YOUR_TOKEN with your token. The Metabase CLI stores its credentials securely after mb auth login, and mb upload csv needs an uploads database enabled first.
Can you generate a Medusa dashboard with AI?
Yes. Use the prompt below with any assistant that can run the Medusa MCP server and the Metabase CLI. It works end to end: if Medusa data already lives in Metabase — the connected Postgres database or CLI-uploaded tables — it analyzes that; otherwise it pulls the data over the Medusa MCP, loads it with mb upload csv, then builds the dashboard — detecting the Medusa version, normalizing money units, and skipping cards it has no data for.
Create a polished Metabase dashboard for Medusa ecommerce analytics.
Work end to end: get the data into Metabase if it isn't there yet, then build.
Goal: Help store operators understand sales, average order value, repeat purchase,
product performance, and fulfillment from Medusa data.
Step 1 — Find or load the data:
- First, check what already exists in Metabase. If the Medusa Postgres database is
already connected — or Medusa tables/models were uploaded earlier — use that data
and skip to Step 2.
- If nothing is there, pull it with the Medusa MCP server in read-only mode: orders,
line items, products, and customers. Write each result to a CSV, then load it with
the Metabase CLI — run "mb upload csv --file <export>.csv" so each upload creates a
table and a ready-to-query model. Use "mb upload replace <table-id> --file
<export>.csv" to refresh an existing table instead of creating duplicates.
Step 2 — Inspect before querying:
Medusa CSV exports are usually flat and pre-aggregated (one row per order or product,
with columns already summarized). The Medusa database is raw: identify the Medusa
version and table layout first. Medusa v2 uses module tables such as "order",
"order_line_item", "product", "product_variant", "customer", and "cart"; v1 differs.
Note that "order" is a reserved word — quote it. Some money fields are stored in
minor units (e.g. cents) — normalize them. Inspect the actual tables and column names
first; do not assume exact names.
Important:
- Build on whatever data is present; don't claim Metabase connects natively to
Medusa — it reads the connected SQL database or CLI-uploaded tables.
- Query a read replica, not the production writer.
- Report NET revenue: subtract refunds and discounts; state the definition.
- Count only completed/captured orders per your definition of paid.
- Use order-level grain for AOV.
- Define a "repeat customer" once (2+ paid orders) and reuse it everywhere.
- Only build a card if its underlying column/metric exists in the data.
- A single CSV is a point-in-time snapshot: connect the database directly or upload
multiple periods before building trend cards.
Dashboard title: Medusa Store Overview
Sections:
1. Executive summary (KPI cards): Net revenue last 30 days; Orders; AOV; Repeat-
purchase rate; Refund rate.
2. Sales & demand: Net revenue by day; Orders by day; AOV trend; New vs returning
revenue.
3. Product performance: Top products by net revenue; Units by variant; Return rate.
4. Customers & retention: Repeat-purchase rate by cohort; Orders per customer;
Revenue by acquisition month.
5. Fulfillment & payments: Order status funnel; Payment capture; Refunds by week.
Filters: Date range, Region, Sales channel, Product, Customer type.
Reuse the models Metabase auto-created from uploaded CSVs, or (for the database)
create reusable models: modeled_medusa_orders, modeled_medusa_line_items,
modeled_medusa_products, and modeled_medusa_customers.
Output: Build the dashboard if you have permission; otherwise provide the exact
questions, SQL, model definitions, and layout. Include caveats for any metric
that cannot be calculated from the available data. Keep it practical, dense,
and executive-readable. Avoid vanity metrics.How should you model Medusa data in Metabase?
Core tables (Medusa v2)
| Table | Grain | Key columns |
|---|---|---|
order | one row per order | id, customer_id, status, total, currency_code, created_at |
order_line_item | one row per line | order_id, variant_id, quantity, unit_price |
product | one row per product | id, title, status |
product_variant | one row per variant | id, product_id, sku |
customer | one row per customer | id, email, created_at |
Modeling advice
- Quote the
ordertable name — it's a SQL reserved word. - Normalize money from minor units and label the currency.
- Compute AOV at the order grain; define net revenue once.
- Join line items through
product_varianttoproductfor product-level analysis.
Which Medusa metrics should you track in Metabase?
| Metric | Definition | Notes |
|---|---|---|
| Net revenue | Gross − discounts − refunds. | Normalize money units first. |
| Average order value (AOV) | Net revenue ÷ orders. | Order grain; segment by region. |
| Repeat-purchase rate | Customers with 2+ paid orders ÷ all. | Fixed window; watch by cohort. |
| Return rate | Returned value ÷ gross. | Break out by product. |
| Cart-to-order conversion | Orders ÷ carts created. | Needs cart data; watch by channel. |
What SQL powers Medusa dashboards in Metabase?
These assume a Medusa v2 PostgreSQL database. Money is divided by 100 to illustrate minor-unit normalization — adjust to your configuration.
Completed-order totals over the last 30 days.
-- Medusa v2 module tables; money often in minor units
SELECT
date_trunc('day', o.created_at) AS day,
COUNT(*) AS orders,
SUM(o.total) / 100.0 AS net_revenue
FROM "order" o
WHERE o.status = 'completed'
AND o.created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY 1
ORDER BY 1;AOV computed at the order grain, on completed orders.
SELECT
date_trunc('week', o.created_at) AS week,
COUNT(*) AS orders,
ROUND((SUM(o.total) / 100.0) / NULLIF(COUNT(*), 0), 2) AS aov
FROM "order" o
WHERE o.status = 'completed'
GROUP BY 1
ORDER BY 1;Units and gross revenue by product over 90 days.
SELECT
p.title AS product,
SUM(li.quantity) AS units,
SUM(li.quantity * li.unit_price) / 100.0 AS gross_revenue
FROM order_line_item li
JOIN "order" o ON o.id = li.order_id
JOIN product_variant v ON v.id = li.variant_id
JOIN product p ON p.id = v.product_id
WHERE o.status = 'completed'
AND o.created_at >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY p.title
ORDER BY gross_revenue DESC
LIMIT 20;Share of customers with two or more paid orders.
WITH customer_orders AS (
SELECT customer_id, COUNT(*) AS paid_orders
FROM "order"
WHERE status = 'completed'
AND customer_id IS NOT NULL
GROUP BY customer_id
)
SELECT
ROUND(100.0 * COUNT(*) FILTER (WHERE paid_orders >= 2)
/ NULLIF(COUNT(*), 0), 2) AS repeat_purchase_rate_pct
FROM customer_orders;