Dashboards
Dashboards turn your saved queries into interactive visual boards. Instead of exporting data to a separate BI tool, you build dashboards directly in Mako — same workspace, same connections, same AI agent.
How It Works
Section titled “How It Works”A dashboard is a collection of widgets (charts, KPI cards, data tables) powered by data sources (queries from your database connections). The data pipeline:
- Data sources — Each data source is a query tied to a database connection. When materialized, Mako executes the query server-side and builds a Parquet file.
- Materialization — The Parquet artifact is stored (filesystem, GCS, or S3 depending on deployment) and served to the browser.
- In-browser DuckDB — The browser loads the Parquet file into a local DuckDB-WASM instance. All widget queries run locally — no round-trips to the server after initial load.
- Widgets — Each widget has a
localSqlquery that runs against the DuckDB instance. Charts use Vega-Lite specs; KPI cards and tables have their own renderers. - Cross-filtering — Powered by Mosaic. Clicking a bar or slice in one chart automatically filters all other widgets sharing the same data source.
Database (Postgres, BQ, etc.) │ ▼ server-side query Parquet artifact (GCS/S3/filesystem) │ ▼ HTTP fetch DuckDB-WASM (in browser, per-dashboard instance) │ ▼ localSql per widget Vega-Lite charts / KPI cards / data tables │ ▼ Mosaic cross-filtering Selection changes propagate across widgetsData Sources
Section titled “Data Sources”A data source connects a dashboard to a database query. Each data source has:
id— unique identifier within the dashboardname— human-readable labeltableRef— the DuckDB table name used in widgetlocalSqlqueriesquery— the SQL/JavaScript/MongoDB query to execute against a database connectionorigin— optional link to a saved console (so you can trace where the query came from)timeDimension— optional column name for time-based filteringrowLimit— optional cap on rows materializedcomputedColumns— optional derived columns (SQL expressions computed at materialization time)
Data sources support SQL, JavaScript, and MongoDB query languages.
Materialization
Section titled “Materialization”When you create or refresh a dashboard, Mako:
- Executes each data source query against its database connection
- Streams results through server-side DuckDB (
@duckdb/node-api) to build Parquet files - Stores the artifacts with version-based cache keys
- Tracks build status per data source:
missing→queued→building→ready(orerror)
Materialization can be triggered manually or on a schedule (cron-based via Inngest). Each data source tracks its own status independently.
Artifact Storage
Section titled “Artifact Storage”Parquet artifacts are stored in one of three backends depending on deployment:
| Backend | Use Case |
|---|---|
filesystem | Local / self-hosted |
gcs | Google Cloud deployments |
s3 | AWS deployments |
The browser fetches artifacts via the API (/api/workspaces/:wid/dashboards/:did/materialization/stream/:dsId), which handles range requests for efficient loading.
Widgets
Section titled “Widgets”Dashboards support three widget types:
Charts (Vega-Lite)
Section titled “Charts (Vega-Lite)”Standard Vega-Lite specifications with a twist: the data block is omitted because data comes from the DuckDB instance. The widget’s localSql query feeds the chart.
Supported mark types: bar, line, area, point, arc (pie/donut), rect (heatmap), and more — anything Vega-Lite supports.
KPI Cards
Section titled “KPI Cards”Single-value displays with optional comparison (delta). Configuration:
valueField— which column to displayformat— number formatting (currency, percentage, etc.)comparisonField— optional field for period-over-period comparison
Data Tables
Section titled “Data Tables”Tabular display of query results. Supports column sorting, formatting, and pagination.
Responsive Layouts
Section titled “Responsive Layouts”Widgets use a grid layout system with responsive breakpoints. The lg breakpoint (12 columns) is the authored source of truth — the only one you (or the AI agent) need to set. The smaller breakpoints md (10 cols), sm (6 cols), and xs (4 cols) are deterministically reflowed from lg: rows of widgets wrap and tile cleanly so the board stays balanced on narrower screens.
Each widget carries position (x, y) and size (w, h) per breakpoint. To keep the reflow balanced, use grid-friendly widths that evenly tile a row (e.g. four KPIs at w: 3, three cards at w: 4, two charts at w: 6, or one full-width widget at w: 12).
Reflow is skipped per breakpoint when a user manually arranges that breakpoint: the affected widgets carry layouts[bp].custom === true, and Mako keeps the stored arrangement instead of recomputing it. Minimum widget sizes are enforced — widgets smaller than their type minimum are automatically enlarged.
Cross-Filtering
Section titled “Cross-Filtering”Cross-filtering is powered by Mosaic (@uwdata/mosaic-core). When a user clicks a bar, slice, or data point in one widget:
- A Mosaic selection is created with the filter predicate
- The Mosaic coordinator propagates the selection to all connected clients (widgets)
- Each widget re-runs its
localSqlwith the filter clause appended - Charts and tables update in real-time — all locally in DuckDB, no server calls
Cross-filtering can be disabled per widget via crossFilter.enabled: false.
Global Filters
Section titled “Global Filters”Dashboard-level filters that apply across all widgets:
- Date range pickers — filter by a time dimension column
- Dropdowns — filter by categorical values
- Search fields — text-based filtering
Global filters are defined at the dashboard level and injected into widget queries.
Multi-Dashboard Support
Section titled “Multi-Dashboard Support”Multiple dashboards can be open simultaneously, each with its own isolated DuckDB instance. The AI agent requires an explicit dashboardId on every tool call — there is no implicit “current dashboard.”
Edit Mode & Locking
Section titled “Edit Mode & Locking”Dashboards use an edit lock to prevent concurrent editing conflicts:
- Call
enter_edit_modeto acquire the lock - Make changes (add/remove widgets, modify data sources, etc.)
- Changes are saved automatically
- Lock releases when the user exits edit mode or navigates away
If another user holds the lock, a confirmation dialog offers to take over.
Version History
Section titled “Version History”Every save of a dashboard creates an immutable version snapshot (widgets, data sources, layout). Authors, timestamps, and commit comments are preserved. Restoring a past version creates a new version record referencing the one it came from — the timeline is append-only. See Version History for the full API and agent tools.
Commit Messages
Section titled “Commit Messages”When you save, the Save version dialog asks for a Commit message describing what changed — this labels the saved version in its history, not the dashboard itself.
Mako can suggest the commit message for you. On save, it diffs the pending dashboard definition against the latest saved snapshot (volatile cache state stripped) and folds in the chat prompts that drove the changes, then drafts a message with the cheap utility model. This mirrors how console versions work.
POST /api/workspaces/:wid/dashboards/:did/version-commentReturns { comment, diff } — the suggested message for the changes about to be saved (plus the diff it was based on). You can accept it as-is or edit before confirming the save.
Sharing & Collaborators
Section titled “Sharing & Collaborators”Dashboards, consoles, and apps use a single Google Workspace-style sharing model, managed from each resource’s Share dialog. Three layers stack on top of each other:
- Owner — the creator (
owner_id, falling back tocreatedBy) always has full access and can manage sharing. - Per-user collaborators (
sharedWith) — specific users granted aviewer(read-only) oreditor(read + write) role, independent of the resource’s general access scope. - General access (
access) —private(only the owner + collaborators) orworkspace(every workspace member). Whenworkspace, theworkspaceRolefield sets the role members get:viewer(default) oreditor. Workspace admins/owners always resolve toeditor; theviewermember role caps them at read-only.
Access resolution is first-match-wins in that order (see api/src/utils/resource-acl.ts). Admins do not gain access to private resources they neither own nor are shared on — this preserves the privacy guarantee. Only the owner, or a workspace owner/admin for non-private resources, can change sharing settings or manage collaborators.
Collaborators are stored on the resource’s sharedWith array ({ userId, role, addedAt, addedBy }), indexed for fast “shared with me” lookups.
Public links (dashboards & apps)
Section titled “Public links (dashboards & apps)”Dashboards and apps can be published as read-only public links at /share/:token, served without authentication:
- Public links serve materialized snapshots only (the last-materialized Parquet artifacts), never live database connections.
- An optional password protects the link. It is stored as a bcrypt hash, plus an AES-encrypted copy so the owner/admin can reveal it later in the UI.
- Links get a readable workspace/title slug (with conflict suffixes), support inline rename, token rotation, and a throttled anonymous refresh endpoint.
- Consoles do not support public links — they share via collaborators and workspace access only.
Scheduled Refresh
Section titled “Scheduled Refresh”Data sources can be refreshed on a schedule using cron expressions:
{ "enabled": true, "cron": "0 6 * * *", "timezone": "Europe/Zurich", "dataFreshnessTtlMs": 3600000}Scheduled refreshes run via Inngest, re-executing the source queries and rebuilding Parquet artifacts. The dataFreshnessTtlMs field controls how long cached data is considered fresh before triggering a new materialization.
AI Agent Integration
Section titled “AI Agent Integration”The Dashboard Agent is a specialized agent that helps create and manage dashboards via natural language. It can:
- Create dashboards from scratch or from saved consoles
- Add, modify, and remove widgets
- Write
localSqlqueries and Vega-Lite specs - Configure cross-filtering, global filters, and layouts
- Manage data sources and materialization
The agent activates automatically when you’re working on a dashboard tab. In the unified agent, modality triage routes dashboard-related requests to the dashboard toolset.