Salesforce · · 39 min read

Deploying Changes to Production in Salesforce

How to move your customizations from sandbox to production — change sets, DevOps Center, scratch orgs, unlocked packages, and the Salesforce CLI.

Part 26: Deploying Changes to Production in Salesforce

You have spent hours — maybe days — building a perfect flow, customizing page layouts, creating validation rules, and setting up record-triggered automations in your sandbox. Everything works beautifully. Now comes the moment of truth: getting those changes into production where real users will interact with them.

Deployment is one of the most critical skills a Salesforce admin or developer can master. A well-executed deployment means your users get new functionality without disruption. A poorly executed one can break existing processes, corrupt data, or leave your org in an inconsistent state.

In this part, we will cover every major deployment method available on the Salesforce platform — from the point-and-click simplicity of change sets to the full power of the Salesforce CLI and unlocked packages. We will also walk through setting up a scratch org from start to finish.


What Does It Mean to Deploy Changes to Production?

In Salesforce, “deploying changes to production” means transferring metadata — the configurations, customizations, and code that define how your org behaves — from a non-production environment (like a sandbox or scratch org) to your live production org.

What Is Metadata?

Metadata is everything that describes your org’s structure and behavior. This includes:

Metadata CategoryExamples
Objects and FieldsCustom objects, custom fields, field-level security, record types
Page LayoutsPage layout assignments, compact layouts, Lightning record pages
AutomationFlows, approval processes, workflow rules (legacy)
Validation RulesField validation rules, formula-based rules
Profiles and Permission SetsField permissions, object permissions, tab visibility
Apex CodeApex classes, Apex triggers, test classes
Lightning Web ComponentsLWC bundles, Aura components
Reports and DashboardsCustom report types, report folders, dashboards
Email TemplatesClassic and Lightning email templates
Custom LabelsCustom labels and translations
Static ResourcesJavaScript libraries, CSS files, images
Connected AppsOAuth settings, API integrations

When you “deploy,” you are packaging some or all of these metadata items and pushing them from one org to another.

Why Not Build Directly in Production?

This is a question that comes up early. If production is where everything needs to end up, why not just build there? Here is why:

  1. Risk of breaking things — If you make a mistake in production, real users are immediately affected. A broken validation rule could prevent your sales team from saving Opportunities. A faulty flow could create duplicate records.

  2. No undo button — Salesforce does not have a universal “undo” for metadata changes. Once you change a page layout in production, the old version is gone unless you have a backup.

  3. Testing requirements — Apex code requires a minimum of 75% test coverage before deployment. You cannot develop and test in production simultaneously without risk.

  4. Change management — Organizations need a review and approval process for changes. Building in a sandbox and deploying through a formal process gives stakeholders visibility and control.

  5. Compliance — Many industries (healthcare, finance, government) require separation of development and production environments as part of their regulatory compliance.

The Deployment Lifecycle

The standard deployment lifecycle in Salesforce looks like this:

Developer Sandbox → Developer Pro / Partial Copy Sandbox → Full Copy / UAT Sandbox → Production

Or in simpler setups:

Developer Sandbox → Production

Each stage serves a purpose:

  • Developer Sandbox: Where admins and developers build and iterate on changes. Contains a copy of metadata but not production data.
  • Developer Pro Sandbox: Similar to Developer but with more storage. Used for larger development efforts.
  • Partial Copy Sandbox: Contains metadata plus a subset of production data, defined by a sandbox template. Used for testing with realistic data.
  • Full Copy Sandbox: An exact copy of production, including all data. Used for user acceptance testing (UAT), performance testing, and training.
  • Production: The live org where real business happens.

Note: The type and number of sandboxes available depend on your Salesforce edition. Enterprise Edition includes one Developer sandbox and one Developer Pro sandbox. Unlimited Edition includes significantly more.


What Are Change Sets?

Change sets are Salesforce’s built-in, point-and-click deployment tool. They let you bundle metadata components from one org and send them to another connected org — without writing any code or using the command line.

How Change Sets Work

Change sets operate on a simple model:

  1. You create an outbound change set in the source org (e.g., your sandbox).
  2. You add metadata components to the change set (fields, flows, page layouts, Apex classes, etc.).
  3. You upload the change set to the target org.
  4. In the target org, you open the inbound change set and validate it (optional but recommended).
  5. You deploy the inbound change set to apply the changes.

Important Characteristics of Change Sets

CharacteristicDetail
Connection requiredOrgs must be connected via a deployment connection. Sandboxes are automatically connected to their parent production org.
One-wayAn outbound change set travels from one org to another. There is no automatic “rollback” change set.
Additive onlyChange sets can add or modify components but cannot delete them. If you need to remove a field from production, you must do it manually.
No dataChange sets only move metadata, not record data.
Component dependenciesYou must manually include all dependent components. If you add a flow that references a custom field, you must also add that custom field.
Deployment connectionsMust be configured and authorized in both the source and target org.
Upload timeUploading a change set is not instant. Depending on the size, it can take minutes to hours.
ValidationYou can validate a change set before deploying to check for errors without actually applying changes.

Benefits of Change Sets

  • No tools required — Everything happens within the Salesforce UI. No CLI, no IDE, no third-party tools.
  • Familiar interface — Admins who are comfortable navigating Setup will feel at home.
  • Built-in validation — You can validate before deploying, catching errors before they reach production.
  • Audit trail — Salesforce records which change sets were deployed and when.

