Salesforce metadata deployments don’t have a built-in rollback button. Unlike a database transaction that can be rolled back atomically, a Salesforce deployment applies metadata changes individually - if it succeeds, those changes are live. There’s no native undo. Understanding this is the starting point for building a deployment strategy that doesn’t leave you helpless when something goes wrong.
Why Rollback Is Hard
A Salesforce deployment touches multiple layers simultaneously:
- Metadata - Apex classes, triggers, flows, layouts, field definitions
- Configuration - permission sets, profiles, custom settings
- Data - any data migrations or setup scripts run post-deployment
The first two can be “rolled back” by redeploying a previous version. The third generally cannot. If your deployment included a data migration that transformed 50,000 records, redeploying the old metadata doesn’t transform those records back.
Additionally, some metadata changes are destructive even without a destructive change file:
- Removing a required field that other code references will break deployments and runtime behavior until the references are also removed
- Renaming a custom object or field changes its API name - deployed code referencing the old name breaks immediately
This is why the emphasis must be on not deploying bad changes rather than recovering from bad changes.
Validation-Only Deployments
The most important tool in your pre-deployment arsenal is the validation deployment - a full dry run that runs all tests but makes no changes to the org.
# Validate without deploying - runs all tests, checks component validity
sf project deploy validate \
--source-dir force-app \
--target-org production \
--test-level RunLocalTests \
--wait 60
# The output includes a job ID if validation succeeds
# Save this ID for quick deploy
Validation is not optional for production deployments in any responsible pipeline. It catches:
- Apex compilation errors
- Test failures
- Field/object dependency issues
- Trigger conflicts
Critically, validation runs in the target org’s actual state with the target org’s actual data, so it catches environment-specific issues that sandbox testing misses.
Quick Deploy: Don’t Rerun Tests Unnecessarily
After a successful validation, Salesforce allows a quick deploy within 10 days that skips re-running tests. This is valuable because:
- You validated during off-peak hours (e.g., Friday afternoon)
- You deploy during a maintenance window (e.g., Sunday night)
- The quick deploy takes 2–5 minutes instead of 30–60
# Get the validation job ID from the validate command output
VALIDATION_JOB_ID="0Af..."
# Quick deploy using the validated job
sf project deploy quick \
--job-id $VALIDATION_JOB_ID \
--target-org production \
--wait 30
If more than 10 days pass, or if the org’s test results have changed, you’ll need to re-validate. Build this into your release process: validate on Thursday, deploy on Friday within the window.
The Metadata Rollback Strategy
When a deployment causes issues and you need to revert, the process is:
Step 1: Identify the previous good state in git
# Find the commit before the problematic deployment
git log --oneline -10
# Check out the previous state of the affected components
git show HEAD~1:force-app/main/default/classes/MyClass.cls > MyClass_rollback.cls
Step 2: Create a rollback branch
git checkout -b hotfix/rollback-bad-deploy
git revert HEAD # creates a new commit that undoes the last commit
Step 3: Deploy the rollback
sf project deploy start \
--source-dir force-app \
--target-org production \
--test-level RunLocalTests \
--wait 60
Step 4: Handle net-new components with destructive changes
If the bad deployment added new Apex classes, custom fields, or other components that now need to be removed, you need a destructive changes manifest:
<!-- destructiveChangesPre.xml - removes components before the rest of the deployment -->
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>BadNewClass</members>
<name>ApexClass</name>
</types>
<types>
<members>Account.Bad_New_Field__c</members>
<name>CustomField</name>
</types>
<version>59.0</version>
</Package>
# Deploy destructive changes to remove the new components
sf project deploy start \
--manifest destructiveChangesPre.xml \
--target-org production \
--test-level NoTestRun \
--wait 30
Note: you cannot delete a custom field that has data in it. The field must be emptied or the records deleted before the field can be removed.
Data Rollback Approaches
For deployments that include data migrations, build rollback capability into the migration itself:
Approach 1: Audit table backup
Before migrating, copy the original values to a backup object:
// Pre-migration: snapshot current values
List<Account_Migration_Backup__c> backups = new List<Account_Migration_Backup__c>();
for (Account acc : [SELECT Id, Old_Field__c FROM Account WHERE Old_Field__c != null]) {
backups.add(new Account_Migration_Backup__c(
Account__c = acc.Id,
Old_Field_Value__c = acc.Old_Field__c,
Migration_Date__c = Datetime.now()
));
}
insert backups;
Approach 2: Data Loader backup before deployment
Export the affected records with Data Loader or sf data export before any deployment that modifies data:
# Export before deployment
sf data export bulk \
--query "SELECT Id, Name, Status__c, Amount FROM Opportunity WHERE CloseDate = THIS_YEAR" \
--target-org production \
--output-file pre-deploy-backup.csv \
--wait 10
Store the CSV in a timestamped location. If rollback is needed, re-import with Data Loader using the backup file.
The Pre-Deployment Checklist
Run through this before every production deployment:
- Validation deployment completed successfully in the last 10 days
- All Apex tests passing at RunLocalTests level (or RunAllTestsInOrg for regulated orgs)
- Change set reviewed by a second developer or architect
- Data backup taken for any objects affected by data migrations
- Rollback plan documented: which components, which git commit, any destructive changes needed
- Maintenance window communicated to stakeholders
- Monitoring plan in place (who watches for errors post-deployment)
- Rollback trigger criteria defined (what error rate or user impact prompts rollback)
When to Forward-Fix Instead of Roll Back
Rollback is not always the right call. Consider forward-fixing when:
- The deployment included data migrations that can’t be safely reversed
- Redeployment would take longer than fixing the specific issue
- The production issue is minor and affects few users
- Rolling back would re-introduce a different bug that was also fixed in the deployment
A forward fix is a targeted patch deployed as quickly as possible. The bar for testing is lower (you’re in crisis mode) but the scope is smaller (only the specific broken component). After the incident, do a proper post-mortem and add test coverage for the failure case.
The best rollback is the one you never need. Invest in validation deployments, sandbox parity, and pre-deployment checklists. The teams that never get caught by production issues aren’t the ones with the fastest rollback processes - they’re the ones who validate most thoroughly before deploying.