dct render¶
Render a dashboard to SVG, HTML, PNG, PDF, JSON, YAML, or terminal output.
Use "-" as the sole BOARDS argument to read YAML from stdin; useful for AI agents and shell pipelines that don't want to write a temp file.
Arguments¶
| Argument | Description |
|---|---|
BOARDS |
One or more paths to board YAML files, or "-" to read YAML from stdin. (required) |
A relative path is read from your working directory first, the way any shell command reads it, so dct render proj/board.yml --project-dir proj works from the parent of proj. A relative path that does not exist there is read against the project instead, including its charts/ directory, so dct render sales.yml finds charts/sales.yml from anywhere inside the project.
Every board renders inside a project: a directory holding a dbt_charts.yml. An empty file is enough, which is all a board of inline values data needs.
Options¶
| Flag | Description |
|---|---|
--output PATH |
Output file path. Default: board name with extension (.thumb.svg for thumbnail). Use "-" to write to stdout. |
--format TEXT |
Output format: svg, html, png, pdf, thumbnail, terminal, json, text, text-data, yaml, data. Default: svg. |
--project-dir PATH |
Project directory for resolving relative paths. |
--var KEY=VALUE |
Variable value (repeatable). |
--chart TEXT |
Render only this chart (by chart id). Valid only with a single board argument or stdin; rejected with multiple boards. |
--no-cache |
Bypass all query caches and re-run from scratch. |
--cache PATH |
Keep the query cache in this DuckDB file (created if absent). Default: a per-user file shared by every project. Also settable via DCT_CACHE_PATH. Mutually exclusive with --no-cache. |
--diagnostics-json |
Emit all diagnostics (errors and warnings) as JSON Lines to stderr. stdout stays the render payload on success; one compact JSON object per diagnostic is written to stderr, line-delimited so a truncated read still yields whole objects. |
--allow-chart-errors |
Allow per-chart errors (missing columns, query failures, data-shape problems caught while sizing the board) without exiting non-zero. Default: fail; see Warnings & CI behavior. |
--fail-fast |
Stop immediately on the first render failure. Default: continue rendering remaining boards, exit 1 at the end. |
--max-workers INT |
Maximum parallel query workers (default: 8, from config). Also settable via DCT_MAX_WORKERS. Effective only for external warehouse executors; DuckDB serializes access internally regardless of this setting. |
--no-warnings |
Suppress warning output to stderr. Warnings still appear in --format json output so agents and consumers always see them. |
--ignore-warning CODE |
Suppress a specific render-time warning code (repeatable). Suppressed warnings move to suppressed_warnings in --format json output. An unknown code, or an ERR-* code, exits 1. A retired warning code prints a WARN-RETIRED-IGNORE-CODE notice and does not change the exit code. |
--print0, -0 |
Delimit produced output paths with NUL (\0) instead of newline, for safe piping to xargs -0 when paths contain spaces. |
Warnings & CI behavior¶
By default, dct render exits 1 if any chart has a runtime error: a missing column, a failed query, an unresolvable reference, or a data-shape problem caught while the board is being sized (a NULL in a pie's theta column, say). This is the CI-safe contract: a broken chart fails the build instead of shipping a silently-partial dashboard.
For live previews and agent iteration, where a partial render is useful feedback rather than a failure, pass --allow-chart-errors to keep exit code 0 and render whatever charts succeeded.
Render-time warnings (as opposed to chart errors) print to stderr by default and don't affect the exit code. Use --no-warnings to silence them, or --ignore-warning CODE to suppress one specific code (repeatable):
The text output groups repeats: each warning code prints once per board with a count, the charts or queries it applies to, and the message and fix once. --diagnostics-json and --format json keep one entry per instance.
The Warning Reference lists every registered warning code and explains each.
Output formats¶
| Format | Purpose |
|---|---|
svg (default) |
Scalable vector graphics: embedding, presentations |
html |
Interactive HTML page with embedded charts |
png |
Raster image: screenshots, thumbnails |
pdf |
PDF document: reports, printing |
thumbnail |
Abstracted SVG preview: no readable text, same size as the board |
terminal |
ASCII/Unicode charts printed to stdout |
json |
Post-execution resolved layout + data |
text |
Plain text rendering |
yaml |
Compiled board YAML |
data |
Flat JSON: queries keyed by name, charts keyed by slug |
Examples¶
Static export¶
dct render charts/sales.yml # → charts/sales.svg
dct render charts/sales.yml --format html
dct render charts/sales.yml --format png
dct render charts/sales.yml --format pdf
dct render charts/sales.yml --output sales.svg
Variables¶
Terminal preview¶
================================================================================
Sales Dashboard
================================================================================
Daily Revenue Trend
┌────────────────────────────────────────────────────────────┐
2400.0┤ ▗ ▟ ▗ │
2141.7┤ ▄▀▖ ▞ ▚ ▞▖ ▄▀▖ │
...
Terminal format writes directly to stdout. Use shell redirection to save:
Reading YAML from stdin¶
dct render - --format terminal --project-dir . <<'EOF'
charts:
revenue:
query:
source: ./data/sales.csv
sql: SELECT * FROM sales
type: bar
x: region
y: revenue
rows:
- revenue
EOF
There's no type: csv / file: query shape; a file is referenced with source: pointing at the path, and the table name is the file's stem (sales.csv → sales). See Inline File Sources.
echo 'charts: {revenue: {query: {source: ./data/sales.csv, sql: "SELECT * FROM sales"}, type: bar, x: region, y: revenue}}
rows: [revenue]' | dct render - --format terminal
--project-dir controls where relative file paths (CSVs, etc.) are resolved from when reading stdin. Defaults to cwd.
CI artifact generation¶
for board in charts/*.yml; do
dct render "$board" \
--format html \
--output "dist/$(basename "$board" .yml).html"
done
Inspect resolved layout for debugging¶
Get exact values in the text summary¶
--format text (the default for agent tools) is a compact per-chart summary:
field mappings and value ranges, not the rows themselves. Use --format text-data
to append a ## Data section with every query's actual rows, grouped by query
name (a query shared by several charts is dumped once). Rows are capped the
same way every other row-embedding format caps them (--max-rows-per-query,
uncapped by default), with an explicit "showing N of M rows" note when capped.
Extract the underlying data¶
--format data drops the layout and returns two flat maps: queries keyed by
query name, and charts keyed by slug. Each query carries its SQL and exactly
one copy of its rows; charts reference their query by name rather than
embedding the rows, so charts sharing a query do not repeat the result set.
{
"id": "sales",
"title": "Sales",
"queries": {
"monthly": {
"type": "sql",
"sql": "SELECT month, revenue FROM dundersign_serving.monthly_metrics ORDER BY month",
"rows": [{"month": "2026-01", "revenue": 48210}]
}
},
"charts": {
"revenue_trend": {
"type": "line",
"query": "monthly",
"title": "Revenue Trend",
"x": "month",
"y": "revenue"
}
}
}
SQL is emitted as compiled; Jinja variable references are left
uninterpolated, and the values they resolve to appear under variables. A row
cap declares itself on the affected query as
rows_truncated: {head, tail, total}; rows are never dropped silently.
Origin-absolute links in exports¶
By default, links inside rendered HTML and SVG files are root-relative
(/org/proj/d/slug/). That works when the file is served from the same host,
but breaks when it is opened as a standalone file (file://) or embedded
cross-origin.
Set public_url in your dbt_charts.yml to make export links fully-qualified:
public_url: "https://dashboards.example.com"
With that setting, dct render produces https://dashboards.example.com/d/slug/
instead of /d/slug/. Leave it unset (the default, empty string) for local use.
Caching¶
Query results are cached on disk by default, so rendering a board again does not send its queries to the warehouse again. A result is reused until it is older than the query's cache: duration, which is 24 hours unless you set it.
When a render reuses cached warehouse results it says so on stderr, with the age of the oldest one:
A render that ran every query prints nothing. Pass --no-cache to skip the cache and run every query from scratch.
A query that reads only a data file (CSV, JSON, Parquet, or a DuckDB or SQLite database) re-runs on its own when the file changes, so reusing its result prints nothing. Incremental queries, and DuckDB sources that can read other data (attach, extensions, secrets), still print the notice.
Where the cache lives¶
With no flag, the cache is one file in your user cache directory, shared by every project and by dct serve:
| Platform | Path |
|---|---|
| macOS | ~/Library/Caches/com.getdbt/charts/query-cache.duckdb |
| Linux | ~/.cache/com.getdbt/charts/query-cache.duckdb (or under $XDG_CACHE_HOME) |
| Windows | %LOCALAPPDATA%\com.getdbt\charts\query-cache.duckdb |
Sharing one file is safe: an entry is keyed on the source's connection identity, the SQL, and the variable values, so two projects, or two git worktrees of one project, reuse each other's results only when they would have run the same query against the same source. The file and its directory are readable only by your user. Entries older than about a week are removed from this file, so query results do not pile up on disk; that includes results of a cache: forever query, which a file you choose with --cache keeps for good. To clear it, delete the file.
To keep the cache somewhere else, pass --cache <path>:
The file is created automatically if it doesn't exist yet, and dbt Charts never removes entries from a file you chose. --cache also reads from the DCT_CACHE_PATH environment variable, and a project can set the location once with the path key under cache: in dbt_charts.yml (shown in the dct serve reference). The flag beats the environment variable, which beats the project config. --cache and --no-cache are mutually exclusive.
Several processes at once¶
Any number of dct render and dct serve processes can share one cache file. Each opens the file only for the moment it reads or writes a result. If another program keeps the file open (a DuckDB shell, for example), dbt Charts retries for about ten seconds and then stops with ERR-CACHE-FILE-UNAVAILABLE, naming the file and giving DuckDB's reason, which says which process holds it where DuckDB knows. A cache file that is damaged or not writable stops the same way. dbt Charts never continues without the cache, because that would quietly re-run every query.
Diagnostics JSON¶
For programmatic consumers (agents, CI, IDE extensions), --diagnostics-json emits all diagnostics (both errors and warnings) as JSON Lines to stderr, leaving stdout free for the render payload:
On success, warnings stream to stderr as JSONL and the rendered output goes to stdout (or --output). On failure, errors stream to stderr as JSONL and the process exits non-zero. Each line is one compact JSON object; line-delimited format means a truncated read still yields whole diagnostics.
Related¶
dct serve: interactive preview with auto-reloaddct validate: fast pre-render validationdct query: inspect one named query without rendering the whole board