Skip to content

Dashboards

Bridge Town dashboards are self-contained HTML pages generated from a successfully completed model run. Each dashboard is backed by a single key in the run’s outputs dict — the same dict your model writes to /outputs/ during execution. The HTML is stored in S3 and can be viewed inside the web app or shared via a time-limited pre-signed URL.

  • A Bridge Town account with at least one project
  • A model that writes chart-compatible data to /outputs/
  • At least Editor access to the project (to create dashboards)
  • A Pro subscription (to generate shareable links)
Model run succeeds
Outputs dict contains chart data under a named key
create_dashboard(project, dashboard_name, chart_type, model_run_id, output_key)
HTML generated (Plotly or AG Grid) → uploaded to S3 → Dashboard record saved
share_dashboard(project, dashboard_name, expires_hours) → pre-signed URL + iframe snippet

Step 1 — Write chart-compatible output in your model

Section titled “Step 1 — Write chart-compatible output in your model”

During a model run, anything your script writes to /outputs/<filename> is collected into the run’s outputs dict. Dashboard creation reads one key from that dict, so you must produce data that matches the schema for your chosen chart type.

The output_key parameter in create_dashboard is the full filename (including the .json extension) that you wrote to /outputs/.

Call list_chart_types to see every supported chart and its required schema:

{
"name": "list_chart_types",
"arguments": {}
}

Response:

{
"chart_types": [
{
"name": "bar",
"description": "Vertical bar chart — compare values across categories.",
"required_keys": ["categories", "values"],
"example_schema": "{\"categories\": [...], \"values\": [...], \"series_name\"?: \"Value\"}"
},
{
"name": "line",
"description": "Line chart over time — single series or multi-series trend.",
"required_keys": ["x"],
"example_schema": "{\"x\": [...], \"y\": [...]} or multi-series {\"x\": [...], \"series\": [{\"name\": \"...\", \"values\": [...]}]}"
},
{
"name": "table",
"description": "Sortable Plotly table — columnar data with row values.",
"required_keys": ["columns", "rows"],
"example_schema": "{\"columns\": [\"Col1\", ...], \"rows\": [[v1, v2, ...], ...]}"
},
{
"name": "waterfall",
"description": "Bridge/waterfall chart — shows cumulative positive/negative changes; last element is always rendered as the running total.",
"required_keys": ["labels", "values"],
"example_schema": "{\"labels\": [...], \"values\": [...]} (last element rendered as total)"
},
{
"name": "revenue_waterfall",
"description": "FP&A waterfall chart with Bridge Town colour palette — revenue bridge from start to end; last element rendered as total.",
"required_keys": ["labels", "values"],
"example_schema": "{\"labels\": [...], \"values\": [...]} (last element rendered as total)"
},
{
"name": "expense_breakdown",
"description": "Horizontal bar chart of expense categories — sorted largest to smallest.",
"required_keys": ["categories", "values"],
"example_schema": "{\"categories\": [...], \"values\": [...], \"series_name\"?: \"Expense\"}"
},
{
"name": "headcount_timeline",
"description": "Headcount trend line — single series or stacked multi-series breakdown.",
"required_keys": ["x"],
"example_schema": "{\"x\": [...], \"y\": [...]} or {\"x\": [...], \"series\": [{\"name\": \"...\", \"values\": [...]}], \"stacked\"?: true}"
},
{
"name": "cash_flow_projection",
"description": "Actual vs projected cash flow — months on the x-axis; either 'actual' or 'projected' series may be omitted for months where unavailable.",
"required_keys": ["months"],
"example_schema": "{\"months\": [...], \"actual\"?: [...], \"projected\"?: [...]}"
},
{
"name": "data_grid",
"description": "Interactive AG Grid table — supports sorting, filtering, and CSV export. Accepts object rows or simplified array rows.",
"required_keys": ["columns", "rows"],
"example_schema": "{\"columns\": [{\"field\": \"Name\", \"type\"?: \"text|number|date\"}, ...], \"rows\": [{...}]} or simplified {\"columns\": [\"Name\", ...], \"rows\": [[\"Alice\", ...], ...]}"
}
],
"count": 9
}

Once you have a successful run with compatible output, call create_dashboard.

