Snowflake DCM Projects — Extended capabilities (early access)

Introduction

This document describes a rolling set of new DCM Project capabilities that are available in private preview to selected customers. These features extend the core DCM Projects functionality with additional object types and deployment capabilities.

Over time, this document will be extended with new capabilities as they become available for early testing. Once a capability is sufficiently tested and stable, it will progress into the Public Preview release of DCM Projects and be removed from this document.

Early access for the following DCM capabilities is currently available in private preview:

Note

For the main DCM documentation of all publicly available functionality see: https://docs.snowflake.com/en/user-guide/dcm-projects/dcm-projects-overview

DEFINE FUNCTION (Python and Java)

The publicly available DCM release supports only SQL-language user-defined functions. This early-access capability extends that support to Python and Java handlers.

Python handler

Inline the handler code in the AS $$...$$ block, exactly as you would in a CREATE FUNCTION statement:

DEFINE FUNCTION DEMO{{env_suffix}}.ANALYTICS.MULTIPLY(x float, y float)
    RETURNS FLOAT
    LANGUAGE PYTHON
    RUNTIME_VERSION = '3.11'
    HANDLER = 'udf.run'
AS $$
def run(x, y):
    return x * y
$$;

To reference a handler uploaded as a staged file, use the IMPORTS clause and omit the inline body:

DEFINE FUNCTION DEMO{{env_suffix}}.ANALYTICS.MY_FUNC(x float)
    RETURNS FLOAT
    LANGUAGE PYTHON
    RUNTIME_VERSION = '3.11'
    HANDLER = 'my_module.my_func'
    IMPORTS = ('@DEMO{{env_suffix}}.INGEST.MY_STAGE/my_module.py')
;

Java handler

DEFINE FUNCTION DEMO{{env_suffix}}.ANALYTICS.ADD_NUMBERS(x int, y int)
    RETURNS INT
    LANGUAGE JAVA
    RUNTIME_VERSION = '11'
    HANDLER = 'MathUtils.add'
AS $$
class MathUtils {
    public static int add(int x, int y) {
        return x + y;
    }
}
$$;

Functional limitations

  • All CREATE OR ALTER FUNCTION limitations apply.
  • PLAN only validates that the function can be created or altered successfully. It doesn’t check whether it will run successfully, as handler logic is compiled at runtime.
  • PLAN treats a changed handler body as a full replace, the same way it handles SQL handlers. There’s no diff of the handler source code in the PLAN output.
  • Inline handler bodies (AS $$...$$), staged imports (IMPORTS), and Artifactory references (ARTIFACT_REPOSITORY) are supported. External functions aren’t supported.
  • Staged files referenced in IMPORTS must be uploaded to that Stage outside of DCM. DCM doesn’t support uploading files into user stages.

DEFINE PROCEDURE (Python and Java)

The publicly available DCM release supports only SQL-language stored procedures. This early-access capability extends that support to Python and Java handlers.

Python handler

DEFINE PROCEDURE DEMO{{env_suffix}}.ANALYTICS.PROCESS_DATA(input varchar)
    RETURNS VARCHAR
    LANGUAGE PYTHON
    RUNTIME_VERSION = '3.11'
    PACKAGES = ('snowflake-snowpark-python')
    HANDLER = 'sproc.run'
AS $$
def run(session, input):
    return input.upper()
$$;

To reference a handler uploaded as a staged file, use the IMPORTS clause and omit the inline body:

DEFINE PROCEDURE DEMO{{env_suffix}}.ANALYTICS.MY_PROC(input varchar)
    RETURNS VARCHAR
    LANGUAGE PYTHON
    RUNTIME_VERSION = '3.11'
    PACKAGES = ('snowflake-snowpark-python')
    HANDLER = 'my_module.run'
    IMPORTS = ('@DEMO{{env_suffix}}.INGEST.MY_STAGE/my_module.py')
;

Java handler

DEFINE PROCEDURE DEMO{{env_suffix}}.ANALYTICS.TRANSFORM(input varchar)
    RETURNS VARCHAR
    LANGUAGE JAVA
    RUNTIME_VERSION = '11'
    PACKAGES = ('com.snowflake:snowpark:latest')
    HANDLER = 'DataTransformer.run'
AS $$
import com.snowflake.snowpark_java.Session;