Drawbacks of Change Sets

  • No version control — There is no history of what was in each change set beyond the deployment log.
  • Manual component selection — You have to manually add every component and its dependencies. Miss one, and the deployment fails.
  • No rollback — If a deployment causes issues, you cannot “undo” it. You have to manually fix the problems or deploy a corrective change set.
  • Slow for large deployments — Upload and deployment times increase with the number of components.
  • Not repeatable — You cannot re-deploy the same change set. If you need to deploy the same changes to another sandbox, you create a new change set.
  • Limited to connected orgs — You can only deploy between orgs that share a deployment connection (typically production and its sandboxes).

Note: Despite these limitations, change sets remain the most commonly used deployment method for Salesforce admins. They are straightforward, require no additional tooling, and work well for small to medium-sized deployments.


How to Deploy Using Change Sets

Here is the complete step-by-step process for deploying changes from a sandbox to production using change sets.

Step 1: Verify Deployment Connections

Before creating a change set, make sure the deployment connection is configured and authorized.

  1. In your production org, navigate to Setup > Deploy > Deployment Settings.
  2. You will see a list of all sandboxes associated with your production org.
  3. Click Edit next to the sandbox you want to deploy from.
  4. Check the box Allow Inbound Changes to authorize the connection.
  5. Click Save.

Without this authorization, the sandbox cannot send change sets to production.

Step 2: Create an Outbound Change Set in the Sandbox

  1. In your sandbox, navigate to Setup > Deploy > Outbound Change Sets.
  2. Click New.
  3. Enter a Name for the change set (e.g., “Spring Release - Custom Fields and Flows”).
  4. Optionally enter a Description that explains what the change set contains and why.
  5. Click Save.

Step 3: Add Components to the Change Set

  1. On the change set detail page, click Add in the Change Set Components section.
  2. Select the Component Type from the dropdown (e.g., Custom Field, Flow Definition, Apex Class, Page Layout).
  3. A list of available components of that type appears. Check the boxes next to the components you want to include.
  4. Click Add to Change Set.
  5. Repeat for each component type you need.

Step 4: Add Dependent Components (Critical Step)

This is where many deployments fail. Salesforce provides a tool to help:

  1. On the change set detail page, click Add Dependencies.
  2. Salesforce analyzes the components you added and identifies their dependencies — other components they reference or rely on.
  3. Review the list of dependencies carefully.
  4. Select the dependencies you want to include and click Add to Change Set.

Note: The dependency analysis does not catch everything. For example, it may not identify that a flow references a specific custom field if the reference is in a formula within the flow. Always review your components manually as well.

Step 5: Upload the Change Set

  1. On the change set detail page, click Upload.
  2. Select the Target Organization (your production org or another sandbox).
  3. Click Upload.
  4. You will see a confirmation message. The upload process runs in the background and can take anywhere from a few minutes to several hours.
  5. You will receive an email notification when the upload is complete.

Note: Once a change set is uploaded, it is locked. You cannot add or remove components. If you need to make changes, you must clone the change set or create a new one.

Validation runs all the checks of a deployment — dependency analysis, Apex test execution, etc. — without actually applying the changes. This lets you catch errors before committing to the deployment.

  1. In your production org, navigate to Setup > Deploy > Inbound Change Sets.
  2. Find the change set you uploaded. It may take a few minutes to appear after the upload email arrives.
  3. Click the change set name.
  4. Click Validate.
  5. Choose a test option:
    • Default — Runs the default set of Apex tests (tests in the change set plus any tests affected by the components).
    • Run Local Tests — Runs all local Apex tests in the target org (excludes managed package tests).
    • Run All Tests — Runs every test class in the target org. This takes the longest.
    • Run Specified Tests — Lets you specify which test classes to run.
  6. Click Validate.
  7. Monitor the validation status. If validation succeeds, you are ready to deploy. If it fails, review the errors, fix them in the sandbox, and upload a new change set.

Step 7: Deploy the Inbound Change Set

  1. On the inbound change set detail page, click Deploy.
  2. Choose the same test option you selected during validation.
  3. Click Deploy.
  4. Monitor the deployment status. You will receive an email when deployment completes or fails.

Step 8: Verify in Production

After deployment:

  1. Navigate to the components you deployed and verify they exist and function correctly.
  2. Test critical business processes that could be affected by the changes.
  3. Check that page layouts, field-level security, and record types are applied as expected.
  4. Ask key users to test their workflows.

Change Set Best Practices

  • Always validate before deploying. This catches most errors without affecting production.
  • Keep change sets small and focused. Large change sets with dozens of components are harder to troubleshoot when something fails.
  • Document your change sets. Use the description field and maintain a separate log of what each change set contains and why it was deployed.
  • Deploy during off-hours. If your organization operates during business hours, deploy during evenings or weekends to minimize impact.
  • Communicate with stakeholders. Let affected users know about upcoming changes and any expected downtime or behavior changes.
  • Test in a staging sandbox first. If you have a Partial Copy or Full Copy sandbox, deploy there first before deploying to production.

What Is the Salesforce DevOps Center?

