Data governance is often treated as an after-thought feature applied reactively when compliance audits approach. In Snowflake, you can embed governance directly into your data model, ensuring security and compliance are applied inline with the data itself. inline In this article, I’ll walk through how to implement a SQL-based governance framework using tags, masking policies, and role-based access. By the end, you’ll see how data visibility dynamically changes based on roles, without needing to manually update policies table-by-table. SQL-based governance framework Why Governance Matters Data governance ensures that the right people have access to the right data at the right time without overexposing sensitive or irrelevant information. Without governance, organizations risk compliance violations, data leaks, and reduced trust in analytics. By applying governance policies directly within Snowflake, companies can enforce fine-grained access control at the column or row level, keeping data secure while still enabling collaboration. Use Case: Company XYZ Company XYZ has multiple departments; Marketing, Compliance, Finance, and Engineering each requiring access to specific data sets. With Governance: Marketing only sees campaign performance and customer engagement data. Compliance has visibility into audit logs and regulatory reports. Finance can access revenue, transactions, and cost data but not engineering roadmaps or marketing leads. Engineering works with operational metrics without being exposed to financial or compliance-sensitive details. Marketing only sees campaign performance and customer engagement data. Marketing Compliance has visibility into audit logs and regulatory reports. Compliance Finance can access revenue, transactions, and cost data but not engineering roadmaps or marketing leads. Finance Engineering works with operational metrics without being exposed to financial or compliance-sensitive details. Engineering This prevents unnecessary overexposure of data across teams, while still maintaining a single source of truth in Snowflake. Governance not only improves security but also streamlines collaboration by ensuring teams work with relevant, trusted data. Dynamic Data Masking Dynamic Data Masking is a Column-level Security feature that uses masking policies to selectively mask plain-text data in table and view columns at query time. Masking policies can include conditions and functions to transform the data at query runtime when those conditions are met. Step 1: Create Setup Workspace -- create database and schema CREATE DATABASE RAW; USE DATABASE RAW; CREATE SCHEMA COMPANY_WIDE; -- simulate data CREATE OR REPLACE TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA ( CUSTOMER_ID INT, FULL_NAME STRING, EMAIL STRING, PHONE STRING, CREDIT_CARD STRING, PURCHASE_AMOUNT NUMBER(10,2), REGION STRING, COMPLIANCE_FLAG STRING ); INSERT INTO RAW.COMPANY_WIDE.CUSTOMER_DATA VALUES (1, 'Alice Johnson', 'alice.johnson@email.com', '+1-202-555-0181', '4111111111111111', 2500.50, 'US', 'GDPR'), (2, 'Bob Smith', 'bob.smith@email.com', '+44-20-7946-0958', '5500000000000004', 780.75, 'EU', 'GDPR'), (3, 'Clara Green', 'clara.green@email.com', '+81-3-1234-5678', '340000000000009', 12000.00, 'APAC', 'PCI'), (4, 'David Lee', 'david.lee@email.com', '+1-415-555-1212', '30000000000004', 6500.90, 'US', 'HIPAA'); -- Department roles CREATE ROLE IF NOT EXISTS MARKETING_ACCESS; CREATE ROLE IF NOT EXISTS FINANCIAL_ACCESS; CREATE ROLE IF NOT EXISTS REGULATORY_ACCESS; CREATE ROLE IF NOT EXISTS ENGINEERING_ACCESS; -- Assign roles to a warehouse and schema GRANT USAGE ON WAREHOUSE COMPUTE_WH TO MARKETING_ACCESS; GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO MARKETING_ACCESS; GRANT USAGE ON WAREHOUSE COMPUTE_WH TO FINANCIAL_ACCESS; GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO FINANCIAL_ACCESS; GRANT USAGE ON WAREHOUSE COMPUTE_WH TO REGULATORY_ACCESS; GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO REGULATORY_ACCESS; GRANT USAGE ON WAREHOUSE COMPUTE_WH TO ENGINEERING_ACCESS; GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO ENGINEERING_ACCESS; -- create database and schema CREATE DATABASE RAW; USE DATABASE RAW; CREATE SCHEMA COMPANY_WIDE; -- simulate data CREATE OR REPLACE TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA ( CUSTOMER_ID INT, FULL_NAME STRING, EMAIL STRING, PHONE STRING, CREDIT_CARD STRING, PURCHASE_AMOUNT NUMBER(10,2), REGION STRING, COMPLIANCE_FLAG STRING ); INSERT INTO RAW.COMPANY_WIDE.CUSTOMER_DATA VALUES (1, 'Alice Johnson', 'alice.johnson@email.com', '+1-202-555-0181', '4111111111111111', 2500.50, 'US', 'GDPR'), (2, 'Bob Smith', 'bob.smith@email.com', '+44-20-7946-0958', '5500000000000004', 780.75, 'EU', 'GDPR'), (3, 'Clara Green', 'clara.green@email.com', '+81-3-1234-5678', '340000000000009', 12000.00, 'APAC', 'PCI'), (4, 'David Lee', 'david.lee@email.com', '+1-415-555-1212', '30000000000004', 6500.90, 'US', 'HIPAA'); -- Department roles CREATE ROLE IF NOT EXISTS MARKETING_ACCESS; CREATE ROLE IF NOT EXISTS FINANCIAL_ACCESS; CREATE ROLE IF NOT EXISTS REGULATORY_ACCESS; CREATE ROLE IF NOT EXISTS ENGINEERING_ACCESS; -- Assign roles to a warehouse and schema GRANT USAGE ON WAREHOUSE COMPUTE_WH TO MARKETING_ACCESS; GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO MARKETING_ACCESS; GRANT USAGE ON WAREHOUSE COMPUTE_WH TO FINANCIAL_ACCESS; GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO FINANCIAL_ACCESS; GRANT USAGE ON WAREHOUSE COMPUTE_WH TO REGULATORY_ACCESS; GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO REGULATORY_ACCESS; GRANT USAGE ON WAREHOUSE COMPUTE_WH TO ENGINEERING_ACCESS; GRANT USAGE ON SCHEMA RAW.COMPANY_WIDE TO ENGINEERING_ACCESS; Step 2: Create Governance Tag We start by creating a governance tag that captures sensitivity categories. -- Create governance tag with allowed values CREATE TAG IF NOT EXISTS RAW.COMPANY_WIDE.MASKING_TAG ALLOWED_VALUES 'MARKETING', 'SENSITIVE', 'FINANCIAL', 'REGULATORY', 'ENGINEERING'; -- Create governance tag with allowed values CREATE TAG IF NOT EXISTS RAW.COMPANY_WIDE.MASKING_TAG ALLOWED_VALUES 'MARKETING', 'SENSITIVE', 'FINANCIAL', 'REGULATORY', 'ENGINEERING'; Step 3: Define Masking Policies Masking policies enforce role-based visibility. Let’s create two policies: one for strings, another for numbers. -- Masking policy for string values CREATE OR REPLACE MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING AS (VAL STRING) RETURNS STRING -> CASE -- MARKETING role: can see PII (SENSITIVE) + REGION WHEN CURRENT_ROLE() = 'MARKETING_ACCESS' AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'SENSITIVE' THEN VAL -- FINANCIAL role: can see FINANCIAL (CREDIT_CARD, PURCHASE_AMOUNT) WHEN CURRENT_ROLE() = 'FINANCIAL_ACCESS' AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'FINANCIAL' THEN VAL -- REGULATORY role: can see COMPLIANCE_FLAG WHEN CURRENT_ROLE() = 'REGULATORY_ACCESS' AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'REGULATORY' THEN VAL -- FOR HIGER ROLES WHEN CURRENT_ROLE() NOT IN ('MARKETING_ACCESS', 'FINANCIAL_ACCESS', 'REGULATORY_ACCESS', 'ENGINEERING_ACCESS') THEN VAL -- REGION is not tagged, ENGINEERING WHEN SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') IS NULL THEN VAL -- Default mask ELSE '*****' END; -- Masking policy for number values CREATE OR REPLACE MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER AS (VAL NUMBER) RETURNS NUMBER -> CASE -- FINANCIAL role: can see FINANCIAL data WHEN CURRENT_ROLE() = 'FINANCIAL_ACCESS' AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'FINANCIAL' THEN VAL -- REGULATORY role: can see COMPLIANCE_FLAG WHEN CURRENT_ROLE() = 'REGULATORY_ACCESS' AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'REGULATORY' THEN VAL -- untagged WHEN SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') IS NULL THEN VAL -- FOR HIGER ROLES WHEN CURRENT_ROLE() NOT IN ('MARKETING_ACCESS', 'FINANCIAL_ACCESS', 'REGULATORY_ACCESS', 'ENGINEERING_ACCESS') THEN VAL -- Default mask ELSE 0 END; -- Masking policy for string values CREATE OR REPLACE MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING AS (VAL STRING) RETURNS STRING -> CASE -- MARKETING role: can see PII (SENSITIVE) + REGION WHEN CURRENT_ROLE() = 'MARKETING_ACCESS' AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'SENSITIVE' THEN VAL -- FINANCIAL role: can see FINANCIAL (CREDIT_CARD, PURCHASE_AMOUNT) WHEN CURRENT_ROLE() = 'FINANCIAL_ACCESS' AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'FINANCIAL' THEN VAL -- REGULATORY role: can see COMPLIANCE_FLAG WHEN CURRENT_ROLE() = 'REGULATORY_ACCESS' AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'REGULATORY' THEN VAL -- FOR HIGER ROLES WHEN CURRENT_ROLE() NOT IN ('MARKETING_ACCESS', 'FINANCIAL_ACCESS', 'REGULATORY_ACCESS', 'ENGINEERING_ACCESS') THEN VAL -- REGION is not tagged, ENGINEERING WHEN SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') IS NULL THEN VAL -- Default mask ELSE '*****' END; -- Masking policy for number values CREATE OR REPLACE MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER AS (VAL NUMBER) RETURNS NUMBER -> CASE -- FINANCIAL role: can see FINANCIAL data WHEN CURRENT_ROLE() = 'FINANCIAL_ACCESS' AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'FINANCIAL' THEN VAL -- REGULATORY role: can see COMPLIANCE_FLAG WHEN CURRENT_ROLE() = 'REGULATORY_ACCESS' AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') = 'REGULATORY' THEN VAL -- untagged WHEN SYSTEM$GET_TAG_ON_CURRENT_COLUMN('RAW.COMPANY_WIDE.MASKING_TAG') IS NULL THEN VAL -- FOR HIGER ROLES WHEN CURRENT_ROLE() NOT IN ('MARKETING_ACCESS', 'FINANCIAL_ACCESS', 'REGULATORY_ACCESS', 'ENGINEERING_ACCESS') THEN VAL -- Default mask ELSE 0 END; Step 4: Attach Policies to Tags Instead of attaching policies column by column, we bind them directly to the tag. ALTER TAG RAW.COMPANY_WIDE.MASKING_TAG SET MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING, MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER; ALTER TAG RAW.COMPANY_WIDE.MASKING_TAG SET MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING, MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER; Now, any column tagged with MASKING_TAG automatically inherits the correct masking behaviour. MASKING_TAG Step 5: Attach Tags to Columns Now we can apply these tags inline to any table’s columns. -- Attach tags to specific columns -- Customer PII (Marketing relevance) ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA MODIFY COLUMN FULL_NAME SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'SENSITIVE'; ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA MODIFY COLUMN EMAIL SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'SENSITIVE'; -- Financial data ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA MODIFY COLUMN CREDIT_CARD SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'FINANCIAL'; ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA MODIFY COLUMN PURCHASE_AMOUNT SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'FINANCIAL'; -- Compliance-specific data ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA MODIFY COLUMN COMPLIANCE_FLAG SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'REGULATORY'; -- Region column left untagged -- Attach tags to specific columns -- Customer PII (Marketing relevance) ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA MODIFY COLUMN FULL_NAME SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'SENSITIVE'; ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA MODIFY COLUMN EMAIL SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'SENSITIVE'; -- Financial data ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA MODIFY COLUMN CREDIT_CARD SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'FINANCIAL'; ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA MODIFY COLUMN PURCHASE_AMOUNT SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'FINANCIAL'; -- Compliance-specific data ALTER TABLE RAW.COMPANY_WIDE.CUSTOMER_DATA MODIFY COLUMN COMPLIANCE_FLAG SET TAG RAW.COMPANY_WIDE.MASKING_TAG = 'REGULATORY'; -- Region column left untagged These tags become metadata that Snowflake’s governance engine can read dynamically. Step 5. Governance in Action Let’s see what this looks like in practice. Governance Relevance per Department Marketing: Needs access to FULL_NAME, EMAIL, REGION, but not sensitive financial or compliance fields. Finance: Needs access to CREDIT_CARD, PURCHASE_AMOUNT, but not personal contact details. Compliance: Needs access to COMPLIANCE_FLAG, REGION, but customer identifiers should be masked. Engineering: Needs synthetic test data (non-sensitive), so most fields should be masked. Marketing: Needs access to FULL_NAME, EMAIL, REGION, but not sensitive financial or compliance fields. Marketing FULL_NAME, EMAIL, REGION Finance: Needs access to CREDIT_CARD, PURCHASE_AMOUNT, but not personal contact details. Finance CREDIT_CARD, PURCHASE_AMOUNT Compliance: Needs access to COMPLIANCE_FLAG, REGION, but customer identifiers should be masked. Compliance COMPLIANCE_FLAG, REGION Engineering: Needs synthetic test data (non-sensitive), so most fields should be masked. Engineering USE ROLE MARKETING_ACCESS; SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG FROM RAW.COMPANY_WIDE.CUSTOMER_DATA; -- FULL_NAME + EMAIL + REGION visible -- CREDIT_CARD + PURCHASE_AMOUNT + COMPLIANCE_FLAG masked USE ROLE FINANCIAL_ACCESS; SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG FROM RAW.COMPANY_WIDE.CUSTOMER_DATA; -- CREDIT_CARD + PURCHASE_AMOUNT visible -- EMAIL + FULL_NAME + COMPLIANCE_FLAG masked USE ROLE REGULATORY_ACCESS; SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG FROM RAW.COMPANY_WIDE.CUSTOMER_DATA; -- COMPLIANCE_FLAG + REGION visible -- FULL_NAME, EMAIL, CREDIT_CARD, PURCHASE_AMOUNT masked USE ROLE ENGINEERING_ACCESS; SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG FROM RAW.COMPANY_WIDE.CUSTOMER_DATA; -- Most fields masked -- Possibly REGION partially visible for non-sensitive testing USE ROLE MARKETING_ACCESS; SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG FROM RAW.COMPANY_WIDE.CUSTOMER_DATA; -- FULL_NAME + EMAIL + REGION visible -- CREDIT_CARD + PURCHASE_AMOUNT + COMPLIANCE_FLAG masked USE ROLE FINANCIAL_ACCESS; SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG FROM RAW.COMPANY_WIDE.CUSTOMER_DATA; -- CREDIT_CARD + PURCHASE_AMOUNT visible -- EMAIL + FULL_NAME + COMPLIANCE_FLAG masked USE ROLE REGULATORY_ACCESS; SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG FROM RAW.COMPANY_WIDE.CUSTOMER_DATA; -- COMPLIANCE_FLAG + REGION visible -- FULL_NAME, EMAIL, CREDIT_CARD, PURCHASE_AMOUNT masked USE ROLE ENGINEERING_ACCESS; SELECT FULL_NAME, EMAIL, REGION, CREDIT_CARD, PURCHASE_AMOUNT, COMPLIANCE_FLAG FROM RAW.COMPANY_WIDE.CUSTOMER_DATA; -- Most fields masked -- Possibly REGION partially visible for non-sensitive testing With no extra code, Snowflake enforces the right level of visibility based on the user’s active role. Step 7. Clean-Up For demos or testing environments, you can reset your environment with: -- remove policy from tags ALTER TAG RAW.COMPANY_WIDE.MASKING_TAG UNSET MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING, MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER; -- drop policy DROP MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING; DROP MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER; -- drop tag DROP TAG RAW.COMPANY_WIDE.MASKING_TAG; -- remove policy from tags ALTER TAG RAW.COMPANY_WIDE.MASKING_TAG UNSET MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING, MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER; -- drop policy DROP MASKING POLICY RAW.COMPANY_WIDE.MASK_STRING; DROP MASKING POLICY RAW.COMPANY_WIDE.MASK_NUMBER; -- drop tag DROP TAG RAW.COMPANY_WIDE.MASKING_TAG; Conclusion Governance in Snowflake transforms compliance from a manual burden into a built-in data feature. By combining tags, masking policies, and roles, you can: built-in data feature Classify sensitive data directly at the schema level. Enforce access dynamically, without rewriting queries. Apply governance consistently across multiple tables using procedures. Classify sensitive data directly at the schema level. Enforce access dynamically, without rewriting queries. Apply governance consistently across multiple tables using procedures. This approach ensures your data remains both useful and compliant scaling with your organization instead of slowing it down. useful compliant Reference Dynamic Data Masking Object Tagging Access Control Overview Dynamic Data Masking Dynamic Data Masking Object Tagging Object Tagging Access Control Overview Access Control Overview