public class DataTransformer {
    public String run(Session session, String input) {
        return input.toUpperCase();
    }
}
$$;

Functional limitations

  • All CREATE OR ALTER PROCEDURE limitations apply.
  • PLAN only validates that the procedure can be created or altered successfully. It doesn’t check whether it will run successfully, as procedure logic is compiled at runtime.
  • PLAN treats a changed handler body as a full replace, the same way it handles SQL handlers. There’s no diff of the handler source code in the PLAN output.
  • Inline handler bodies (AS $$...$$) and staged imports (IMPORTS) are supported.
  • Staged files referenced in IMPORTS must be uploaded to that Stage outside of DCM. DCM doesn’t support uploading files into user stages.

DEFINE PIPE

You can define Snowflake pipes directly in DCM Projects. DCM manages the pipe lifecycle (CREATE, ALTER, DROP) across environments using Jinja templating, so you don’t have to maintain separate pipe definitions per environment.

All pipe properties supported by CREATE PIPE are available in DEFINE PIPE.

DEFINE PIPE DEMO{{env_suffix}}.INGEST.INGEST_EVENTS
    AUTO_INGEST = TRUE
    COMMENT = 'Loads raw events from the landing stage'
AS
    COPY INTO DEMO{{env_suffix}}.INGEST.RAW_EVENTS
    FROM @DEMO{{env_suffix}}.INGEST.LANDING_STAGE
    FILE_FORMAT = (TYPE = 'JSON');

Functional limitations

  • Only the pipe COMMENT can be changed after creation. The COPY INTO body and all other pipe properties are immutable.
  • AUTO_INGEST = TRUE requires an S3/Azure/GCS event notification to be configured outside of DCM. DCM creates the pipe but doesn’t configure the cloud-side notification.

DEFINE SHARE

You can define shares in DCM to then manage and deploy all GRANTS on these shares, letting you declaratively control which objects are exposed to the share.

All share properties supported by CREATE SHARE are available in DEFINE SHARE.

DEFINE SHARE DCM_DEMO_SHARE{{env_suffix}}
    COMMENT = 'Exposes curated sales data to partner accounts';

Functional limitations

  • Consumer account assignment (ALTER SHARE ... ADD ACCOUNTS) must be done manually outside of DCM, after database usage is granted to the share. DCM creates and manages the share object and its grants, but doesn’t configure which accounts can access the share.

ATTACH TAG

The publicly available DCM release supports defining tag objects (DEFINE TAG) but not attaching them to other objects. This early-access capability adds ATTACH TAG support, so you can declaratively assign Snowflake object tags to any DCM-managed entity. DCM reconciles the declared tag assignments on every deployment, replacing manual ALTER <object> SET TAG calls.

The tag and the target object don’t need to be defined in the same DCM Project. You can reference tags and objects anywhere in the account, as long as the deploying role has the required privileges.

Syntax

You can group tag-to-target associations in different ways within a single statement. The following examples show the supported patterns.

Single tag, single target:

ATTACH TAG <tag_fqn> = '<value>'
  TO <entity_keyword> <entity_fqn>;
-- Attach a tag to a table
ATTACH TAG DEMO{{env_suffix}}.GOV.SENSITIVITY = 'PII'
  TO TABLE DEMO{{env_suffix}}.SALES.CUSTOMERS;

-- Attach a tag to a column (tables, views, and dynamic tables only)
ATTACH TAG DEMO{{env_suffix}}.GOV.SENSITIVITY = 'PII'
  TO TABLE DEMO{{env_suffix}}.SALES.CUSTOMERS COLUMN EMAIL;

-- Attach a tag to a schema
ATTACH TAG DEMO{{env_suffix}}.GOV.DATA_DOMAIN = 'marketing'
  TO SCHEMA DEMO{{env_suffix}}.MARKETING;

Multiple tags and multiple targets (NxM):

A single ATTACH TAG statement can list multiple tag assignments and multiple targets. DCM expands them as a Cartesian product: every listed tag is attached to every listed target. You can mix entity-level and column-level targets in the same statement.

ATTACH TAG DEMO{{env_suffix}}.GOV.TAG_PII = 'true',
           DEMO{{env_suffix}}.GOV.TAG_SENSITIVITY = 'high'
  TO TABLE DEMO{{env_suffix}}.SALES.CUSTOMERS,
     TABLE DEMO{{env_suffix}}.SALES.ORDERS,
     VIEW  DEMO{{env_suffix}}.SALES.ACTIVE_ACCOUNTS;