Salesforce DevOps Center is a relatively new tool (General Availability as of Spring ‘23) that brings modern source control and release management practices directly into the Salesforce UI. It bridges the gap between the simplicity of change sets and the sophistication of a full CI/CD pipeline.

Key Concepts

ConceptDescription
ProjectA DevOps Center project represents a body of work. It connects to a GitHub repository and defines which orgs are involved.
Work ItemsIndividual units of work (similar to user stories or tasks). Each work item tracks a set of related changes.
EnvironmentsThe Salesforce orgs involved in the release pipeline (e.g., development sandbox, staging sandbox, production).
Source ControlDevOps Center uses GitHub as its source control backend. Every change is tracked as a commit in a Git repository.
PipelinesThe sequence of environments that changes move through (e.g., Dev → Staging → Production).
BranchesEach work item gets its own Git branch. Changes are tracked per work item and merged through the pipeline.

Benefits of DevOps Center

  • Source control built in — Every change is tracked in GitHub. You get full version history, branching, and the ability to revert changes.
  • Work item tracking — You can associate changes with specific work items, creating a clear audit trail.
  • Conflict detection — DevOps Center detects when two developers modify the same component and provides merge conflict resolution.
  • Pipeline visibility — You can see exactly which changes are in each environment at any point in time.
  • No CLI required — While DevOps Center uses Git under the hood, administrators interact with it through the Salesforce UI.
  • Rollback capability — Because changes are tracked in Git, you can revert to a previous state.

Drawbacks of DevOps Center

  • GitHub dependency — DevOps Center currently only supports GitHub as a source control provider. If you use GitLab, Bitbucket, or Azure DevOps, you cannot use DevOps Center.
  • Learning curve — Admins who have never worked with source control concepts (branches, commits, merges) will need time to learn.
  • Limited automation — DevOps Center does not include built-in CI/CD automation (automated testing, automated deployment). For that, you need additional tooling.
  • Still maturing — As a relatively new feature, DevOps Center may lack certain capabilities compared to established third-party DevOps tools like Copado or Gearset.

Note: DevOps Center is free and available in Enterprise, Unlimited, and Performance editions. You need a GitHub account (free tier works) and the DevOps Center managed package installed in your production org.


How to Deploy Using DevOps Center

Prerequisites

Before you can use DevOps Center, you need:

  1. A GitHub account with a repository for your Salesforce project.
  2. DevOps Center installed in your production org. Go to Setup > DevOps Center and follow the installation prompts if it is not already installed.
  3. Connected sandboxes that will serve as development and staging environments.
  4. GitHub authentication configured in Salesforce. DevOps Center will prompt you to authorize access to your GitHub account.

Step 1: Create a Project

  1. Open DevOps Center from the App Launcher.
  2. Click New Project.
  3. Enter a Project Name (e.g., “Spring 2026 Release”).
  4. Connect your GitHub Repository — select the repository where project metadata will be stored.
  5. Select the Branch that represents your production baseline (usually main).
  6. Click Save.

Step 2: Add Environments to the Pipeline

  1. On the project detail page, go to the Pipeline tab.
  2. Click Add Environment.
  3. Add your development sandbox as the first environment in the pipeline.
  4. Add your staging or UAT sandbox as the second environment (optional but recommended).
  5. Production is automatically the final environment.
  6. Authorize each environment by logging in when prompted.

Step 3: Create a Work Item

  1. In the project, click New Work Item.
  2. Enter a Name and Description (e.g., “Add Contact Preferred Language Field”).
  3. Assign a Development Environment — the sandbox where the developer will build this change.
  4. Click Save.
  5. DevOps Center creates a dedicated Git branch for this work item.

Step 4: Make Changes in the Sandbox

  1. Open the assigned development sandbox.
  2. Build your changes — create fields, modify flows, update page layouts, write Apex code, whatever the work item requires.
  3. Return to DevOps Center when you are done.

Step 5: Pull and Commit Changes

  1. In DevOps Center, open the work item.
  2. Click Pull Changes to detect what changed in the sandbox since the work item was created.
  3. DevOps Center shows a list of modified components. Review them carefully.
  4. Select the components that belong to this work item.
  5. Enter a commit message describing the changes.
  6. Click Commit. The selected changes are committed to the work item’s Git branch.

Step 6: Promote Through the Pipeline

  1. Once changes are committed, click Promote to move them to the next environment in the pipeline (e.g., from Dev to Staging).
  2. DevOps Center deploys the changes to the staging sandbox.
  3. Test in the staging environment.
  4. If everything looks good, Promote again to move changes to production.
  5. If there are merge conflicts (another work item modified the same component), DevOps Center will alert you and guide you through resolution.

Step 7: Review and Deploy to Production

  1. Before the final promotion to production, review all changes one more time.
  2. Confirm the promotion. DevOps Center deploys the changes to production and merges the work item branch into the main branch in GitHub.
  3. The work item status updates to reflect the completed deployment.

Note: DevOps Center is excellent for teams that want source control benefits without fully committing to a CLI-based workflow. It works especially well for admin-led teams that need more rigor than change sets but are not ready for a full CI/CD pipeline.


What Are Scratch Orgs?

Scratch orgs are temporary, fully configurable Salesforce environments that developers create from the command line using the Salesforce CLI. Unlike sandboxes, which are copies of production, scratch orgs are brand-new, empty orgs that you configure from scratch using a definition file.

