Note: This is the English translation of the following article.
https://dev.classmethod.jp/articles/snowflake-try-dashboards-2/
Hi, I'm Sagara.
Snowflake has a new feature called Dashboards 2.0. It's an AI-first BI artifact. You build a dashboard as a .dash file in Snowsight Workspaces, share it with roles, and viewers open it in CoWork or Snowsight.
https://docs.snowflake.com/en/user-guide/snowflake-dashboards
The official docs below cover the sharing details.
https://docs.snowflake.com/user-guide/snowflake-artifacts-create-share
In this article, I'll define a Semantic View on the TPC-H sample data and then visualize it with Dashboards 2.0. I'll also briefly explain what's inside the generated .dash file and try granting privileges through roles.
Note: As of November 13, 2026, the official docs list this feature as "Preview Feature — Open." The specs may change before GA, so please keep that in mind.
Dashboards 2.0 is a SQL-based, multi-tile dashboard that you create in Snowsight Workspaces. Analysts build it by chatting with Cortex Code (CoCo) and then share it with roles. Viewers open it in CoWork or Snowsight and can explore by clicking on charts. In CoWork, viewers can also ask follow-up questions about a tile.
A dashboard is mainly made up of these components:
| Component | Description |
|---|---|
| Dashboard | The main body, containing the layout, filters, and tiles (the .dash file) |
| Tile | A single display unit with SQL and a visualization type (chart / table / markdown). Placed on a 12-column grid |
| Tab | A group of tiles. Up to 20 per dashboard. Filters apply across all tabs |
| Filter | Defined per dashboard. Connected to tile SQL with {{ filter('name') }} |
| Hierarchy | A hierarchy definition used for drill-down |
| Chart action | Clicking or dragging on a chart filters or highlights other tiles |
| Artifact | A read-only copy of the .dash file. This is the unit you share with roles |
Tile queries run with the viewer's own privileges (caller's rights), and no data is extracted.
Even though queries run with the viewer's privileges, a tile that references a Semantic View only needs the viewer to have SELECT on that Semantic View. You don't need to grant privileges on the underlying tables. The privileges required to query a Semantic View are listed on this page of the official docs:
https://docs.snowflake.com/en/user-guide/views-semantic/querying
The official docs state clearly that Dashboards 2.0 is not a drop-in replacement for the legacy version. The main differences are:
| Item | Legacy | Dashboards 2.0 |
|---|---|---|
| Follow-up questions | Not supported | Conversational in CoWork |
| How to create | Worksheet UI | CoCo + manual |
| Access control | UBAC | RBAC |
| Filter scope | Account-wide | Per dashboard |
| Query cache | Shared within a role | Per user (results reused per viewer) |
Version 2.0 doesn't recognize the legacy :filter_name syntax. You have to replace it with {{ filter('name') }}. An upgrade tool can also do this conversion automatically.
Pei-san, a Snowflake Data Superhero, wrote a great article (in Japanese) titled "Why Snowflake Dashboards 2.0 Is the Best, and What BI Will Need Going Forward." It covers the feature's characteristics and where BI is heading. It was a big help when I wrote the overview section of this article.
Please check it out as well.
https://zenn.dev/dataheroes/articles/snowflake-dashboards-2-and-future-of-bi
Here are the items from the "Limitations (public preview)" section of the official docs that seem relevant to this walkthrough:
.dash file in Workspaces. Users granted Can edit (the WRITE privilege) can publish changes to the artifact. Note that having Open source file will point to a path that no longer exists. Renaming or moving the file may make its link to the artifact outdated or break it. If you then create the artifact again, it may become a separate artifact with a different link.{{ hierarchy_key(...) }} must also use {{ hierarchy_rows(...) }}. SHOW ARTIFACTS, DESCRIBE ARTIFACT, etc.) is listed as "Coming soon" as of November 13, 2026.
For this walkthrough, I'll set up an author role and a viewer role.
USE ROLE ACCOUNTADMIN;
-- Author role and viewer role
CREATE ROLE IF NOT EXISTS DASH_AUTHOR;
CREATE ROLE IF NOT EXISTS DASH_VIEWER;
-- Database, schema, and warehouse for testing
CREATE DATABASE IF NOT EXISTS DASH_TEST_DB;
CREATE SCHEMA IF NOT EXISTS DASH_TEST_DB.SV;
CREATE WAREHOUSE IF NOT EXISTS DASH_TEST_WH WAREHOUSE_SIZE = XSMALL AUTO_SUSPEND = 60;
-- Create the sample database (run only if SNOWFLAKE_SAMPLE_DATA does not exist in your account)
CREATE DATABASE SNOWFLAKE_SAMPLE_DATA FROM SHARE SFC_SAMPLES.SAMPLE_DATA;
Next, grant the required privileges to the author role DASH_AUTHOR.
-- Privileges on the sample data (shared DB) - author only; not granted to the viewer
GRANT IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE_SAMPLE_DATA TO ROLE DASH_AUTHOR;
-- Author role privileges (limited to what's needed)
GRANT USAGE ON DATABASE DASH_TEST_DB TO ROLE DASH_AUTHOR;
GRANT USAGE ON SCHEMA DASH_TEST_DB.SV TO ROLE DASH_AUTHOR;
GRANT CREATE SEMANTIC VIEW ON SCHEMA DASH_TEST_DB.SV TO ROLE DASH_AUTHOR;
GRANT USAGE ON WAREHOUSE DASH_TEST_WH TO ROLE DASH_AUTHOR;
-- Privilege to share artifacts (don't rely on the default grant to PUBLIC)
GRANT MANAGE ACCESS ON DASHBOARD ARTIFACTS
ON ACCOUNT TO ROLE DASH_AUTHOR;
-- Grant the author role to your own user
GRANT ROLE DASH_AUTHOR TO USER <your_user>;
I'll define a Semantic View using the TPC-H ORDERS, CUSTOMER, and LINEITEM tables.
USE ROLE DASH_AUTHOR;
USE WAREHOUSE DASH_TEST_WH;
USE SCHEMA DASH_TEST_DB.SV;
CREATE OR REPLACE SEMANTIC VIEW TPCH_SALES_SV
TABLES (
customers AS SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER
PRIMARY KEY (c_custkey)
COMMENT = 'Customers',
orders AS SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS
PRIMARY KEY (o_orderkey)
COMMENT = 'Orders',
line_items AS SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.LINEITEM
PRIMARY KEY (l_orderkey, l_linenumber)
COMMENT = 'Order line items'
)
RELATIONSHIPS (
orders_to_customers AS orders (o_custkey) REFERENCES customers,
line_items_to_orders AS line_items (l_orderkey) REFERENCES orders
)
FACTS (
line_items.net_price AS l_extendedprice * (1 - l_discount)
)
DIMENSIONS (
customers.customer_name AS c_name,
customers.market_segment AS c_mktsegment,
orders.order_date AS o_orderdate,
orders.order_year AS YEAR(o_orderdate),
orders.order_priority AS o_orderpriority
)
METRICS (
orders.order_count AS COUNT(o_orderkey),
line_items.total_net_sales AS SUM(line_items.net_price)
)
COMMENT = 'Semantic View for TPC-H sales analysis';
I based the DDL syntax on this page of the official docs:
https://docs.snowflake.com/en/sql-reference/sql/create-semantic-view
To confirm the Semantic View was created, query it directly (run these as the DASH_AUTHOR role).
-- Total sales by year
SELECT *
FROM SEMANTIC_VIEW(
DASH_TEST_DB.SV.TPCH_SALES_SV
DIMENSIONS orders.order_year
METRICS line_items.total_net_sales
)
ORDER BY order_year;
-- Order count by order priority
SELECT *
FROM SEMANTIC_VIEW(
DASH_TEST_DB.SV.TPCH_SALES_SV
DIMENSIONS orders.order_priority
METRICS orders.order_count
)
ORDER BY order_priority;
If you get one row per year and one row per priority, you're good. I based the SEMANTIC_VIEW clause syntax on this page of the official docs:
Note that the sales metric total_net_sales is the sum of each line item's extended price (l_extendedprice) minus its discount. It won't match amounts that include tax or the sum of ORDERS.O_TOTALPRICE.
Open Workspaces in Snowsight and switch the role to DASH_AUTHOR and the warehouse to DASH_TEST_WH in the Workspace.
Then select Add new → Dashboard.
The CoCo chat panel opens on the right side of the Workspace. In the prompt, I specify which Semantic View to use. If you don't specify one, CoCo searches for a relevant Semantic View, but this time I named it explicitly. I also told CoCo not to query the underlying tables directly and not to redefine the metrics on its own. This keeps the dashboard consistent with the setup, where the viewer has no privileges on the underlying tables.
/dashboard Using DASH_TEST_DB.SV.TPCH_SALES_SV, please create a TPC-H sales dashboard.
- Total sales trend by year (line chart)
- Total sales by market segment (bar chart)
- Order count by order priority (table)
- Filter: order year
All tile SQL, and any SQL used to fetch the options for the order year filter, must reference DASH_TEST_DB.SV.TPCH_SALES_SV.
Do not reference the underlying tables in SNOWFLAKE_SAMPLE_DATA directly.
Use line_items.total_net_sales for sales and orders.order_count for order count, and do not redefine the metrics on your own.
(The prompt in the screenshot is the original Japanese version.)
CoCo shows this as in the screenshot below and starts by drafting an implementation plan. For some reason it wrote the plan in English, so I asked it to reply in Japanese.
The implementation plan then appeared in Japanese. It looked fine, so I asked CoCo to build it.
After a short wait, a .dash file was created in Workspaces and opened, with charts showing in the tiles.
Next, I asked CoCo to add a chart action. Chart actions can't be added from the tile editor; they can only be added through CoCo.
I sent CoCo the following request:
When a bar in the sales by market segment chart is clicked,
add a chart action that filters the sales by year and
the order count by order priority to the selected market segment.
As a result, a chart action was added: clicking a bar in "Total Sales by Market Segment" now updates the other tiles, as shown below.
By the way, here's what the definition of the added filter looked like.
(You can also add filters manually through the GUI. However, the GUI didn't let me pick a Semantic View dimension, so the filter uses a SELECT statement that extracts a single dimension from the Semantic View.)
Let's open the generated .dash file and look at its contents.
You can view it by clicking View JSON in the top-right corner of the dashboard.
The file is in JSON format. The file generated this time is made up of the following main blocks. (The full file is included in a collapsible section at the end of this step.)
| Key | Contents |
|---|---|
version /spec_version /spec_revision |
Format version information ( 2 /2 /17 in this case) |
title |
Dashboard title |
filters |
Filter definitions ( order_year ,market_segment ) |
datasources |
SQL definitions used by tiles and filter options |
tabs |
Tabs and the tiles (charts and tables) inside them |
layout |
Position and size of each tile on the 12-column grid |
scopes |
Definitions of how far chart actions reach |
filters)
Order year is defined as single_select, and market segment as multi_select. The options use "source": "query" and reference a datasource that fetches them (such as ds-order_year-options).
The official docs write filter modes with hyphens, like multi-select, but the actual file uses underscores, like multi_select.
datasources)
Each datasource is "type": "sql", with the SQL embedded as a string. For example, the SQL for sales by year looked like this:
SELECT
order_year,
AGG(total_net_sales) AS TOTAL_NET_SALES
FROM DASH_TEST_DB.SV.TPCH_SALES_SV
WHERE
{{ filter('order_year') }}
AND {{ filter('market_segment') }}
GROUP BY
order_year
ORDER BY
order_year
Key points:
FROM and aggregates metrics with AGG(). TPCH_SALES_SV and none touched the underlying tables in SNOWFLAKE_SAMPLE_DATA directly. Sales used total_net_sales, order count used order_count, and neither metric was redefined. So the instructions from Step 1 were followed.{{ filter('order_year') }} in the SQL and the filter_id in filter_bindings. ds-sales-by-segment) is bound only to order_year; the market_segment filter isn't connected. My guess is that this is because this tile is the source of the chart action.ds-order_year-options) use SQL that extracts a single dimension from the Semantic View with GROUP BY, as mentioned in Step 2. tabs)
There are two tile types, chart and table. A chart tile's spec uses mark (line / bar) and encoding (field, type, and title for x / y). The official docs say chart tiles are rendered with Vega-Lite, and this format is indeed close to Vega-Lite syntax.
The chart action added in Step 2 is defined as interactions on the sales by market segment tile.
"interactions": [
{
"trigger": "select",
"operator": "set_filter",
"fields": ["MARKET_SEGMENT"],
"targets": [
{
"scope": "segment_action",
"fields": ["MARKET_SEGMENT"],
"filter": "market_segment"
}
]
}
]
When a bar is selected (select), this sets the market_segment filter (set_filter). The target tiles (sales by year and order count by order priority) have "scope": "segment_action", and segment_action is defined in scopes at the end of the file.
layout)
layout specifies x / y / w / h for each tile, per tab. Sales by year has a width of 12 (full width). Sales by market segment and order count by order priority have a width of 6 each and sit side by side in the bottom row. This matches the "12-column grid" described in the official docs.
{
"version": 2,
"title": "TPC-H 売上ダッシュボード",
"filters": {
"order_year": {
"label": "注文年",
"mode": "single_select",
"valueType": "STRING",
"options": {
"source": "query",
"datasources": [
"ds-order_year-options"
]
},
"default_values": []
},
"market_segment": {
"label": "市場セグメント",
"mode": "multi_select",
"valueType": "STRING",
"options": {
"source": "query",
"datasources": [
"ds-market_segment-options"
]
},
"default_values": []
}
},
"datasources": {
"ds-order_year-options": {
"type": "sql",
"sql": "SELECT\n order_year\nFROM DASH_TEST_DB.SV.TPCH_SALES_SV\nGROUP BY\n order_year\nORDER BY\n order_year"
},
"ds-sales-by-year": {
"type": "sql",
"columns": [
{
"name": "ORDER_YEAR",
"type": "NUMBER"
},
{
"name": "TOTAL_NET_SALES",
"type": "NUMBER"
}
],
"sql": "SELECT\n order_year,\n AGG(total_net_sales) AS TOTAL_NET_SALES\nFROM DASH_TEST_DB.SV.TPCH_SALES_SV\nWHERE\n {{ filter('order_year') }}\n AND {{ filter('market_segment') }}\nGROUP BY\n order_year\nORDER BY\n order_year",
"filter_bindings": [
{
"filter_id": "order_year"
},
{
"filter_id": "market_segment"
}
]
},
"ds-sales-by-segment": {
"type": "sql",
"columns": [
{
"name": "MARKET_SEGMENT",
"type": "VARCHAR"
},
{
"name": "TOTAL_NET_SALES",
"type": "NUMBER"
}
],
"sql": "SELECT\n market_segment,\n AGG(total_net_sales) AS TOTAL_NET_SALES\nFROM DASH_TEST_DB.SV.TPCH_SALES_SV\nWHERE\n {{ filter('order_year') }}\nGROUP BY\n market_segment\nORDER BY\n TOTAL_NET_SALES DESC",
"filter_bindings": [
{
"filter_id": "order_year"
}
]
},
"ds-orders-by-priority": {
"type": "sql",
"columns": [
{
"name": "ORDER_PRIORITY",
"type": "VARCHAR"
},
{
"name": "ORDER_COUNT",
"type": "NUMBER"
}
],
"sql": "SELECT\n order_priority,\n AGG(order_count) AS ORDER_COUNT\nFROM DASH_TEST_DB.SV.TPCH_SALES_SV\nWHERE\n {{ filter('order_year') }}\n AND {{ filter('market_segment') }}\nGROUP BY\n order_priority\nORDER BY\n order_priority",
"filter_bindings": [
{
"filter_id": "order_year"
},
{
"filter_id": "market_segment"
}
]
},
"ds-market_segment-options": {
"type": "sql",
"sql": "SELECT\n market_segment\nFROM DASH_TEST_DB.SV.TPCH_SALES_SV\nGROUP BY\n market_segment\nORDER BY\n market_segment"
}
},
"tabs": [
{
"tab_id": "tab-0",
"title": "概要",
"tiles": [
{
"tile_id": "sales-by-year",
"type": "chart",
"title": "年別 売上合計の推移",
"datasources": [
"ds-sales-by-year"
],
"spec": {
"mark": "line",
"encoding": {
"x": {
"field": "ORDER_YEAR",
"type": "ordinal",
"title": "注文年"
},
"y": {
"field": "TOTAL_NET_SALES",
"type": "quantitative",
"title": "売上合計"
}
}
},
"scope": "segment_action"
},
{
"tile_id": "sales-by-segment",
"type": "chart",
"title": "市場セグメント別 売上合計",
"datasources": [
"ds-sales-by-segment"
],
"spec": {
"mark": "bar",
"encoding": {
"x": {
"field": "MARKET_SEGMENT",
"type": "nominal",
"title": "市場セグメント",
"sort": "-y"
},
"y": {
"field": "TOTAL_NET_SALES",
"type": "quantitative",
"title": "売上合計"
}
}
},
"interactions": [
{
"trigger": "select",
"operator": "set_filter",
"fields": [
"MARKET_SEGMENT"
],
"targets": [
{
"scope": "segment_action",
"fields": [
"MARKET_SEGMENT"
],
"filter": "market_segment"
}
]
}
]
},
{
"tile_id": "orders-by-priority",
"type": "table",
"title": "注文優先度別 注文件数",
"datasources": [
"ds-orders-by-priority"
],
"spec": {
"columns": [
{
"field": "ORDER_PRIORITY"
},
{
"field": "ORDER_COUNT"
}
]
},
"scope": "segment_action"
}
]
}
],
"layout": {
"tab-0": {
"meta": {
"default": true
},
"positions": {
"sales-by-year": {
"x": 0,
"y": 0,
"w": 12,
"h": 4
},
"sales-by-segment": {
"x": 0,
"y": 4,
"w": 6,
"h": 4
},
"orders-by-priority": {
"x": 6,
"y": 4,
"w": 6,
"h": 4
}
}
}
},
"spec_version": 2,
"spec_revision": 17,
"scopes": {
"segment_action": {}
}
}
To show the dashboard to other users, you share it as an artifact.
First, as an administrator, set up the privileges for the viewer role. Viewer queries run with the viewer's own privileges, but when they reference a Semantic View, only SELECT on the Semantic View is needed. We don't grant any privileges on the underlying tables (SNOWFLAKE_SAMPLE_DATA).
USE ROLE ACCOUNTADMIN;
-- Privileges required for the viewer role (no privileges on the underlying tables)
GRANT USAGE ON WAREHOUSE DASH_TEST_WH TO ROLE DASH_VIEWER;
GRANT USAGE ON DATABASE DASH_TEST_DB TO ROLE DASH_VIEWER;
GRANT USAGE ON SCHEMA DASH_TEST_DB.SV TO ROLE DASH_VIEWER;
GRANT SELECT ON SEMANTIC VIEW DASH_TEST_DB.SV.TPCH_SALES_SV TO ROLE DASH_VIEWER;
Next, in Snowsight, with the role set to DASH_AUTHOR and the warehouse set to DASH_TEST_WH, open the .dash file in Workspaces and select Publish artifact.
In the dialog that opens, do the following:
Who has access, add the DASH_VIEWER role.Can view. Publish.
When you click Publish, the artifact link is copied to your clipboard.
You can find published dashboards under Workspaces → Artifacts in Snowsight.
How the artifact relates to the original .dash file, and how updates work
A shared artifact is stored separately from the original .dash file.
https://docs.snowflake.com/en/user-guide/snowflake-artifacts-create-share
An artifact is a securable object identified by a UUID. The UUID appears after /artifacts/ in the artifact's URL.
You can grant three types of privileges:
| Privilege | Description |
|---|---|
OWNERSHIP |
Delete and transfer ownership |
READ |
View the contents |
WRITE |
View and modify (including publishing changes to the artifact) |
If you already granted Can view to DASH_VIEWER through the UI earlier, the SQL below isn't a required step. I'm including it as an example of how to grant privileges with SQL, run by an administrator (ACCOUNTADMIN).
USE ROLE ACCOUNTADMIN;
-- Example: grant READ to the viewer role
GRANT READ
ON ARTIFACT "<artifact UUID>"
TO ROLE DASH_VIEWER;
SQL for checking artifacts, such as SHOW ARTIFACTS and DESCRIBE ARTIFACT, is listed as "Coming soon" as of November 13, 2026. So this article doesn't check privileges with SQL.
The user who created the dashboard owns the artifact, so I checked viewer access with a different user. As an administrator, grant the viewer role to an existing test user.
USE ROLE ACCOUNTADMIN;
GRANT ROLE DASH_VIEWER TO USER <viewer_user>;
This assumes the test user doesn't have DASH_AUTHOR or any administrator role.
I logged in to Snowsight as this user and opened Workspaces → Artifacts. The dashboard was viewable, as shown below.
I tried Snowflake Dashboards 2.0 together with a Semantic View and confirmed that sharing through roles works.
It's an AI-first, code-based dashboard, and I found it very easy to use. Give it a try!