This single statement creates six tag-target pairs: both tags are attached to each of the three targets.

Supported entities

Entity keywordColumn target supported
DATABASE <db>
SCHEMA <db>.<schema>
TABLE <db>.<schema>.<name>
VIEW <db>.<schema>.<name>
DYNAMIC TABLE <db>.<schema>.<name>
FUNCTION <db>.<schema>.<name>(<arg_types>)
PROCEDURE <db>.<schema>.<name>(<arg_types>)
STAGE <db>.<schema>.<name>
TASK <db>.<schema>.<name>
ROLE <name>
DATABASE ROLE <db>.<name>
WAREHOUSE <name>

Note

To attach a tag to a Data Metric Function, use the FUNCTION keyword with TABLE(...) argument notation, not DATA METRIC FUNCTION:

ATTACH TAG DEMO{{env_suffix}}.GOV.MY_TAG = 'v1'
  TO FUNCTION DEMO{{env_suffix}}.GOV.MY_DMF(TABLE(VARCHAR));

Lifecycle

DCM tracks individual (tag, target) pairs, not whole statements. On each deployment:

  • Create: Any pair that is newly declared in the definitions is attached.
  • Alter: If the value for a pair changes, DCM updates it on the next deployment.
  • Detach: If a pair is removed from the definitions, DCM detaches the tag from that target on the next deployment.

Tags attached to objects outside of DCM aren’t tracked by DCM and won’t be affected by any deployment.

To assign a different value to the same tag on different targets, split them into separate statements:

ATTACH TAG DEMO{{env_suffix}}.GOV.TAG_ENV = 'production'
  TO TABLE DEMO{{env_suffix}}.SALES.CUSTOMERS;

ATTACH TAG DEMO{{env_suffix}}.GOV.TAG_ENV = 'staging'
  TO TABLE DEMO{{env_suffix}}.SALES.ORDERS;

Uniqueness constraint

Each (tag, target) pair must appear at most once across all files in the project. Declaring the same pair in two different statements is an error.

You can freely reorganize how pairs are grouped across statements without affecting deployed state. DCM considers a single 2×2 statement, two 1×2 statements, and four 1×1 statements covering the same pairs to be equivalent.

Native tagging behaviors

ATTACH TAG exercises the same engine path as ALTER <object> SET TAG, so all native Snowflake tagging behaviors apply, including tag inheritance (from containers to child objects), tag propagation (from tables to columns), and masking policy association (when a tag has a masking policy attached).

See Object tagging for the full description of these behaviors.

Functional limitations

  • Column-level targets don’t appear as dependencies in the PLAN dependency graph. Only the owning entity (the table, view, or dynamic table) and the tags are listed.
  • The account-level GRANT APPLY TAG ON ACCOUNT privilege is enforced only when the grantee can also see the target entity. If the grantee doesn’t hold a privilege that lets them see the target, the attachment isn’t applied.
  • All native Snowflake object tagging limitations and quotas apply.
  • Masking policies and row access policies are not yet supported as ATTACH TAG targets.

DEFINE MASKING POLICY

You can define Snowflake masking policies directly in DCM Projects to manages their lifecycle (CREATE, ALTER, DROP) across environments using Jinja templating. All masking policy properties supported by CREATE MASKING POLICY are available in DEFINE MASKING POLICY.

DEFINE MASKING POLICY DCM_DEMO_1{{env_suffix}}.GOV.EMAIL_MASK
    AS (VAL STRING) RETURNS STRING ->
    CASE
        WHEN CURRENT_ROLE() IN ('ACCOUNTADMIN', 'DCM_ADMIN') THEN VAL
        WHEN CURRENT_ROLE() IN ('DCM_DEVELOPER') THEN REGEXP_REPLACE(VAL, '.+\\@', '*****@')
        ELSE '***MASKED***'
    END
    COMMENT = 'Masks email addresses for non-privileged roles'
;

Functional limitations

  • Masking Policies can not yet be ATTACHED to tables as part of DCM definitions. You can manually apply policies outside of DCM.

DEFINE ROW ACCESS POLICY

