
I tried creating a dashboard from Athena dbt models using dbt Charts
This page has been translated by machine translation. View original
Introduction
I'm Kasama from the Data Business Division.
dbt Charts is a tool that allows you to write dashboards in YAML within a dbt project. It connects to Athena via a dbt profile. This time, I'd like to use the dbt Charts CLI (dct) to create an order status dashboard from an Amazon Athena + Iceberg dbt project.
Prerequisites
What is dbt Charts?
dbt Charts is a tool for writing dashboards in YAML within a dbt project. A single YAML file is called a board, and you define queries, charts, and layouts in a board. dct reads the board, executes SQL, and displays it in the browser with dct serve.
Requirements for using Athena with dct (CLI)
There are two ways for dct to connect to a database.
| Method | Where to write connection settings | Example supported databases |
|---|---|---|
| Direct connection | Write connection settings in dbt_charts.yml with type: postgres, etc. |
Postgres, Snowflake, BigQuery, Redshift, DuckDB |
| dbt_profile | dbt's profiles.yml. Write only type: dbt_profile and the profile name in dbt_charts.yml |
Athena, SQL Server, ClickHouse, Spark |
Direct connection makes the connection settings self-contained within dbt_charts.yml. With dbt_profile, you can reuse the connection settings from the profiles.yml you already use with dbt. On the Sources page of the web documentation, the connection method for Athena is listed as type: dbt_profile, and the installation is dbt-charts[athena].
The requirements for using Athena with dct are as follows.
| Item | Details |
|---|---|
| Connection method | Write type: dbt_profile in dbt_charts.yml |
| Installation | Install with dbt-charts[athena]. The athena extra adds dbt-athena>=1.10,<2.0. |
Verification Environment
We will use dbt-charts (dct) version 0.9.1, which is the latest release as of October 12, 2026. The list of releases can be checked on GitHub.
| Item | Value |
|---|---|
| dbt-charts (dct) | 0.9.1 |
| dbt-core | 1.12.5 |
| dbt-athena | 1.11.1 |
| Region | ap-northeast-1 |
Architecture

