Data Sharing and the Marketplace

Secure Data Sharing, reader accounts, listings, and why sharing is a core Snowflake primitive rather than an export job.

Every data platform can hand data to someone else. The old way — the way you’ve done it your whole career — is to export a CSV or a Parquet file, drop it on an SFTP server or an S3 bucket, and let the recipient build a pipeline to load it. Then you do it again next week, because the data changed and the file they loaded is now stale. The recipient ends up maintaining ingestion code, you end up maintaining export code, and both sides argue about which version is current. That whole apparatus exists to solve one problem: getting a copy of your bytes onto their disk.

Snowflake’s answer is to skip the copy entirely. A share is a live reference to your data that another account can query directly — no export, no file, no pipeline, no second copy of the data anywhere. When you update the underlying table, the consumer sees the update on their very next query, because they aren’t querying a copy; they’re querying your micro-partitions through a permission grant. This is one of the genuinely differentiated things about the platform, and once it clicks, a lot of integration work you used to think of as unavoidable simply evaporates.

Sharing without copying — what actually happens

Recall from the architecture chapters that a Snowflake table is stored as immutable micro-partitions in cloud storage, and that storage is separated from the compute that reads it. Secure Data Sharing leans directly on that separation. When you share a table, Snowflake doesn’t duplicate its micro-partitions into the consumer’s account. It records metadata that says “this consumer account is allowed to read these objects,” and the consumer’s own warehouse reads the same underlying storage your account already owns.

Two consequences fall out of that, and both matter:

  • There is no data movement and no second copy. You keep paying to store the data exactly once. The consumer stores nothing. If your orders table is a terabyte, sharing it with fifty accounts adds zero terabytes of storage anywhere.
  • The consumer pays for the compute they use. Querying shared data runs on the consumer’s warehouse, billed to the consumer’s account. You provide the data; they provide the horsepower to read it. (The one exception — reader accounts — is where the provider foots that bill, and we’ll get to why.)

Compare that to the CSV world. There, you pay to export, they pay to store their copy, they pay to run the ingest pipeline, and the data is stale the moment it lands. With sharing, there’s one copy, it’s always current, and the reader brings their own compute. The mental shift is from “delivering a file” to “granting a query” — the same shift you already made internally when you stopped emailing spreadsheets and started granting SELECT.

Your first direct share

The most direct form of sharing is, sensibly, called a direct share: you name specific consumer accounts and expose objects to them. Let’s share the bookshop’s sales schema — or rather a piece of it — with a partner account.

A share is its own object type, created and populated by ACCOUNTADMIN (or a role that’s been granted the CREATE SHARE privilege). You build it in three moves: create the share, grant it usage down the object hierarchy so the consumer can traverse to the data, then grant SELECT on the specific objects.

USE ROLE ACCOUNTADMIN;

-- 1. create the share object
CREATE SHARE bookshop_share
  COMMENT = 'Curated sales data for our distribution partner';

-- 2. grant usage down the hierarchy, so the consumer can "see" the path
GRANT USAGE ON DATABASE sales          TO SHARE bookshop_share;
GRANT USAGE ON SCHEMA   sales.public   TO SHARE bookshop_share;

-- 3. grant SELECT on the actual objects to expose
GRANT SELECT ON TABLE sales.public.orders   TO SHARE bookshop_share;
GRANT SELECT ON TABLE sales.public.customer TO SHARE bookshop_share;

Notice the grant target is TO SHARE, not TO ROLE. A share behaves a lot like a role in this respect — it’s a container you grant privileges into — but it’s a role whose “members” are entire other accounts rather than users. The USAGE grants on the database and schema aren’t optional decoration: without USAGE ON DATABASE sales, the consumer can’t even resolve the path to the table, and the SELECT grant has nothing to hang on.

One case-folding reminder, because it bites here exactly as it bites everywhere else in Snowflake: CREATE SHARE bookshop_share stores the share as BOOKSHOP_SHARE. When the consumer references it later, they’ll write the name unquoted and it’ll fold to match. Don’t double-quote share names to force lowercase; you’ll be quoting them in every account that consumes them, forever.

With the objects in place, add the consumer account:

ALTER SHARE bookshop_share ADD ACCOUNTS = partner_acct;

partner_acct is the consumer’s account identifier. In a modern org-based setup that’s their organization_name.account_name; you can add several at once with a comma-separated list. That single statement is the entire “delivery” step. There’s no file to transfer and nothing for the consumer to ingest — the next time they log in, the share is simply there.