You can define Snowflake row access policies directly in DCM Projects. DCM manages the row access policy lifecycle (CREATE, ALTER, DROP) across environments using Jinja templating. All row access policy properties supported by CREATE ROW ACCESS POLICY are available in DEFINE ROW ACCESS POLICY.

DEFINE ROW ACCESS POLICY DCM_DEMO_1{{env_suffix}}.GOV.REGION_ROW_FILTER
    AS (REGION_NAME VARCHAR) RETURNS BOOLEAN ->
    CASE
        WHEN CURRENT_ROLE() IN ('ACCOUNTADMIN', 'DCM_ADMIN') THEN TRUE
        WHEN CURRENT_ROLE() = 'SALES_NA' AND REGION_NAME = 'NORTH_AMERICA' THEN TRUE
        WHEN CURRENT_ROLE() = 'SALES_EU' AND REGION_NAME = 'EUROPE' THEN TRUE
        ELSE FALSE
    END
    COMMENT = 'Filters rows by region based on the active role'
;

Functional limitations

  • Row Access Policies can not yet be ATTACHED to tables as part of DCM definitions. You can manually apply policies outside of DCM.

DEFINE NETWORK RULE

You can define Snowflake network rules directly in DCM Projects. DCM manages the network rule lifecycle (CREATE, ALTER, DROP) across environments using Jinja templating, so you don’t have to maintain separate network rule definitions per environment.

All network rule properties supported by CREATE NETWORK RULE are available in DEFINE NETWORK RULE.

DEFINE NETWORK RULE DEMO{{env_suffix}}.SECURITY.EXTERNAL_EGRESS_RULE
    TYPE = HOST_PORT
    MODE = EGRESS
    VALUE_LIST = ('example.com', 'example.com:443')
    COMMENT = 'Allow outbound traffic to example.com';

Functional limitations

  • The TYPE and MODE properties can’t be changed after creation. To change either property, drop and recreate the network rule.
  • Setting or unsetting a tag isn’t supported.

DEFINE NETWORK POLICY

You can define Snowflake network policies directly in DCM Projects. DCM manages the network policy lifecycle (CREATE, ALTER, DROP) across environments using Jinja templating, so you don’t have to maintain separate policy definitions per environment.

All network policy properties supported by CREATE NETWORK POLICY are available in DEFINE NETWORK POLICY.

DEFINE NETWORK POLICY DEMO{{env_suffix}}.SECURITY.CORP_ACCESS_POLICY
    ALLOWED_NETWORK_RULE_LIST = ('DEMO{{env_suffix}}.SECURITY.ALLOW_VPC_RULE')
    BLOCKED_NETWORK_RULE_LIST = ('DEMO{{env_suffix}}.SECURITY.BLOCK_PUBLIC_RULE')
    COMMENT = 'Allow VPC access only';

Functional limitations

  • You can’t replace an existing network policy if it’s currently assigned to an account, security integration, or user. Unassign the policy before replacing it.
  • Associating the policy with an account, user, or integration must be done outside of DCM using ALTER ACCOUNT, ALTER USER, or ALTER SECURITY INTEGRATION.

DEFINE STREAMLIT

You can define one or more Streamlit apps, their infrastructure, underlying tables, and access control together in a single DCM Project folder, then deploy everything to any environment with one command.

This is especially useful for dashboard and data app deployments that depend on objects (tables, views, dynamic tables) also managed by DCM. The entire stack (data pipeline and the app consuming it) can be version-controlled and promoted through environments together.

Create a DCM Project for Streamlit

You can take your existing Streamlit app folder, which includes:

  • streamlit_app.py (or any entrypoint file)
  • environment.yml (for warehouse runtime) or requirements.txt or pyproject.toml (for container runtime)
  • Any supporting Python modules, pages, or asset files

and move it inside the sources/ folder of your DCM Project, since the Snowflake CLI only uploads files from the sources/ folder hierarchy. Place it outside of sources/definitions/ and organize it in any subfolder structure you like, for example sources/streamlit/my_dashboard/.

my_dcm_project/
├── manifest.yml
└── sources/
    ├── definitions/
    │   ├── pipeline.sql
    │   ├── access.sql
    │   └── dashboard.sql         ← DEFINE STREAMLIT statement
    └── streamlit/
        └── my_dashboard/         ← path referenced in FROM clause
            ├── streamlit_app.py
            ├── page_2.py
            ├── pyproject.toml
            └── snowflake.yml

