Interactive Charts in Documents

TeamDay documents support interactive chart and table blocks that render inline in markdown. Charts can display static data (baked in by the AI agent) or query a live database connection on every view.

Chart Block Syntax

Chart blocks use fenced code blocks with a special language annotation:

```data:chart(type=bar, title="Revenue by Category")
[{"category":"Hiking","revenue":45000},{"category":"Running","revenue":62000}]
```

The annotation format is: source:blockType(param=value, param="value with spaces")

Sources

SourceDescription
dataStatic data embedded in the block. The AI bakes numbers in when writing the report.
sqlLive SQL query against a database connection. Runs when someone views the document.

Block Types

TypeDescription
chartRenders a Chart.js visualization (bar, line, pie, etc.)
tableRenders a formatted data table with headers and rows

Parameters

ParameterRequiredDescription
typechart onlyChart type: bar, line, pie, doughnut, area
titlenoDisplay title above the chart/table
connectionsql onlyName of the database connection (as configured in Integrations)
cachenoCache duration: 30s, 5m, 1h, none. Default: 5m

Static Charts (data:chart)

Static charts contain their data directly in the block body as JSON. The AI agent computes or fetches the data during the conversation, then writes it into the markdown file.

Array of Objects

The simplest format — an array of objects where the first key becomes the x-axis label and remaining keys become data series:

```data:chart(type=bar, title="Q2 Sales by Region")
[
  {"region": "Europe", "revenue": 145000, "orders": 2340},
  {"region": "Americas", "revenue": 98000, "orders": 1560},
  {"region": "Asia", "revenue": 67000, "orders": 890}
]
```

This renders a bar chart with “Europe”, “Americas”, “Asia” as labels and two series: “revenue” and “orders”.

Labels + Datasets Format

For more control, use the structured format with explicit labels and named datasets:

```data:chart(type=line, title="Monthly Growth")
{
  "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
  "datasets": [
    {"label": "Revenue", "values": [45000, 48000, 52000, 51000, 58000, 63000]},
    {"label": "Costs", "values": [32000, 33000, 34000, 33500, 35000, 36000]}
  ]
}
```

Live SQL Charts (sql:chart)

SQL charts query a database connection every time the document is viewed. The query runs server-side with caching.

```sql:chart(type=line, title="Monthly Orders", connection=production-db, cache=1h)
SELECT DATE_TRUNC('month', created_at) as month,
       COUNT(*) as orders
FROM orders
WHERE created_at > NOW() - INTERVAL '12 months'
GROUP BY 1
ORDER BY 1
```

Column Mapping

The SQL result columns map to chart axes automatically:

  • First column = labels (x-axis)
  • Remaining columns = data series (y-axis)
  • Column names become series labels in the legend
SELECT month,    revenue,   cost,    profit
--     ^ labels  ^ series1  ^ series2 ^ series3
FROM monthly_summary
ORDER BY month

Connection Name

The connection parameter matches the name you gave the database when setting it up in Integrations. For example, if you created a PostgreSQL connection named belenka-db, use connection=belenka-db.

Caching

Live queries are cached server-side to avoid hitting the database on every page view:

ValueDuration
30s30 seconds
5m5 minutes (default)
1h1 hour
noneNo caching, always fresh

Charts display a “cached” indicator when serving cached data.

Data Tables (sql:table / data:table)

Tables work the same way as charts but render as formatted HTML tables instead of visualizations:

```sql:table(title="Top 10 Products by Revenue", connection=production-db)
SELECT product_name,
       SUM(quantity) as units_sold,
       SUM(revenue) as revenue
FROM order_items
GROUP BY product_name
ORDER BY revenue DESC
LIMIT 10
```

Static data tables:

```data:table(title="Team Allocation")
[
  {"team": "Engineering", "headcount": 12, "budget": "$1.2M"},
  {"team": "Marketing", "headcount": 5, "budget": "$400K"},
  {"team": "Sales", "headcount": 8, "budget": "$600K"}
]
```

Tables automatically:

  • Format numbers with commas (e.g., 45,000)
  • Show null values as ”—”
  • Highlight rows on hover
  • Indicate when results are truncated (>1,000 rows)

Chart Types

Bar Chart

Best for comparing categories. Use when you have discrete categories on the x-axis.

type=bar

Line Chart

Best for trends over time. Use when the x-axis represents a time series.

type=line

Area Chart

Like a line chart with the area filled. Good for showing volume or cumulative data.

type=area

Pie / Doughnut Chart

Best for showing composition or parts of a whole. Use with a single data series.

type=pie
type=doughnut

Where Charts Render

Chart blocks render in these surfaces:

SurfaceStatic (data:)Live (sql:)
Chat messagesYesYes
File viewer / editor previewYesYes
Feed previewNo (shows raw)No (shows raw)

The feed shows truncated text previews. Click through to the full document to see rendered charts.

Teaching Your Agent

To have your AI agent write reports with charts, include the chart syntax in the agent’s system prompt or as a skill. Example instruction:

When writing reports with data, embed charts using this syntax:

  ```data:chart(type=bar, title="...")
  [{"label": "...", "value": ...}, ...]

For live dashboards, use SQL chart blocks:

SELECT ...

Available chart types: bar, line, pie, doughnut, area Available connections: production-db, analytics-db


## Limitations

- **Read-only queries** — INSERT, UPDATE, DELETE, DROP, and other mutations are blocked
- **5-second timeout** — queries that take longer are cancelled
- **1,000 row limit** — results exceeding this are truncated
- **PostgreSQL only** for live queries — MySQL, BigQuery, and others coming soon
- **No cross-database joins** — each chart block queries a single connection

## API Reference

The chart blocks use the [Connections Query API](/docs/api/connections) under the hood. You can call it directly for programmatic access.