Before you hand it off, sanity-check what you’ve built:

SHOW SHARES;                          -- lists outbound (and inbound) shares
SHOW GRANTS TO SHARE bookshop_share;  -- exactly which objects are exposed

SHOW GRANTS TO SHARE is the one to run before every “yes, it’s ready.” It tells you precisely which objects a consumer can read, which is the answer to the only question that matters when data is about to cross an organizational boundary: what did I just expose?

The consumer side

Now flip to the partner’s account. From their seat, your share shows up as an inbound share they can mount as a database. Their ACCOUNTADMIN runs:

USE ROLE ACCOUNTADMIN;

-- see inbound shares that have been offered to this account
SHOW SHARES;

-- mount the share as a local, read-only database
CREATE DATABASE shared_bookshop
  FROM SHARE provider_acct.bookshop_share;

provider_acct.bookshop_share is your account identifier followed by the share name. The moment that runs, the consumer has a database called shared_bookshop whose tables are your tables. They query it like any other database:

SELECT o.o_orderdate, SUM(o.o_totalprice) AS revenue
FROM shared_bookshop.public.orders o
GROUP BY o.o_orderdate
ORDER BY o.o_orderdate;

That query runs on the consumer’s warehouse, against your storage, and returns your current data. A few properties to internalize about the mounted database:

  • It’s strictly read-only. The consumer cannot INSERT, UPDATE, or CREATE TABLE inside a shared database. They can, of course, CREATE TABLE ... AS SELECT into their own database from it — which is them choosing to make a copy, on their dime, fully aware they’re now responsible for its freshness.
  • It’s always live. When you load new orders tonight, their dashboard reflects them tomorrow with no action on either side.
  • Access is delegated, not duplicated. The consumer’s ACCOUNTADMIN typically grants IMPORTED PRIVILEGES on the shared database onward to their own analyst roles: GRANT IMPORTED PRIVILEGES ON DATABASE shared_bookshop TO ROLE analyst;. That’s the consumer-side equivalent of the object grants you made — it lets their non-admin roles read the share.

Crossing regions and clouds

There’s a hard constraint hiding in that clean picture: direct sharing works only within the same region and the same cloud provider. Sharing is a metadata trick over shared storage, and storage lives in a specific region on a specific cloud. If your account is in AWS us-east-1 and the consumer is in Azure westeurope, there are no shared micro-partitions to point at — the bytes physically aren’t co-located.

The fix is replication. You replicate the database (or the whole account’s relevant objects) to a secondary account in the consumer’s region/cloud, and share from there. Snowflake keeps the replica in sync on a schedule you set, and the consumer shares against the replica exactly as they would a local one.

-- in the target region's account: create a replica of the source database
CREATE DATABASE sales
  AS REPLICA OF myorg.primary_acct.sales;

-- then refresh it (usually on a scheduled task)
ALTER DATABASE sales REFRESH;

The trade-off is the thing you were trying to avoid: replication does copy data, so you pay for the replica’s storage and for the transfer, and the remote copy is only as fresh as your last refresh. It’s the exception that proves the rule — within a region, sharing is free and instant; across regions, you pay the price of physics. For a genuinely global data product, this is a real cost line, not an afterthought; budget for it.

Reader accounts — sharing with people who aren’t on Snowflake

Direct sharing assumes the consumer already has a Snowflake account. Plenty of the people you’d want to share with don’t. A reader account solves that: the provider creates a lightweight, provider-managed account that a non-Snowflake consumer logs into purely to read shared data.

USE ROLE ACCOUNTADMIN;

CREATE MANAGED ACCOUNT partner_reader
  ADMIN_NAME     = 'reader_admin',
  ADMIN_PASSWORD = 'Ch4nge-me-now!',
  TYPE           = READER;

The statement returns an account locator and a login URL you hand to the consumer. Then you share into it just like any other account:

ALTER SHARE bookshop_share ADD ACCOUNTS = partner_reader;

Two things make reader accounts distinctive. First, they can’t do anything except consume your shares — no loading their own data, no creating shares of their own. Second, and this is the crucial one: the provider pays for all the compute a reader account uses. There’s no billing relationship between Snowflake and the reader’s operator, so every warehouse-second they burn querying your data lands on your bill. That’s the trade for reaching consumers who’ll never sign a Snowflake contract, and it’s exactly why you’ll want to size the reader’s warehouses conservatively and keep an eye on their usage. A reader account is a generous gift; make sure it’s a bounded one.

