DEV Community

Takuya Shoji
Takuya Shoji

Posted on

Near-Zero-Maintenance Open Data Sharing with Snowflake Managed Iceberg Open Data Sharing

Introduction

Note: This article reflects my personal views and does not represent Snowflake's official position.

Note: (August 2026) Open Data Sharing is in Public Preview as of this writing. The feature may change before it reaches general availability.

With the release of Open Data Sharing, Snowflake's Iceberg Table offering now covers three dimensions: catalog, storage, and how you share the data. This gives you the flexibility to pick the combination that best fits your use case.

For a great overview of the catalog and storage options, see this article (Japanese):
https://zenn.dev/snowflakejp/articles/fdf4ec46570d86

In this article I focus on the near-zero-maintenance combination: Snowflake Managed Storage + Open Data Sharing, implemented with TPC-H sample data. Here's what we'll cover:

  1. Create Snowflake Managed Iceberg Tables
  2. Load TPC-H sample data
  3. Publish via Open Data Sharing as an Iceberg REST API (both SQL and Snowsight UI walkthrough)
  4. Query from an external engine (DuckDB) — no Snowflake account on the consumer side

Implementation uses an Azure Japan East account.


tl;dr

Catalog options

  • Snowflake as catalog — Snowflake manages table metadata (CATALOG = 'SNOWFLAKE'). Supports full DML, Time Travel, clustering, replication, and most Snowflake features. Best when you want the full Snowflake experience.
  • External catalog — Metadata is managed by an external system (AWS Glue, Databricks Unity Catalog, etc.) and accessed through a catalog integration or catalog-linked database. Use this when you need to connect to an existing Iceberg catalog or work with Delta format. Note: cloning, clustering, replication, and standard streams are not available.

Storage options

  • Snowflake Managed Storage — Snowflake stores and manages the table files. No cloud storage setup or IAM configuration needed. Just specify EXTERNAL_VOLUME = SNOWFLAKE_MANAGED in CREATE ICEBERG TABLE.
  • External volume — Table files are stored in your own S3, GCS, or Azure Blob. Use when you want to keep data in your existing cloud storage or need to work with an external catalog.

Sharing options

  • Secure Data Sharing — Snowflake's established data sharing mechanism for sharing tables, views, Semantic Views, AI products (Cortex Search, Cortex Agents), and Native Apps between Snowflake accounts. Best for secure sharing within the Snowflake ecosystem.
  • Open Data Sharing — Provides read-only access to Iceberg tables via an Iceberg REST API using a PAT (Programmatic Access Token) issued for an External Consumer object. No Snowflake account required on the consumer side. Best for sharing with external partners outside the Snowflake ecosystem.
  • IRC API through Horizon Catalog — Provides read/write access from external engines like Spark and Trino through Snowflake's existing auth mechanisms (PAT, key pair, External OAuth, WIF). Supports masking/row-access policies and vended credentials. Best for trusted internal consumers that need write access.

Storage comparison

Snowflake Managed External Volume
Setup Just specify EXTERNAL_VOLUME = SNOWFLAKE_MANAGED Requires creating cloud storage, IAM configuration, and an external volume object
Table file location Snowflake-provided storage Your own cloud storage (S3, GCS, Azure, S3-compatible)
Fail-safe Available for Permanent tables (7 days) None (manage via cloud storage versioning)
Time Travel Available (up to 90 days) None
Bucket & IAM management Handled automatically by Snowflake You configure buckets and IAM policies
Data compaction Free and automatic when Snowflake writes Charged separately
Supported clouds AWS and Azure only AWS, Azure, GCP
Best for Minimizing operational overhead on new or existing workloads Reusing existing cloud storage or working with external Iceberg catalogs

Sharing comparison

Both Open Data Sharing and Horizon Catalog use the Iceberg REST Catalog API. Here's how they compare alongside traditional Secure Data Sharing:

Secure Data Sharing Open Data Sharing IRC API through Horizon Catalog
Consumer Snowflake account Required Not required Not required
Authentication Snowflake credentials (account-to-account) External consumer PAT (other methods coming) Snowflake user PAT / key pair / External OAuth / WIF
Access level Read-only Read-only Read + write
Vended credentials Not supported Not supported Supported
Data protection policies Supported (masking, row access) Not supported Masking and row access policies supported
Shareable objects Tables, views, Semantic Views, AI products, and more Only tables included in the EXTERNAL LISTING All Iceberg tables in the account*¹
Status GA Public Preview GA
Best for Secure sharing within and outside the organization with Snowflake accounts Read-only sharing with external partners who don't have a Snowflake account Read/write access for internal external engines

Note: *¹ Access to External Iceberg tables via catalog-linked database is in Public Preview as of August 2026. See the documentation for details.


Architecture Overview

Overall architecture: SNOWFLAKE_SAMPLE_DATA (TPC-H SF1) is loaded into an Iceberg Table with SNOWFLAKE_MANAGED storage via INSERT/CTAS. The table flows through a SHARE to an EXTERNAL LISTING. An EXTERNAL CONSUMER is linked to the EXTERNAL LISTING, and an external client (DuckDB/Spark/PyIceberg) accesses the data via Iceberg REST API + PAT.


Prerequisites

Item Requirement
Snowflake account AWS or Azure region (SNOWFLAKE_MANAGED is not available on GCP — see note below)
Role ACCOUNTADMIN (or a custom role with the necessary privileges)
Warehouse An active warehouse is required when creating Iceberg tables
TPC-H sample data SNOWFLAKE_SAMPLE_DATA database (available in all accounts)
Open Data Sharing Public Preview. Data is served from the provider account's region (no cross-region replication)

Note: If you're using a Google Cloud Platform account, SNOWFLAKE_MANAGED is not available. Create an external volume pointing to Google Cloud Storage and specify EXTERNAL_VOLUME = '<your_gcs_volume>' instead. See the official documentation.


Step 1: Set Up the Database, Schema, and Warehouse

USE ROLE ACCOUNTADMIN;

-- Create warehouse (skip if you already have one)
CREATE WAREHOUSE IF NOT EXISTS iceberg_demo_wh
  WAREHOUSE_SIZE = 'XSMALL'
  AUTO_SUSPEND   = 60
  AUTO_RESUME    = TRUE;

-- Create database and schema
CREATE DATABASE IF NOT EXISTS iceberg_demo_db;
CREATE SCHEMA IF NOT EXISTS iceberg_demo_db.tpch;

USE DATABASE iceberg_demo_db;
USE SCHEMA tpch;
USE WAREHOUSE iceberg_demo_wh;
Enter fullscreen mode Exit fullscreen mode

Step 2: Create Managed Iceberg Tables

'SNOWFLAKE_MANAGED' is a reserved keyword, not the name of an external volume you create. Specifying it means Snowflake manages the underlying storage for you — no S3 bucket, Azure Blob, or IAM setup required.

We'll demonstrate two creation patterns:

  • CUSTOMER: define columns explicitly, load data in Step 3
  • ORDERS: use CTAS to create and load in one shot

First, create the CUSTOMER table with an explicit column definition:

CREATE OR REPLACE ICEBERG TABLE customer (
  c_custkey    INTEGER,
  c_name       STRING,
  c_address    STRING,
  c_nationkey  INTEGER,
  c_phone      STRING,
  c_acctbal    NUMBER(12, 2),
  c_mktsegment STRING,
  c_comment    STRING
)
EXTERNAL_VOLUME = 'SNOWFLAKE_MANAGED'
CATALOG         = 'SNOWFLAKE';
Enter fullscreen mode Exit fullscreen mode

Then create ORDERS using CTAS — this creates and loads the table in one statement:

CREATE OR REPLACE ICEBERG TABLE orders
  EXTERNAL_VOLUME = 'SNOWFLAKE_MANAGED'
  CATALOG         = 'SNOWFLAKE'
AS
  SELECT * FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS;
