Until August 2026, managing Snowflake infrastructure as code meant one of two paths. You either adopted Terraform with the Snowflake provider — an external tool with its own state file, version lag behind new Snowflake features, and a separate CI/CD pipeline to learn and maintain. Or you ran Schemachange or a migration-script pattern — imperative SQL files executed in order, no dry-run, no idempotency, no rollback if something failed halfway through.
Both paths work. Both also mean that your Snowflake infrastructure is being managed by something that lives outside Snowflake and has an imperfect model of what’s actually in your account.
Snowflake DCM Projects — Database Change Management, GA on August 7 2026 — is the native alternative. You write DEFINE statements in SQL files describing your desired state. You run PLAN to see exactly what will change. You run DEPLOY and Snowflake reconciles the diff. No external tool. No state file to lose. No provider lag. And from July 2026, you can have Cortex Code author and debug your DEFINE files with a natural-language prompt.
This is the complete practitioner guide: how DCM Projects works, when to reach for it over Terraform, the full CI/CD pipeline with GitHub Actions, and every production gotcha documented in the official docs.
TL;DR
- DCM Projects is a native Snowflake object (schema-level) that manages other Snowflake objects declaratively. You write
DEFINEstatements in SQL files, runPLANto preview changes, andDEPLOYto apply them. All state is tracked inside Snowflake — no external state file. - The workflow is Terraform-like but Snowflake-native: one DCM project per target environment (DEV, STAGING, PROD), all pointing at the same parameterized definition files, deployed independently via Jinja template variables.
- Jinja2 templating is first-class: dictionaries, loops, conditionals, and macros work in definition files. Use loops to provision the same database + role + warehouse stack for multiple teams in one DEFINE file.
- PLAN DELTA (preview, July 2026) evaluates only changed definitions and their dependents — dramatically faster feedback during active development on large projects.
- Inherited grants (preview, July 2026) let you write one
GRANT ... INHERITEDstatement that automatically applies to every current and future object of a specified type within a container. No more grant drift on new tables. - Snowflake provides reusable GitHub Actions (
dcm/plan,dcm/deploy) and sample workflows for full PR-based CI/CD. OIDC authentication is recommended — no stored secrets. - DCM Projects supports up to 10,000 entities per project and a maximum of 10 MB total definition file size. Projects exceeding these limits may timeout during PLAN or DEPLOY.
- DCM Projects are available on all Snowflake editions at no additional cost beyond the cloud services compute consumed by PLAN and DEPLOY operations.
The Plan-Then-Deploy Lifecycle
The mental model maps directly to Terraform if you’ve used it. You write desired state. You preview the diff. You apply. The difference is that the state Snowflake compares against is the live account — not a separate state file that can diverge from reality.

