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
| Source | Description |
|---|---|
data | Static data embedded in the block. The AI bakes numbers in when writing the report. |
sql | Live SQL query against a database connection. Runs when someone views the document. |
Block Types
| Type | Description |
|---|---|
chart | Renders a Chart.js visualization (bar, line, pie, etc.) |
table | Renders a formatted data table with headers and rows |
Parameters
| Parameter | Required | Description |
|---|---|---|
type | chart only | Chart type: bar, line, pie, doughnut, area |
title | no | Display title above the chart/table |
connection | sql only | Name of the database connection (as configured in Integrations) |
cache | no | Cache 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:
| Value | Duration |
|---|---|
30s | 30 seconds |
5m | 5 minutes (default) |
1h | 1 hour |
none | No 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:
| Surface | Static (data:) | Live (sql:) |
|---|---|---|
| Chat messages | Yes | Yes |
| File viewer / editor preview | Yes | Yes |
| Feed preview | No (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.