If your team uses one deploy command for every environment, you are probably either too slow or too risky. sf project deploy start and sf project deploy validate solve different problems, and choosing correctly makes CI/CD both safer and faster.
This article expands the deploy strategy in the Salesforce CLI DevOps Playbook.
What is the difference between deploy start and deploy validate?
deploy start performs a real deployment, while deploy validate checks whether deployment would succeed without committing changes. Use validate when you need confidence before promoting to production.
Quick comparison:
sf project deploy start: deploys metadata now.sf project deploy validate: runs validation for later quick deploy.sf project deploy quick: promotes a successful validation by job ID.
When should I use sf project deploy validate?
Use validate in pull request checks, release candidate pipelines, and production preflight stages. It catches metadata and test failures early without changing org state.
Example validation command:
sf project deploy validate \
--source-dir force-app \
--target-org "$SF_USERNAME" \
--test-level RunLocalTests \
--wait 60
If validation passes, keep the job ID for quick deploy.
When should I use sf project deploy start?
Use start when you are intentionally applying changes now, such as to scratch orgs, dev sandboxes, or approved release stages. It is the command for actual state change.
sf project deploy start \
--source-dir force-app \
--target-org "$SF_USERNAME" \
--test-level RunLocalTests \
--wait 60
In production flows, run this only after validation or approval gates.
How does quick deploy fit into this workflow?
Quick deploy promotes a previously validated deployment without rerunning the full validation path. It is ideal for reducing final release windows.
# After a successful validate step
sf project deploy quick \
--job-id "$SF_VALIDATE_JOB_ID" \
--target-org "$SF_USERNAME" \
--wait 60
Use this when your release process separates validation and promotion.
Quick deploy has constraints worth knowing upfront:
- Validations expire after 10 days. After that, you must revalidate.
- Quick deploy only works against production orgs. Sandboxes do not support it.
- Any deployment to the same org between validation and quick deploy invalidates the validation.
- You must run tests during validation (
RunLocalTests,RunAllTestsInOrg, orRunSpecifiedTests).NoTestRunis not compatible with quick deploy.
If your release window is tight, validate early the same day rather than days ahead.
What is the difference between deploy validate and deploy start –dry-run?
Both check whether a deployment would succeed, but only validate returns a job ID you can quick deploy later. deploy start --dry-run is a lighter check that does not feed the quick deploy workflow.
Use validate when you plan to quick deploy to production. Use --dry-run when you only need a pass/fail signal in a sandbox pipeline or PR check, especially when you want to skip tests with --test-level NoTestRun (which validate does not allow).
# Lighter check — no quick deploy, but NoTestRun is allowed
sf project deploy start \
--source-dir force-app \
--target-org "$SF_USERNAME" \
--test-level NoTestRun \
--dry-run \
--wait 60
Which test level should I pair with these commands?
Test level should match environment risk, and the rules differ between sandbox and production.
Sandbox deployments accept any test level, including NoTestRun. There is no code coverage requirement.
Production deployments require RunLocalTests at minimum when you are deploying Apex classes, triggers, or active flows. Every deployed class and trigger must have 75% code coverage individually, not just 75% overall.
Practical defaults:
- Scratch orgs and dev sandboxes:
NoTestRunfor speed. - PR validation targeting production:
RunSpecifiedTestsfor faster signal (must still hit 75% per deployed class). - Integration/UAT and production validation:
RunLocalTests. RunAllTestsInOrgincludes managed package tests — only use this when your org’s managed packages have flaky tests you need to catch.
What pipeline pattern works best for most teams?
Run validate on pull requests, then quick deploy or start after approval. This pattern catches failures early and reduces risky, long-running production deploys.
Simple pattern:
- PR pipeline: authenticate, run
deploy validate, store job ID. - Release pipeline: manual approval, run
deploy quickusing that job ID. - Post-deploy checks: smoke tests and org health checks.
What mistakes cause the most deployment confusion?
Most issues come from command misuse, inconsistent test levels, or missing auth prechecks. Standardize these in-repo so every pipeline behaves the same.
Common mistakes to avoid:
- Running
deploy startin PR checks when validate is enough. - Using different test levels across branches without policy.
- Skipping
sf --versionand auth checks before deploy. - Losing validation job IDs needed for quick deploy.
- Using 15-character job IDs in CI/CD — always use 18-character IDs when validation and quick deploy run in separate pipeline jobs.
How do I troubleshoot a failed validation?
Run validate with --verbose for detailed error output instead of the generic failure message. If you are parsing output in CI, add --json as well.
sf project deploy validate \
--source-dir force-app \
--target-org "$SF_USERNAME" \
--test-level RunLocalTests \
--verbose \
--wait 60
Common errors and what they mean:
FailedValidationError: Failed to validate the deployment— the default message hides the real cause. Rerun with--verboseor check the deploy report withsf project deploy report --job-id <id>.Expected --test-level=NoTestRun to be one of: RunAllTestsInOrg, RunLocalTests, RunSpecifiedTests—validatedoes not acceptNoTestRunbecause quick deploy requires test results. Usedeploy start --dry-runinstead if you do not need quick deploy.RequiresProjectError: This command is required to run within a Salesforce project directory— the command must run from the root of an SFDX project containingsfdx-project.json.- Job ID not found or invalid — use 18-character job IDs in CI/CD. The
--use-most-recentflag can also retrieve the last validation from the past 3 days.
If your pipeline cannot authenticate reliably, fix that first with Headless Auth: Salesforce CLI + GitHub Actions (No Browser Login).