Enter fullscreen mode Exit fullscreen mode

After creation, run SHOW ICEBERG TABLES to verify the storage type:

SHOW ICEBERG TABLES IN SCHEMA iceberg_demo_db.tpch;
Enter fullscreen mode Exit fullscreen mode

If the external_volume_name column shows SNOWFLAKE_MANAGED and iceberg_table_type shows MANAGED, the tables were created correctly.

SHOW ICEBERG TABLES result showing CUSTOMER and ORDERS with SNOWFLAKE_MANAGED storage and MANAGED type


Step 3: Load TPC-H Data

SNOWFLAKE_SAMPLE_DATA is read-only, so we use INSERT INTO ... SELECT to pull the data into our Iceberg tables.

-- Load CUSTOMER (TPC-H SF1: ~150,000 rows)
INSERT INTO iceberg_demo_db.tpch.customer
SELECT
  c_custkey,
  c_name,
  c_address,
  c_nationkey,
  c_phone,
  c_acctbal,
  c_mktsegment,
  c_comment
FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER;

-- Verify row count
SELECT COUNT(*) FROM iceberg_demo_db.tpch.customer;
-- → 150,000
Enter fullscreen mode Exit fullscreen mode

Iceberg tables support full DML just like regular Snowflake tables:

-- Check customer distribution by market segment
SELECT c_mktsegment, COUNT(*) AS cnt
FROM iceberg_demo_db.tpch.customer
GROUP BY c_mktsegment
ORDER BY cnt DESC;
Enter fullscreen mode Exit fullscreen mode

Step 4: Set Up Open Data Sharing

With the data loaded, we can now set up external sharing. Open Data Sharing can be configured via SQL or the Snowsight UI — the underlying operations are identical.

Open Data Sharing setup flow: Snowflake Admin creates an External Listing with shared tables, gets catalog_uri, creates an External Consumer, receives PAT Secret, then provides catalog_uri + PAT to the external client who accesses via Iceberg REST API

⚠️ Warning: The PAT secret is shown only once at creation time. Copy it immediately — there's no way to retrieve it later. If you lose it, drop the PAT and create a new one.

Note: If your account has a network policy, you need to add an entry for each external consumer. See the Open Data Sharing documentation for details.


Option A: SQL Setup

Step 4-SQL-1: Create a Share and Grant Privileges

-- Create the share
CREATE OR REPLACE SHARE iceberg_open_share;

-- Grant access to the database, schema, and tables
GRANT USAGE  ON DATABASE iceberg_demo_db                 TO SHARE iceberg_open_share;
GRANT USAGE  ON SCHEMA   iceberg_demo_db.tpch            TO SHARE iceberg_open_share;
GRANT SELECT ON TABLE    iceberg_demo_db.tpch.customer   TO SHARE iceberg_open_share;
GRANT SELECT ON TABLE    iceberg_demo_db.tpch.orders     TO SHARE iceberg_open_share;
Enter fullscreen mode Exit fullscreen mode

Step 4-SQL-2: Create the External Listing

CREATE EXTERNAL LISTING IF NOT EXISTS iceberg_open_listing
  SHARE iceberg_open_share AS
$$
title: "TPC-H Iceberg Demo"
description: "Snowflake Managed Iceberg Table demo using TPC-H SF1 dataset"
open_sharing:
  catalog_identifier: "iceberg-demo-db"
listing_terms:
  type: "OFFLINE"  # Term acceptance happens outside the Snowflake platform
external_targets:
  access:
    - external_consumers: [ICEBERG_CONSUMER]
$$;
Enter fullscreen mode Exit fullscreen mode

catalog_identifier is the catalog name that consumers will use to reference the data (corresponds to the "Catalog Identifier" field in the Snowsight UI).

Step 4-SQL-3: Verify the Listing and Get the Connection Info

-- Check listing status
SHOW LISTINGS LIKE 'ICEBERG_OPEN_LISTING';
DESC LISTING iceberg_open_listing;