Both dbt and dct run locally, with their roles divided as follows.
- dbt (
dbt build)- Runs seeds and models in Athena to create tables and views
- Table definitions are stored in Glue Data Catalog, and data is stored in S3
- Generates
target/manifest.json
- dct (dbt Charts)
- Connects to Athena using dbt's
profiles.yml - Reads
target/manifest.jsonand replacesref()in the board's SQL with table names - Executes SELECT statements written in the board against Athena and turns the results retrieved from dbt-created tables into charts
- Displays the board in the browser with
dct serve
- Connects to Athena using dbt's
The sample data is based on jaffle_shop, which dbt Labs has published for dbt demonstrations. jaffle_shop contains customer and order data for a fictional e-commerce site. This time, we kept the same column structure but reduced the number of rows, and included it as seeds (CSV) in the dbt project.
The dbt models create staging views from seeds, and Iceberg tables in the mart from staging.
| mart | Description | Rows |
|---|---|---|
order_status_summary |
Number of orders by status | 5 |
customer_orders |
Number of orders per customer, first order date, last order date | 10 |
dct displays one board that lets you check order status on a single screen. The board arranges total order count, daily order count, count by status, and a list of orders by customer. Table joins are handled in dbt's mart, and the board's SQL only needs to read order_status_summary, customer_orders, and stg_orders.
Implementation
The implementation code is stored on GitHub.
73_dbt_charts_athena/
├── cfn/
│ └── datastore.yaml # S3 bucket and Glue Database
├── docs/
│ └── architecture.drawio # Architecture diagram
└── dbt/
├── pyproject.toml # Fixed versions of dbt-core and dbt-athena
├── dbt_project.yml
├── profiles.yml # Profile shared by dbt and dct
├── dbt_charts.yml # dct source definition
├── seeds/
│ ├── raw_customers.csv
│ └── raw_orders.csv
├── models/
│ ├── staging/
│ │ ├── stg_customers.sql
│ │ └── stg_orders.sql
│ └── marts/
│ ├── order_status_summary.sql
│ └── customer_orders.sql
└── charts/
└── order_overview.yml # dct board
CloudFormation
datastore.yaml defines the S3 bucket and Glue Database. The S3 bucket stores seed and mart data, as well as Athena query results. The Glue Database is used as the dbt schema.
dbt
profiles.yml is read by both dbt and dct. The bucket name is received from the environment variable DBT_S3_BUCKET. s3_staging_dir is the destination for Athena query results, and s3_data_dir is the destination for data in tables created by dbt. They are separated into athena-results/ and data/ within the same bucket. Since dbt creates tables and views in the Glue Database specified in schema, it should be set to the same value as the Glue Database name created with CloudFormation.
In dbt_project.yml, the mart is configured as an Iceberg table (materialized: table). Since dbt-athena uses RENAME when creating Iceberg tables, the S3 path of the table needs to be different each time. Therefore, s3_data_naming in profiles.yml is set to schema_table_unique, resulting in a different path for each run, like data/<schema_name>/<table_name>/<UUID>/.
dbt Charts
dbt_charts.yml is placed at the root of the dbt project and defines the source that dct connects to. When type: dbt_profile is specified, dct connects to Athena using the profile and target from profiles.yml. profiles_dir is a relative path from the dbt project. Since we placed profiles.yml directly under the dbt project, we specified ..
For ref() calls like {{ ref('order_status_summary') }} in SQL, dct reads target/manifest.json and replaces them with relation names. dct reads ref() calls as strings and looks up table names from the manifest.
The manifest is generated by dbt parse or dbt build. If you use ref() without a manifest present, dct validate will issue a warning (WARN-DBT-MANIFEST-MISSING) and succeed. dct render will fail with an error (ERR-DBT-MANIFEST-MISSING) because it cannot replace ref() with table names. You need to generate the manifest with dbt before running dct.
Deployment
Clone the Repository
Clone the sample repository and navigate to the 73_dbt_charts_athena directory. All subsequent commands are run from this directory as the base.
git clone https://github.com/cm-yoshikikasama/blog_code.git
cd blog_code/73_dbt_charts_athena
Create the CloudFormation Stack
Create the S3 bucket and Glue Database, then retrieve the bucket name from Outputs.
aws cloudformation deploy \
--profile <your-profile> \
--region ap-northeast-1 \
--stack-name cm-kasama-dbt-charts-athena \
--template-file cfn/datastore.yaml
aws cloudformation describe-stacks \
--profile <your-profile> \
--region ap-northeast-1 \
--stack-name cm-kasama-dbt-charts-athena \
--query 'Stacks[0].Outputs'
Install dct and dbt
Install dct with the Athena extra using dbt-charts[athena]. Adding the extra also installs dbt-athena. Pin the dbt version in dbt/pyproject.toml and install it into the virtual environment for the dbt project with uv sync.
uv tool install 'dbt-charts[athena]'
cd dbt
uv sync
Set Credentials and Bucket Name as Environment Variables
Set the bucket name obtained from the stack Outputs to DBT_S3_BUCKET, which is referenced by profiles.yml. Specify the AWS profile used by dbt and dct with AWS_PROFILE.
export AWS_PROFILE=<your-profile>
export DBT_S3_BUCKET=<your-bucket>
Trying It Out
dbt build
Create everything from seeds to mart.
uv run dbt build --profiles-dir .
1 of 6 OK loaded seed file cm_kasama_jaffle_shop_dev.raw_customers ............. [INSERT 10 in 7.10s]
2 of 6 OK loaded seed file cm_kasama_jaffle_shop_dev.raw_orders ................ [INSERT 20 in 8.18s]
3 of 6 OK created sql view model cm_kasama_jaffle_shop_dev.stg_customers ....... [OK -1 in 1.95s]
4 of 6 OK created sql view model cm_kasama_jaffle_shop_dev.stg_orders .......... [OK -1 in 1.92s]
5 of 6 OK created sql table model cm_kasama_jaffle_shop_dev.customer_orders .... [OK 10 in 8.47s]
6 of 6 OK created sql table model cm_kasama_jaffle_shop_dev.order_status_summary [OK 5 in 8.59s]
Done. PASS=6 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=6
All 6 items from seeds to mart were created successfully.
Verifying the Connection and Board
Use dct query to confirm the connection to Athena, and dct validate to validate the board YAML. Run both from the dbt directory.
dct query athena 'SELECT 1'
dct validate charts/
_col0
1
1 rows
✓ charts/order_overview.yml OK
Check with text format while editing
While editing a board, use --format text to check aggregation results. --format text outputs the values, row counts, and value ranges for each chart.
dct render charts/order_overview.yml --format text
# Order Status
## Order Status_row0
### kpi_orders (kpi)
- value: 20
### kpi_customers (kpi)
- value: 10
## Order Status_row1
### Daily Order Count (line)
- x: order_date, y: order_count
- 18 rows | order_count: 1–2
### Count by Status (bar)
- x: status, y: order_count
- 5 rows | status: completed, placed, return_pending, returned, shipped | order_count: 1–9
## Orders by Customer (table)
- columns: Customer Name, Order Count, First Order Date, Last Order Date
- showing first 3 of 10 rows
| customer_name | order_count | first_order_date | last_order_date |
| --- | --- | --- | --- |
| Frank R. | 2 | 2018-01-11 | 2018-01-19 |
| Henry W. | 2 | 2018-01-12 | 2018-01-20 |
| Jennifer F. | 2 | 2018-01-04 | 2018-01-17 |
- 10 rows
The total order count of 20 and customer count of 10 matched the number of rows in the seeds.
With --format terminal, which draws charts in the terminal, the x-axis labels for the count-by-status bar chart were misaligned. Only 4 labels appeared for 5 bars, and "completed" was displayed below the bar with a height of 9 (placed). Use --format text to verify values.
┌───────────────────────────────────────────────────────────────┐
9.0┤███████████ │
7.5┤███████████ ███████████ │
6.0┤███████████ ███████████ │
3.0┤███████████ ███████████ │
1.5┤███████████ ███████████ ███████████ ███████████ ███████████│
0.0┤███████████ ███████████ ███████████ ███████████ ███████████│
└─────┬────────────┬────────────┬────────────┬──────────────────┘
completed returned return_pending shipped
Display in the browser with dct serve
dbt Charts is intended to be viewed either via the dct serve URL or the hosted dbt Charts Cloud URL. This time, we serve it locally with dct serve.
dct serve --port 8080
The board file path corresponds to the URL, so charts/order_overview.yml is displayed at http://localhost:8080/order_overview/. When opened in a browser, the following screen appeared.

A board for checking order status on a single screen was displayed in the browser. The first row shows the total order count and customer count, the second row shows a line chart of daily order counts and a horizontal bar chart of counts by status, and the third row shows a list of orders by customer.
Export to HTML for sharing
To share a board as a file, export it with --format html.
dct render charts/order_overview.yml --format html
renders/order_overview.html is output. This HTML embeds the data at the time of rendering, and the displayed data remains fixed at that point in time. Other output formats include svg, png, pdf, terminal, json, text, text-data, yaml, and data.
Conclusion
We connected dbt Charts' dct to an Athena + Iceberg dbt project via dbt_profile and displayed an order status board with dct serve. Since boards are placed as YAML within the dbt project and reference marts via ref(), you can manage models and dashboards in the same repository and review them via pull requests. This will be useful in projects building a data platform with dbt and Athena — for example, when checking aggregation results as charts during model development, or when confirming numbers with stakeholders before introducing a BI tool. If you want to update data regularly, one approach would be to run a job on CI or similar that executes dbt build followed by dct render --format html, and then share the exported HTML. I hope this is helpful to someone.