Sharing a filtered slice, safely

So far we’ve shared whole tables. You rarely want to. The partner should see their orders, or this region’s customers, not the entire book. The right instrument is the secure view we met back in the table-types-and-views chapter — and this is the setting where “secure” earns its name.

A normal view is just saved SQL; its definition is visible, and the optimizer can sometimes push predicates in ways that leak information about rows a consumer shouldn’t even know exist. A secure view hides its own definition and disables those optimizations precisely so that a consumer on the far side of a share can’t reverse-engineer the underlying data. When you share across an org boundary, you share secure views, not raw tables.

The pattern that makes this sing is row-level entitlement driven by a mapping table. You keep a small table that maps each consumer account to the rows it’s allowed to see, and the secure view filters against it using CURRENT_ACCOUNT() — a function that, inside a share, returns the consumer’s account identifier at query time.

-- a mapping table the consumers never see
CREATE TABLE sales.public.account_entitlements (
  snowflake_account STRING,   -- the consumer's account identifier
  region            STRING    -- the region they're entitled to
);

INSERT INTO sales.public.account_entitlements VALUES
  ('PARTNER_ACCT',   'EUROPE'),
  ('PARTNER_READER', 'AMERICA');

-- one secure view that shows each consumer only their entitled rows
CREATE SECURE VIEW sales.public.orders_shared AS
SELECT o.*
FROM sales.public.orders o
JOIN sales.public.account_entitlements e
  ON o.region = e.region
WHERE e.snowflake_account = CURRENT_ACCOUNT();

Now share the view, and only the view — never the base table or the mapping table:

GRANT SELECT ON VIEW sales.public.orders_shared TO SHARE bookshop_share;

Both consumer accounts mount the same share, run SELECT * FROM shared_bookshop.public.orders_shared, and each gets a different result set — EUROPE rows for the partner, AMERICA rows for the reader — decided entirely by CURRENT_ACCOUNT() at runtime. One view, one grant, per-consumer data. The base orders table and the entitlements table stay invisible; the secure view is the only surface anyone outside your account can touch.

The same logic extends to secure UDFs. A regular UDF’s definition is readable and therefore can’t be shared safely; a secure UDF hides its body, which lets you share computed logic — a scoring function, a bucketing rule — without revealing the formula or the reference data behind it. The principle is identical: SECURE is what makes an object safe to expose across an account boundary, and it’s the reason “just share the table” is almost never the right answer for real, entitled, multi-tenant data.

The Marketplace and listings

Direct shares are point-to-point: you know the consumer, you name their account. The Snowflake Marketplace turns that same sharing mechanism into a storefront — a public catalog where providers publish data products and any Snowflake account can find and mount them.

A published product is called a listing. Under the hood it’s still a share; a listing just wraps that share with a title, a description, usage examples, and terms, and points it at an audience rather than a named account. Listings come in a few flavors:

  • Free listings — the consumer clicks “Get,” and the data mounts into their account instantly as a shared database, exactly like a direct share they never had to be individually named for.
  • Paid listings — the provider sets a price (Snowflake handles the billing), and the consumer subscribes.
  • Personalized listings — the consumer requests access, the provider approves and provisions a tailored share, often with per-consumer entitlement like the secure-view pattern above. This is how a vendor sells your slice of a larger dataset.

Listings can also be private — shared to specific accounts through the Marketplace UI rather than posted publicly — which is a common middle ground for a data product you sell to named customers but want to manage through the listing tooling instead of hand-rolled ALTER SHARE statements.

Here’s the detail that makes all of this concrete, and you’ve been using it since the very first chapter: SNOWFLAKE_SAMPLE_DATA is itself a share. The TPC-H tables you’ve been querying throughout this book aren’t stored in your account — they’re a listing Snowflake provides, mounted into every account as a shared, read-only database. That’s why you can query terabytes of sample data on a trial without it counting against your storage, and why you can’t write to it. You’ve been a data-sharing consumer this whole time without noticing, which is the highest praise the feature could get.

On the provider side, publishing is done through Provider Studio in Snowsight — a UI where you attach a share to a listing, write the description and sample queries, set the audience and pricing, and submit. You can do a lot of it in SQL too (CREATE ORGANIZATION LISTING ...), but for a first listing the Studio walks you through the metadata the Marketplace requires. The important framing for a newcomer: the Studio is packaging, not plumbing. The plumbing is the same CREATE SHARE/GRANT ... TO SHARE you already learned; the Studio just gives it a shopfront and a billing relationship.