Scratch Orgs vs. Sandboxes

FeatureScratch OrgSandbox
SourceCreated from a definition fileCopied from production
LifespanTemporary (1-30 days, default 7)Persistent until deleted
DataEmpty (no records)Can include production data (Partial/Full Copy)
ConfigurationDefined by a JSON fileMirrors production metadata
Creation timeMinutesMinutes to hours (Full Copy can take days)
CostIncluded with Dev Hub (limited daily count)Included based on edition (limited number)
Use caseFeature development, testing, CI/CDDevelopment, testing, UAT, training
Requires CLIYesNo (can create from Setup)

Why Use Scratch Orgs?

  • Clean slate — Every scratch org starts fresh. No leftover configurations, no stale data, no accumulated technical debt from years of production changes.
  • Reproducible — Because scratch orgs are created from a definition file, every developer on the team gets an identical environment.
  • Fast — Scratch orgs spin up in minutes.
  • Disposable — When you are done, delete the scratch org. No cleanup required.
  • CI/CD friendly — Scratch orgs are designed for automated pipelines. Create a scratch org, push code, run tests, destroy the org — all automated.

Note: Scratch orgs require a Salesforce Dev Hub, which is available in Developer Edition (free), Enterprise Edition, Unlimited Edition, and Performance Edition orgs. You enable Dev Hub in Setup > Dev Hub.


How to Set Up a Scratch Org

Setting up a scratch org requires the Salesforce CLI and a connected Dev Hub. Here is the complete process.

Prerequisites

  1. Salesforce CLI (sf) installed on your machine. Install it from developer.salesforce.com/tools/salesforcecli.
  2. A Dev Hub org — This is usually your production org or a Developer Edition org with Dev Hub enabled.
  3. A Salesforce DX project — A local directory structure that the CLI uses to manage your source code.

Step 1: Install the Salesforce CLI

If you have not installed the CLI yet:

macOS (using Homebrew):

brew install sf

Windows (using the installer):

Download the installer from the Salesforce CLI page and follow the installation wizard.

Verify installation:

sf --version

You should see output like:

@salesforce/cli/2.x.x darwin-arm64 node-v20.x.x

Step 2: Authorize Your Dev Hub

sf org login web --set-default-dev-hub --alias my-devhub

This opens a browser window where you log in to your Dev Hub org. After authentication, the CLI stores the connection locally.

Verify the connection:

sf org list

You should see your Dev Hub listed with the alias my-devhub.

Step 3: Create a Salesforce DX Project

If you do not already have a project:

sf project generate --name my-salesforce-project
cd my-salesforce-project

This creates the following structure:

my-salesforce-project/
  config/
    project-scratch-def.json
  force-app/
    main/
      default/
        aura/
        classes/
        lwc/
        objects/
        triggers/
  sfdx-project.json
  .gitignore
  README.md

Step 4: Configure the Scratch Org Definition File

The scratch org definition file (config/project-scratch-def.json) tells Salesforce what kind of org to create. Here is an example:

{
  "orgName": "My Feature Development Org",
  "edition": "Developer",
  "features": [
    "EnableSetPasswordInApi",
    "Communities",
    "ServiceCloud",
    "LightningSalesConsole"
  ],
  "settings": {
    "lightningExperienceSettings": {
      "enableS1DesktopEnabled": true
    },
    "mobileSettings": {
      "enableS1EncryptedStoragePref2": false
    },
    "securitySettings": {
      "passwordPolicies": {
        "enableSetPasswordInApi": true
      }
    }
  }
}

Key properties:

PropertyDescription
orgNameA friendly name for the scratch org
editionThe Salesforce edition to emulate: Developer, Enterprise, Group, or Professional
featuresA list of features to enable (e.g., Communities, ServiceCloud, Multi-Currency)
settingsOrg settings to configure, matching the Metadata API settings structure
hasSampleDataIf true, Salesforce populates the org with sample data

Step 5: Create the Scratch Org

sf org create scratch --definition-file config/project-scratch-def.json --alias my-scratch --set-default --duration-days 14

Flags explained:

FlagPurpose
--definition-filePath to the scratch org definition file
--aliasA short name for the scratch org
--set-defaultMakes this the default org for subsequent CLI commands
--duration-daysHow many days the scratch org should exist (max 30)

After a minute or two, the scratch org is ready.

Step 6: Open the Scratch Org

sf org open --target-org my-scratch

This opens the scratch org in your browser, already authenticated.

Step 7: Push Your Source to the Scratch Org

If your project contains existing source code:

sf project deploy start --target-org my-scratch

This pushes all the metadata in your force-app directory to the scratch org.

Step 8: Work in the Scratch Org

Make your changes in the scratch org — create objects, fields, flows, Apex classes, whatever you need.

Step 9: Pull Changes Back to Your Local Project

After making changes in the scratch org UI:

sf project retrieve start --target-org my-scratch

This pulls the metadata changes back into your local project directory.

Step 10: Delete the Scratch Org (When Done)

sf org delete scratch --target-org my-scratch --no-prompt

Note: Scratch orgs have daily creation limits. A Developer Edition Dev Hub allows 6 active scratch orgs. Enterprise and Unlimited editions allow more. Check your limits with sf org list limits --target-org my-devhub.


