From rill
Detailed instructions and examples for developing canvas dashboard resources in Rill
npx claudepluginhub rilldata/agent-skillsThis skill uses the workspace's default tool permissions.
Canvas dashboards are free-form dashboard resources that display custom chart and table components laid out in a grid. They enable building overview and report-style dashboards with multiple visualizations, similar to traditional business intelligence tools.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
Canvas dashboards are free-form dashboard resources that display custom chart and table components laid out in a grid. They enable building overview and report-style dashboards with multiple visualizations, similar to traditional business intelligence tools.
Canvas dashboards differ from explore dashboards in important ways:
Canvas dashboards are lightweight resources found downstream of metrics views in the project DAG. Each component within a canvas fetches data individually, typically from a metrics view resource.
When to use canvas dashboards:
A canvas dashboard is defined in a YAML file with type: canvas. Here is the basic structure (most canvas dashboards work great without any of the optional properties here):
type: canvas
display_name: "Sales Overview Dashboard"
# Optional filter settings
filters:
enable: true
pinned:
- region
- product_category
# Optional time range presets
time_ranges:
- P7D
- P30D
- P90D
- inf
# Optional maximum dashboard width
max_width: 1400
# Optional theme reference
theme: my_theme
# Default time settings for all components
defaults:
time_range: P7D
comparison_mode: time
# Optional security access control
security:
access: "'{{ .user.domain }}' == 'company.com'"
# Required dashboard content organized in rows
rows:
- height: 240px
items:
- width: 12
kpi_grid:
metrics_view: sales_metrics
measures:
- total_revenue
- order_count
- height: 400px
items:
- width: 6
line_chart:
metrics_view: sales_metrics
title: "Revenue Trend"
x:
type: temporal
field: event_time
y:
type: quantitative
field: total_revenue
- width: 6
bar_chart:
metrics_view: sales_metrics
title: "Revenue by Region"
color: primary
x:
type: nominal
field: region
limit: 10
sort: -y
y:
type: quantitative
field: total_revenue
Canvas dashboards use a 12-unit grid system for layout.
Each row defines a horizontal section with a specific height:
rows:
- height: 240px # Row height in pixels
items:
# Components go here
Recommended row heights:
Items within a row share the 12-unit width:
rows:
# Full width (1 component per row)
- items:
- width: 12
markdown:
content: "# Dashboard Title"
# Half width (2 components per row)
- items:
- width: 6
line_chart:
# ...
- width: 6
bar_chart:
# ...
# Third width (3 components per row)
- items:
- width: 4
donut_chart:
# ...
- width: 4
bar_chart:
# ...
- width: 4
area_chart:
# ...
Width guidelines:
width: 12 - Full width; use for KPI grids, markdown headers, wide chartswidth: 6 - Half width; use for side-by-side comparisonswidth: 4 - Third width; use for three equal chartswidth: 3 - Quarter width; use for four small components (minimum practical width)When building a new canvas dashboard, follow this recommended structure:
Choosing chart types:
line_chart or area_chart with temporal x-axisbar_chart or stacked_bar with nominal x-axisdonut_chart or stacked_bar_normalizedheatmapcombo_chart for two measures with different scalesfunnel_chart to visualize sequential stage drop-offsThe field names are case sensitive and should match exactly to the fields present in the metrics view.
Time dimension restrictions: The time dimension (timeseries field from the metrics view) is special and can ONLY be used in the x-axis field for temporal charts. Never use the time dimension in:
Add text content, headers, and documentation:
markdown:
content: |
## Dashboard Overview
This dashboard tracks key sales metrics across all regions.
---
alignment:
horizontal: left # left, center, right
vertical: middle # top, middle, bottom
Best practices:
--- for horizontal rules to separate sectionsDisplay key metrics with comparison values and sparklines:
kpi_grid:
metrics_view: sales_metrics
measures:
- total_revenue
- order_count
- average_order_value
- customer_count
comparison:
- delta # Absolute change
- percent_change # Percentage change
- previous # Previous period value
sparkline: right # right, bottom, none
With dimension filters:
kpi_grid:
metrics_view: sales_metrics
measures:
- total_revenue
- order_count
dimension_filters: region IN ('North America', 'Europe')
comparison:
- percent_change
sparkline: bottom
hide_time_range: true
Display ranked dimension values by measures:
leaderboard:
metrics_view: sales_metrics
title: "Top Products"
description: "Products ranked by total revenue"
dimensions:
- product_category
measures:
- total_revenue
- order_count
num_rows: 10
With multiple dimensions:
leaderboard:
metrics_view: sales_metrics
dimensions:
- region
- product_category
measures:
- total_revenue
- average_order_value
- order_count
num_rows: 7
Important: Never use time dimensions in leaderboard dimensions. Leaderboards are for categorical ranking, not time-series analysis.
Show trends over time:
line_chart:
metrics_view: sales_metrics
title: "Revenue Trend"
color: primary
x:
field: order_date
type: temporal
limit: 30
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
With color dimension breakdown:
line_chart:
metrics_view: sales_metrics
title: "Revenue by Region"
color:
field: region
type: nominal
limit: 5
legendOrientation: top
x:
field: order_date
type: temporal
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
With custom color mapping:
line_chart:
metrics_view: sales_metrics
title: "Performance Comparison"
color:
field: status
type: nominal
colorMapping:
- value: "active"
color: hsl(120, 70%, 45%)
- value: "inactive"
color: hsl(0, 70%, 50%)
x:
field: event_date
type: temporal
y:
field: event_count
type: quantitative
Compare values across categories:
bar_chart:
metrics_view: sales_metrics
title: "Revenue by Product Category"
color: hsl(210, 70%, 50%)
x:
field: product_category
type: nominal
limit: 10
sort: -y
labelAngle: 0
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
With color dimension:
bar_chart:
metrics_view: sales_metrics
title: "Revenue by Category and Region"
color:
field: region
type: nominal
limit: 5
x:
field: product_category
type: nominal
limit: 8
sort: -y
y:
field: total_revenue
type: quantitative
Show cumulative values across categories or time:
stacked_bar:
metrics_view: sales_metrics
title: "Revenue Over Time by Region"
color:
field: region
type: nominal
limit: 5
x:
field: order_date
type: temporal
limit: 20
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
With multiple measures:
stacked_bar:
metrics_view: sales_metrics
title: "Cost Breakdown Over Time"
color:
field: rill_measures
type: value
legendOrientation: top
x:
field: order_date
type: temporal
limit: 20
y:
field: cost_of_goods
fields:
- cost_of_goods
- shipping_cost
- marketing_cost
type: quantitative
zeroBasedOrigin: true
Show proportional distribution (100% stacked):
stacked_bar_normalized:
metrics_view: sales_metrics
title: "Revenue Share by Region"
color:
field: region
type: nominal
limit: 5
x:
field: order_date
type: temporal
limit: 20
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
With custom color mapping for measures:
stacked_bar_normalized:
metrics_view: inventory_metrics
title: "Inventory Status Distribution"
color:
field: rill_measures
type: value
legendOrientation: top
colorMapping:
- value: "in_stock"
color: hsl(120, 60%, 50%)
- value: "low_stock"
color: hsl(45, 90%, 50%)
- value: "out_of_stock"
color: hsl(0, 70%, 50%)
x:
field: report_date
type: temporal
limit: 20
y:
field: in_stock
fields:
- in_stock
- low_stock
- out_of_stock
type: quantitative
Show magnitude over time with optional stacking:
area_chart:
metrics_view: sales_metrics
title: "Order Volume Over Time"
color: primary
x:
field: order_date
type: temporal
limit: 30
y:
field: order_count
type: quantitative
zeroBasedOrigin: true
With color dimension:
area_chart:
metrics_view: sales_metrics
title: "Revenue by Channel"
color:
field: sales_channel
type: nominal
limit: 4
x:
field: order_date
type: temporal
limit: 20
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
Show proportional breakdown:
donut_chart:
metrics_view: sales_metrics
title: "Revenue by Region"
innerRadius: 50
color:
field: region
type: nominal
limit: 8
sort: -measure
measure:
field: total_revenue
type: quantitative
showTotal: true
Show patterns across two dimensions:
heatmap:
metrics_view: activity_metrics
title: "Activity by Day and Hour"
color:
field: event_count
type: quantitative
x:
field: day_of_week
type: nominal
limit: 7
y:
field: hour_of_day
type: nominal
limit: 24
sort: -color
With custom color range:
heatmap:
metrics_view: performance_metrics
title: "Performance Score Matrix"
color:
field: score
type: quantitative
colorRange:
mode: scheme
scheme: sequential
x:
field: category
type: nominal
limit: 10
y:
field: subcategory
type: nominal
limit: 15
With custom Vega-Lite config for colors:
heatmap:
metrics_view: utilization_metrics
title: "Resource Utilization"
vl_config: |
{
"range": {
"heatmap": ["#F4A261", "#D63946", "#457B9D"]
}
}
color:
field: utilization_rate
type: quantitative
x:
field: resource_name
type: nominal
limit: 20
y:
field: time_slot
type: nominal
limit: 12
Combine bar and line on dual axes:
combo_chart:
metrics_view: sales_metrics
title: "Revenue and Order Count"
color:
field: measures
type: value
legendOrientation: top
x:
field: order_date
type: temporal
limit: 20
y1:
field: total_revenue
type: quantitative
mark: bar
zeroBasedOrigin: true
y2:
field: order_count
type: quantitative
mark: line
zeroBasedOrigin: true
With custom color mapping:
combo_chart:
metrics_view: funnel_metrics
title: "Conversions and Conversion Rate"
color:
field: measures
type: value
legendOrientation: top
colorMapping:
- value: "Conversions"
color: hsl(210, 100%, 73%)
- value: "Conversion Rate"
color: hsl(280, 70%, 55%)
x:
field: event_date
type: temporal
limit: 30
y1:
field: conversions
type: quantitative
mark: bar
y2:
field: conversion_rate
type: quantitative
mark: line
Show flow through stages or conversion processes:
funnel_chart:
metrics_view: conversion_metrics
title: "Conversion Funnel"
breakdownMode: dimension
color: stage
mode: width
stage:
field: funnel_stage
type: nominal
limit: 10
measure:
field: user_count
type: quantitative
With multiple measures breakdown:
funnel_chart:
metrics_view: engagement_metrics
title: "Engagement Funnel"
breakdownMode: measures
color: value
mode: width
measure:
field: impressions
type: quantitative
fields:
- impressions
- clicks
- signups
- purchases
Breakdown modes and color options:
breakdownMode: dimension with color: stage (different colors per stage) or color: measure (similar colors by value)breakdownMode: measures with color: name (different colors per measure) or color: value (similar colors by value)Create pivot tables with row and column dimensions:
pivot:
metrics_view: sales_metrics
title: "Sales by Region and Category"
row_dimensions:
- region
- product_category
col_dimensions:
- quarter
measures:
- total_revenue
- order_count
- average_order_value
Simple pivot (rows only):
pivot:
metrics_view: sales_metrics
row_dimensions:
- region
col_dimensions: []
measures:
- total_revenue
- order_count
- margin_rate
Display tabular data with specified columns:
table:
metrics_view: sales_metrics
title: "Product Performance"
description: "Detailed breakdown of product metrics"
columns:
- product_name
- product_category
- total_revenue
- order_count
- average_price
With dimension filters:
table:
metrics_view: sales_metrics
title: "North America Sales"
columns:
- product_name
- total_revenue
- order_count
dimension_filters: region IN ('North America')
Display external images:
image:
url: https://example.com/logo.png
alignment:
horizontal: center
vertical: middle
Build fully custom visualizations using Metrics SQL queries and Vega-Lite specifications. Use this when the built-in chart types are insufficient and you need complete control over the visualization.
Custom charts use metrics_sql to query data from metrics views and vega_spec to define the Vega-Lite visualization. The data from each query is available in the Vega-Lite spec as named datasets: query1, query2, etc.
metrics_sql lets you write SELECT queries against metrics views as virtual tables. Each metrics view exposes its dimensions and measures as columns.
Query rules:
SUM(), COUNT(), AVG(), or other aggregate functionsGROUP BY unless combining with expressions like date_trunc()date_trunc('<grain>', <time_dimension>) for time bucketing (grain: minute, hour, day, week, month, quarter, year)ORDER BY for deterministic resultsLIMIT to keep result sets reasonable (default to 50 for top-N queries, 500 for time series)WHERE clauses for themquery1, query2, etc.Example queries:
Single view:
SELECT publisher, total_bids, bid_price FROM bids_metrics ORDER BY total_bids DESC LIMIT 20
Time series:
SELECT date_trunc('day', __time) as day, impressions, revenue FROM ad_metrics ORDER BY day
Cross-view (two queries):
-- query1:
SELECT campaign, spend FROM spend_metrics ORDER BY spend DESC LIMIT 10
-- query2:
SELECT campaign, conversions FROM conversion_metrics ORDER BY conversions DESC LIMIT 10
{"name": "query1"}, {"name": "query2"}, etc. Do not include "data": {"values": [...]} sections; data comes from query results"width": "container" and "height": "container" so the chart fills its parent"autosize": {"type": "fit"} at the top level of the specdisplay_name values from the metrics view schema for axis titles, legend titles, and tooltip labelsformat_d3 or format_preset from measure metadata to axis and tooltip format strings"type": "temporal" and choose an appropriate timeUnit"layer" or "concat" composition operatorsSimple custom chart with one query:
custom_chart:
metrics_sql:
- |
SELECT publisher, total_bids, bid_price
FROM bids_metrics
ORDER BY total_bids DESC
LIMIT 20
vega_spec: |
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": "container",
"height": "container",
"autosize": {"type": "fit"},
"data": {"name": "query1"},
"mark": "bar",
"encoding": {
"x": {"field": "publisher", "type": "nominal", "sort": "-y", "axis": {"labelAngle": -45}},
"y": {"field": "total_bids", "type": "quantitative", "title": "Total Bids"},
"tooltip": [
{"field": "publisher", "type": "nominal"},
{"field": "total_bids", "type": "quantitative", "title": "Total Bids"},
{"field": "bid_price", "type": "quantitative", "title": "Bid Price", "format": ",.2f"}
]
}
}
Time series custom chart:
custom_chart:
metrics_sql:
- |
SELECT date_trunc('day', __time) as day, impressions, revenue
FROM ad_metrics
ORDER BY day
LIMIT 500
vega_spec: |
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": "container",
"height": "container",
"autosize": {"type": "fit"},
"data": {"name": "query1"},
"layer": [
{
"mark": {"type": "line", "color": "#4C78A8"},
"encoding": {
"x": {"field": "day", "type": "temporal", "title": "Date"},
"y": {"field": "impressions", "type": "quantitative", "title": "Impressions"}
}
}
],
"encoding": {
"tooltip": [
{"field": "day", "type": "temporal", "title": "Date"},
{"field": "impressions", "type": "quantitative", "title": "Impressions"},
{"field": "revenue", "type": "quantitative", "title": "Revenue", "format": "$,.2f"}
]
}
}
Custom chart with multiple queries (cross-view):
custom_chart:
metrics_sql:
- |
SELECT campaign, spend
FROM spend_metrics
ORDER BY spend DESC
LIMIT 10
- |
SELECT campaign, conversions
FROM conversion_metrics
ORDER BY conversions DESC
LIMIT 10
vega_spec: |
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": "container",
"height": "container",
"autosize": {"type": "fit"},
"layer": [
{
"data": {"name": "query1"},
"mark": {"type": "bar", "opacity": 0.7},
"encoding": {
"x": {"field": "campaign", "type": "nominal", "sort": "-y"},
"y": {"field": "spend", "type": "quantitative", "title": "Spend"},
"color": {"datum": "Spend"}
}
},
{
"data": {"name": "query2"},
"mark": {"type": "line", "point": true},
"encoding": {
"x": {"field": "campaign", "type": "nominal"},
"y": {"field": "conversions", "type": "quantitative", "title": "Conversions", "axis": {"orient": "right"}},
"color": {"datum": "Conversions"}
}
}
]
}
Additional guidelines:
vega_spec field must contain valid JSON (not YAML) as a stringprompt field is optional and stores the user's natural language description for referencenominal: Categorical data (strings, categories). Use for dimensions.temporal: Time-based data (dates, timestamps). Use for time dimensions.quantitative: Numerical data (counts, amounts). Use for measures.value: Special type for multiple measures. Use only in color field with rill_measures.x:
field: category_name # Field name from metrics view
type: nominal # Data type
limit: 10 # Max values to display
sort: -y # Sort order (see below)
showNull: true # Include null values
labelAngle: 45 # Label rotation angle
"x" or "-x": Sort by x-axis values (ascending/descending)"y" or "-y": Sort by y-axis values (ascending/descending)"color" or "-color": Sort by color field (heatmaps)"measure" or "-measure": Sort by measure (donut charts)["Mon", "Tue", "Wed"])y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true # Start y-axis at zero
Multiple measures:
y:
field: revenue
fields:
- revenue
- cost
- profit
type: quantitative
Simple color string:
color: primary # Named color
color: secondary
color: "#FF5733" # Hex color
color: hsl(210, 70%, 50%) # HSL color
Field-based color:
color:
field: region
type: nominal
limit: 10
legendOrientation: top # top, bottom, left, right, none
Custom color mapping:
color:
field: status
type: nominal
colorMapping:
- value: "success"
color: hsl(120, 70%, 45%)
- value: "warning"
color: hsl(45, 90%, 50%)
- value: "error"
color: hsl(0, 70%, 50%)
Color scheme:
color:
field: score
type: quantitative
colorRange:
mode: scheme
scheme: sequential
Use rill_measures in the color field when displaying multiple measures in stacked charts:
color:
field: rill_measures
type: value
legendOrientation: top
y:
field: revenue
fields:
- revenue
- cost
- profit
type: quantitative
Filter component data without affecting other components:
kpi_grid:
metrics_view: sales_metrics
measures:
- total_revenue
dimension_filters: region IN ('North America') AND status IN ('active')
Override the default time range for a specific component:
heatmap:
metrics_view: activity_metrics
time_range:
preset: last_7_days
# ... other config
Override time settings with detailed control:
stacked_bar:
metrics_view: sales_metrics
time_filters: tr=P12M&compare_tr=rill-PY&grain=week
# ... other config
Customize chart appearance with Vega-Lite config:
bar_chart:
metrics_view: sales_metrics
vl_config: |
{
"axisX": {
"grid": true,
"labelAngle": 45
},
"range": {
"category": ["#D63946", "#457B9D", "#F4A261", "#2A9D8F"]
}
}
# ... other config
type: canvas
display_name: "Monthly Business Report"
defaults:
time_range: P30D
comparison_mode: time
max_width: 1400
theme: corporate_theme
rows:
- height: 100px
items:
- width: 12
markdown:
content: |
# Monthly Business Report
Comprehensive overview of business performance metrics.
---
alignment:
horizontal: center
vertical: middle
- height: 50px
items:
- width: 12
markdown:
content: "## Key Metrics"
alignment:
horizontal: left
vertical: middle
- height: 200px
items:
- width: 12
kpi_grid:
metrics_view: business_metrics
measures:
- revenue
- profit
- customers
- orders
comparison:
- percent_change
- previous
sparkline: right
- height: 50px
items:
- width: 12
markdown:
content: "## Revenue Analysis"
alignment:
horizontal: left
vertical: middle
- height: 400px
items:
- width: 8
combo_chart:
metrics_view: business_metrics
title: "Revenue and Profit Margin"
color:
field: measures
type: value
legendOrientation: top
x:
field: report_date
type: temporal
limit: 30
y1:
field: revenue
type: quantitative
mark: bar
y2:
field: profit_margin
type: quantitative
mark: line
- width: 4
donut_chart:
metrics_view: business_metrics
title: "Revenue by Segment"
innerRadius: 50
color:
field: customer_segment
type: nominal
limit: 5
measure:
field: revenue
type: quantitative
showTotal: true
- height: 50px
items:
- width: 12
markdown:
content: "## Regional Performance"
alignment:
horizontal: left
vertical: middle
- height: 350px
items:
- width: 6
leaderboard:
metrics_view: business_metrics
dimensions:
- region
measures:
- revenue
- profit
- order_count
num_rows: 8
- width: 6
heatmap:
metrics_view: business_metrics
title: "Revenue by Region and Product"
color:
field: revenue
type: quantitative
x:
field: product_category
type: nominal
limit: 8
y:
field: region
type: nominal
limit: 6