Skip to content

Quick Guide

Dataface faces are YAML files that render as boards. This guide shows the minimal setup, the core face syntax, and the commands you use to render or serve your work.


Setup

Install Dataface, then scaffold a project with dft init:

pip install dataface
dft init

dft init writes a dataface.yml project anchor and a faces/ directory with a starter dashboard (faces/guide.yaml) that renders without a database. Preview it right away with dft serve, then edit the starter or add your own face alongside it. dft init can also set up editor and AI integrations.

If you are starting in an existing dbt project, run dft init in the project root: dft serve discovers the root from dbt_project.yml, and query sources come from your dbt profiles.yml.

See Getting Started for the full setup path.


Syntax

The board is the base element: a layout tree that can hold text, variables, queries, charts, and other boards. Start with board layout, then add the pieces that make a board interactive.

Boards

A board can be as small as a title and Markdown text:

title: "Simple board"
text: |
  This is Markdown text.

  - **bold** for important points
  - *italics* for nuance
  - `code` for field names
Simple board Simple Board This is Markdown text. bold for important points italics for nuance code for field names 2026-07-17 15:04 UTC made with dataface

Boards become powerful because they nest. When you need structure, rows stacks items vertically, cols places them side by side, and each item is itself a board:

title: "This is the parent board"
width: 600

