CLI reference
Everything Dashdown does from the terminal goes through the dashdown command.
Each subcommand takes a project directory (default .) and prints its result
to stdout — diagnostics and row counts go to stderr, so json/csv output
pipes cleanly. This page is the quick reference; the deeper guides linked below
cover each feature in full.
dashdown --help # list every command
dashdown <command> --help # options for one command
| Command | What it does |
|---|---|
serve |
Run the project locally with live-reload |
new |
Scaffold a new project |
check |
Validate the project without serving or running queries |
connectors |
List (and optionally probe) the configured connectors |
query |
Run raw SQL/DAX against a connector |
metric |
Query the semantic layer by metric + grouping |
build |
Export a static site (HTML + pre-rendered data) |
pdf |
Export a presentation PDF |
screenshot |
Capture a page to a PNG and verify its charts drew |
embed-token |
Mint a signed token for an authenticated embed |
serve #
Serve the project on a local web server, watching files and live-reloading the browser as you edit pages, components, queries, config, and data.
dashdown serve . # http://127.0.0.1:8000
dashdown serve my-dashboard --port 8001
dashdown serve . --host 0.0.0.0 --no-watch
| Option | Default | Notes |
|---|---|---|
--host |
127.0.0.1 |
Bind address. Use 0.0.0.0 to expose on your LAN. |
--port |
8000 |
Bind port. |
--no-watch |
off | Disable the file watcher (no live-reload). |
The watcher reloads on changes under pages/, components/, data/, assets/,
queries/, semantic/, and to dashdown.yaml / sources.yaml. Page edits
hot-reload the browser; config, sources, components, queries, and data trigger a
full project reload (connectors are rebuilt). See Getting started.
new #
Scaffold a new project directory — a dashdown.yaml, sources.yaml, a sample
page and data, and a tool-agnostic AGENTS.md authoring guide (plus a per-tool skill)
so a coding agent opening the project knows the platform.
dashdown new my-dashboard
cd my-dashboard
dashdown serve .
--target picks which coding agents to set up a wrapper for (default claude); the
choice is recorded in dashdown.yaml as agents:, so later dashdown skill runs keep
them in sync. AGENTS.md + references/ are always installed regardless.
dashdown new my-dashboard --target claude,cursor # set up both wrappers
See Coding agents for the supported tools.
skill #
Install or update the bundled coding-agent guide (AGENTS.md + the references/
shards + a per-tool authoring skill) in an existing project. The guide is
versioned with the framework, so a project scaffolded on an older release pulls the
current one without re-scaffolding. See Coding agents for the full
story.
dashdown skill # fill in anything missing (keeps your local edits)
dashdown skill --refresh # overwrite to this version's guide (prunes stale shards)
dashdown skill -p ./dashboard # target another project directory
dashdown skill --target cursor # also/instead install the Cursor wrapper
Which tools it installs for resolves by precedence: an explicit --target a,b → the
project's dashdown.yaml agents: list → tools it auto-detects (a marker dir like
.claude/ or .cursor/ already present) → claude.
check #
Validate the project without serving it or running any queries. It loads
dashdown.yaml/sources.yaml, the query library, and the semantic models
(surfacing any config or parse error), then renders every page — queries are
never executed during render — and reports component/render errors (an unknown
tag, a bad attribute). Exits non-zero if anything is wrong, so it's the fast
edit→validate loop for authors and coding agents.
dashdown check
dashdown check -p my-dashboard
| Option | Default | Notes |
|---|---|---|
-p, --project |
. |
Project directory. |
A clean project prints ✓ project is valid; otherwise each problem is listed as
✗ <page>: <message> on stderr and the command exits 1.
connectors #
List the connectors configured in sources.yaml with their types — a quick way
to see what data sources a project has before writing SQL against them.
dashdown connectors
dashdown connectors --test -p my-dashboard
| Option | Default | Notes |
|---|---|---|
-p, --project |
. |
Project directory. |
--test |
off | Probe each connector with a trivial SELECT 1 to confirm it connects. A dax connector takes DAX, not SQL, so its probe is skipped. |
With --test, each connector is marked ✓ reachable or ✗ <error>. See
Connectors.
query #
Run a SQL statement (or DAX, against a dax connector) verbatim against any
connector in sources.yaml and print the rows — without opening the app. The
quickest way to test that a connector connects and inspect real data while
authoring; an agent uses it the same way.
dashdown query "SELECT * FROM sales LIMIT 5" # connector: main
dashdown query "SELECT count(*) FROM orders" -c warehouse -f json
dashdown query "SELECT region, sum(amount) FROM sales GROUP BY region" -p .
| Option | Default | Notes |
|---|---|---|
-p, --project |
. |
Project directory. |
-c, --connector |
main |
Connector from sources.yaml. An unknown name lists the configured ones. |
-f, --format |
table |
table, json ({columns, rows}), or csv. |
--max-rows |
50 |
Cap rows printed (0 = all). Total count goes to stderr. |
--tables |
off | List the connector's tables/views (table/schema/type) and exit — instead of a SQL argument. |
--schema <table> |
— | Describe one table's columns (column/type/nullable) and exit. |
This runs raw SQL — there is no ${param} substitution here (that is a
page/query-library concern). A failing query prints the connector's error and
exits non-zero. See Connectors.
Schema introspection (--tables / --schema) #
Instead of hand-writing a SELECT * … LIMIT 0 (and remembering each warehouse's
information_schema dialect), ask the connector directly:
dashdown query --tables -c main # what tables/views exist?
dashdown query --schema sales -c main -f json # what columns does `sales` have?
Each connector knows how to answer in its own dialect, so the same two commands
work everywhere: SQL warehouses and DuckDB/CSV/Excel/Sheets answer via
information_schema; BigQuery qualifies it from a dataset:/location: in
sources.yaml; DAX (Fabric/Power BI) reads the model's INFO.VIEW.* metadata;
Cube lists its cubes and their measures/dimensions from /meta. A connector
that can't introspect prints a one-line hint and exits non-zero. The --schema
table name is matched as an escaped literal, never spliced as SQL.
For a semantic model's metrics and dimensions (not a connector's physical
tables), use metric --list instead.
metric #
Query the semantic layer by metric + grouping — the same
metric={sales.revenue} by={sales.region} grammar your chart components use,
compiled and pushed down to the warehouse. The semantic-layer counterpart of
query: where query probes a connector with raw SQL, metric probes a model
by name. Start with --list when you don't know the model.
dashdown metric --list # models + their metrics/dimensions
dashdown metric "sales.revenue" --by sales.region
dashdown metric "sales.revenue,sales.orders" -b sales.order_date -g month -f json
dashdown metric "sales.revenue" -b sales.region --param region=East
| Option | Default | Notes |
|---|---|---|
-l, --list |
off | List every model with its measures and dimensions (the time dimension is tagged [time]), then exit. |
-b, --by |
— | Group by a dimension (model.dim or bare dim). |
-s, --series |
— | Split into a coloured series by a second dimension. |
-g, --grain |
— | Bucket a time --by/--series (second…year). |
--param key=value |
— | Repeatable filter: a dimension name, or date_start/date_end for the model's time dimension. Passed as data, never substituted into SQL. |
-p, --project |
. |
Project directory. |
-f, --format |
table |
table, json, or csv. |
--max-rows |
50 |
Cap rows printed (0 = all). |
Needs the dashdown-md[semantic] extra. A bad model/metric/dimension prints the
resolution error (with the known names) and exits non-zero.
build #
Export the project to a static site — pre-rendered HTML plus each page's query data as JSON — that you can host on any static file server, with no Python backend.
dashdown build . # → <project>/.dist
dashdown build my-dashboard -o dist
python -m http.server -d .dist # preview the export
| Option | Default | Notes |
|---|---|---|
-p, --project |
. |
Project directory (positional). |
-o, --out |
<project>/.dist |
Output directory. |
Each query runs once at build time with default parameters; filter controls are
stripped (a fixed snapshot can't re-query). Dynamic [slug] pages are skipped, and
a failing query is recorded but doesn't abort the build. See Exporting.
pdf #
Export the project to a presentation PDF, rendered from the static export with headless Chromium so charts draw exactly as in the live app. By default the whole project is merged into one deck.
dashdown pdf . # combined deck → <project>/.pdf
dashdown pdf . --page /sales --orientation landscape
dashdown pdf . --separate --format Letter
| Option | Default | Notes |
|---|---|---|
-o, --out |
<project>/.pdf |
Output directory. |
--page |
all | Limit to one page URL (repeatable), e.g. --page /sales. |
--dist |
— | Reuse an existing static build instead of rebuilding. |
--separate |
off | One PDF per page instead of a combined deck. |
--orientation |
portrait |
portrait or landscape. |
--format |
A4 |
Page size: A4, Letter, Legal, A3, … |
--scale |
1.0 |
Render scale passed to Chromium (0.1–2.0). |
Requires the pdf extra and a one-time browser download:
pip install 'dashdown-md[pdf]'
playwright install chromium
See Exporting.
screenshot #
Capture a page to a PNG and report whether its charts actually drew. Charts
paint client-side with ECharts, so check confirms a page renders
but not that a chart painted; screenshot closes that gap. It drives headless
Chromium (the same engine as pdf) over the interactive page — no
print cover, no reflow — waits for the chart-render handshake, saves the image,
and prints a verdict: how many chart canvases drew vs stayed blank, plus any
browser console errors.
dashdown screenshot /sales # → <project>/.shots/sales.png
dashdown screenshot / --full-page -o home.png
dashdown screenshot /sales --server http://127.0.0.1:8000 # capture a running serve
| Argument / Option | Default | Notes |
|---|---|---|
page |
/ |
Page URL to capture (positional), e.g. /sales. |
-o, --out |
<project>/.shots/<page>.png |
Output PNG path. |
-p, --project |
. |
Project directory. |
--dist |
— | Reuse an existing static build instead of rebuilding. |
--server |
— | Capture a page from an already-running dashdown serve instead of building. |
--full-page |
off | Capture the full scroll height, not just the viewport. |
--width / --height |
1280 / 800 |
Viewport size in px (desktop, sidebar-visible layout). |
By default it builds and serves a static export — scoped to just the page you
name, so unrelated pages' queries (a slow or flaky external source elsewhere in
the project) never run — then captures it. It exits non-zero when a chart
fails to draw, so an agent or CI can use it as a visual gate. Requires the pdf
extra and the Chromium download (same as pdf):
pip install 'dashdown-md[pdf]'
playwright install chromium
See Exporting.
embed-token #
Mint a signed embed token and a ready-to-paste <script> snippet for one page.
Needed only when the dashboard has auth enabled — a cross-origin
iframe can't send credentials, so it carries a scoped token instead. Requires an
embed: block with a secret in dashdown.yaml.
dashdown embed-token . /sales
dashdown embed-token . /sales --ttl 3600 --host https://dash.example
| Argument / Option | Default | Notes |
|---|---|---|
page |
— | Page path to embed (positional), e.g. /sales. |
-p, --project |
. |
Project directory (positional). |
--ttl |
embed.token_ttl |
Token lifetime in seconds. |
--host |
— | Public dashboard origin baked into the snippet. |
The token is scoped to that exact page and the queries it reads, so it can't be replayed against other resources. See Embedding.