Part of the Salesforce Admin Git & sf CLI series. Articles are standalone; read in any order. Foundation: Why Version Control · Environment Setup · Connect Your Org
TL;DR
- Deploy to a sandbox:
sf project deploy start --source-dir force-app --target-org mysandbox - Always deploy to a sandbox first: never deploy untested metadata directly to production
- Validate without making changes: add
--dry-runto any deploy command. It runs all validations but changes nothing in the org - Check deploy status at any time:
sf project deploy report --target-org mysandbox - When a deploy fails, read the error messages. sf CLI tells you exactly which component failed and why
What You'll Learn
- How to deploy metadata from your local project to a Salesforce sandbox or production org
- What each deploy flag does and when to use it
- How to run a check-only deploy to validate before committing
- How to read deploy results, both success and failure output
- How to handle the most common deploy errors
- How to deploy to production safely
The Problem
You've retrieved metadata from your org, tracked it in git, and made changes, either by editing the XML directly or by making changes in Setup and re-retrieving. Now you need to push those changes somewhere.
For a dev sandbox this might be routine: deploy, test, move on. For production it needs to be careful: validate, test, then deploy with confidence.
sf CLI handles both, with the same command and a few different flags. This article shows you exactly how.
Common Questions This Article Answers:
- How do I deploy my local Salesforce project to a sandbox?
- What is a check-only deploy and how do I run one?
- How do I deploy only specific metadata types, not the whole project?
- What does a failed deploy output look like and what do I do with it?
- How do I deploy to production?
- Can I cancel a deploy that's in progress?
Quick Answer
Here are the most common deploy commands. The sections below explain each one in detail.
# Deploy everything in force-app to a sandbox
sf project deploy start --source-dir force-app --target-org mysandbox
# Deploy a specific metadata type only
sf project deploy start --metadata "Flow:My_Flow" --target-org mysandbox
# Check-only deploy — validate without making any changes
sf project deploy start --source-dir force-app --target-org mysandbox --dry-run
# Deploy via a package.xml manifest
sf project deploy start --manifest package.xml --target-org mysandbox
# Check deploy status
sf project deploy report --target-org mysandbox
# Deploy to production
sf project deploy start --source-dir force-app --target-org prod
The Golden Rule: Always Deploy to Sandbox First
Before you deploy anything to production, deploy it to a sandbox and verify it works.
This is not optional advice. Production Salesforce orgs are live systems with real users. A failed deploy to production can lock components, surface errors to users, and in some cases require Salesforce Support to clean up.
A full safe deployment sequence looks like this:
- Retrieve metadata from production into your project (your baseline)
- Make changes in a dev or full-copy sandbox
- Retrieve the updated metadata into your project
- Deploy to a staging or partial-copy sandbox and verify everything works there
- Run a check-only deploy against production (
--dry-run) to confirm it will validate - Deploy to production once all validations pass
If you skip step 4 and go straight from dev sandbox to production, you're flying blind. A five-minute check in a staging sandbox can prevent an hour of production cleanup.
Basic Deploy Commands
Deploy your entire project
sf project deploy start --source-dir force-app --target-org mysandbox
What each flag does:
--source-dir force-app: tells sf CLI which local directory contains the metadata to deploy. For most Salesforce projects this isforce-app. You can point it at a subdirectory too, e.g.force-app/main/default/flows--target-org mysandbox: the alias or username of the org you want to deploy to. This is the alias you set when you ransf org login web --alias mysandbox
sf CLI scans the force-app directory, packages up all the metadata it finds, and deploys it to the target org in one operation. It waits for the deploy to complete and shows you the result.
Deploy a single metadata component
sf project deploy start --metadata "Flow:My_Flow" --target-org mysandbox
The --metadata flag takes a Type:Name format. This deploys only My_Flow rather than your entire project. Useful when you've changed one thing and don't want to accidentally push other in-progress work.
Deploy multiple specific components
sf project deploy start --metadata Flow CustomObject --target-org mysandbox
Space-separate multiple values after --metadata to deploy more than one type. This deploys all Flows and all Custom Objects in your project. You can mix types and named components: --metadata "Flow:My_Flow" "CustomObject:Account".
Deploy via package.xml manifest
sf project deploy start --manifest package.xml --target-org mysandbox
A package.xml file is a manifest: a list of exactly which components to deploy. It gives you precise, repeatable control over what gets included. This is useful for production deployments where you want to be explicit about scope rather than deploying everything in force-app.
A minimal package.xml for deploying a single Flow looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>My_Flow</members>
<name>Flow</name>
</types>
<version>60.0</version>
</Package>
Check-Only Deploys: Validate Before You Commit
A check-only deploy runs the full validation process (checking that all components are syntactically valid, all dependencies exist, and for production all required tests pass) but makes no changes to the org.
# Check-only deploy against a sandbox
sf project deploy start --source-dir force-app --target-org mysandbox --dry-run
# Check-only deploy against production
sf project deploy start --source-dir force-app --target-org prod --dry-run
The --dry-run flag is the only difference. Everything else is identical to a real deploy: sf CLI sends the same payload, runs the same validations, and returns the same results. It just doesn't commit the changes.
When to use --dry-run:
- Before any production deployment: validate that it will succeed before you pull the trigger
- When you want to confirm your metadata is valid without touching the org
- In CI/CD pipelines where you want a passing validation before merging code
What --dry-run does not do:
- It does not test anything that requires running in the org post-deploy (e.g. manual user verification)
- It does not guarantee a real deploy will succeed if org state changes between validation and deploy
- For production: it does not run Apex tests by default unless you add
--test-levelflags
To run Apex tests during a check-only deploy:
sf project deploy start --source-dir force-app --target-org prod --dry-run --test-level RunLocalTests
The --test-level flag options are:
NoTestRun: skip tests entirely (sandbox only; not allowed for production)RunLocalTests: run all tests in your org (not managed package tests)RunAllTestsInOrg: run every test in the org including managed package testsRunSpecifiedTests: run only the tests you name with--tests
Reading Deploy Results
What a successful deploy looks like
Deploying v60.0 metadata to [email protected] using the v60.0 SOAP API.
Deploy ID: 0Af5g00000AJkXnCAL
Status: Succeeded ████████████████████ 100%
Component errors: 0
Components deployed: 4
Components total: 4
Tests errors: 0
Tests completed: 0
Tests total: 0
The key numbers are:
- Component errors: 0 means everything deployed without errors
- Components deployed matches Components total means nothing was skipped
What a failed deploy looks like
Deploying v60.0 metadata to [email protected] using the v60.0 SOAP API.
Deploy ID: 0Af5g00000AJkXnCAL
Status: Failed ██████████ 52%
Component errors: 1
Components deployed: 3
Components total: 4
Component Failures [1]
──────────────────────────────────────────────────────
Type: Flow
File: force-app/main/default/flows/My_Flow.flow-meta.xml
Error: Required field is missing: label [My_Flow]
Read the "Component Failures" section carefully. sf CLI tells you:
- Which file failed (
force-app/main/default/flows/My_Flow.flow-meta.xml) - What the error is (
Required field is missing: label) - The type of component that failed (
Flow)
This is enough information to fix the problem directly. In this case, the Flow XML is missing a <label> element.
Check deploy status after the fact
If you started a deploy and the terminal closed, or you're checking on a long-running deploy:
sf project deploy report --target-org mysandbox
This queries the status of the most recent deploy to that org. It shows the same results as the live output.
Common Deploy Errors and Fixes
Missing dependency
Error: This component requires that [Account.My_Custom_Field__c] be present
The metadata you're deploying depends on something that doesn't exist in the target org. Usually this means a custom field, object, or component that exists in your source org but hasn't been deployed to the target org yet.
Fix: Deploy the dependency first. Retrieve the dependent component from your source org, add it to your deploy, and redeploy. If the dependency is a managed package component, install that package in the target org first.
Apex test failure
Error: System.AssertException: Assertion Failed
Class.MyTestClass.testMethod: line 42, column 1
A test class in the target org is failing. This can happen when your metadata change is valid but it breaks an existing test that wasn't updated to match.
Fix: Run the failing test class in the target org's Developer Console to see the full stack trace. Update the test to match the new behaviour, retrieve the updated test class, and redeploy everything together.
Insufficient access or permission error
Error: You do not have permission to deploy [CustomPermission:My_Permission]
Your authenticated user doesn't have the rights to deploy this component type to this org.
Fix: Check that your user in the target org has the "Deploy Change Sets" permission (for sandboxes) or the "Modify All Data" and "Customize Application" permissions (for production). System Administrators have these by default.
Component is locked
Error: This record is locked. You can't deploy to it while a deployment is in progress.
Another deploy is already running against this org, or a previous deploy didn't fully clean up.
Fix: Wait for the other deploy to complete, or cancel it:
sf project deploy cancel --target-org mysandbox
Then retry your deploy.
Rollback: Undoing a Bad Deploy
Salesforce does not have a one-click rollback button for metadata deployments. Once metadata is deployed, it's in the org.
Your options if a deploy causes problems:
Option 1: Deploy the previous version from git
This is why you commit your metadata before changing it. Check out the previous version of the file from git and redeploy it:
# See your commit history
git log --oneline force-app/main/default/flows/My_Flow.flow-meta.xml
# Restore the file to what it looked like in a specific commit
git checkout <commit-hash> -- force-app/main/default/flows/My_Flow.flow-meta.xml
# Deploy the restored version
sf project deploy start --metadata "Flow:My_Flow" --target-org mysandbox
Option 2: Fix the issue and deploy the corrected version
If the previously deployed version had a bug, restore from git (or edit the XML directly), fix the issue, and deploy again.
Option 3: Deactivate in Setup
For Flows and some other components, you can deactivate the component in Setup immediately while you prepare a fix. This limits impact to users without requiring a technical rollback.
The lesson: commit your baselines before making changes. If you haven't committed the "before" state, you can still retrieve it from another org. Your production org usually has the last stable version.
Deploying to Production
Production deployments require Apex tests to pass (unless you're deploying only metadata types that don't trigger the test requirement). sf CLI enforces this automatically.
# Deploy to production — triggers test requirement
sf project deploy start --source-dir force-app --target-org prod
For most production deploys, also specify your test level explicitly:
sf project deploy start --source-dir force-app --target-org prod --test-level RunLocalTests
The production deploy checklist:
- All changes are committed in git
- The same set of changes deployed cleanly to a staging sandbox
--dry-runagainst production passes with no errors- You know which Apex tests cover your changes (if any)
- You have a rollback plan (previous version committed in git, or source org available)
- You're deploying during a low-traffic window
Production requires tests: Salesforce requires at least 75% Apex code coverage for production deployments that include Apex classes or triggers. If your deploy includes only declarative metadata (Flows, Objects, Page Layouts), this requirement doesn't apply.
Troubleshooting
"No results found for the deploy"
You ran sf project deploy report but there's no recent deploy to report on. Make sure you're using the same --target-org alias as the deploy you want to check.
Deploy hangs and never completes
Add --wait 0 to run the deploy asynchronously, then use sf project deploy report to check status:
sf project deploy start --source-dir force-app --target-org mysandbox --wait 0
# Note the Deploy ID in the output, then:
sf project deploy report --target-org mysandbox
"The source you provided does not contain any supported metadata"
sf CLI scanned your --source-dir and found nothing it recognises as Salesforce metadata. Common causes:
- You're pointing at the wrong directory. Check that
force-app/main/default/exists and contains files - Your
sfdx-project.jsondoesn't list the source path. Open it and confirmpackageDirectoriesincludesforce-app
Different results in sandbox vs production
Sandbox and production orgs can have different installed packages, custom settings, and permission configurations. A deploy that succeeds in sandbox can fail in production if a dependency is missing. Always run --dry-run against production before the real deploy.
Frequently Asked Questions
Q: What's the difference between --dry-run and sf project deploy validate?
A: Both run a check-only validation without committing changes. The key difference is what you can do after: sf project deploy validate returns a job ID that you can use with sf project deploy quick to do a quick deploy to production (skipping full re-validation). --dry-run on sf project deploy start is a lighter check. It does not feed the quick deploy workflow. For most sandbox workflows, --dry-run is simpler. For production quick-deploy scenarios, use sf project deploy validate.
Q: How do I deploy only the files I've changed in git?
A: Use --metadata with specific component names, or create a targeted package.xml listing only the components you changed. There is no built-in git-diff-to-deploy flag, but you can look at git diff --name-only HEAD~1 to see which files changed, then construct your --metadata list from that.
Q: Can I deploy from one org directly to another without local files?
A: Not with sf project deploy start. It deploys from local files, not directly between orgs. The retrieve-then-deploy workflow (retrieve from source org, commit, deploy to target org) is intentional: it gives you a local copy to version-control and review before pushing.
Q: What happens if a deploy partially succeeds? Some components deploy, others fail.
A: By default, Salesforce deployments are transactional: if any component fails, the entire deploy is rolled back and nothing is committed to the org. You'll see "Status: Failed" and a list of which components caused errors. Fix all the errors and redeploy the full set.
Q: How do I speed up deploys to a developer sandbox?
A: Add --test-level NoTestRun to skip Apex test execution entirely. This is safe for developer sandboxes where you're iterating quickly. Do not use NoTestRun for production or full-copy sandboxes where test coverage matters.
sf project deploy start --source-dir force-app --target-org mydevsandbox --test-level NoTestRun
Key Takeaways
sf project deploy start --source-dir force-app --target-org <alias>is the core deploy command- Always deploy to a sandbox and verify before touching production
--dry-runvalidates the full deploy without making changes. Use it before every production deployment- Read deploy failure output carefully: sf CLI tells you the exact file and error for every failed component
- Deploy rollback means re-deploying the previous version from git. Commit your baselines before making changes
- Production deployments require 75% Apex test coverage if your deploy includes Apex classes or triggers
sf project deploy cancel --target-org <alias>stops a deploy that's stuck or unwanted
What's Next?
- Article 12: Org-Dependent Packages: metadata that has runtime dependencies on managed packages, and how to handle those dependencies in your project and deploy process
Resources & References
- sf CLI Command Reference: project deploy start: full flag list for deploy commands
- Salesforce Metadata API Developer Guide: detailed documentation on every deployable metadata type
- Salesforce DX Developer Guide: Deploying: Salesforce's own deployment workflow documentation
- Git Basics for Salesforce Admins: how to track and compare your metadata changes before deploying
- Connect Your Org: how to authenticate sf CLI to your orgs if you haven't done that yet