Then add the DEFINE STREAMLIT statement to your DCM definitions with:

  • The fully qualified name for the Streamlit object
  • The relative path from the manifest to the Streamlit folder (always starting with sources/)
  • The entrypoint file name (MAIN_FILE)
  • The warehouse to use for query execution (QUERY_WAREHOUSE)
  • The compute pool and runtime (COMPUTE_POOL, RUNTIME_NAME) for container-runtime apps
  • An optional display title (TITLE)
  • Any external access integrations (EXTERNAL_ACCESS_INTEGRATIONS) and stage imports (IMPORTS) the app requires
define streamlit DEMO{{env_suffix}}.SERVE.MY_DASHBOARD
    from 'sources/streamlit/my_dashboard'    -- relative path from manifest to Streamlit folder
    main_file = 'streamlit_app.py'
    query_warehouse = DEMO_WH{{env_suffix}}
    compute_pool = SYSTEM_COMPUTE_POOL_CPU
    runtime_name = SYSTEM$ST_CONTAINER_RUNTIME_PY3_11
    title = 'My Dashboard'
    external_access_integrations = ()
    imports = ()
;

Plan & deploy a DCM Project with a Streamlit app

Run your regular DCM plan and deploy commands. If either the DEFINE STREAMLIT statement in your definitions or the file hash for any of the files within the Streamlit app folder has changed since the last successful deployment, PLAN shows the Streamlit object as part of the changeset. DEPLOY replaces any modified files and creates a new version.

PLAN only validates that the Streamlit object can be created. It doesn’t test whether the app itself will run successfully when started.

After the first successful deployment, the Streamlit app is immediately live. DCM automatically initializes the live version after creating the Streamlit object, so you don’t need to run ALTER STREAMLIT manually.

Removing the DEFINE STREAMLIT statement drops the Streamlit object on the next deployment.

Functional limitations

  • DCM Jinja templating variables are not passed through to Streamlit Python files. You can use Jinja in the DEFINE STREAMLIT statement itself (for example, to set the warehouse or compute pool name), but not inside your app code.

    • To reference environment-specific objects from inside your Streamlit app at runtime, query the active context using CURRENT_DATABASE(), CURRENT_SCHEMA(), or similar functions to infer the environment.
  • Only relative paths to the Streamlit folder are supported. You can’t specify a path to another repo or folder outside of the DCM Project.

DEFINE DBT PROJECT

You can define a dbt project, its orchestration, infrastructure, and access control together in a single DCM Project folder, then deploy everything to any environment with one command.

Most commonly used is the combination of dbt projects + Tasks to execute dbt test and dbt run on a defined schedule. You can define a DAG of Tasks to orchestrate runs of different dbt projects or individual models.

Create a DCM Project for dbt

You can take your existing dbt project folder, which includes:

  • models
  • dbt_project.yml
  • packages.yml
  • profiles.yml

and move it inside the sources/ folder of your DCM Project, since the Snowflake CLI only uploads files from the sources/ folder hierarchy. Place it outside of sources/definitions/ and organize it in any subfolder structure you like, for example sources/dbt/dbt_pipeline/.

my_dcm_project/
├── manifest.yml
└── sources/
    ├── definitions/
    │   ├── pipeline.sql          ← DEFINE DBT PROJECT statement
    │   ├── access.sql
    │   └── infra.sql
    └── dbt/
        └── dbt_pipeline/         ← path referenced in FROM clause
            ├── dbt_project.yml
            ├── packages.yml
            ├── profiles.yml
            └── models/

Then add the DEFINE DBT PROJECT statement to your DCM definitions with:

  • The relative path from the manifest to the dbt folder (always starting with sources/)
  • A default target (which can use jinja templating to match the DCM deployment target)

In addition, you can add Tasks to execute dbt commands after the deployment as well as grants on the dbt project object or future tables and views.

define dbt project {{db}}.PROJECTS.DBT_PIPELINE
    from 'sources/dbt/dbt_pipeline'    --relative path from manifest to dbt folder
    default_target = '{{dbt_env}}'
;