A nod to data clean rooms

There’s a governed evolution of sharing worth naming even though it’s beyond a foundations build: the data clean room. Where a share lets a consumer read your rows (filtered, but still read them), a clean room lets multiple parties jointly analyze their combined data without any party seeing the others’ row-level records. Two companies can compute the overlap of their customer lists, or measure how an ad campaign drove purchases, while the underlying tables stay private on each side and only aggregate, policy-approved results come out. It’s built on the same sharing and secure-object machinery you’ve seen here, wrapped in access policies that constrain which queries are even allowed to run. When “we want to combine data with a partner but neither of us can hand over the raw records” comes up — and in advertising, finance, and healthcare it comes up constantly — clean rooms are the governed multi-party answer.

What can and can’t be shared

Sharing has edges, and hitting one produces a confusing error, so learn them up front:

  • You can share tables, external tables, Iceberg tables, secure views, secure materialized views, and secure UDFs. The through-line is that anything with a readable definition must be in its secure form to be shareable.
  • You cannot share standard (non-secure) views, ordinary UDFs, stored procedures, stages, sequences, or streams. Try to GRANT SELECT a non-secure view to a share and Snowflake refuses — this is the platform steering you toward secure objects on purpose.
  • You cannot share a share. A database a consumer mounted FROM SHARE is a terminal node: the consumer can’t turn around and re-share it onward to a third account. Re-sharing would defeat the provider’s control over who sees the data, so it’s simply not allowed. If a downstream party needs the data, they get it from the original provider, not second-hand.
  • A single database at a time. Objects in a share must come from one database (though replication and multiple shares let you work around that at the account level).
  • No cross-account references in shared views. A secure view you share can only reference objects the share also grants — a view that joins to a table you didn’t add to the share fails for the consumer, since they can’t see the other side of the join.

For monitoring, you have the same live-plus-historical split you saw with grants. Live, from the provider side:

SHOW SHARES;                           -- all shares in/out of this account
SHOW GRANTS TO SHARE bookshop_share;   -- what's exposed
DESC SHARE bookshop_share;             -- the objects and their kinds

For historical, account-wide analysis — who actually consumed what, and how often — Snowflake surfaces a dedicated schema of usage views. SNOWFLAKE.DATA_SHARING_USAGE.LISTING_ACCESS_HISTORY and its siblings let you see which consumer accounts queried your listings and when, as plain SQL you can join and trend:

SELECT consumer_account_name,
       COUNT(*)                    AS query_count,
       MAX(query_date)             AS last_seen
FROM snowflake.data_sharing_usage.listing_access_history
WHERE query_date >= DATEADD('day', -30, CURRENT_DATE())
GROUP BY consumer_account_name
ORDER BY query_count DESC;

That’s how you answer “is anyone actually using the share we published in March?” without guessing — and, just as usefully, “which consumer is hammering the reader account we’re paying for?” These views carry the usual Account Usage latency (a couple of hours), so they’re for trends and audits, not real-time alerting — the same discipline the cost chapter preached, now applied to the data you send out the door instead of the compute you burn behind it.

Final thoughts

Secure Data Sharing is the feature that most cleanly separates Snowflake from the export-a-file world you came from, and the whole idea reduces to a single reframing: stop delivering copies, start granting queries. A share is a live reference, not a file — one copy of the data, always current, read on the consumer’s compute. Build a direct share with CREATE SHARE, GRANT ... TO SHARE, and ALTER SHARE ADD ACCOUNTS; the consumer mounts it with CREATE DATABASE ... FROM SHARE and it’s simply there. Reach for reader accounts when the consumer isn’t on Snowflake (and remember you’re paying their compute), for replication when you must cross a region or cloud, and for secure views with a CURRENT_ACCOUNT()-driven mapping table whenever “everyone sees everything” is the wrong policy — which, for real shared data, is almost always. The Marketplace is that same machinery given a shopfront, and SNOWFLAKE_SAMPLE_DATA is the proof you’ve trusted it since page one. Treat every share as a live dependency with an owner, a contract, and a monitoring query — because on the other side of it is someone building on data they can’t see you change.

Next: Everyone Queries the Same Table and Sees a Different Answer

Comments