How to Deploy Using Unlocked Packages

Unlocked packages are Salesforce’s modern packaging solution for organizing and deploying metadata. Think of them as versioned containers for your customizations, similar to how software libraries are packaged and distributed in traditional software development.

What Are Unlocked Packages?

An unlocked package is a collection of metadata components bundled together and assigned a version number. Unlike change sets, packages are:

  • Versioned — Each deployment is a specific version (1.0.0, 1.1.0, 2.0.0, etc.).
  • Repeatable — The same package version can be installed in multiple orgs.
  • Modular — You can split your org’s metadata into multiple packages based on functional areas.
  • Upgradeable — You can install a new version of a package over an older version, and Salesforce handles the upgrade.

Unlocked Packages vs. Managed Packages

FeatureUnlocked PackageManaged Package
PurposeInternal deployment and org managementDistribution on AppExchange
Code visibilitySource code is visible and editable after installationSource code is hidden (IP protection)
NamespaceOptionalRequired
UpgradabilityCan upgrade and uninstall freelyStrict upgrade rules, limited uninstall
AudienceYour own orgsExternal customers

Step 1: Create a Package

First, make sure your SFDX project is set up and your Dev Hub is authorized.

sf package create --name "Sales Customizations" --package-type Unlocked --path force-app --target-dev-hub my-devhub

This registers the package with your Dev Hub. The sfdx-project.json file is updated with the package information.

Step 2: Develop Your Components

Build your customizations in a scratch org or sandbox. Organize them in your local project under the path specified when creating the package (e.g., force-app).

Step 3: Create a Package Version

When you are ready to deploy:

sf package version create --package "Sales Customizations" --installation-key my-secret-key --wait 20 --target-dev-hub my-devhub

Flags:

FlagPurpose
--packageThe package name or ID
--installation-keyA password required to install the package (use --installation-key-bypass for no password)
--waitHow many minutes to wait for the version creation to complete
--target-dev-hubYour Dev Hub org

This compiles the metadata, runs validation, and creates a versioned package artifact. The output includes a Subscriber Package Version Id (starts with 04t), which you need for installation.

Step 4: Install the Package in the Target Org

sf package install --package 04tXXXXXXXXXXXXXXX --target-org production --installation-key my-secret-key --wait 20

Replace 04tXXXXXXXXXXXXXXX with the actual package version ID from the previous step.

Step 5: Verify the Installation

sf package installed list --target-org production

This shows all installed packages in the target org, including version numbers.

Package Versioning Strategy

Use semantic versioning to communicate the nature of changes:

Version ChangeMeaningExample
Major (X.0.0)Breaking changes, major new features1.0.0 → 2.0.0
Minor (0.X.0)New features, backward compatible1.0.0 → 1.1.0
Patch (0.0.X)Bug fixes, minor adjustments1.1.0 → 1.1.1

Benefits of Unlocked Packages

  • Versioned history — You always know exactly what version is installed in each org.
  • Repeatable deployments — Install the same version in dev, staging, and production.
  • Modular architecture — Split your org into logical packages (Sales, Service, Marketing, Integration).
  • Dependency management — Packages can declare dependencies on other packages.
  • Rollback — You can uninstall a package version or install a previous version.
  • CI/CD integration — Package creation and installation can be fully automated.

Drawbacks of Unlocked Packages

  • CLI required — Unlocked packages are managed entirely through the Salesforce CLI. There is no point-and-click interface.
  • Dev Hub required — You need a Dev Hub-enabled org.
  • Learning curve — Understanding package concepts, versioning, and dependency management takes time.
  • Not all metadata supported — Some metadata types cannot be included in unlocked packages (e.g., certain org-wide settings).
  • Setup overhead — Initial setup is more complex than change sets.

Note: Unlocked packages are the recommended long-term strategy for organizations with multiple developers or complex orgs. They provide the structure and repeatability that change sets lack.


How to Deploy Via the Salesforce CLI

The Salesforce CLI (sf) is the most powerful and flexible deployment tool in the Salesforce ecosystem. It can do everything the other methods can do and more. Whether you are deploying a single Apex class or an entire org’s worth of metadata, the CLI handles it.

Core CLI Deployment Commands

The Salesforce CLI offers two primary deployment approaches:

This is the modern approach, using source format metadata.

Deploy all source in the project:

sf project deploy start --target-org production

Deploy specific metadata components:

sf project deploy start --metadata ApexClass:MyController --target-org production

Deploy a specific directory:

sf project deploy start --source-dir force-app/main/default/classes --target-org production

Deploy with specific test execution:

sf project deploy start --target-org production --test-level RunLocalTests

Deploy and run specific tests:

sf project deploy start --target-org production --test-level RunSpecifiedTests --tests MyControllerTest MyServiceTest

2. Metadata Deploy (Legacy)

This uses the older Metadata API format (ZIP-based). You may encounter this in older projects.

sf project deploy start --metadata-dir ./metadata --target-org production

Test Level Options

When deploying to production, Salesforce requires Apex tests to run. Here are the test levels:

Test LevelDescriptionWhen to Use
NoTestRunSkips all testsOnly for non-production orgs
RunSpecifiedTestsRuns only the tests you specifyWhen you know exactly which tests cover your changes
RunLocalTestsRuns all tests in the org except managed package testsStandard production deployment
RunAllTestsInOrgRuns every test, including managed package testsThorough validation, takes the longest