define task {{db}}.PROJECTS.RUN_DBT_PIPELINE  -- optional: Task(s) to execute your deployed dbt project
    warehouse = {{wh}}
    schedule = '60 MINUTE'
    started     -- (optional) new DCM-specific property that defines the target-state after a DCM deployment
  as
    execute dbt project {{db}}.PROJECTS.DBT_PIPELINE args='run';

define task {{db}}.PROJECTS.TEST_DBT_PIPELINE
    warehouse = {{wh}}
    after {{db}}.PROJECTS.RUN_DBT_PIPELINE
    started
  as
    execute dbt project {{db}}.PROJECTS.DBT_PIPELINE args='test';

If you need to run dbt deps to get external packages, you can run CREATE NETWORK RULE IF NOT EXISTS and CREATE EXTERNAL ACCESS INTEGRATION IF NOT EXISTS in a DCM pre-hook (DCM Hooks are also part of this private preview).

Pass DCM variables to dbt

DCM Jinja templating and dbt templating variables are completely isolated. There’s no automatic pass-through between the DCM manifest.yml configuration and the dbt profiles.yml targets. The two configurations must be maintained separately and kept in sync.

If you need values from the DCM templating context inside a dbt run (for example, the active dbt target), pass them explicitly through the args of the EXECUTE DBT PROJECT statement. Jinja in args is rendered by DCM before the command is executed, so any DCM templating variable can be injected.

define dbt project DCM_DEMO_2_MARKETING{{env_suffix}}.PROJECTS.DBT_PIPELINE
    from 'sources/dbt/dbt_pipeline'
    default_target = '{{dbt_env}}'
;


-- Task graph to schedule dbt project test and run
define task DCM_DEMO_2_MARKETING{{env_suffix}}.PROJECTS.DBT_PIPELINE_RUN
    warehouse = DCM_DEMO_2_MARKETING_WH{{env_suffix}}
    schedule = '60 MINUTE'
    started
  as
    execute dbt project DCM_DEMO_2_MARKETING{{env_suffix}}.PROJECTS.DBT_PIPELINE
        args='run --target {{dbt_env}}'
;

Plan & deploy a DCM Project for dbt

Run your regular DCM plan and deploy commands. If the DEFINE DBT PROJECT statement in your definitions has changed since the last successful deployment, then PLAN will:

  • Render the jinja templating
  • Compile the entire DCM Project
  • Show the dbt project as part of the plan output

The dbt project is compiled during DEPLOY, not during PLAN. PLAN only validates that the dbt project object can be created successfully. It doesn’t check whether the dbt project will run successfully.

Tables created by dbt don’t show as “DCM managed entities” because they aren’t defined directly in the DCM definitions. Removing the DEFINE DBT PROJECT statement drops the dbt project object on the next deployment, but it won’t drop the tables created by dbt.

You can also consider creating a new DCM Project for dbt on top of an existing “platform” project.

Functional limitations

  • PLAN and DEPLOY output only show the operation for a Snowflake dbt project object (CREATE / ALTER / DROP) and don’t show more granular changes in the dbt project configuration or models.
  • Dependencies: dbt models can refer to other objects defined in DCM, but other DCM objects can’t reference tables created by dbt, meaning dbt projects can’t have downstream dependencies.
  • Currently, only relative paths to dbt project files are supported. You can’t specify a path to another repo or folder outside of the DCM Project.

PLAN DELTA

PLAN DELTA is a faster variant of the DCM PLAN command for quickly validating incremental changes to an existing project. Instead of checking all definitions against the current account state, PLAN DELTA only evaluates the definitions you changed and any downstream definitions in the project that depend on them.

Use PLAN DELTA during active development to get faster feedback on your edits. Because it skips unchanged definitions, it doesn’t detect changes that happened outside of DCM on your account since the last deployment (for example, a view dropped or altered by another user). Always run a full PLAN before deploying to ensure no external changes will cause a deployment failure.

Syntax

CLI:

snow dcm plan --delta

SQL (see EXECUTE DCM PROJECT):

EXECUTE DCM PROJECT <project_name> PLAN DELTA [USING ...] FROM ...;

Functional limitations

  • PLAN DELTA doesn’t detect external changes to account objects (such as tables or views altered or dropped outside of DCM since the last deployment). Always run a full snow dcm plan before deploying.
  • There is no Snowflake Workspaces UI equivalent yet. Use the CLI or SQL syntax directly.