Model output (/outputs/quarterly_revenue.json):

{
"categories": ["Q1", "Q2", "Q3", "Q4"],
"values": [1200000, 1350000, 1500000, 1800000],
"series_name": "Revenue"
}

Tool call:

{
"name": "create_dashboard",
"arguments": {
"project_name": "forecasts",
"dashboard_name": "Q4 Revenue",
"chart_type": "bar",
"model_run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"output_key": "quarterly_revenue.json",
"description": "Quarterly revenue breakdown",
"subtitle": "FY2026",
"currency": "USD"
}
}

Response:

{
"dashboard_id": "d1e2f3a4-b5c6-7890-def1-234567890abc",
"dashboard_name": "Q4 Revenue",
"project": "forecasts",
"chart_type": "bar",
"model_run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"has_html": true,
"created": true,
"created_at": "2026-04-28T10:00:00+00:00",
"updated_at": "2026-04-28T10:00:00+00:00"
}

Model output (/outputs/plan_vs_actual.json):

{
"columns": ["Metric", "Budget", "Actual", "Variance"],
"rows": [
["Revenue", 5000000, 5200000, 200000],
["COGS", 2000000, 2100000, -100000],
["Gross Profit", 3000000, 3100000, 100000],
["OpEx", 1500000, 1450000, 50000],
["Net Income", 1500000, 1650000, 150000]
]
}

Tool call:

{
"name": "create_dashboard",
"arguments": {
"project_name": "forecasts",
"dashboard_name": "P&L Summary",
"chart_type": "table",
"model_run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"output_key": "plan_vs_actual.json",
"currency": "USD"
}
}

Model output (/outputs/headcount.json):

{
"x": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
"series": [
{"name": "Engineering", "values": [30, 32, 35, 38, 40, 42]},
{"name": "Sales", "values": [10, 12, 12, 14, 16, 18]},
{"name": "G&A", "values": [5, 5, 6, 6, 6, 7]}
]
}

Tool call:

{
"name": "create_dashboard",
"arguments": {
"project_name": "hr-planning",
"dashboard_name": "Headcount Trend",
"chart_type": "line",
"model_run_id": "b2c3d4e5-f6a7-8901-bcde-f2345678901b",
"output_key": "headcount.json",
"subtitle": "H1 2026"
}
}

Model output (/outputs/revenue_bridge.json):

{
"labels": ["Opening ARR", "New ARR", "Expansion", "Churn", "Closing ARR"],
"values": [5000000, 1500000, 500000, -300000, 6700000]
}

Tool call:

{
"name": "create_dashboard",
"arguments": {
"project_name": "forecasts",
"dashboard_name": "ARR Bridge",
"chart_type": "revenue_waterfall",
"model_run_id": "c3d4e5f6-a7b8-9012-cdef-3456789012cd",
"output_key": "revenue_bridge.json",
"currency": "USD"
}
}

Cash flow projection (/outputs/cash_flow.json):

{
"months": ["Jan", "Feb", "Mar", "Apr"],
"actual": [100000, 120000, null, null],
"projected": [null, null, 130000, 140000]
}

Tool call:

{
"name": "create_dashboard",
"arguments": {
"project_name": "forecasts",
"dashboard_name": "Cash Flow",
"chart_type": "cash_flow_projection",
"model_run_id": "d4e5f6a7-b8c9-0123-defa-4567890123de",
"output_key": "cash_flow.json",
"currency": "USD"
}
}

Model output (/outputs/customers.json):

{
"columns": [
{"field": "Name", "type": "text"},
{"field": "ARR", "type": "number"},
{"field": "Renewal Date", "type": "date"}
],
"rows": [
{"Name": "Acme Corp", "ARR": 240000, "Renewal Date": "2026-06-15"},
{"Name": "Globex", "ARR": 120000, "Renewal Date": "2026-09-01"}
]
}

Tool call:

{
"name": "create_dashboard",
"arguments": {
"project_name": "sales",
"dashboard_name": "Customer List",
"chart_type": "data_grid",
"model_run_id": "e5f6a7b8-c9d0-1234-efab-5678901234ef",
"output_key": "customers.json"
}
}

Use list_dashboards to see what has already been created for a project:

{
"name": "list_dashboards",
"arguments": {
"project_name": "forecasts"
}
}

Response:

{
"project": "forecasts",
"dashboards": [
{
"name": "Q4 Revenue",
"description": "Quarterly revenue breakdown",
"is_shared": false,
"has_html": true,
"created_at": "2026-04-28T10:00:00+00:00",
"updated_at": "2026-04-28T10:00:00+00:00"
},
{
"name": "ARR Bridge",
"description": null,
"is_shared": true,
"has_html": true,
"created_at": "2026-04-27T14:30:00+00:00",
"updated_at": "2026-04-27T16:00:00+00:00"
}
],
"count": 2
}

Call share_dashboard to generate a time-limited, anonymous pre-signed URL:

{
"name": "share_dashboard",
"arguments": {
"project_name": "forecasts",
"dashboard_name": "ARR Bridge",
"expires_hours": 48
}
}

Response:

{
"dashboard_name": "ARR Bridge",
"project": "forecasts",
"share_url": "https://s3.example.com/dashboards/...?X-Amz-Expires=172800",
"expires_hours": 48,
"embed_snippet": "<iframe\n src=\"https://s3.example.com/dashboards/...\"\n title=\"ARR Bridge\"\n width=\"100%\"\n height=\"600px\"\n frameborder=\"0\"\n allowfullscreen\n></iframe>"
}

The embed_snippet is a ready-to-paste <iframe> that works in Notion, Confluence, or any HTML page. The URL expires after the requested window (default 24 hours, maximum 168 hours / 7 days).

The Bridge Town web app provides a first-class dashboard experience alongside the MCP tools.

After logging in, navigate to Dashboards in the sidebar. The gallery page (/dashboards) lists every dashboard saved by your tenant, ordered by most-recently updated first. Each card shows:

  • Dashboard name and description
  • Whether the dashboard has rendered HTML (has_html)
  • Whether it has been shared (is_shared)
  • Creation and update timestamps

Click any card to open the full-screen viewer. A delete action is available on each card.

The viewer page fetches a fresh pre-signed S3 URL for the dashboard HTML and renders it in a sandboxed iframe:

  • allow-scripts is required for Plotly interactivity
  • allow-popups lets linked drill-downs open in new tabs
  • Navigation and form submission are blocked

If the dashboard HTML has not been generated yet, or if S3 is not configured, the page shows an error state with a link back to the gallery.

Share URLs generated by share_dashboard are anonymous — anyone with the link can view the dashboard without signing in. There is no sign-in gate on the share URL. Because the URL is pre-signed S3, it works even if the Bridge Town web app is temporarily unavailable.

To revoke access before the expiry, call share_dashboard again with a short expires_hours (e.g. 1) or delete the dashboard entirely. There is no explicit “revoke share” tool — shortening the TTL is the recommended method.

ActionRequired accessPlan restriction
list_chart_typesNone (static metadata)None
create_dashboardEditor or Owner on projectNone
list_dashboardsViewer or higher on projectNone
share_dashboardViewer or higher on projectPro required
View in web app (/dashboards)Authenticated tenant memberNone
Delete in web appAuthenticated tenant memberNone
ErrorCauseFix
output-not-foundThe output_key does not exist in the run’s outputs dictCheck the filename your model wrote to /outputs/ and pass the full filename including .json
output-shape-invalidThe output dict exists but is missing required keys or has the wrong typeCompare your data against the schema returned by list_chart_types
model_run_id … not a valid UUIDMalformed run IDCopy the exact run_id from run or list_runs
status failedThe model run did not complete successfullyRe-run the model and wait for success status
Dashboard … not founddashboard_name does not exist in the projectCheck spelling or call list_dashboards to see valid names
expires_hours must be between 1 and 168TTL out of rangePass a value between 1 and 168 hours
Pro subscription requiredFree-tier tenant calling share_dashboardUpgrade to Pro in Settings → Billing
ToolDescription
list_chart_typesDiscover supported charts and their schemas
create_dashboardGenerate and save dashboard HTML from a model run
list_dashboardsList saved dashboards for a project
share_dashboardGenerate a pre-signed share URL and iframe snippet
runExecute a model and produce the outputs dict
list_runsFind completed runs and their run_id values