Deployment Validation (Check-Only)

Just like validating a change set, you can validate a CLI deployment without applying changes:

sf project deploy start --target-org production --dry-run --test-level RunLocalTests

The --dry-run flag runs validation only. If it succeeds, you can then deploy with confidence.

Quick Deploy After Validation

If you validated a deployment and it passed, you can deploy immediately without re-running tests:

sf project deploy quick --job-id 0AfXXXXXXXXXXXXXXX --target-org production

The --job-id comes from the validation output. This is extremely useful for large deployments where tests take a long time — validate during the day, quick-deploy during off-hours.

Monitoring Deployment Status

sf project deploy report --target-org production

Or for a specific deployment:

sf project deploy report --job-id 0AfXXXXXXXXXXXXXXX --target-org production

Retrieving Metadata

You can also retrieve metadata from an org to your local project:

Retrieve all metadata in your project:

sf project retrieve start --target-org production

Retrieve specific metadata:

sf project retrieve start --metadata ApexClass:MyController --target-org production

Retrieve from a manifest (package.xml):

sf project retrieve start --manifest manifest/package.xml --target-org production

The package.xml Manifest

A package.xml file explicitly lists the metadata components you want to deploy or retrieve. Here is an example:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>MyController</members>
        <members>MyService</members>
        <name>ApexClass</name>
    </types>
    <types>
        <members>Account.Custom_Field__c</members>
        <members>Contact.Custom_Field__c</members>
        <name>CustomField</name>
    </types>
    <types>
        <members>My_Custom_Flow</members>
        <name>Flow</name>
    </types>
    <types>
        <members>Account-Account Layout</members>
        <name>Layout</name>
    </types>
    <version>62.0</version>
</Package>

Deploy using the manifest:

sf project deploy start --manifest manifest/package.xml --target-org production --test-level RunLocalTests

Common CLI Workflows

Scenario 1: Deploy a bug fix to production

# 1. Retrieve the current version from production
sf project retrieve start --metadata ApexClass:BuggyController --target-org production

# 2. Fix the bug in your local editor

# 3. Deploy the fix with tests
sf project deploy start --metadata ApexClass:BuggyController --target-org production --test-level RunSpecifiedTests --tests BuggyControllerTest

Scenario 2: Full org deployment

# 1. Validate first
sf project deploy start --target-org production --dry-run --test-level RunLocalTests

# 2. If validation passes, quick deploy
sf project deploy quick --job-id <job-id-from-validation> --target-org production

Scenario 3: Deploy to multiple sandboxes

# Deploy to QA sandbox
sf project deploy start --target-org qa-sandbox --test-level RunLocalTests

# Deploy to UAT sandbox
sf project deploy start --target-org uat-sandbox --test-level RunLocalTests

# Deploy to production
sf project deploy start --target-org production --test-level RunLocalTests

CLI Best Practices

  • Always validate before deploying to production. Use --dry-run to catch issues early.
  • Use specific test levels. RunSpecifiedTests is faster than RunLocalTests when you know which tests cover your changes.
  • Use quick deploy for production. Validate during the day, quick-deploy during off-hours.
  • Version control everything. Store your SFDX project in Git. Every change should be committed before deployment.
  • Use a .forceignore file. This tells the CLI which files to exclude from deployments and retrieves, similar to .gitignore.
  • Automate with scripts. Write shell scripts or CI/CD pipelines that handle the full deploy workflow.

Comparing Deployment Methods

Here is a side-by-side comparison to help you choose the right deployment method for your situation:

FeatureChange SetsDevOps CenterSalesforce CLIUnlocked Packages
UI-basedYesYesNoNo
Source controlNoYes (GitHub)Yes (any)Yes (any)
RollbackNoYes (via Git)ManualYes (version-based)
Version trackingNoYesYes (with Git)Yes (built-in)
AutomationNoLimitedFullFull
Learning curveLowMediumHighHigh
Best forAdmins, small changesAdmin teams, medium projectsDevelopers, CI/CDLarge orgs, modular architecture
Dependency managementManualAutomaticManual (package.xml)Built-in
RepeatableNoYesYesYes
Data migrationNoNoNo (use Data Loader)No
DeletionsNot supportedSupportedSupported (destructive changes)Supported (via upgrade)

Note: Many organizations use a combination of these methods. Admins might use change sets for quick configuration changes while developers use the CLI and unlocked packages for code-heavy deployments. Choose the method that fits your team’s skills and your project’s complexity.


Handling Destructive Changes

One limitation of change sets is that they cannot delete metadata from the target org. The Salesforce CLI, however, supports destructive changes.

Creating a Destructive Changes Manifest

Create a file called destructiveChanges.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>ObsoleteController</members>
        <name>ApexClass</name>
    </types>
    <types>
        <members>Account.Deprecated_Field__c</members>
        <name>CustomField</name>
    </types>
    <version>62.0</version>
</Package>

You also need an empty package.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <version>62.0</version>
</Package>

Deploying Destructive Changes

sf project deploy start --manifest package.xml --post-destructive-changes destructiveChanges.xml --target-org production --test-level RunLocalTests