CLI enhancements

An early-access version of the Snowflake CLI includes improvements to several DCM Projects commands. To install it:

uv tool install 'git+https://github.com/snowflakedb/snowflake-cli.git@dcm-early-access'

Note

Details of these improvements (such as syntax and output format) are subject to change while they’re in early access.

All of the commands below support the --save-output flag, which saves the command output as a .json file under out/.

snow dcm plan

  • Shows a compressed file upload summary (file counter per path) with a progress bar
  • Shows more granular changes for ALTER operations in the output

snow dcm deploy

  • Shows a compressed file upload summary (file counter per path) with a progress bar
  • Shows a progress bar for the PLAN and DEPLOY execution phases
  • Shows more granular changes for ALTER operations in the output

snow dcm compile (new command)

snow dcm compile runs a static analysis of all DCM definitions and returns any errors or warnings found, grouped by file and entity. It’s intended for quickly checking iterative definition changes and catching errors before committing.

  • Validates syntax and dependencies
  • Runs faster than PLAN, but doesn’t catch all possible errors. (Always run PLAN to preview changes before deploying)
  • Shows a compressed file upload summary (file counter per path) with a progress bar

snow dcm dependencies (new command)

snow dcm dependencies runs a static analysis of all DCM definitions and builds a Mermaid flowchart representing the dependencies between all objects in the project (tables, dynamic tables, views, functions, procedures, and tasks).

The diagram is written to out/dependencies.md. The CLI prints a link to the file so you can open it in your IDE’s Markdown preview and explore the dependency graph visually.

Note that these dependencies refer to the deployment of objects (CREATE). It does not resolve run-time dependencies (for example, a Task that calls a stored procedure).

  • Generate a dependency diagram for the current project:
    snow dcm dependencies
    
    snow dcm dependencies --target dev
    

snow dcm refresh

  • Shows updated output formatting

snow dcm test

  • Shows updated output formatting

Revert to the official release

To revert to the latest official release on main:

uv tool uninstall snowflake-cli
uv tool install snowflake-cli

Pre-hooks for Integrations

DDL Pre-hooks are intended as an interim solution for defining integrations until they are supported natively in DCM using DEFINE statements. DDL Hooks do not offer full functional parity to regular DCM definitions.

Capabilities of DDL Pre-hooks

  • Each DCM Project can contain only 1 pre-hook
  • The pre-hook can contain multiple DDL statements
  • DDL statements inside the hook are executed in the order they are defined
  • The hook supports only 2 types of commands:
  • CREATE IF NOT EXISTS (recommended)
    • For one-time execution to create the object
    • Skipped any time an integration with this name already exists
  • CREATE OR REPLACE
    • Executed at every DCM deployment
    • Use when the definition of an existing object has changed and should be replaced completely
  • Only DDL statements are supported (no USE, no SET, no COPY INTO…)

Key advantages of pre-hooks compared to custom SQL pre-scripts

  1. Pre-hooks are plannable. Other DCM definitions can declare dependencies on objects created in the pre-hook.
    • Example: A Notification Integration defined in the pre-hook can be referenced by an Alert defined in DCM definitions.
  2. Pre-hooks support Jinja templating, using the same variables as the rest of the DCM Project.

Functional limitations

  • Create statements from pre-hooks show as operations in the PLAN changeset and the deployment history, but don’t include granular details about their individual properties.
  • Errors from executing pre-hooks don’t show the exact error line and in some cases not the full stack-trace.
  • Removing a DDL statement from a pre-hook does NOT drop the object.
  • Pre-hooks can’t be defined inside Jinja loops, as it would create multiple pre-hooks. However, Jinja code including loops can be used inside a hook.

Warning

Do not use pre-hooks for object definitions that contain sensitive information or credentials. The rendered SQL definitions will not redact any values inserted by environment variables!

Comparison between DCM definitions, DCM pre-hooks, custom SQL pre-scripts

FunctionalityDCM DefinitionsDCM pre-hooksCustom SQL scripts
Uses DCM Jinja templating values🚫
Plannable dependencies🚫
DDL operations visible in PLAN output🚫
DDL operations stored in DCM deployment artifacts🚫
Removing definition -> drops object🚫🚫
Changed definition -> alters object✅ (when using CREATE OR REPLACE)🚫
Automatically executed in the correct order based on dependencies🚫🚫

