+1 (415) 997-4269

Snowflake Secure Data Sharing in Practice: Direct Shares, Reader Accounts, Listings, and Clean Rooms

Most Snowflake teams discover data sharing the day a partner asks for a nightly CSV drop. You build an export job, an S3 bucket, a PGP key, a re-ingest pipeline on the other side, and a support ticket queue for the days the file is late. Snowflake's sharing stack exists to delete all of that: the consumer queries your storage, live, with no copy and no pipeline.

This tutorial walks the four levels of that stack — direct shares, reader accounts, listings (private and Marketplace), and clean rooms — with the SQL for each and a decision rule for which one a given relationship needs.

The mental model

A share is a named object holding grants: "these tables, views, and functions, readable by these accounts." Nothing is copied. The consumer mounts the share as a read-only database and pays for the compute they use against it; you keep paying for storage.

Everything above a direct share is packaging:

MechanismConsumer needsDiscoveryBest for
Direct shareTheir own Snowflake account, same region/cloud (or replicated)You send the account identifier1-to-few named partners
Reader accountNothing — you create the account for themN/APartners with no Snowflake
Private listingTheir own Snowflake account, any regionAppears in their Marketplace under "Private"Many named consumers, cross-region
Marketplace listingAny Snowflake accountPublic catalogProductising data, lead-gen, monetisation
Clean roomTheir own accountInviteTwo parties who cannot see each other's rows

Level 1: the direct share

Start from a dedicated share database so you never accidentally expose a base table.

USE ROLE accountadmin;   -- or a role with CREATE SHARE

CREATE DATABASE IF NOT EXISTS share_out;
CREATE SCHEMA IF NOT EXISTS share_out.partner_a;

-- Expose a shaped view, never the raw table
CREATE OR REPLACE SECURE VIEW share_out.partner_a.orders_v AS
SELECT
    order_id,
    order_date,
    region,
    product_sku,
    units,
    net_amount
FROM analytics.public.fact_orders
WHERE partner_code = 'PARTNER_A';

CREATE SHARE partner_a_share
  COMMENT = 'Order feed for Partner A, contract #4412';

GRANT USAGE   ON DATABASE share_out            TO SHARE partner_a_share;
GRANT USAGE   ON SCHEMA   share_out.partner_a  TO SHARE partner_a_share;
GRANT SELECT  ON VIEW     share_out.partner_a.orders_v TO SHARE partner_a_share;

ALTER SHARE partner_a_share ADD ACCOUNTS = ORGNAME.PARTNER_ACCOUNT;

The consumer, in their account:

CREATE DATABASE partner_a_feed FROM SHARE PROVIDERORG.PROVIDERACCT.partner_a_share;
GRANT IMPORTED PRIVILEGES ON DATABASE partner_a_feed TO ROLE analyst;

SELECT region, SUM(net_amount) FROM partner_a_feed.partner_a.orders_v GROUP BY 1;

Three rules that save incidents later:

  1. Always share secure views, not tables. A SECURE VIEW suppresses optimizer behaviours that can leak filtered-out rows, and it gives you a stable contract you can refactor behind.
  2. Never share SELECT *. Name columns so a new PII column added upstream does not silently land in a partner's hands.
  3. Version the interface. orders_v1, orders_v2; deprecate on a schedule. Consumers build dashboards on your view names.

Multi-tenant sharing with one view

If you have fifty partners on the same fact table, do not build fifty views. Use CURRENT_ACCOUNT() in the shared view and a mapping table:

CREATE OR REPLACE SECURE VIEW share_out.multi.orders_v AS
SELECT o.*
FROM analytics.public.fact_orders o
JOIN admin.public.partner_accounts m
  ON o.partner_code = m.partner_code
WHERE m.snowflake_account = CURRENT_ACCOUNT();

One share, one view, row access determined by who is asking. Add a row access policy on the base table if you want the same guarantee enforced centrally.

Level 2: reader accounts for partners without Snowflake

CREATE MANAGED ACCOUNT partner_b_reader
  ADMIN_NAME = 'partner_b_admin',
  ADMIN_PASSWORD = '<generated>',
  TYPE = READER,
  COMMENT = 'Reader account for Partner B';

SHOW MANAGED ACCOUNTS;   -- returns the URL and locator to hand over

ALTER SHARE partner_b_share ADD ACCOUNTS = <reader_locator>;

The catch that surprises people: you pay for the reader account's compute. Put a resource monitor on its warehouses on day one.

CREATE RESOURCE MONITOR rm_partner_b
  WITH CREDIT_QUOTA = 50
  FREQUENCY = MONTHLY
  START_TIMESTAMP = IMMEDIATELY
  TRIGGERS ON 80 PERCENT DO NOTIFY
           ON 100 PERCENT DO SUSPEND;

Reader accounts are a good on-ramp and a bad long-term home. Treat them as a 6–12 month bridge while the partner adopts Snowflake, or as the delivery mechanism for a small customer who will never adopt it.

Level 3: listings, cross-region, and auto-fulfillment

A direct share only works between accounts in the same cloud and region. A listing wraps the share in a product — title, description, sample SQL, usage terms — and, with auto-fulfillment, replicates the data to whatever region the consumer lives in.