The --post-destructive-changes flag tells Salesforce to delete the specified components after deploying any new components.

Note: Be extremely careful with destructive changes. Deleting a custom field removes all data in that field across every record. Always back up your data before running destructive deployments.


Common Deployment Errors and How to Fix Them

Deployments fail. It is part of the process. Here are the most common errors and their solutions:

1. Missing Dependent Components

Error: Cannot find a definition for CustomField: Account.Custom_Field__c

Cause: You deployed a component (like a flow or page layout) that references a custom field, but the custom field was not included in the deployment.

Fix: Add the missing component to your change set or source directory. Use the “Add Dependencies” feature in change sets, or check your package.xml for completeness.

2. Insufficient Test Coverage

Error: Average test coverage across all Apex Classes and Triggers is 65%, at least 75% test coverage is required.

Cause: Your org’s overall Apex test coverage is below the 75% threshold.

Fix: Write additional test classes or improve existing tests. Remember, the 75% requirement is an org-wide average, not per-class.

3. Test Failures

Error: System.AssertException: Assertion Failed: Expected: 5, Actual: 3

Cause: An Apex test class is failing, either because of a bug in the test or because your changes broke existing functionality.

Fix: Debug the failing test. Run it in your sandbox to reproduce the failure. Fix either the test or the underlying code.

4. Component Already Exists

Error: A component with the name 'My_Custom_Object__c' already exists.

Cause: You are trying to deploy a component that already exists in the target org, and the deployment type does not support updating it.

Fix: This usually happens with certain component types during package installations. Check if the component needs to be updated rather than created.

5. Field Integrity Exception

Error: FIELD_INTEGRITY_EXCEPTION: Cannot delete field that is referenced by other components

Cause: You are trying to delete a field that is referenced by flows, page layouts, reports, or other components.

Fix: Remove the references first (update flows, page layouts, etc.), then delete the field.

6. Invalid Cross-Reference Key

Error: INVALID_CROSS_REFERENCE_KEY: Record Type ID is not valid for the given object.

Cause: A record type referenced in the deployment does not exist in the target org, or its ID is different.

Fix: Ensure all referenced record types exist in the target org. Use record type developer names (not IDs) in your configurations.


Deployment Best Practices

Regardless of which deployment method you use, follow these best practices:

Before Deployment

  1. Test thoroughly in sandbox. Every change should be tested in at least one sandbox before reaching production.
  2. Document your changes. Maintain a change log that describes what is being deployed and why.
  3. Communicate with stakeholders. Inform affected teams about upcoming changes, timelines, and any expected behavior differences.
  4. Create a rollback plan. Know how you will undo changes if something goes wrong. For change sets, this means having a corrective change set ready. For CLI and packages, this means knowing which version to revert to.
  5. Back up critical data. If your deployment affects custom fields, export the data in those fields before deploying.

During Deployment

  1. Deploy during maintenance windows. Schedule deployments during low-usage hours to minimize user impact.
  2. Validate before deploying. Always run validation first, whether through change set validation, CLI --dry-run, or package version testing.
  3. Monitor the deployment. Watch the deployment status for errors. Do not walk away and assume everything will succeed.
  4. Deploy in stages. For large changes, split them into smaller deployments. Deploy infrastructure changes (objects, fields) first, then automation (flows, triggers), then UI changes (page layouts, Lightning pages).

After Deployment

  1. Verify in production. Immediately after deployment, spot-check key functionality. Open records, test flows, verify page layouts.
  2. Monitor for errors. Check the debug log and error email notifications for the first few hours after deployment.
  3. Notify users. Let users know that the changes are live. Provide documentation or training if the changes affect their workflows.
  4. Update your documentation. Keep your change log, release notes, and org documentation current.

PROJECT: Set Up a Scratch Org and Deploy Using the Salesforce CLI

Let us put everything together with a hands-on project. We will create a scratch org, build a custom object with a flow, and deploy it to a sandbox using the Salesforce CLI.

Project Goal

Create a custom “Project Tracker” object with fields and an auto-number, build a record-triggered flow that sets a default status, and deploy the entire solution using the CLI.

Prerequisites

  • Salesforce CLI installed and verified.
  • A Dev Hub org authorized.
  • Git installed on your machine.

Step 1: Create the Project

sf project generate --name project-tracker-app
cd project-tracker-app
git init

Step 2: Configure the Scratch Org Definition

Edit config/project-scratch-def.json:

{
  "orgName": "Project Tracker Dev Org",
  "edition": "Developer",
  "features": [],
  "settings": {
    "lightningExperienceSettings": {
      "enableS1DesktopEnabled": true
    }
  }
}

Step 3: Create the Scratch Org

sf org create scratch --definition-file config/project-scratch-def.json --alias project-tracker --set-default --duration-days 7

Wait for the org to be created. Then open it:

sf org open --target-org project-tracker

Step 4: Create the Custom Object and Fields

In the scratch org:

  1. Navigate to Setup > Object Manager > Create > Custom Object.
  2. Create a new custom object:
    • Label: Project Tracker
    • Plural Label: Project Trackers
    • Record Name: Project Number
    • Data Type: Auto Number
    • Display Format: PROJ-{0000}
    • Starting Number: 1
  3. Save the object.
  4. Add custom fields:
    • Project Name (Text, 255 characters, required)
    • Status (Picklist: Not Started, In Progress, On Hold, Completed, Cancelled)
    • Priority (Picklist: Low, Medium, High, Critical)
    • Start Date (Date)
    • Target End Date (Date)
    • Assigned To (Lookup to User)
    • Description (Long Text Area, 5000 characters)