📷 three zones, one workflow — source files parameterised with jinja, commands run against the live account, environments deployed independently
The key difference from Terraform’s state model: when you run PLAN, Snowflake queries your live account to compare current state against your definitions. If someone manually altered a table outside of DCM, PLAN will show that as a change. The source of truth is always the account, not a file on disk. This eliminates the class of bugs that come from state file drift — but it also means that manual changes outside DCM are always detected and potentially overwritten on the next DEPLOY.
Writing Your First DEFINE File
DEFINE statements are the core primitive. They look like SQL DDL but describe desired state rather than imperative commands. Snowflake figures out whether to CREATE, ALTER, or do nothing based on the diff between your DEFINE and the live object.
-- definitions.sql
-- Describes the desired state of a complete analytics stack for one team
create DATABASE {{ team_name }}_DB
COMMENT = 'Analytics database for {{ team_name }} team';
create SCHEMA {{ team_name }}_DB.RAW
COMMENT = 'Raw ingestion layer';
create SCHEMA {{ team_name }}_DB.ANALYTICS
COMMENT = 'Curated analytics layer — analyst-facing';
create WAREHOUSE {{ team_name }}_WH
WITH
WAREHOUSE_SIZE = '{{ wh_size | default("SMALL") }}'
AUTO_SUSPEND = 300
AUTO_RESUME = TRUE
COMMENT = 'Compute for {{ team_name }} team';
use ROLE {{ team_name }}_ADMIN;
use ROLE {{ team_name }}_ANALYST;
-- Grants declared alongside objects — reconciled on every DEPLOY
GRANT OWNERSHIP ON DATABASE {{ team_name }}_DB TO ROLE {{ team_name }}_ADMIN;
GRANT OWNERSHIP ON WAREHOUSE {{ team_name }}_WH TO ROLE {{ team_name }}_ADMIN;
GRANT USAGE ON WAREHOUSE {{ team_name }}_WH TO ROLE {{ team_name }}_ANALYST;
GRANT USAGE ON DATABASE {{ team_name }}_DB TO ROLE {{ team_name }}_ANALYST;
GRANT USAGE ON SCHEMA {{ team_name }}_DB.ANALYTICS TO ROLE {{ team_name }}_ANALYST;
GRANT SELECT ON ALL TABLES IN SCHEMA {{ team_name }}_DB.ANALYTICS
TO ROLE {{ team_name }}_ANALYST;
GRANT ROLE {{ team_name }}_ADMIN TO ROLE SYSADMIN;
This file is parameterised with {{ team_name }} and {{ wh_size }}. To provision the Finance team with a LARGE warehouse, you pass those variables at plan or deploy time. To provision five teams in one shot, wrap the whole file in a Jinja {% for team_name in teams %} loop and pass a list.
The manifest.yml — environment targets and templating config
# manifest.yml
# Declares target environments and their templating configurations
version: 1
targets:
DEV:
account_identifier: myorg-myaccount-dev
project_name: ANALYTICS.PROJECTS.ANALYTICS_DCM_DEV
project_owner: DCM_DEVELOPER_ROLE
templating_config: dev_config
STAGING:
account_identifier: myorg-myaccount-staging
project_name: ANALYTICS.PROJECTS.ANALYTICS_DCM_STAGING
project_owner: DCM_STAGING_ROLE
templating_config: staging_config
PROD:
account_identifier: myorg-myaccount-prod
project_name: ANALYTICS.PROJECTS.ANALYTICS_DCM_PROD
project_owner: DCM_PROD_ROLE # service account only — not individual devs
templating_config: prod_config
default_target: DEV
configurations:
dev_config:
variables:
teams: ['Engineering']
wh_size: 'XSMALL'
suffix: 'DEV'
staging_config:
variables:
teams: ['Engineering', 'Analytics']
wh_size: 'SMALL'
suffix: 'STAGING'
prod_config:
variables:
teams: ['Engineering', 'Analytics', 'Finance', 'HR']
wh_size: 'MEDIUM'
suffix: ''
The manifest is the single source of truth for your environment topology. Each CI/CD workflow reads account_identifier, project_name, and project_owner directly from the manifest — environment-specific config lives here, not scattered across GitHub secrets.
Running PLAN and DEPLOY
The full command set is available via SQL, Snowflake CLI, Snowsight UI, or Cortex Code. The CLI is the best choice for local development and CI/CD pipelines:
# Navigate to your project directory
cd ./analytics-dcm-project/
# Run PLAN against the default target (DEV from manifest)
snow dcm plan
# Run PLAN against PROD to see what a production deployment would change
snow dcm plan --target PROD --save-output
# Run PLAN DELTA — only evaluate definitions changed since last deploy
# Use during active development for fast feedback; always run full PLAN before deploying
snow dcm plan --delta
# Deploy to DEV
snow dcm deploy
# Deploy to PROD with a deployment alias (like a commit message for the deployment)
snow dcm deploy --target PROD --alias "Add HR team infrastructure - ticket PLAT-421"
# Preview what PURGE would do (dry run)
snow dcm plan --target DEV # should show all objects as DROPs if you purge after
# Purge a sandbox DCM project — drops all managed objects
EXECUTE DCM PROJECT MY_SCHEMA.PROJECTS.ANALYTICS_DCM_DEV PURGE AS "sandbox_cleanup";
DROP DCM PROJECT MY_SCHEMA.PROJECTS.ANALYTICS_DCM_DEV;
Always run full PLAN before DEPLOY. PLAN DELTA is fast but only evaluates changed definitions — it won’t catch external changes (a table manually dropped or altered outside DCM since the last deployment). PLAN DELTA is for development feedback; full PLAN is the pre-deploy gate.
DCM Projects vs Terraform vs Schemachange

