Snowflake just made it possible to attach a data protection policy to a tag, and have every object carrying that tag inherit the policy automatically. Tag-based data protection policies entered public preview on July 21, 2026, covering four policy types: row access, aggregation, projection, and join policies. The shift is from object-by-object governance to attribute-based access control (ABAC), where a single tag assignment can lock down a column, a table, or an entire schema without writing a separate ALTER TABLE ... SET POLICY for each one.
The core mechanic: one tag, one policy, automatic propagation. You create a tag, set a policy on the tag, then set the tag on any object. The object inherits the policy through Snowflake's existing tag propagation rules. Add a new table and tag it, and the policy is already enforced. This is a concrete decision for any team managing access control at scale on Snowflake, and it pairs naturally with the fundamentals covered in our Snowflake RBAC and masking guide.
What exactly shipped in this release?
Snowflake announced tag-based policy support as a public preview feature on July 21, 2026. It covers four policy types:
- Row access policies: filter rows based on the querying role or context.
- Aggregation policies: enforce minimum group sizes, suppressing small aggregates to prevent inference attacks.
- Projection policies: restrict which columns a role can project, complementing column-level masking.
- Join policies: control which tables can be joined together, preventing cross-domain data leakage.
The general workflow for all four types follows the same three steps. First, create a tag. Second, set the policy on the tag with an ALTER TAG command. Third, set the tag on the object. The object then inherits the policy automatically.
Here is the DDL for creating a tag and attaching a row access policy:
-- Step 1: create the tag
CREATE TAG IF NOT EXISTS governance.pii_sensitive
COMMENT = 'Marks objects subject to PII row-level controls';
-- Step 2: set a row access policy on the tag
ALTER TAG governance.pii_sensitive
SET ROW ACCESS POLICY governance.pii_row_filter;
-- Step 3: set the tag on a table
ALTER TABLE sales.customers
SET TAG governance.pii_sensitive = 'true';
The table now enforces governance.pii_row_filter for any query against it, with no direct ALTER TABLE ... SET ROW ACCESS POLICY call. The same pattern works for the other three policy types, each with its own ALTER TAG variant.
For aggregation policies, the syntax includes an optional ENTITY KEY clause that specifies the grouping columns the policy applies to:
ALTER TAG governance.aggregate_sensitive
SET AGGREGATION POLICY governance.min_group_size_5
ENTITY KEY (department_id);
The tag-based aggregation policy documentation confirms that smaller groups are suppressed when the aggregate query produces results below the policy's minimum threshold. The entity key tells the policy which columns define the group boundary.
For projection policies, the pattern is the same:
ALTER TAG governance.restricted_columns
SET PROJECTION POLICY governance.limit_projection;
How does tag inheritance propagate the policy?
This is where the feature earns its keep. Snowflake tags already support inheritance and propagation across the object hierarchy. When you set a tag on a database or schema, every table and view inside inherits that tag. Now that policies can ride on tags, the inheritance carries the policy with it.
The inheritance chain works like this:
| Object level | Tag set here | Policy applies to |
|---|---|---|
| Database | ALTER DATABASE db SET TAG pii = 'true' |
All schemas, tables, views in the database |
| Schema | ALTER SCHEMA sch SET TAG pii = 'true' |
All tables and views in the schema |
| Table | ALTER TABLE tbl SET TAG pii = 'true' |
All columns and the table itself |
| View | ALTER VIEW vw SET TAG pii = 'true' |
The view, queried through the policy |
The key behavior: if a tag is set at the database level, every new table created in that database inherits the tag and the policy. You do not need to touch the new table at all. This is the operational win. A data engineer creating a derived table from a tagged source does not need to remember to attach the policy. The tag does the remembering.
There is a subtlety. Tag propagation is not the same as policy cascading. The tag propagates, and the policy is attached to the tag, so the effective policy on any object is the one bound to the most specific tag that applies. If a table has a tag set directly and the schema has the same tag set, the table-level tag wins. If different tags with different policies are set at different levels, you need to understand the precedence rules.
How do you migrate from per-object policies to tag-based policies?
If you already have row access or projection policies attached directly to dozens of tables, you can migrate to the tag-based model. The process is straightforward but requires care.
First, inventory what you have. This ACCOUNT_USAGE query finds every object with a row access policy attached directly:
SELECT
policy_db,
policy_schema,
policy_name,
ref_database_name,
ref_schema_name,
ref_entity_name,
ref_entity_domain,
tag
FROM snowflake.account_usage.policy_references
WHERE policy_kind = 'ROW_ACCESS_POLICY'
AND tag IS NULL -- direct attachment, not tag-based
ORDER BY ref_database_name, ref_schema_name;
The tag column in POLICY_REFERENCES is NULL for direct attachments and non-NULL when the policy comes through a tag. This is how you audit the transition.
Second, create a tag for each logical policy group. If you have a row access policy called sales_region_filter attached to 30 tables, create a tag called region_filtered and set the policy on it:
CREATE TAG governance.region_filtered;
ALTER TAG governance.region_filtered
SET ROW ACCESS POLICY governance.sales_region_filter;
Third, set the tag on the objects and drop the direct policy attachment:
-- set the tag
ALTER TABLE sales.east_region SET TAG governance.region_filtered = 'true';
ALTER TABLE sales.west_region SET TAG governance.region_filtered = 'true';
-- drop the direct policy attachment
ALTER TABLE sales.east_region
UNSET ROW ACCESS POLICY governance.sales_region_filter;
ALTER TABLE sales.west_region
UNSET ROW ACCESS POLICY governance.sales_region_filter;
Do this in batches and verify with the ACCOUNT_USAGE query after each batch. The policy enforcement does not interrupt during the migration: the direct policy is active until you unset it, and the tag-based policy is active as soon as the tag is set. There is a brief window where both are active, but since they are the same policy, the behavior is identical.
What does this cost in credits and complexity?
Tag-based policies do not introduce new credit charges. The compute cost is the same as per-object policies. Row access policies, aggregation policies, and projection policies all execute during query runtime, and that runtime cost has not changed. What changes is the management overhead.
The chart below illustrates the operational difference for a 400-object estate where each object needs a row access policy:

In the per-object model, you maintain 1,200 policy attachments (400 objects, each with up to three policy types). In the tag-based model, you maintain one tag per logical policy group and set tags on objects. The audit query time drops from roughly three hours of manual reconciliation to a 15-minute automated query against POLICY_REFERENCES.
But tag-based policies introduce their own complexity costs:
- Tag sprawl: if you create a tag for every policy variant, you end up with dozens of tags that are hard to name, govern, and audit. Tags need a naming convention and a lifecycle.
- Policy precedence: when multiple tags with different policies apply to the same object, the resolution order matters. You need to test and document which policy wins.
- Audit complexity: the
POLICY_REFERENCESview now has two entry types (direct and tag-based). Auditors need to understand the difference and query both. - Privilege separation: the ability to set tags and the ability to create policies should be separated. A role that can both create a policy and attach it to a tag is a governance bypass.
The privilege model uses existing Snowflake tag privileges. To create and manage tags, a role needs the CREATE TAG privilege on the schema. To set a policy on a tag, the role needs APPLY ROW ACCESS POLICY (or the equivalent for other policy types) plus APPLY TAG on the tag. To set a tag on an object, the role needs APPLY TAG on the tag and ownership of the object.
Here is the grant set for a governance role:
-- governance role can create tags and set policies on them
GRANT CREATE TAG ON SCHEMA governance TO ROLE governance_admin;
GRANT APPLY ROW ACCESS POLICY ON ACCOUNT TO ROLE governance_admin;
GRANT APPLY AGGREGATION POLICY ON ACCOUNT TO ROLE governance_admin;
GRANT APPLY PROJECTION POLICY ON ACCOUNT TO ROLE governance_admin;
-- data engineering roles can set tags on objects they own
GRANT APPLY TAG ON TAG governance.pii_sensitive TO ROLE data_engineer;
The separation matters. If data_engineer can create tags and set policies on them, they can effectively bypass any governance control by creating a new tag with no policy. Keep tag creation and policy attachment in the governance role, and give data engineers only the APPLY TAG grant for setting existing tags on their objects.
When is tag-based policy the wrong choice?
Tag-based policies shine when you have a policy that applies to many objects with the same protection needs. They are the wrong choice in three scenarios.
First, when each object needs a slightly different policy. Row access policies that filter by region, for example, might use a different region column name on different tables. The tag-based model attaches one policy to a tag, and the policy must work for every object carrying that tag. If customers uses cust_region and orders uses order_region, a single row access policy on a tag will not work without a mapping table or a consistent column convention. In that case, direct per-object policies are simpler.
Second, when the policy is temporary or experimental. If you are testing a new aggregation policy on a single table for two weeks, attaching it directly is faster than creating a tag, setting the policy on the tag, setting the tag on the table, and then cleaning up all three when the test ends. The tag-based model adds two extra DDL steps. For one object, that is overhead with no payoff.
Third, when you need precise per-object audit trails. Tag-based policies make it harder to answer the question "what policy is on this specific table right now?" because the answer is indirect: the table has a tag, the tag has a policy. You can query it, but it is one more join in your audit logic. For environments where regulators require a direct, per-object policy inventory, per-object attachment may be easier to evidence.
How do you audit tag-based policy enforcement?
The POLICY_REFERENCES view in ACCOUNT_USAGE is the primary audit surface. It now includes a TAG column that is non-NULL when the policy is applied through a tag.
SELECT
ref_database_name,
ref_schema_name,
ref_entity_name,
ref_entity_domain,
policy_name,
policy_kind,
tag -- NULL = direct, non-NULL = tag-based
FROM snowflake.account_usage.policy_references
WHERE ref_database_name = 'SALES'
AND policy_kind IN ('ROW_ACCESS_POLICY', 'AGGREGATION_POLICY',
'PROJECTION_POLICY', 'JOIN_POLICY')
ORDER BY ref_entity_name, policy_kind;
For real-time auditing (the ACCOUNT_USAGE view has latency), use the Information Schema equivalent:
SELECT *
FROM sales.information_schema.policy_references
WHERE policy_kind = 'ROW_ACCESS_POLICY';
The Information Schema view is current as of the query time, while ACCOUNT_USAGE has a latency of up to a few hours. For compliance reports, use ACCOUNT_USAGE for history and Information Schema for point-in-time checks.
A useful governance query finds objects that have a tag with a policy but also have a direct policy attached, which can cause unexpected precedence conflicts:
SELECT
ref_entity_name,
ref_entity_domain,
policy_name,
policy_kind,
tag
FROM snowflake.account_usage.policy_references
WHERE tag IS NOT NULL
AND ref_entity_name IN (
SELECT inner_q.ref_entity_name
FROM snowflake.account_usage.policy_references inner_q
WHERE inner_q.tag IS NULL
)
ORDER BY ref_entity_name;
This surfaces objects where both a tag-based and a direct policy exist. Clean those up during migration to avoid ambiguity.
What should you do during public preview?
Public preview means the feature is usable but not GA. Three things to keep in mind.
First, preview features can change. The SQL syntax, the privilege model, and the inheritance behavior may shift before GA. If you build automation around tag-based policies now, wrap it in a layer that can absorb syntax changes. Use stored procedures or a governance tool rather than hardcoding DDL in pipelines.
Second, test tag propagation in a non-production account first. Create a tag at the schema level, set a row access policy on it, and verify that a new table created in that schema inherits the policy. Then test edge cases: renaming a table with a tag, dropping a tag that has a policy attached, and setting multiple tags with different policies on the same object.
Third, start with one policy type. Row access policies are the most common and the easiest to validate. Attach one row access policy to one tag, set the tag on a small group of tables, and run your existing audit queries to confirm the behavior. Expand from there.
The governance shortcut you still have to earn
Tag-based policies solve the hardest part of data governance at scale: the fact that every new table is a potential gap. With tags, the policy travels with the classification, not the object. That is a real shift. But the tag still has to be set correctly, the policy still has to be written correctly, and the privilege model still has to separate the people who classify data from the people who create policies. The feature gives you the mechanism. The discipline is still yours.
