Skip to content

Board Content

Boards can contain narrative content using the content field. This is useful for adding context, explanations, or summary text alongside your data.


Basic Content

Add content at the top level of a board:

title: "Executive Summary"
text: |
  ## Q4 Performance
  Revenue is up **15%** quarter-over-quarter.
rows:
  - revenue_chart

The board's title: already renders as an <h1>, so start your body text at ## (h2) or as a paragraph; don't repeat the title with a leading # Title, or it will render twice. To change which heading level titles render at (for example, when embedding a board inside a page that already owns H1/H2), see Title Heading Levels below.


Nested Content

Use content in nested boards to create rich, document-like layouts:

rows:
  - text: |
      ## Monthly Analysis
      Here is the breakdown of our performance by region.

  - cols:
      - text: |
          ### Key Takeaways
          - North region is leading
          - South region needs attention

      - text: |
          ### Action Items
          1. Increase ad spend in South
          2. Optimize North inventory

Mixed Content Layouts

Combine content blocks with charts for rich, document-like dashboards:

source: examples_db
title: "Sales Report"

charts:
  revenue_chart:
    query:
      sql: |
        SELECT category, SUM(revenue) AS revenue, SUM(units_sold) AS units_sold FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
    type: bar
    title: "Revenue by Category"
    x: category
    y: revenue
    color: category

rows:
  - text: |
      This report shows **Q4 performance** across product categories.

  - cols:
      - revenue_chart

      - text: |
          ### Key Takeaways
          - **Electronics**: Top performer
          - **Accessories**: Growing steadily
          - **Tools**: Stable revenue
Sales Report This report shows Q4 performance across product categories. 01mn2mn3mn4mnElectronicsAccessoriesToolsAccessoriesElectronicsToolsRevenue by Category Key Takeaways Electronics: Top performer Accessories: Growing steadily Tools: Stable revenue Data as of 14:30 UTC on 20 Oct 2026 made withdbt Charts
▶

Markdown Features

Because content is Markdown, you can use all standard Markdown features:

Tables

text: |
  | Metric | Value | Notes |
  | --- | --- | --- |
  | Revenue | $4.2M | `SUM(revenue)` over the last quarter |
  | Margin | 32% | Consistent with forecast |

Images

Drop in logos, diagrams, or badges:

text: |
  # dbt-powered Insights
  ![dbt logo](https://upload.wikimedia.org/wikipedia/commons/7/7a/DBT_logo.svg)
  Our revenue model is compiled with dbt.

A project image (.png, .jpg, .jpeg, .gif, .svg, .webp, .bmp) is embedded directly in the render, so it shows up the same way in SVG/PNG/PDF export, dct serve, and Cloud, with no separate asset hosting to set up. Its path takes one of two forms:

  • A relative path resolves against the directory of the file it is written in: the board's own file, or the file an imported board or chart comes from.
  • A path starting with / resolves from the project's charts/ directory, the directory dct serve serves.
text: |
  ![Company Logo](img/logo.png)
  ![Brand mark](/assets/brand.svg)

The image fills its column at its own aspect ratio. http(s):// and data: image URLs work too and are never fetched over the network at render time. A project image's path is written out, not built by a template: {% if %} can choose between whole images, and alt text can be templated, but a {{ }} path that renders to a project file renders as an error. A templated http(s):// URL is fine. A path that doesn't exist, or that isn't one of the supported image types, renders as an error naming the path and the directory it was looked for in; a relative path that only exists under charts/ says to write it with a leading /.

An image also renders inline, wherever it sits: mid-sentence, in a heading, a list item, or a table cell. A table of thumbnails is one markdown table:

text: |
  | Logo | Company |
  | --- | --- |
  | ![Acme](img/acme.png) | Acme Corp |
  | ![Globex](img/globex.png) | Globex Inc |

Callouts & Quotes

text: |
  > :sparkles: Markdown lets you mix prose, tables, inline `code`,
  > and callouts in a single content block.
text: |
  Want a deeper dive? [Open the dbt docs](https://docs.getdbt.com/)
  or [view another board](../guides/getting-started.md).

Inline Code

text: |
  Reference variables like `{{ selected_month }}` or
  metric names like `total_revenue`.

Title Heading Levels

style.title.level controls the H-level (H1–H6) used for board and chart titles.

# Default: compute from the count of titled ancestors.
# A titled root board is H1, a titled child section is H2, and so on.
# Bare layout wrappers (no title) do not advance the counter.
style:
  title:
    level: auto
# Lock the level to a specific heading, useful for embedded boards where
# H1/H2 are already owned by the outer page. This board and all descendants
# start from H3; titled children render at level + 1, and bare wrappers
# inherit the locked level unchanged.
style:
  title:
    level: 3

Query values in text and titles

text:, board title:, and a chart's title, subtitle, KPI label and callout title and message read query results through queries.<name>. The query is defined under queries: like any other; text runs after it executes.

queries:
  summary:
    columns: [net_revenue, vs_plan, churn]
    values:
      - [4210000.5, 0.042, null]
  top_regions:
    columns: [region, revenue]
    values:
      - [EMEA, 1800000]
      - [AMER, 1500000]
      - [APAC, 910000]
text: |
  {% set s = queries.summary.one %}
  Net revenue is **{{ s.net_revenue | format('currency') }}**,
  {{ 'ahead of' if s.vs_plan >= 0 else 'behind' }} plan by
  {{ s.vs_plan | abs | format('percent') }}.

  Churn: {{ 'not available' if s.churn is none else s.churn | format('percent') }}.

  Top regions:
  {% for r in queries.top_regions.rows %}
  - **{{ r.region }}**: {{ r.revenue | format('currency') }}
  {% endfor %}

  Leading: {{ queries.top_regions.rows | map(attribute='region') | join(', ') }}.
  {{ queries.top_regions.rows | length }} regions; {{ queries.top_regions.rows[0].region }} leads.

Renders as:

Net revenue is **$4.21mn**, ahead of plan by 4.2%.

Churn: not available.

Top regions:
- **EMEA**: $1.8mn
- **AMER**: $1.5mn
- **APAC**: $910k

Leading: EMEA, AMER, APAC.
3 regions; EMEA leads.
Form Meaning
queries.q.one.col Scalar. Requires exactly 1 row.
queries.q.rows List of rows. for, length, [i], map and join work. [i] past the end is an error, and so is first, last, min, max or random on an empty list.
queries.q.columns Column names. [i] past the end is an error.
row.col, row['col'] Raw cell value. A column named items or values is that column.
v \| format(spec) Formats v with a preset, a board format alias, or a d3 or strftime string. Large numbers use narrative notation ($4.21mn, 910k); an alias that sets notation: keeps its own.
{{ queries.q.one.col }} Python str() of the raw value (4210000.5). Nothing is formatted implicitly.
  • .one raises unless the query returned exactly one row. Use LIMIT 1 or .rows[0] to pick a row; .rows[0] on a query with no rows is an error too. There is no .first.
  • A NULL cell is none. Guard with x is none before comparing; x > 0.05 on NULL is an error.
  • In text and titles, format replaces Jinja's printf-style format filter. It takes exactly one spec; {{ '%.1f' | format(x) }} is an error.
  • A column whose name starts with _ is read with a subscript, row['_fivetran_synced']; row._fivetran_synced is an error.
  • Reference queries by literal name: queries.name or queries['name']. queries[var], queries | list and {% set q = queries %} are errors.
  • Unknown queries and non-literal use fail at compile (dct validate). Row-count, column and NULL errors fail at render: a board title or text: error is the board's error, and a chart's text error shows in that chart's card while the rest of the board renders.
  • An imported chart's text reads the queries of the file the chart is defined in.
  • A board whose title reads query data or uses format is as wide as prose, one readable line (style.text.column.max_chars characters, 75 by default), not as wide as the title.

Titles and chart text

title: "Q3 revenue {{ queries.summary.one.net_revenue | format('currency') }}"
queries:
  summary:
    columns: [net_revenue, vs_plan]
    values:
      - [4210000.5, 0.042]
  by_region:
    columns: [region, revenue]
    values:
      - [EMEA, 1800000]
      - [AMER, 1500000]
charts:
  regions:
    query: by_region
    type: bar
    x: region
    y: revenue
    title: "{{ queries.by_region.rows | length }} regions"
    subtitle: "{{ queries.summary.one.vs_plan | format('percent_delta') }} vs plan"
  headline:
    query: summary
    type: kpi
    value: net_revenue
    label: "Net revenue ({{ 'ahead' if queries.summary.one.vs_plan >= 0 else 'behind' }})"
rows:
  - headline
  - regions

The board title reads "Q3 revenue $4.21mn", the bar chart's title "2 regions" with subtitle "+4.2% vs plan", and the KPI label "Net revenue (ahead)".


Raw HTML Content

Set html_policy: trusted-raw at the board root to render text: as raw HTML. See HTML Policy for the full tier table, security notes, and guidance on when to use each tier.


Content Tips

  • Use tables to align KPIs and explainers side by side
  • Add > blockquotes as callouts for assumptions or warnings
  • Drop [links](...) to help viewers jump to related docs
  • Embed inline `code` to reference variables or query snippets
  • Combine headings, lists, and images for mini write-ups