Supported object types for DDL Hooks

Only integration object types are supported:

  • API Integration
  • Notification Integration
  • External Access Integration
  • Catalog Integration
  • Security Integration
  • Storage Integration

Once these object types are supported natively with DCM DEFINE statements, the hook support will be deprecated.

Examples:

ATTACH PRE_HOOK
AS [

    CREATE API INTEGRATION IF NOT EXISTS GITHUB_API_{{env_suffix}}
        API_PROVIDER = git_https_api
        API_ALLOWED_PREFIXES = ('https://github.com')
        ALLOWED_AUTHENTICATION_SECRETS = all
        ENABLED = true;

    CREATE NOTIFICATION INTEGRATION IF NOT EXISTS DCM_EMAIL_NOTIFICATIONS_{{env_suffix}}
        TYPE = EMAIL
        ENABLED = true
        ALLOWED_RECIPIENTS = ('example@example.com');

];

Inherited grants

DCM supports inherited grants (PuPr), which let you declaratively define a single grant on a container (ACCOUNT, DATABASE, or SCHEMA) that automatically applies to every current and future object of a specified type within that container.

Prerequisites

Inherited grants require a separate account-level opt-in that’s independent of DCM. Before you can include them in your DCM definitions, run:

ALTER ACCOUNT SET FEATURE_RBAC_INHERITED_GRANTS = 'ENABLED';

Syntax

Use the INHERITED keyword in a standard GRANT statement inside your DCM definitions:

-- Grant SELECT on all current and future tables in a schema
GRANT INHERITED SELECT ON ALL TABLES
  IN SCHEMA DEMO{{env_suffix}}.ANALYTICS
  TO ROLE analyst_role;

-- Grant SELECT on all current and future tables in a database
GRANT INHERITED SELECT ON ALL TABLES
  IN DATABASE DEMO{{env_suffix}}
  TO ROLE reporting_role;

DCM manages the lifecycle of these grants across deployments. Removing an inherited grant statement from your definitions revokes the grant on the next deployment.

Functional limitations

  • All inherited grant limitations apply, including unsupported privilege types (for example, OWNERSHIP) and unsupported object types (for example, SHARE, APPLICATION, INTEGRATION).
  • Inherited grants can’t be combined with WITH GRANT OPTION, CASCADE, or RESTRICT.
  • Granting inherited privileges on imported (shared) databases or on objects owned by foreign accounts isn’t supported.
  • Inherited grants on APPLICATION and APPLICATION PACKAGE objects aren’t supported.

Container-level MANAGE GRANTS

In conjunction with inherited grants we also add support for container-level MANAGE GRANTS, which let you delegate grant administration for a specific database or schema. A role granted MANAGE GRANTS on a container can manage all grant types on objects inside that container without needing account-level SECURITYADMIN privileges.

Prerequisites

Container-level MANAGE GRANTS requires the same account-level opt-in as inherited grants. Before you can include it in your DCM definitions, run:

ALTER ACCOUNT SET FEATURE_RBAC_INHERITED_GRANTS = 'ENABLED';

Syntax

-- Delegate grant administration for a schema
GRANT MANAGE GRANTS ON SCHEMA DEMO{{env_suffix}}.ANALYTICS
  TO ROLE analytics_admin;

-- Delegate grant administration for a database, with the ability to re-delegate to sub-containers
GRANT MANAGE GRANTS WITH GRANT OPTION ON DATABASE DEMO{{env_suffix}}
  TO ROLE prod_access_admin;

DCM manages the lifecycle of these grants across deployments. Removing a MANAGE GRANTS statement from your definitions revokes the privilege on the next deployment.

Functional limitations

  • Container ownership alone doesn’t imply MANAGE GRANTS. The deploying role must explicitly grant this privilege to the target role.
  • A role with container-level MANAGE GRANTS can’t transfer object ownership. Account-level MANAGE GRANTS (held by SECURITYADMIN) is still required for ownership transfers.
  • A role with container-level MANAGE GRANTS can’t re-delegate MANAGE GRANTS on the same container to another role unless it holds MANAGE GRANTS WITH GRANT OPTION.
  • Cascading a revocation of MANAGE GRANTS removes only dependent MANAGE GRANTS grants, not the other grants those roles created inside the container.