rows:
  - text: |
      With just **some text** on it. It's Markdown, so we can put things like
      [links](https://docs.it-dataface.com/), *emphasis*, and lists:

      - keep prose close to the data
      - call out important details
      - add structure without adding a chart
    style:
      border:
        width: 1
        color: "#cbd5e1"

  - title: "Nested board"
    text: |
      This is a nested board with its own border.
    style:
      border:
        width: 1
        color: "#cbd5e1"

  - title: "Two nested boards"
    cols:
      - text: |
          And here's another with just text, no title.

          - **Bold text** works inside nested boards.
          - *Italic text* does too.
          - Bullets make short notes easy to scan.
        style:
          border:
            width: 1
            color: "#cbd5e1"
      - title: "And another with just a title"
        style:
          border:
            width: 1
            color: "#cbd5e1"
This is the parent board This Is the Parent Board With just some text on it. It's Markdown, so we can put things like links, emphasis, and lists: keep prose close to the data call out important details add structure without adding a chart Nested Board This is a nested board with its own border. Two Nested Boards And here's another with just text, no title. Bold text works inside nested boards. Italic text does too. Bullets make short notes easy to scan. And Another with Just a Title 2026-07-17 15:04 UTC made with dataface

When a face defines charts but no explicit layout, Dataface renders those charts as implicit rows. Use rows, cols, tabs, or grid when the arrangement matters.

See Boards for layout patterns.


Variables

Variables are the controls readers use to change a board. This one creates a segment selector with three static options:

variables:
  segment:
    input: select
    options:
      static:
        - Enterprise
        - SMB
        - Mid-Market

text: |
  # Segment: {{ segment or 'All segments' }}

  Change the selector above. The text reads **{{ segment or 'All segments' }}**
  from the current variable value.
Dataface Segment: All segments Change the selector above. The text reads All segments from the current variable value. Segment: All
2026-07-17 15:04 UTC made with dataface

The {{ segment }} expression can appear in text, titles, links, SQL, and query filters. A variable does not filter data by itself; you wire it into a query explicitly.

Variables are usually not static like this first example. In production, you often populate them from a query, a source column, a dbt model column, or a MetricFlow dimension or measure; they also support many UI element and styling options. Read more in Variables.


Queries

Queries define the data a board can use. A face can mix database-backed SQL queries with static values for targets, labels, or small reference tables:

queries:
  revenue: # sql to a database
    sql: |
      SELECT month, segment, revenue
      FROM mart_revenue
      ORDER BY month
    source: analytics

  targets: # static values
    type: values
    columns: [segment, target]
    values:
      - [Enterprise, 438000]
      - [SMB, 228000]
      - [Mid-Market, 285000]

Queries can use the same {{ segment }} variable from above. The filter helper writes the SQL predicate for the selected value, or a no-op predicate when no segment is selected:

queries:
  revenue:
    sql: |
      SELECT month, segment, revenue
      FROM mart_revenue
      WHERE {{ filter('segment', segment) }}
      ORDER BY month
    source: analytics

Each query has a name (revenue, targets) and returns named columns. Queries can reference other queries, read previous query results, tune cache behavior, and combine SQL with CSV, dbt model data, MetricFlow data, HTTP, and static values. Read more in Queries.


Charts

Charts reference queries and map result columns to visual channels. Start with a query, then bind its columns to a chart:

queries:
  monthly_revenue:
    sql: |
      SELECT date, SUM(revenue) AS revenue
      FROM ecommerce_orders
      GROUP BY date
      ORDER BY date
    source: examples_db

charts:
  revenue_trend:
    query: monthly_revenue
    type: line
    title: "Revenue trend"
    x: date
    y: revenue
Dataface DF-UNKNOWN-INTERNALChart Error: revenue_trendQuery execution failed: Source 'examples_db' not found. No source profiles are configured. Declare sources under `sources:` in your dataface.yml. (query: monthly_revenue)https://docs.it-dataface.com/guides/error-handling/?code=DF-UNKNOWN-INTERNAL 2026-07-17 15:04 UTC made with dataface

The chart does not aggregate or infer meaning. The query owns the rows and columns; the chart owns the visual encoding. Here, the SQL groups orders by date, and the line chart maps date to the x-axis and revenue to the y-axis.

Variables can then feed the same query layer. This example populates a category selector from SQL and uses the selected value in the revenue query:

variables:
  category:
    input: select
    options:
      query: category_options

queries:
  category_options:
    sql: |
      SELECT DISTINCT category AS value
      FROM ecommerce_orders
      ORDER BY category
    source: examples_db

  revenue:
    sql: |
      SELECT category, SUM(revenue) AS revenue
      FROM ecommerce_orders
      WHERE {{ filter('category', category) }}
      GROUP BY category
      ORDER BY revenue DESC
    source: examples_db

charts:
  revenue_by_category:
    query: revenue
    type: bar
    title: "Revenue by category"
    x: category
    y: revenue
Dataface Category: All
DF-UNKNOWN-INTERNALChart Error: revenue_by_categoryQuery execution failed: Source 'examples_db' not found. No source profiles are configured. Declare sources under `sources:` in your dataface.yml. (query: revenue)https://docs.it-dataface.com/guides/error-handling/?code=DF-UNKNOWN-INTERNAL 2026-07-17 15:04 UTC made with dataface

Here, the variable changes the SQL predicate, while the chart still only maps the category and revenue columns to the axes.

Charts come in many families, and extensible charts can render custom SVG or HTML when the built-in families are not enough. Dive into Charts.


Styling

Dataface keeps styling declarative too. Choose a theme for the whole board, then use typed chart options for labels, axes, formats, colors, marks, and scale behavior. Built-in themes include default, cream, stark, dark, light, looker, and carbong100; chart-local style only needs to override what should differ from the theme:

title: "Styled revenue trend"
theme: cream
width: 600

queries:
  monthly_revenue:
    sql: |
      SELECT date, SUM(revenue) AS revenue
      FROM ecommerce_orders
      GROUP BY date
      ORDER BY date
    source: examples_db

charts:
  revenue_trend:
    query: monthly_revenue
    type: line
    title: "Revenue trend"
    x: date
    y: revenue
    x_label: Order day
    y_label: Daily revenue
    style:
      number_format: currency_whole
      marks:
        line:
          stroke:
            color: "#b45309"
            width: 5
          curve: step
        point:
          size: 70
          color: "#7c2d12"
          filled: true
Styled revenue trend Styled Revenue Trend DF-UNKNOWN-INTERNALChart Error: revenue_trendQuery execution failed: Source 'examples_db' not found. No source profiles are configured. Declare sources under `sources:` in your dataface.yml. (query: monthly_revenue)https://docs.it-dataface.com/guides/error-handling/?code=DF-UNKNOWN-INTERNAL 2026-07-17 15:04 UTC made with dataface

Use styles when the default rendering needs more polish, but keep data meaning in the query and visual mapping in the chart definition. See Styling and the chart family pages for the full option set.


Bringing It All Together Cheat Sheet

Start with the same pattern in a small board: one variable, one query, two KPIs above one single-value chart.

title: "Category snapshot"
width: 600

variables:
  category:
    label: "Category"
    input: select
    options:
      query: category_options

queries:
  category_options:
    sql: |
      SELECT DISTINCT category AS value
      FROM ecommerce_orders
      ORDER BY category
    source: examples_db

  summary:
    sql: |
      SELECT
        SUM(revenue) AS revenue,
        COUNT(*) AS orders,
        AVG(revenue) AS avg_order
      FROM ecommerce_orders
      WHERE {{ filter('category', category) }}
    source: examples_db

charts:
  revenue:
    query: summary
    type: kpi
    label: "Revenue"
    value: revenue
    style:
      value:
        format: currency_whole

  orders:
    query: summary
    type: kpi
    label: "Orders"
    value: orders

  avg_order:
    query: summary
    type: kpi
    label: "Avg order"
    value: avg_order
    style:
      value:
        format: currency_whole

rows:
  - cols:
      - revenue
      - orders
  - avg_order
Category snapshot Category Snapshot Category: All
DF-UNKNOWN-INTERNALChart Error: revenueQuery execution failed: Source 'examples_db' not found. No source profiles are configured. Declare sources under `sources:` in your dataface.yml. (query: summary)https://docs.it-dataface.com/guides/error-handling/?code=DF-UNKNOWN-INTERNALDF-UNKNOWN-INTERNALChart Error: ordersQuery execution failed: Source 'examples_db' not found. No source profiles are configured. Declare sources under `sources:` in your dataface.yml. (query: summary)https://docs.it-dataface.com/guides/error-handling/?code=DF-UNKNOWN-INTERNAL DF-UNKNOWN-INTERNALChart Error: avg_orderQuery execution failed: Source 'examples_db' not found. No source profiles are configured. Declare sources under `sources:` in your dataface.yml. (query: summary)https://docs.it-dataface.com/guides/error-handling/?code=DF-UNKNOWN-INTERNAL 2026-07-17 15:04 UTC made with dataface

Dataface is flexible enough that you could use it to write the quick guide itself. The fuller cheat sheet below uses the same pieces, then nests the examples so the board reads like a compact version of this page. The parent board uses the cream theme, then the embedded example boards switch to a white stark surface so they read as boards nested inside the guide. Notice the chart definitions live at the top, then nested boards list chart IDs like revenue_by_month and target_by_month where those charts should appear.

title: "Dataface quick start cheat sheet"
theme: cream
style:
  border:
    width: 0
    radius: 0

variables:
  segment:
    label: "Segment"
    input: select
    options:
      static: [Enterprise, SMB, Mid-Market]

queries:
  revenue:
    type: values
    columns: [month, segment, revenue, target]
    values:
      - [Jan, Enterprise, 142000, 130000]
      - [Feb, Enterprise, 151000, 138000]
      - [Mar, Enterprise, 164000, 146000]
      - [Jan, SMB, 68000, 72000]
      - [Feb, SMB, 74000, 76000]
      - [Mar, SMB, 81000, 80000]
      - [Jan, Mid-Market, 97000, 91000]
      - [Feb, Mid-Market, 101000, 95000]
      - [Mar, Mid-Market, 109000, 99000]

charts:
  revenue_by_month:
    query: revenue
    type: bar
    title: "Revenue by month"
    x: month
    y: revenue
    color: segment

  target_by_month:
    query: revenue
    type: line
    title: "Target by month"
    x: month
    y: target
    color: segment

rows:
  - title: "1. Boards"
    cols:
      - text: |
          A board starts with a `title`, then stacks `rows` or places `cols`
          beside each other. Text cells support **markdown**, including links,
          lists, emphasis, and inline `code`.

          ```yaml
          title: "Basic board"

          rows:
            - text: |
                This is markdown text.

                - **bold** for important points
                - *italics* for nuance
                - `code` for field names

            - cols:
                - title: "Left panel"
                  text: "Boards can sit side by side."
                - title: "Right panel"
                  text: "Each item is itself a board."
          ```

      - theme: stark
        style:
          background: "#ffffff"
          border:
            width: 0
            radius: 0
        title: "Basic board"
        rows:
          - text: |
              This is markdown text.

              - **bold** for important points
              - *italics* for nuance
              - `code` for field names

          - cols:
              - title: "Left panel"
                text: "Boards can sit side by side."
              - title: "Right panel"
                text: "Each item is itself a board."

  - title: "2. Variables"
    cols:
      - text: |
          Variables define controls. This board has a `segment` selector, and
          both the text and charts read the current value. The board on the
          right is an actual nested board with its own variable definition.


          ```yaml
          variables:
            example_segment:
              label: "Segment"
              input: select
              options:
                static: [Enterprise, SMB, Mid-Market]

          rows:
            - text: |
                Current segment:
                **{{ example_segment or 'All segments' }}**
          ```


      - theme: stark
        style:
          background: "#ffffff"
          border:
            width: 0
            radius: 0
        title: "Rendered variable"
        variables:
          example_segment:
            label: "Segment"
            input: select
            options:
              static: [Enterprise, SMB, Mid-Market]
        text: |
          Current segment: **{{ example_segment or 'All segments' }}**.

          Change this nested board's selector and the text updates with it.

  - title: "3. Queries"
    cols:
      - text: |
          Queries own the rows and columns. They can come from SQL, CSV, dbt
          models, MetricFlow data, HTTP data, or inline values.

          ```yaml
          queries:
            revenue:
              type: values
              columns: [month, segment, revenue, target]
              values:
                - [Jan, Enterprise, 142000, 130000]
                - [Feb, Enterprise, 151000, 138000]
                - [Mar, Enterprise, 164000, 146000]
          ```

      - theme: stark
        style:
          background: "#ffffff"
          border:
            width: 0
            radius: 0
        title: "Query result"
        text: |
          The `revenue` query in this cheat sheet returns monthly revenue,
          segment, and target columns. The chart definitions below decide how to
          encode those columns visually.

  - title: "4. Charts"
    rows:
      - text: |
          Charts reference a query and bind result columns to visual channels.
          The chart definitions are declared once above, then this nested board
          lists the chart IDs in `cols` to place them side by side.

          ```yaml
          charts:
            revenue_by_month:
              query: revenue
              type: bar
              title: "Revenue by month"
              x: month
              y: revenue
              color: segment
          ```

      - theme: stark
        style:
          background: "#ffffff"
          border:
            width: 0
            radius: 0
        title: "Rendered charts"
        cols:
          - revenue_by_month
          - target_by_month

  - title: "5. Nested boards"
    cols:
      - text: |
          Nested boards are how larger dashboards stay organized. This nested
          board has its own variable, query, charts, and layout. Inside it,
          chart IDs are placed where they belong.

          ```yaml
          title: "Segment review"
          variables:
            review_segment:
              label: "Segment"
              input: select
              options:
                static: [Enterprise, SMB, Mid-Market]

          queries:
            review_revenue:
              type: values
              columns: [month, segment, revenue, target]
              values:
                - [Jan, Enterprise, 142000, 130000]
                - [Feb, Enterprise, 151000, 138000]
                - [Mar, Enterprise, 164000, 146000]

          charts:
            review_revenue_by_month:
              query: review_revenue
              type: bar
              x: month
              y: revenue

          cols:
            - text: |
                Current segment:
                **{{ review_segment or 'All segments' }}**
            - review_revenue_by_month
          ```

      - theme: stark
        style:
          background: "#ffffff"
          border:
            width: 0
            radius: 0
        title: "Segment review"
        variables:
          review_segment:
            label: "Segment"
            input: select
            options:
              static: [Enterprise, SMB, Mid-Market]
        queries:
          review_revenue:
            type: values
            columns: [month, segment, revenue, target]
            values:
              - [Jan, Enterprise, 142000, 130000]
              - [Feb, Enterprise, 151000, 138000]
              - [Mar, Enterprise, 164000, 146000]
              - [Jan, SMB, 68000, 72000]
              - [Feb, SMB, 74000, 76000]
              - [Mar, SMB, 81000, 80000]
              - [Jan, Mid-Market, 97000, 91000]
              - [Feb, Mid-Market, 101000, 95000]
              - [Mar, Mid-Market, 109000, 99000]
        charts:
          review_revenue_by_month:
            query: review_revenue
            type: bar
            title: "Revenue"
            x: month
            y: revenue
            color: segment

          review_target_by_month:
            query: review_revenue
            type: line
            title: "Target"
            x: month
            y: target
            color: segment
        cols:
          - style:
              background: "#ffffff"
              border:
                width: 0
                radius: 0
            text: |
              Current segment: **{{ review_segment or 'All segments' }}**

              - compare revenue against target
              - keep the explanation beside the visuals

          - style:
              background: "#ffffff"
              border:
                width: 0
                radius: 0
            rows:
              - review_revenue_by_month
              - review_target_by_month
Dataface quick start cheat sheet Dataface Quick Start Cheat Sheet Segment: All
1. Boards A board starts with a title, then stacks rows or places cols beside each other. Text cells support markdown, including links, lists, emphasis, and inline code. title: "Basic board" rows: - text: | This is markdown text. - **bold** for important points - *italics* for nuance - `code` for field names - cols: - title: "Left panel" text: "Boards can sit side by side." - title: "Right panel" text: "Each item is itself a board." Basic Board This is markdown text. bold for important points italics for nuance code for field names Left Panel Boards can sit side by side. Right Panel Each item is itself a board. 2. Variables Variables define controls. This board has a segment selector, and both the text and charts read the current value. The board on the right is an actual nested board with its own variable definition. variables: example_segment: label: "Segment" input: select options: static: [Enterprise, SMB, Mid-Market] rows: - text: | Current segment: **{{ example_segment or 'All segments' }}** Rendered Variable Segment: All
Current segment: All segments. Change this nested board's selector and the text updates with it.
3. Queries Queries own the rows and columns. They can come from SQL, CSV, dbt models, MetricFlow data, HTTP data, or inline values. queries: revenue: type: values columns: [month, segment, revenue, target] values: - [Jan, Enterprise, 142000, 130000] - [Feb, Enterprise, 151000, 138000] - [Mar, Enterprise, 164000, 146000] Query Result The revenue query in this cheat sheet returns monthly revenue, segment, and target columns. The chart definitions below decide how to encode those columns visually. 4. Charts Charts reference a query and bind result columns to visual channels. The chart definitions are declared once above, then this nested board lists the chart IDs in cols to place them side by side. charts: revenue_by_month: query: revenue type: bar title: "Revenue by month" x: month y: revenue color: segment Rendered Charts 0k20k40k60k80k100k120k140k160k180kFebJanMarEnterpriseMid-MarketSMBSegmentRevenue by MonthJanFebMar80k100k120k140kTarget by MonthEnterpriseMid-MarketSMB 5. Nested Boards Nested boards are how larger dashboards stay organized. This nested board has its own variable, query, charts, and layout. Inside it, chart IDs are placed where they belong. title: "Segment review" variables: review_segment: label: "Segment" input: select options: static: [Enterprise, SMB, Mid-Market] queries: review_revenue: type: values columns: [month, segment, revenue, target] values: - [Jan, Enterprise, 142000, 130000] - [Feb, Enterprise, 151000, 138000] - [Mar, Enterprise, 164000, 146000] charts: review_revenue_by_month: query: review_revenue type: bar x: month y: revenue cols: - text: | Current segment: **{{ review_segment or 'All segments' }}** - review_revenue_by_month Segment Review Segment: All
Current segment: All segments compare revenue against target keep the explanation beside the visuals 0k100k50k150kFebJanMarEnterpriseMid-MarketSMBSegmentRevenueJanFebMar80k100k120k140kTargetEnterpriseMid-MarketSMB
2026-07-17 15:04 UTC made with dataface

Small boards compose into larger reports, and those reports can compose again. When a nested board gets large, move it to its own face YAML file and import it from the layout.


Quick Reference

title: "My Board"

variables:
  segment:
    input: select
    options:
      static: [Enterprise, SMB, Mid-Market]

queries:
  revenue:
    type: values
    columns: [month, segment, revenue]
    values:
      - [Jan, Enterprise, 142000]

charts:
  revenue_by_month:
    query: revenue
    type: bar
    x: month
    y: revenue

rows:
  - title: "Revenue"
    text: "Monthly revenue by selected segment."
  - revenue_by_month

Start with the data and interaction you need, then add the board shape that makes it readable.


Tools

Use dft render when you want a static artifact from one face file:

dft render faces/revenue.yml
dft render faces/revenue.yml --format html
dft render faces/revenue.yml --format png --output revenue.png

Use --var to render a specific variable state:

dft render faces/revenue.yml --var segment=Enterprise

See dft render for every output format.

Use dft serve when you want live browser previews while editing YAML:

dft serve
dft serve --port 3000
dft serve --connection ./data.db --dialect duckdb

Face paths map to URLs. For example, faces/revenue.yml renders at /revenue/, and query parameters set variables:

http://localhost:3000/revenue/?segment=Enterprise

See dft serve for routing, ports, and database options.