Skip to content

dct render

Render a dashboard to SVG, HTML, PNG, PDF, JSON, YAML, or terminal output.

dct render [OPTIONS] {boards}...

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.

dct render charts/sales.yml --allow-chart-errors

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):

dct render charts/sales.yml --ignore-warning WARN-REDUNDANT-ENCODING

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

dct render charts/sales.yml \
  --var region=West \
  --var category=Electronics

Terminal preview

dct render charts/sales.yml --format terminal
================================================================================
 Sales Dashboard
================================================================================

Daily Revenue Trend
      ┌────────────────────────────────────────────────────────────┐
2400.0┤    ▗                ▟                                 ▗    │
2141.7┤   ▄▀▖              ▞ ▚               ▞▖              ▄▀▖   │
...

Terminal format writes directly to stdout. Use shell redirection to save:

dct render charts/sales.yml --format terminal > dashboard.txt

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
cat generated_dashboard.yml | dct render - --output - > output.svg

--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

dct render charts/sales.yml --format json > sales_resolved.json

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.

dct render charts/sales.yml --format text-data

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.

dct render charts/sales.yml --format data > sales_data.json
{
  "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.

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.

dct render charts/sales.yml
dct render charts/sales.yml   # reuses cached query results

When a render reuses cached warehouse results it says so on stderr, with the age of the oldest one:

3 queries served from cache, oldest result 2h 14m old.

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>:

dct render charts/sales.yml --cache cache.duckdb

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:

dct render charts/sales.yml --diagnostics-json --format svg > output.svg 2> diags.jsonl

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.

dct render charts/sales.yml --diagnostics-json 2>&1 | grep '^{' | jq .
  • dct serve: interactive preview with auto-reload
  • dct validate: fast pre-render validation
  • dct query: inspect one named query without rendering the whole board