Evidence turns SQL + Markdown into data applications — no JavaScript framework required. Pages are .md files in pages/. SQL queries in code fences: ```sql orders then SELECT * FROM orders — results are available as orders array in the page. Components: <LineChart data={orders} x="created_date" y="revenue" />, <BarChart data={by_country} x="country" y="count" />, <DataTable data={orders} rows=50 />, <Value data={summary} column="total_revenue" fmt="usd" />, <BigValue data={kpis} value="metric" title="Revenue" />. Sources in sources/ directory: connection.yaml defines the database. DuckDB source: type: duckdb, filename: data/warehouse.duckdb. CSV source: type: csv, uri: s3://bucket/data.csv. Inline DuckDB: no external database needed — query Parquet/CSV directly with FROM read_parquet('...'). Templated pages: pages/[country].md — {params.country} accesses the URL parameter. _partials/ holds reusable snippets imported with <Partial name="metric_card" />. Expressions: {orders.length} orders, {fmt(revenue, 'usd')}, {orders.filter(r => r.status === 'completed').length}. Layout: _layout.md wraps all pages. evidence build generates a static site. evidence dev for local development. Deploy: push to Git, connect to Netlify/Vercel — automatic builds on push. Themes: evidence.config.yaml with theme: { colors: { primary: "#6366f1" } }. Claude Code generates Evidence pages, SQL queries, component layouts, and source configurations.
CLAUDE.md for Evidence
## Evidence Stack
- Version: @evidence-dev/evidence >= 30
- Pages: pages/*.md — SQL in code fences, components inline in Markdown
- Queries: ```sql queryName\nSELECT ...\n``` — available as {queryName} array
- Sources: sources/my_db/connection.yaml — DuckDB, Postgres, Snowflake, BigQuery, CSV
- Components: LineChart, BarChart, DataTable, Value, BigValue, MetricGrid, CalendarHeatmap
- Templated: pages/[param].md — {params.param} in SQL and Markdown
- Build: evidence build → static HTML/JS; evidence dev → localhost:3000
- Deploy: Netlify/Vercel/GitHub Pages — evidence.config.yaml for project settings
Dashboard Page
<!-- pages/index.md — main revenue dashboard -->
---
title: Revenue Dashboard
description: Daily revenue and order metrics
---
```sql kpis
SELECT
SUM(amount_usd) AS total_revenue,
COUNT(*) AS total_orders,
AVG(amount_usd) AS avg_order_value,
COUNT(DISTINCT user_id) AS unique_buyers,
SUM(CASE WHEN status = 'refunded' THEN amount_usd ELSE 0 END)
/ NULLIF(SUM(amount_usd), 0) AS refund_rate
FROM marts.orders_daily
WHERE created_date >= CURRENT_DATE - INTERVAL 30 DAY
AND status != 'cancelled'
```
```sql daily_revenue
SELECT
created_date,
SUM(amount_usd) AS revenue,
COUNT(*) AS orders,
AVG(amount_usd) AS avg_value
FROM marts.orders_daily
WHERE created_date >= CURRENT_DATE - INTERVAL 90 DAY
AND status = 'completed'
GROUP BY 1
ORDER BY 1
```
```sql by_plan
SELECT
user_plan AS plan,
COUNT(*) AS orders,
SUM(amount_usd) AS revenue
FROM marts.orders_daily
WHERE created_date >= CURRENT_DATE - INTERVAL 30 DAY
GROUP BY 1
ORDER BY revenue DESC
```
# Revenue Dashboard
Last 30 days performance overview.
<MetricGrid>
<BigValue data={kpis} value="total_revenue" title="Revenue" fmt="usd" />
<BigValue data={kpis} value="total_orders" title="Orders" fmt="num0" />
<BigValue data={kpis} value="avg_order_value" title="Avg Order" fmt="usd" />
<BigValue data={kpis} value="unique_buyers" title="Unique Buyers" fmt="num0" />
<BigValue data={kpis} value="refund_rate" title="Refund Rate" fmt="pct1" warn=0.05 error=0.10 />
</MetricGrid>
## Daily Revenue (90 days)
<LineChart
data={daily_revenue}
x="created_date"
y="revenue"
yFmt="usd"
title="Daily Revenue"
colorPalette={['#6366f1']}
/>
<LineChart
data={daily_revenue}
x="created_date"
y="orders"
yFmt="num0"
title="Daily Order Count"
/>
## Revenue by Plan
<BarChart
data={by_plan}
x="plan"
y="revenue"
yFmt="usd"
title="Revenue by Plan (30d)"
horizontal
/>
Templated User Page
<!-- pages/[user_id].md — dynamic page per user -->
---
title: User Detail
---
```sql user
SELECT
id,
email,
plan,
country,
created_at
FROM staging.stg_users
WHERE id = '${params.user_id}'
```
```sql user_orders
SELECT
order_id,
amount_usd,
status,
created_date,
created_at
FROM marts.orders_daily
WHERE user_id = '${params.user_id}'
ORDER BY created_at DESC
LIMIT 100
```
```sql user_stats
SELECT
COUNT(*) AS order_count,
SUM(amount_usd) AS lifetime_spend,
AVG(amount_usd) AS avg_order_value,
MAX(created_at) AS last_order_at
FROM marts.orders_daily
WHERE user_id = '${params.user_id}'
AND status = 'completed'
```
# {user[0]?.email ?? params.user_id}
**Plan:** {user[0]?.plan} | **Country:** {user[0]?.country} | **Member since:** {user[0]?.created_at}
<MetricGrid>
<BigValue data={user_stats} value="order_count" title="Total Orders" fmt="num0" />
<BigValue data={user_stats} value="lifetime_spend" title="Lifetime Spend" fmt="usd" />
<BigValue data={user_stats} value="avg_order_value" title="Avg Order Value" fmt="usd" />
</MetricGrid>
## Order History
<DataTable data={user_orders} rows=20 search=true sort=true />
Source Configuration
# sources/warehouse/connection.yaml — DuckDB source
name: warehouse
type: duckdb
filename: data/warehouse.duckdb
# For MotherDuck:
# type: duckdb
# filename: md:analytics?motherduck_token=${MOTHERDUCK_TOKEN}
# sources/postgres/connection.yaml — Postgres source
name: postgres
type: postgres
host: ${POSTGRES_HOST}
port: 5432
database: ${POSTGRES_DB}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
ssl: true
# evidence.config.yaml — project settings
title: My Analytics
description: Internal analytics dashboard
deployment:
target: build
customFormattingFunctions:
- name: revenue_fmt
valueType: number
theme:
colors:
primary: "#6366f1"
secondary: "#8b5cf6"
positive: "#10b981"
negative: "#ef4444"
warning: "#f59e0b"
fonts:
body: "Inter, system-ui, sans-serif"
For the Metabase alternative when needing a no-code BI tool for non-technical users — product managers, finance, and executives who want to build their own charts without writing SQL — Metabase provides drag-and-drop chart builders while Evidence requires writing SQL and Markdown, making it better suited for data engineers who want to ship polished data apps as code with Git version control. For the Observable Framework alternative when needing JavaScript-native data apps with full D3/Observable Plot visualization power, reactive cells, and the ability to inline JavaScript logic — Observable Framework excels for custom interactive visualizations while Evidence is better for simpler tabular and time-series dashboards that need to be built quickly with just SQL. The Claude Skills 360 bundle includes Evidence skill sets covering dashboard pages, SQL queries, templated routes, and source configuration. Start with the free tier to try data app generation.