📷 dcm projects is not a replacement for every terraform use case — but for snowflake-only infrastructure it removes an entire layer of external tooling
| Dimension | Terraform + Snowflake Provider | Schemachange | Snowflake DCM Projects |
|---|---|---|---|
| State management | External state file (S3/GCS/local) | Migration history table | Inside Snowflake — always live |
| Dry-run / preview | terraform plan | None — scripts run directly | PLAN / PLAN DELTA |
| Idempotency | Yes | Depends on migration design | Yes — DEPLOY skips matching objects |
| Multi-env support | Workspaces / vars | Manual per-env scripts | manifest.yml targets + Jinja vars |
| New Snowflake feature support | Provider update lag (weeks) | Immediate (plain SQL) | Day-0 (DEFINE added same GA) |
| Non-Snowflake infra | Yes — multi-cloud providers | No | Snowflake objects only |
| AI-assisted authoring | None native | None | Cortex Code DCM skill |
| Cost | Tool + provider maintenance | Minimal | Cloud Services compute only |
The honest comparison: if your team already runs Terraform for multi-cloud infrastructure — S3 buckets, IAM roles, Kafka clusters, and Snowflake objects in one plan — stick with Terraform. DCM Projects doesn’t replace it for cross-cloud IaC. It replaces it for Snowflake-only infrastructure, where the external tool overhead was never really worth it.
CI/CD with GitHub Actions
Snowflake publishes reusable GitHub Actions for DCM Projects in the snowflakedb/snowflake-actions repository. Four actions cover the full lifecycle: dcm/parse-manifest, dcm/connection-test, dcm/plan, and dcm/deploy.