CREATE LISTING retail_signals_private
  IN DATA EXCHANGE snowflake_data_marketplace
  SHARE retail_signals_share AS
$$
title: "Retail Demand Signals — Weekly"
description: "SKU-level demand and out-of-stock signals, refreshed Mondays 06:00 UTC."
terms_of_service:
  type: "OFFLINE"
auto_fulfillment:
  refresh_schedule: "10 MINUTE"
  refresh_type: "SUB_DATABASE"
targets:
  accounts: ["ORGX.ACCTY"]
$$
PUBLISH = TRUE;

Notes from real projects:

  • Auto-fulfillment costs you replication compute and duplicate storage in each target region. Bill it back or price it in; it is the single most common surprise line item in a data-product P&L.
  • Private listings are the right default for enterprise partner programmes. You get discovery, documentation, versioning, and usage telemetry without a public catalog entry.
  • Consumer telemetry lives in SNOWFLAKE.DATA_SHARING_USAGELISTING_ACCESS_HISTORY, LISTING_TELEMETRY_DAILY, LISTING_CONSUMPTION_DAILY. This is how you find out which partner actually uses the feed before the renewal conversation.
SELECT consumer_account_name,
       SUM(query_count) AS queries,
       COUNT(DISTINCT DATE_TRUNC('day', query_date)) AS active_days
FROM snowflake.data_sharing_usage.listing_access_history
WHERE query_date >= DATEADD('day', -30, CURRENT_DATE())
GROUP BY 1
ORDER BY queries DESC;

Monetising: paid listings

Snowflake supports paid listings with usage-based or flat-fee pricing, billed through Snowflake and paid out to you. The engineering work is identical to a private listing; the work that actually takes time is legal terms, a support SLA, and deciding your refresh contract. Do not build a paid listing until at least two private-listing consumers are querying it weekly.

Level 4: clean rooms, when neither side can see the other's rows

The classic case: a retailer and a brand want overlap analysis on customers, and neither may expose customer identifiers. A Snowflake Data Clean Room lets both parties join their data under templates that only emit aggregates above a minimum threshold.

The modern path is the Data Clean Rooms native app rather than hand-rolled row access policies. Conceptually:

  1. Both sides install the clean room app and create an environment.
  2. The provider links datasets and defines analysis templates — parameterised SQL the consumer may run but not read row-level output from.
  3. Policies enforce minimum aggregation (for example, no cell with fewer than 50 distinct identities) and column allow-lists.
  4. The consumer runs a template; only the aggregate result is returned.
-- Illustrative shape of a template body
SELECT p.audience_segment,
       COUNT(DISTINCT p.identity_hash) AS overlap_identities,
       SUM(c.conversions)              AS conversions
FROM provider_data.identity p
JOIN consumer_data.exposure c
  ON p.identity_hash = c.identity_hash
GROUP BY 1
HAVING COUNT(DISTINCT p.identity_hash) >= 50;

If you are hand-building, the primitives are: secure views + row access policies + a jobs table the consumer writes requests into and a task that executes approved templates. It works, and it is roughly ten times the maintenance of the native app. Only do it when a compliance team demands full control of the code path.

Governance you should put in before the first share goes out

  • A dedicated SHARING_ADMIN role that owns share_out and holds CREATE SHARE. Nobody shares from ACCOUNTADMIN ad hoc.
  • Tag every shared object. ALTER VIEW ... SET TAG governance.exposure = 'external' so a tag-based masking policy and your audit query both find them.
  • A weekly drift check. Anything granted to a share that is not in your registry is an incident:
SHOW SHARES;
SELECT "name", "to" FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));

SHOW GRANTS TO SHARE partner_a_share;
  • Egress reality check. Cross-region auto-fulfillment and reader-account compute are the two costs that grow silently. Both belong in a monthly review.
  • Deprovisioning runbook. ALTER SHARE partner_a_share REMOVE ACCOUNTS = ... on the day a contract ends, tied to the same checklist as SSO offboarding.

Choosing, in one paragraph

If the consumer is a Snowflake account in your region and the relationship is bilateral, use a direct share — it is one afternoon of work. If they have no Snowflake, use a reader account with a resource monitor and a sunset date. If you have more than a handful of consumers, or they live in other regions, move to private listings so you get versioning, documentation, and usage telemetry. Go Marketplace/paid only when the data is a product with an owner and a roadmap. Use a clean room when the blocker is not distribution but the fact that neither side is allowed to see the other's rows.

Checklist

  1. Shared objects are secure views with named columns, in a dedicated share database.
  2. Multi-tenant filtering uses CURRENT_ACCOUNT() or a row access policy, not one view per partner.
  3. Reader accounts have resource monitors from day one.
  4. Cross-region delivery goes through listings with auto-fulfillment, with replication cost accounted for.
  5. Consumption is reviewed monthly from SNOWFLAKE.DATA_SHARING_USAGE.
  6. A single role owns sharing; a weekly job diffs live grants against the registry.
  7. Every share has an owner, a contract reference, and an end-date runbook.

PowderInsights designs and builds Snowflake data sharing and data-product programmes — from a first partner share to private listings, auto-fulfillment economics, and clean room templates — with senior Snowflake architects and developers who have done it in production. Get in touch with the partner scenario you are trying to solve and we will map it to the right mechanism.