Publisher Snowflake Setup (Grants)

The one-time Snowflake grants a publisher runs so apps can read their source tables — the complete script, why each grant exists, and how to know it worked.

Before any organization can successfully run your app, the platform needs permission to read your source tables. This is a one-time setup per table: once done, every additional organization that installs your app works with zero extra Snowflake work (the platform handles per-org grants automatically). Verified end to end in production, August 2026.

The three layers (mental model)

Layer

What it needs

Who sets it up

1. MTA_ADMIN_ROLE on your tables

SELECT with grant option, plus USAGE on database and schema with grant option

You (publisher), once per table

2. The per-table proxy role

The role itself, plus SELECT on the table and USAGE on database and schema

You (publisher), once per table

3. Each org's app runner holding the proxy role

USAGE on the proxy role

Sync engine, automatically

Layer 1 lets the platform manage access on your behalf. Layer 2 is the role that actually reads your table. Layer 3 is automatic and per-org. A run fails if any layer is missing, and the error text ("does not exist or not authorized") is identical in all three cases.

Important: the sync engine grants proxy roles to app runners, but it does not create proxy roles. That is your job. This trips up every first-time publisher.

The script

For each source table your app reads, run this in a Snowflake worksheet (adjust names; role names are the table's fully qualified name with dots replaced by double underscores, plus __ROLE):

-- ============ Layer 1: let the platform manage access ============
GRANT USAGE  ON DATABASE <DB>                  TO ROLE MTA_ADMIN_ROLE WITH GRANT OPTION;
GRANT USAGE  ON SCHEMA   <DB>.<SCHEMA>         TO ROLE MTA_ADMIN_ROLE WITH GRANT OPTION;
GRANT SELECT ON TABLE    <DB>.<SCHEMA>.<TABLE> TO ROLE MTA_ADMIN_ROLE WITH GRANT OPTION;

-- ============ Layer 2: create and equip the proxy role ============
CREATE ROLE IF NOT EXISTS <DB>__<SCHEMA>__<TABLE>__ROLE;
GRANT SELECT ON TABLE    <DB>.<SCHEMA>.<TABLE> TO ROLE <DB>__<SCHEMA>__<TABLE>__ROLE;
GRANT USAGE  ON DATABASE <DB>                  TO ROLE <DB>__<SCHEMA>__<TABLE>__ROLE;
GRANT USAGE  ON SCHEMA   <DB>.<SCHEMA>         TO ROLE <DB>__<SCHEMA>__<TABLE>__ROLE;

Worked example for a table SANDBOX_TYNA.APP.STORES:

GRANT USAGE  ON DATABASE SANDBOX_TYNA   TO ROLE MTA_ADMIN_ROLE WITH GRANT OPTION;
GRANT USAGE  ON SCHEMA SANDBOX_TYNA.APP TO ROLE MTA_ADMIN_ROLE WITH GRANT OPTION;
GRANT SELECT ON TABLE SANDBOX_TYNA.APP.STORES TO ROLE MTA_ADMIN_ROLE WITH GRANT OPTION;

CREATE ROLE IF NOT EXISTS SANDBOX_TYNA__APP__STORES__ROLE;
GRANT SELECT ON TABLE SANDBOX_TYNA.APP.STORES TO ROLE SANDBOX_TYNA__APP__STORES__ROLE;
GRANT USAGE  ON DATABASE SANDBOX_TYNA   TO ROLE SANDBOX_TYNA__APP__STORES__ROLE;
GRANT USAGE  ON SCHEMA SANDBOX_TYNA.APP TO ROLE SANDBOX_TYNA__APP__STORES__ROLE;

Notes:

  • Plain SELECT to MTA_ADMIN_ROLE is not enough — WITH GRANT OPTION is required, because the platform re-grants access to app roles on your behalf.

  • USAGE on database and schema is required at both layers. Without it, runs fail with "Database '<DB>' does not exist or not authorized" even though the SELECT grants exist.

  • This applies to views as well as tables.

  • ⚠️ Always CREATE ROLE IF NOT EXISTS, never CREATE OR REPLACE ROLE. Replacing a role destroys every grant to and from it, silently breaking every app that used it, while the sync engine keeps reporting those grants as healthy.

When can I run this?

Any time — before or after you build, publish, or install. Order does not matter, because runs wait on the sync engine to finish provisioning before they execute. What DOES matter is that the grants exist before a run needs them.

How the fix takes effect after a failure

If installs or runs already failed because grants were missing:

  1. Run the script above.

  2. Open the Sync Engine Visualizer, select the affected org, and click Retry failed resources. The retry queues behind any running sync ("Waiting for lock" is normal). Failed items turn yellow (TRANSIENT).

  3. Rerun the variant. The run completes the remaining work; items turn green after a successful run. Transient items do not resolve on their own — they re-evaluate when the app is next touched.

Keeping grants alive

When a pipeline drops and recreates a source table, Snowflake silently removes all grants on it, and apps reading that table start failing with "does not exist or not authorized" while every platform tool still shows green. Use TRUNCATE + INSERT patterns instead of drop/recreate so grants persist.

How to verify (read-only, safe)

-- Does the proxy role have SELECT on the table?
SHOW GRANTS ON TABLE <DB>.<SCHEMA>.<TABLE>;
-- Does the proxy role exist, and when was it (re)created?
SHOW ROLES LIKE '<DB>__<SCHEMA>__%';
-- Which orgs' runners hold the proxy role? (populated automatically as orgs install)
SHOW GRANTS OF ROLE <DB>__<SCHEMA>__<TABLE>__ROLE;

Error signature quick reference

You see

It means

Sync Visualizer: "Role '<DB>__<SCHEMA>__<TABLE>__ROLE' does not exist or not authorized"

Layer 2 missing — you haven't created the proxy role yet

Run fails: "Database '<DB>' does not exist or not authorized"

USAGE grants missing (either layer)

Run fails: "Object ... does not exist or not authorized"

Any of the three layers; work through them in order — see Troubleshooting App Installs & Runs

First run after changing a source mapping fails, second succeeds

Known sync timing race (CRE-764), not a grants problem

Related articles

⏭️ Next: Prerequisites for App Installs