In Salesforce architecture, every sObject belongs to one of two foundational categories: Setup Objects or Non-Setup Objects. Distinguishing between them is essential not only for security governance and org configuration, but also for avoiding transactional conflicts in Apex development.
1. What Are Setup Objects?
Setup objects (often called metadata configuration objects) govern system security, administrative permissions, user provisioning, and org-level behavior. They define the rules of engagement across your platform.
User&UserRole: Individual user accounts, role hierarchy definitions, and profile assignments.PermissionSet&PermissionSetAssignment: Fine-grained object, field, and system capability grants assigned directly to users.Group&GroupMember: Public groups and queues used for automated record assignment and sharing rules.QueueSobject: Defines which sObjects (e.g., Lead, Case, Custom Objects) are supported by specific Queues.- Territory Management Objects:
UserTerritory2Associationand territory assignment models.
2. What Are Non-Setup Objects?
Non-setup objects represent real-world customer interactions and core business transactional data. Every standard customer-facing object and user-defined custom object falls under this category.
- Standard CRM Entities:
Account,Contact,Lead,Opportunity,Case,Order, andContract. - Custom Business Objects: Any custom table ending in
__c(e.g.,Invoice__c,Project__c,Expense__c). - Task & Event Activities: Standard activity records linked directly to customer objects.
- Setup Purpose: Configures permissions, visibility, users, and security boundaries.
- Non-Setup Purpose: Stores transactional operational data and customer relationship records.
- Database Restriction: Synchronous DML cannot modify both categories within the same transaction context.
- Transaction Error: Violating this boundary triggers
System.MixedDmlException: DML operation on setup object not permitted after you have updated a non-setup object.
3. The Mixed DML Conflict Explained
Salesforce blocks developers from modifying a Setup object and a Non-Setup object in the same transaction to prevent permission and sharing recalculation corruption. Because altering a user's role, public group, or permission set changes record visibility across the entire platform, Salesforce must commit those security alterations in an isolated transaction before allowing record inserts.
Executing an insert or update on an
Account followed immediately by an insert on a UserRole or PermissionSetAssignment within the same synchronous execution path causes an immediate, fatal transaction rollback.
4. How to Resolve Mixed DML Exceptions in Apex
To perform operations on both object types, you must split the transaction boundary using either asynchronous processing in production code or System.runAs() in test classes.
Inserting an Account synchronously and delegating the Setup Object assignment to an asynchronous thread:
public with sharing class UserOnboardingService {
public static void onboardNewHire(String accountName, Id targetUserId, Id permissionSetId) {
// 1. DML on Non-Setup Object (Account)
Account acc = new Account(Name = accountName);
insert as user acc;
// 2. Offload Setup Object DML to an asynchronous thread
assignPermissionSetAsync(targetUserId, permissionSetId);
}
@future
public static void assignPermissionSetAsync(Id userId, Id permSetId) {
PermissionSetAssignment psa = new PermissionSetAssignment(
AssigneeId = userId,
PermissionSetId = permSetId
);
insert psa;
}
}
In test classes, separating setup and non-setup DML using isolated user context blocks:
@isTest
private class UserOnboardingTest {
@isTest
static void testMixedDmlResolution() {
User adminUser = [SELECT Id FROM User WHERE Profile.Name = 'System Administrator' AND IsActive = true LIMIT 1];
System.runAs(adminUser) {
// 1. Setup Object DML inside runAs block
UserRole r = new UserRole(DeveloperName = 'CustomSalesRole', Name = 'Sales Rep');
insert r;
}
// 2. Non-Setup Object DML in main test execution
Test.startTest();
Account testAcc = new Account(Name = 'Acme Test Corp');
insert testAcc;
Test.stopTest();
Assert.isNotNull(testAcc.Id, 'Account should be inserted successfully without Mixed DML errors.');
}
}
5. Core Best Practices & Rules
@future or Queueable Apex in production, and System.runAs() in test methods.
- Use Queueable Apex for Complex Chaining: When passing complex sObject parameters rather than primitive IDs, use
Queueable Apexinstead of@futureto handle setup modifications. - Design Trigger Handlers Defensively: Ensure user creation triggers do not invoke synchronous helper methods that create default business records (
Account,Task) without asynchronous delegation. - Enforce User Mode Security: Always execute queries and DML with
WITH USER_MODEandas userto maintain compliance with Object-Level and Field-Level permissions.
Summary
Understanding the clear boundary between Setup and Non-Setup objects is essential for building scalable, error-free Salesforce architectures. By keeping administrative security records separate from business data transactions and decoupling them with asynchronous Apex patterns, you eliminate MixedDmlException errors and ensure seamless operations across your org.