-- Get the catalog_uri to give to consumers
CALL SYSTEM$GET_LISTING_URL_FOR_EXTERNAL_CONSUMER('ICEBERG_OPEN_LISTING');
Enter fullscreen mode Exit fullscreen mode

Sample result:

{
  "catalog": "iceberg-demo-db",
  "catalog_uri": "https://<account-identifier>.snowflakecomputing.com/polaris/open-sharing/api/catalog",
  "scope": "session:role:external"
}
Enter fullscreen mode Exit fullscreen mode

The URL contains /polaris/open-sharing/api/catalog — this is Open Data Sharing's dedicated endpoint, distinct from the Horizon Catalog endpoint (/polaris/api/catalog).

Give consumers four pieces of information: catalog_uri, catalog (catalog name), scope, and the PAT secret.

Step 4-SQL-4: Create the External Consumer and Issue a PAT

USE ROLE ACCOUNTADMIN;

-- Create the External Consumer object
CREATE OR REPLACE EXTERNAL CONSUMER iceberg_consumer
  COMMENT = 'Test consumer';

-- Issue a PAT (copy the secret from the result and save it securely)
ALTER EXTERNAL CONSUMER iceberg_consumer ADD PAT demo_pat;
Enter fullscreen mode Exit fullscreen mode

Sample result:

+----------+-------------------------------------------------+
| pat_name | secret                                          |
+----------+-------------------------------------------------+
| DEMO_PAT | <copy this secret and store it safely>          |
+----------+-------------------------------------------------+
Enter fullscreen mode Exit fullscreen mode

Option B: Snowsight UI Setup

The UI walkthrough covers the same operations as the SQL approach. Follow along with the screenshots.

Step 4-UI-1: Open the "Open Sharing" wizard

In Snowsight, go to Data sharing → External sharing. Click the Share dropdown in the top right and select Open share.

External Sharing page —

Step 4-UI-2: Select the tables to share

From the Select Data dropdown, expand ICEBERG_DEMO_DB > TPCH and check both CUSTOMER and ORDERS. Click Done. Only Iceberg tables appear as selectable in the UI.

Table selection: CUSTOMER and ORDERS checked under ICEBERG_DEMO_DB.TPCH

Step 4-UI-3: Configure the listing and add an External Consumer

On the next screen, configure the listing:

  • Catalog Identifier: the name consumers use to reference the catalog (set to iceberg-open-listing here)
  • Open Sharing Identifier / SQL Listing Name: auto-generated, but editable

Listing configuration: catalog identifier set to iceberg-open-listing with auto-generated identifiers

Click + Add under "Add external consumers".

Clicking

Step 4-UI-4: Create a new External Consumer

In the consumer search dropdown, select + New external consumer. Fill in the name and comment in the dialog. The region (Azure - Japan East) and authentication method (PAT Token) are set automatically. Click Create external consumer.

Create External Consumer dialog: name ICEBERG_CONSUMER, region Azure - Japan East (Tokyo), auth method PAT Token

Step 4-UI-5: Confirm and create the open sharing

Verify that the new consumer appears in the "Add External Consumer" field, then click Create open share.

Final confirmation: ICEBERG_CONSUMER added, catalog identifier iceberg-open-listing, listing name ICEBERG_OPEN_LISTING

Step 4-UI-6: Check the endpoint on the detail page

After creation, you land on the listing detail page. The Share Endpoint section shows the catalog name, catalog URI, and OAuth URI. You can also download all connection details as a .txt file using Download config (.txt).

ICEBERG_OPEN_SHARE detail page: sharing endpoints (catalog URI, OAuth URI), external consumer ICEBERG_CONSUMER, and data tables CUSTOMER/ORDERS

This information corresponds to the JSON returned by CALL SYSTEM$GET_LISTING_URL_FOR_EXTERNAL_CONSUMER in the SQL path.

Step 4-UI-7: Issue the PAT from the External Consumer detail page

Click ICEBERG_CONSUMER in the "External consumers" section, then click Generate token in the "Programmatic access tokens" section.