Step 5: Create a Record-Triggered Flow

  1. Navigate to Setup > Flows > New Flow.
  2. Select Record-Triggered Flow.
  3. Configure the trigger:
    • Object: Project Tracker
    • Trigger: A record is created
    • Condition Requirements: None (runs on every creation)
    • Optimize for: Actions and Related Records
  4. Add an Update Triggering Record element:
    • Set Status to “Not Started”
    • Set Priority to “Medium”
  5. Save and activate the flow.

Step 6: Retrieve the Metadata to Your Local Project

sf project retrieve start --target-org project-tracker

This pulls all the metadata you created (the custom object, fields, and flow) into your local force-app directory.

Step 7: Inspect the Retrieved Metadata

Check your project structure:

ls force-app/main/default/objects/Project_Tracker__c/
ls force-app/main/default/flows/

You should see:

force-app/main/default/objects/Project_Tracker__c/
  fields/
    Assigned_To__c.field-meta.xml
    Description__c.field-meta.xml
    Priority__c.field-meta.xml
    Project_Name__c.field-meta.xml
    Start_Date__c.field-meta.xml
    Status__c.field-meta.xml
    Target_End_Date__c.field-meta.xml
  Project_Tracker__c.object-meta.xml

force-app/main/default/flows/
  Set_Default_Project_Status.flow-meta.xml

Step 8: Commit to Version Control

git add .
git commit -m "feat: add Project Tracker custom object with default status flow"

Step 9: Deploy to a Target Org

Now deploy to your target sandbox or another org:

# Authorize the target org first
sf org login web --alias my-sandbox

# Deploy
sf project deploy start --target-org my-sandbox --test-level NoTestRun

Since we do not have Apex code in this project, NoTestRun is acceptable for sandbox deployments. For production, you would need at least RunLocalTests if the org has Apex.

Step 10: Verify the Deployment

sf org open --target-org my-sandbox

Navigate to the App Launcher, search for “Project Trackers,” and:

  1. Verify the object exists with all custom fields.
  2. Create a new Project Tracker record.
  3. Confirm the flow triggers and sets Status to “Not Started” and Priority to “Medium.”

Step 11: Clean Up the Scratch Org

sf org delete scratch --target-org project-tracker --no-prompt

What You Accomplished

In this project, you:

  • Created a Salesforce DX project from scratch.
  • Configured and created a scratch org.
  • Built a custom object with multiple fields.
  • Built a record-triggered flow.
  • Retrieved metadata to your local project.
  • Committed changes to Git.
  • Deployed to a target org using the Salesforce CLI.
  • Verified the deployment.
  • Cleaned up the temporary scratch org.

This is the foundation of the source-driven development workflow that Salesforce recommends for professional development teams.


Choosing the Right Deployment Strategy for Your Team

There is no single “best” deployment method. The right choice depends on your team, your org complexity, and your development maturity.

If You Are a Solo Admin

Start with change sets. They require no additional tooling, no command line experience, and no source control setup. As your org grows and you want more control, explore DevOps Center as your next step.

If You Are an Admin Team (2-5 People)

Consider DevOps Center. It gives you source control and work item tracking without requiring everyone to learn the CLI. The GitHub integration provides version history and conflict resolution that change sets cannot offer.

If You Are a Development Team

Use the Salesforce CLI with Git and consider unlocked packages for your core functionality. Set up a CI/CD pipeline (GitHub Actions, GitLab CI, Jenkins, Bitbucket Pipelines) that automates testing and deployment. Use scratch orgs for isolated development.

If You Have a Mixed Admin/Developer Team

Use a combination. Admins can use DevOps Center or change sets for declarative changes. Developers use the CLI and unlocked packages for code. Establish clear processes for how declarative and code changes are coordinated and deployed together.


Summary

Deploying changes to production is where all your hard work becomes reality. We covered four deployment methods in this post, each suited to different team sizes, skill levels, and project complexities:

  1. Change Sets — The simplest method. Point-and-click, no tools required, but limited in tracking, rollback, and automation.
  2. DevOps Center — The bridge between simplicity and sophistication. Source control through a UI, work item tracking, and pipeline visibility.
  3. Unlocked Packages — The modular approach. Versioned, repeatable, and ideal for large orgs with multiple teams.
  4. Salesforce CLI — The power tool. Full control over what, when, and how you deploy, with automation capabilities for CI/CD pipelines.

We also walked through setting up a scratch org from scratch, building a small project, and deploying it using the CLI.

The key takeaway: start with the method that matches your current skills and team size, and grow into more sophisticated methods as your needs evolve. Every Salesforce professional should be comfortable with at least change sets and have a working knowledge of the CLI.


In Part 27, we will explore OmniStudio in Salesforce — Salesforce’s toolkit for building guided, industry-specific digital experiences using OmniScripts, FlexCards, DataRaptors, and Integration Procedures. If you have worked with Salesforce Industries or Vlocity, this one is for you. See you there.