If you have ever encountered the dreaded System.DmlException: DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa)
error in Salesforce, you’ve bumped into the Mixed DML Operation restriction. This error is common for developers and admins automating business processes with Apex, Flows, or Triggers. This blog explains what mixed DML is, why Salesforce enforces it, the error’s implications, and how to fix or work around it.
Salesforce has two types of objects:
- Setup Objects: Configuration/meta-data objects (e.g., User, UserRole, Group, PermissionSetAssignment, etc.)
- Non-Setup Objects (Business Objects): Standard or custom objects for business data (e.g., Account, Opportunity, Custom__c, etc.).
Mixed DML occurs when you try to perform DML operations on both setup and non-setup objects within the same transaction context.
Setup Objects | Non-Setup Objects |
---|---|
User | Account |
UserRole | Opportunity |
PermissionSetAssignment | Contact |
Group | Custom__c |
Salesforce enforces this rule to protect the integrity of your configuration and business data. Setup objects affect your org's security and access configuration, so mixing their changes with business data in a single transaction could create security risks or data inconsistencies.
When you try to do mixed DML, you'll see an error like:
System.DmlException: DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): User, original object: Account
- Apex Triggers: Updating a User record after inserting an Account in the same transaction.
- Batch Apex: Processing Account records and updating related User records.
- Flows/Process Builder: Flows that update both User and Account in the same transaction.
Salesforce allows you to separate DML contexts in test methods using System.runAs()
:
@isTest
public static void testMixedDML() {
User u = new User(...); // Setup object
insert u;
System.runAs(u) {
Account a = new Account(Name='Test Account');
insert a; // now safe in test context
}
}
Note: This only works in test methods.
Offload the DML operation to an asynchronous context:
public static void updateUserAsync(Id userId) {
update [SELECT Id, IsActive FROM User WHERE Id = :userId];
}
@future
public static void updateUserFuture(Id userId) {
updateUserAsync(userId);
}
How to Use:
- Perform non-setup DML synchronously.
- Call the future/queueable method to perform setup DML.
Example:
// Synchronous
insert new Account(Name='Test Acc');
// Asynchronous
updateUserFuture(userId);
Sometimes, simply changing the order of DML operations fixes the issue. Perform all setup object DMLs first, then non-setup object DMLs (or vice versa), but not both in the same context.
- Use screen flows or orchestrations to split the operations.
- Use external integrations (like middleware) to separate the two operations.
- Never mix setup and non-setup DML in the same trigger or method.
- Document your automation flows to track possible mixed DML risks.
- Refactor triggers to separate DML on User/Permission objects from business object DML.
- Use asynchronous processing for setup DML whenever possible.
Solution | Synchronous | Asynchronous | Test Context |
---|---|---|---|
Rearrangement | ✓ | ✓ | |
@future/Queueable | ✓ | ✓ | |
System.runAs | ✓ |
- Salesforce Docs: Mixed DML Operations
- Trailhead: Mixed DML Operations
- Salesforce Developer Blog: Mixed DML
Mixed DML errors can be frustrating, but they’re there for a good reason. By understanding the difference between setup and non-setup objects and using the right workarounds (like asynchronous Apex or rearranging your logic), you can design robust automations that respect Salesforce’s security model.
Have you struggled with Mixed DML? Share your experiences and solutions in the comments!
Post a Comment