ICEBERG_CONSUMER detail page: Azure - Japan East (Tokyo) region, PAT Token auth method, and

Set a name and expiry in the dialog, then click Create PAT.

New programmatic access token dialog: name DEMO_PAT, expires in 3 months

PAT successfully generated for ICEBERG_CONSUMER — copy or download the token immediately

Click Copy to clipboard and close. Give consumers the catalog URI from Step 4-UI-6 and this PAT secret.

⚠️ Warning: Once the PAT is generated, copy or download the secret immediately. Closing this screen makes it permanently inaccessible.

After setup, your open sharing listing appears under Data Sharing → Share with your account as type "Open Sharing".

External sharing list: ICEBERG_OPEN_SHARE with type


Step 5: Query from DuckDB

We'll use DuckDB 1.2+ with the iceberg extension as our external client.

pip install duckdb
Enter fullscreen mode Exit fullscreen mode
import json, duckdb

# Connection info from Step 4-UI-6 or Step 4-SQL-3
# (result of CALL SYSTEM$GET_LISTING_URL_FOR_EXTERNAL_CONSUMER('ICEBERG_OPEN_LISTING'))
OPEN_SHARING_JSON = '{"catalog_uri": "https://<account-identifier>.snowflakecomputing.com/polaris/open-sharing/api/catalog", "catalog": "<catalog_name>", "scope": "session:role:external"}'
open_sharing = json.loads(OPEN_SHARING_JSON)

CATALOG_URI  = open_sharing["catalog_uri"]  # /polaris/open-sharing/api/catalog
CATALOG_NAME = open_sharing["catalog"]       # value of the catalog field
SCOPE        = open_sharing["scope"]         # session:role:external (fixed for Open Sharing)
TOKEN_URI    = f"{CATALOG_URI.rstrip('/')}/v1/oauth/tokens"

PAT_SECRET   = "<PAT secret saved at issuance time>"

con = duckdb.connect()
con.execute("INSTALL iceberg; LOAD iceberg;")
con.execute("INSTALL httpfs;  LOAD httpfs;")

# Register the PAT as an OAuth2 credential
# DuckDB automatically runs the client_credentials flow against TOKEN_URI
con.execute(f"""
    CREATE OR REPLACE SECRET open_sharing_secret (
        TYPE              ICEBERG,
        CLIENT_ID         '',
        CLIENT_SECRET     '{PAT_SECRET}',
        OAUTH2_SERVER_URI '{TOKEN_URI}',
        OAUTH2_GRANT_TYPE 'client_credentials',
        OAUTH2_SCOPE      '{SCOPE}'
    )
""")

# Attach the Iceberg REST Catalog
con.execute(f"""
    ATTACH '{CATALOG_NAME}' AS open_catalog (
        TYPE                   ICEBERG,
        ENDPOINT               '{CATALOG_URI}',
        SECRET                 open_sharing_secret,
        ACCESS_DELEGATION_MODE 'vended_credentials'
    )
""")

# List all tables (schema column gives the query path)
print(con.execute("SHOW ALL TABLES").fetchdf())
Enter fullscreen mode Exit fullscreen mode

Sample SHOW ALL TABLES output:

       database schema      name
0  open_catalog   TPCH  CUSTOMER
1  open_catalog   TPCH    ORDERS
Enter fullscreen mode Exit fullscreen mode

Tables are referenced as open_catalog."<schema>"."<table>". Always check SHOW ALL TABLES to get the exact schema name before querying.

Check the CUSTOMER table schema and fetch the first rows:

# Describe schema
print(con.execute('DESCRIBE open_catalog."TPCH"."CUSTOMER"').fetchdf())

# Fetch first 10 rows
print(con.execute('''
    SELECT c_custkey, c_name, c_mktsegment, c_acctbal
    FROM open_catalog."TPCH"."CUSTOMER"
    LIMIT 10
''').fetchdf())
Enter fullscreen mode Exit fullscreen mode
   C_CUSTKEY              C_NAME C_MKTSEGMENT  C_ACCTBAL