📷 every pr triggers a full production plan — reviewers see create/alter/drop before approving, not after
# .github/workflows/dcm_pr_to_main.yml
# Runs PLAN against PROD on every PR — gives reviewers a full diff before merge
name: DCM Plan on PR
on:
pull_request:
branches: [main]
paths:
- 'dcm/**'
jobs:
plan:
runs-on: ubuntu-latest
environment: PROD
permissions:
contents: read
id-token: write # Required for OIDC — no stored secrets
pull-requests: write # Post plan summary as PR comment
steps:
- uses: actions/checkout@v4
- uses: snowflakedb/snowflake-actions/dcm/plan@v3
id: plan
with:
target: PROD
project-path: dcm/analytics/
snowflake-user: ${{ env.SNOWFLAKE_USER }}
# OIDC: no SNOWFLAKE_PASSWORD secret needed
- name: Post plan summary to PR
uses: actions/github-script@v7
with:
script: |
const summary = `${{ steps.plan.outputs.summary }}`;
github.rest.issues.createComment({
...context.repo,
issue_number: context.payload.pull_request.number,
body: summary
});
# .github/workflows/dcm_deploy_prod.yml
# Deploys to STAGING then PROD on merge to main
name: DCM Deploy
on:
push:
branches: [main]
paths: ['dcm/**']
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: STAGING
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: snowflakedb/snowflake-actions/dcm/deploy@v3
with:
target: STAGING
project-path: dcm/analytics/
snowflake-user: ${{ env.SNOWFLAKE_USER }}
alias: "Deploy from ${{ github.sha }}"
deploy-prod:
needs: deploy-staging # Production blocked until staging succeeds
runs-on: ubuntu-latest
environment: PROD
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: snowflakedb/snowflake-actions/dcm/deploy@v3
with:
target: PROD
project-path: dcm/analytics/
snowflake-user: ${{ env.SNOWFLAKE_SERVICE_USER }}
alias: "Promote from staging - ${{ github.sha }}"
The sample workflows in the Snowflake Labs DCM repository include a DROP detection step that parses plan_result.json and blocks deployment if the changeset contains top-level DROPs on databases, schemas, tables, or stages. This is a safety guardrail, not a guarantee — nested destructive changes or ALTER operations that effectively drop data won’t be caught by it.
The Three New Capabilities to Know (July 2026 Preview)
PLAN DELTA — fast feedback during development
On a project with 500+ entities, a full PLAN can take several minutes. PLAN DELTA evaluates only the definition files you changed since the last deployment, plus any downstream definitions that depend on them. For tightening a masking policy or tweaking a dynamic table schedule, PLAN DELTA gives you sub-30-second feedback. Always switch to a full PLAN before deploying.
Inherited grants — close the grant drift problem permanently
The most painful recurring problem in Snowflake governance is grant drift: a new table is created, nobody remembers to grant SELECT to the analyst role, and the dashboard breaks. Inherited grants solve this at the DCM level:
-- grants.sql
-- One INHERITED grant covers all current AND future tables in this schema
GRANT SELECT
ON ALL TABLES IN SCHEMA ANALYTICS_DB.ANALYTICS
TO ROLE ANALYTICS_ANALYST
INHERITED; -- applies automatically to tables created after this DEPLOY
GRANT SELECT
ON ALL VIEWS IN SCHEMA ANALYTICS_DB.ANALYTICS
TO ROLE ANALYTICS_ANALYST
INHERITED;
Inherited grants require a behavior-change parameter enabled at account level, independent of DCM Projects. This parameter affects all future object grants in your account — not just DCM-managed ones. Read the Managing access with inherited grants section before enabling it in production.
ATTACH TAG — declarative tagging reconciled on every deploy
Tag assignment used to be a post-deployment manual step or a separate Terraform resource. With ATTACH TAG in DCM Projects, tag assignments are part of your definition files and reconciled on every DEPLOY — including column-level tags for PII classification that your masking policies depend on:
-- tags.sql
-- Declaratively assigns PII tags — reconciled on every DEPLOY
ATTACH TAG GOVERNANCE_DB.TAGS.PII_EMAIL
TO COLUMN ANALYTICS_DB.ANALYTICS.CUSTOMERS.EMAIL;
ATTACH TAG GOVERNANCE_DB.TAGS.PII_PHONE
TO COLUMN ANALYTICS_DB.ANALYTICS.CUSTOMERS.PHONE_NUMBER;
ATTACH TAG GOVERNANCE_DB.TAGS.GDPR_SUBJECT
TO TABLE ANALYTICS_DB.ANALYTICS.CUSTOMERS;
The Gotchas
Removing a DEFINE statement expresses intent to DROP — not intent to unmanage.
If you delete a DEFINE TABLE ... line from your definitions file and run DEPLOY, DCM drops the table. This is the correct declarative behaviour — the desired state no longer includes that table. If you want to stop managing an object without dropping it, use ALTER TABLE ... UNSET DCM PROJECT to detach it first, then remove the DEFINE statement. Missing this step is how teams accidentally drop production tables during a refactor.
DEPLOY failure mid-execution can leave objects in a partially-applied state.
Unlike Terraform which can plan a rollback, a DCM DEPLOY that fails partway through has applied some DDL statements and not others. The fix in most cases is to fix the root cause and run DEPLOY again — DCM reconciles from wherever it left off. For a critical failure, the Recover an earlier defined state workflow lets you replay an earlier deployment artifact. This is why PLAN + manual review before DEPLOY is non-negotiable in production.
DCM project owner must hold every role that receives GRANT OWNERSHIP — or PLAN fails.
If your definitions include GRANT OWNERSHIP ON TABLE ... TO ROLE TEAM_ADMIN, the DCM project owner role must hold TEAM_ADMIN (directly or through role hierarchy). DCM checks for potential owner lockout at PLAN time and fails before making any changes if the hierarchy isn’t right. This is a safety feature — but it means your DCM project owner role needs a carefully designed privilege stack before you can express ownership transfers in definitions.
Projects over 1,000 entities can timeout on PLAN or DEPLOY.
The official docs state that PLAN or DEPLOY for large projects can take 10 minutes or more, and exceeding the 10,000-entity or 10 MB limits can cause timeouts. Mitigation: split large projects along natural boundaries (separate business units, separate data domains) rather than running one mega-project. Consolidating definitions into fewer files also speeds up PLAN and DEPLOY. And use PLAN DELTA during active development to avoid full-project evaluations on every edit.
Jinja template variables render in cleartext — never put credentials in them.
The official docs include this as an explicit warning: rendered SQL definitions don’t redact any values inserted by Jinja template variables, and those rendered files are stored in the DCM project’s deployment artifacts. Use opaque identifiers, environment names, and configuration values in Jinja — never API keys, passwords, or connection strings. Use Snowflake Secrets for credentials, referenced by name, not by value.
PLAN DELTA won’t catch external changes to your account since the last deployment.
PLAN DELTA only evaluates changed definition files — it doesn’t query the live account for the unchanged portions. If someone manually altered a view outside of DCM since your last deployment, PLAN DELTA won’t detect that drift. Full PLAN always compares against the live account. The rule: PLAN DELTA for development speed, full PLAN as the pre-deploy gate in every CI/CD pipeline.
The One Principle
“Write what you want. Let Snowflake figure out how to get there. The PLAN is the diff — read it before every DEPLOY, especially the DROP lines.”
FAQ
What is Snowflake DCM Projects and when did it go GA?
DCM Projects (Database Change Management) is Snowflake’s native infrastructure-as-code system. You write DEFINE statements in SQL files describing your desired Snowflake object state, run PLAN to preview changes, and DEPLOY to apply them. Snowflake tracks state internally — no external state file. It went generally available on August 7 2026 and is available on all Snowflake editions at no additional cost beyond cloud services compute.
Should I replace Terraform with Snowflake DCM Projects?
It depends on your scope. If you manage only Snowflake objects and have been using Terraform purely for that, DCM Projects is a cleaner native alternative with day-zero feature support and no state file drift. If you manage multi-cloud infrastructure — S3 buckets, IAM, Kafka, and Snowflake together — keep Terraform for the cross-cloud layer. DCM Projects only manages Snowflake objects.
What is the difference between PLAN and PLAN DELTA in DCM Projects?
PLAN compares all your definition files against the current live account state and produces a full changeset. PLAN DELTA evaluates only the definition files that changed since the last deployment, plus any downstream definitions that depend on them. PLAN DELTA is dramatically faster on large projects — use it during active development for quick feedback. Always run a full PLAN before deploying to catch any external changes to your account that PLAN DELTA would miss.
How do I prevent DCM Projects from accidentally dropping objects?
Two safeguards: first, always review the PLAN output before DEPLOY — look specifically at the DROP entries. Second, if using GitHub Actions, add the DROP detection step from the Snowflake Labs sample workflows, which parses plan_result.json and blocks the deploy workflow if it finds top-level DROPs on databases, schemas, tables, or stages. If you want to stop managing an object without dropping it, use ALTER TABLE … UNSET DCM PROJECT to detach it before removing its DEFINE statement.
What are inherited grants in DCM Projects and why do they matter?
Inherited grants let you write a single GRANT statement with the INHERITED keyword that automatically applies to every current and future object of a specified type within a container — for example, all current and future tables in a schema. This permanently closes the grant drift problem: a new table is created, the grant applies automatically without any manual step or separate Terraform resource. Inherited grants require enabling a behavior-change parameter at the account level, independent of DCM Projects itself.
Can Cortex Code write my DCM Project definition files for me?
Yes. The Cortex Code DCM skill can scaffold a new project from scratch, author and edit DEFINE statements and Jinja templates, run PLAN and DEPLOY, interpret plan output, and diagnose failures. Available in Cortex Code CLI, Snowsight, the VS Code extension, and Cortex Code Desktop. Use natural-language prompts like “Add a new dynamic table for customer spending” or “Why did my last plan fail?” and Cortex Code handles the authoring and debugging loop.
Related reading: Snowflake Dynamic Data Masking & Row Access Policies · Using MCP Servers with Snowflake · Cortex AI token usage monitoring · Identifying hidden Cortex AI token costs · dbt State — stop rebuilding what hasn’t changed · DCM Projects overview (official) · Deploy and manage DCM Projects (official) · Snowflake Labs DCM repository (GitHub)