0          1  Customer#000000001     BUILDING     711.56
1          2  Customer#000000002   AUTOMOBILE     121.65
2          3  Customer#000000003   AUTOMOBILE    7498.12
3          4  Customer#000000004    MACHINERY    2866.83
4          5  Customer#000000005    HOUSEHOLD     794.47
...
Enter fullscreen mode Exit fullscreen mode

Row count and market segment breakdown (TPC-H SF1: 150,000 total):

print(con.execute('''
    SELECT c_mktsegment, COUNT(*) AS cnt
    FROM open_catalog."TPCH"."CUSTOMER"
    GROUP BY c_mktsegment ORDER BY cnt DESC
''').fetchdf())
Enter fullscreen mode Exit fullscreen mode
  C_MKTSEGMENT    cnt
0    HOUSEHOLD  30189
1     BUILDING  30142
2    FURNITURE  29968
3    MACHINERY  29949
4   AUTOMOBILE  29752
Enter fullscreen mode Exit fullscreen mode

Join CUSTOMER and ORDERS for a revenue summary by segment:

print(con.execute('''
    SELECT
        c.c_mktsegment,
        COUNT(o.o_orderkey)             AS order_count,
        ROUND(SUM(o.o_totalprice), 2)   AS total_revenue,
        ROUND(AVG(o.o_totalprice), 2)   AS avg_order_price
    FROM open_catalog."TPCH"."ORDERS"   o
    JOIN open_catalog."TPCH"."CUSTOMER" c ON o.o_custkey = c.c_custkey
    GROUP BY c.c_mktsegment
    ORDER BY total_revenue DESC
''').fetchdf())
Enter fullscreen mode Exit fullscreen mode
  C_MKTSEGMENT  order_count   total_revenue  avg_order_price
0     BUILDING       303959  45906759082.38        151029.44
1    HOUSEHOLD       300147  45393200282.40        151236.57
2    FURNITURE       299461  45312940516.95        151314.99
3    MACHINERY       298980  45201073855.83        151184.26
4   AUTOMOBILE       297453  45015339723.27        151335.97
Enter fullscreen mode Exit fullscreen mode

Registering the PAT as an OAuth2 credential via CREATE SECRET is all it takes — DuckDB handles the token exchange automatically. From a machine with no Snowflake account at all, you can query TPC-H data with plain SQL.


Conclusion

Step What we did
Create Iceberg tables Delegated storage management to Snowflake with EXTERNAL_VOLUME = 'SNOWFLAKE_MANAGED' — no external storage or IAM setup
Load data Loaded TPC-H sample data with INSERT INTO ... SELECT (CUSTOMER) and CTAS (ORDERS)
Set up Open Data Sharing (SQL) EXTERNAL CONSUMER → PAT → SHAREEXTERNAL LISTING
Set up Open Data Sharing (UI) External Sharing → Open Sharing wizard — table selection and consumer creation in a few clicks
External access Iceberg REST API + PAT enables data access with no Snowflake account on the consumer side

Snowflake Managed Storage dramatically simplifies setup compared to a custom external volume. No external storage, no IAM to configure. You still get Time Travel, Fail-safe, clustering, and other Snowflake operational capabilities — and because data is stored in Iceberg format, you don't lose interoperability with external engines.

Open Data Sharing differs from Horizon Catalog IRC access in one key way: it doesn't require consumers to have a Snowflake account, and read access is strictly enforced. A single PAT hands off Iceberg REST API access, making it well-suited for sharing data with external partners or systems outside the Snowflake ecosystem. Use the UI for a quick setup; use SQL when you need IaC or automation.

One current limitation: per-table data protection policies (masking, row access) are not yet supported in Open Data Sharing, so you may need to prepare consumer-specific views or tables per share. More capabilities are coming.


Change Log

(20260820) Initial post

Original Japanese Article

Snowflake Managed Iceberg × Open Data Sharing によるニアゼロメンテナンスなオープン共有

Top comments (0)