INACTIVE_OWNER_OR_USER: operation performed with inactive User
You set a record's Owner (or another User reference) to a deactivated user. Salesforce blocks new ownership pointing at inactive users by default. Either reassign to an active user, reactivate the user, or enable the org setting that allows assignment to inactive users.
Also seen asINACTIVE_OWNER_OR_USER·operation performed with inactive User·INACTIVE_OWNER_OR_USER: operation
A sales-rep transition leaves a queue of stale records. Your data-cleanup script tries to reassign cases from a departed rep to her replacement and dies with INACTIVE_OWNER_OR_USER: operation performed with inactive User. The replacement is active; the script doesn't reference any inactive users explicitly. The error comes from a chained workflow that tries to add the departed rep as a co-owner of a custom log object.
What the platform is enforcing
Salesforce blocks DML that sets a User-type lookup (OwnerId, CreatedById on system-managed paths, custom User lookups) to an inactive user. The rule applies to:
- Setting a record's
OwnerIddirectly. - Inserting a
GroupMemberwith an inactiveUserOrGroupId. - Creating a
TaskorEventwhoseOwnerIdis inactive. - Custom User lookup fields on standard or custom objects.
- Some other system fields that reference Users (Assigned_To__c on certain custom objects, etc.).
The check fires at save time. Apex DML, the UI, the API, and integrations all hit the same wall.
The reason: inactive users can't receive notifications, can't be assigned work, and can't authenticate. Assigning records to them silently breaks downstream workflows. The platform refuses the assignment up front.
The broken example
A data-cleanup script that reassigns cases by previous owner email:
public class CaseReassignment {
public static void reassign(String oldEmail, String newEmail) {
User oldUser = [SELECT Id FROM User WHERE Email = :oldEmail LIMIT 1];
User newUser = [SELECT Id FROM User WHERE Email = :newEmail LIMIT 1];
List<Case> toUpdate = new List<Case>();
for (Case c : [SELECT Id FROM Case WHERE OwnerId = :oldUser.Id]) {
c.OwnerId = newUser.Id;
toUpdate.add(c);
}
update toUpdate;
}
}
If newUser is inactive, the update fails with INACTIVE_OWNER_OR_USER. The script doesn't check user status before assignment.
The fix: verify user activity before assignment
public class CaseReassignment {
public class InactiveUserException extends Exception {}
public static void reassign(String oldEmail, String newEmail) {
User newUser = [SELECT Id, IsActive FROM User WHERE Email = :newEmail LIMIT 1];
if (!newUser.IsActive) {
throw new InactiveUserException(
'Target user ' + newEmail + ' is inactive. Choose an active user.'
);
}
User oldUser = [SELECT Id FROM User WHERE Email = :oldEmail LIMIT 1];
List<Case> toUpdate = new List<Case>();
for (Case c : [SELECT Id FROM Case WHERE OwnerId = :oldUser.Id]) {
c.OwnerId = newUser.Id;
toUpdate.add(c);
}
if (!toUpdate.isEmpty()) Database.update(toUpdate, false);
}
}
The pre-check catches inactive-user errors before DML, with a clear diagnostic message. Bulk operations don't fail mid-way.
The fixed example, end to end
A complete reassignment service with active-user verification:
public class CaseReassignmentService {
public class Result {
@AuraEnabled public Id caseId;
@AuraEnabled public Boolean success;
@AuraEnabled public String error;
}
public static List<Result> reassign(String fromEmail, String toEmail) {
Map<String, User> usersByEmail = new Map<String, User>();
for (User u : [SELECT Id, IsActive, Email FROM User WHERE Email IN :new List<String>{fromEmail, toEmail}]) {
usersByEmail.put(u.Email, u);
}
if (!usersByEmail.containsKey(toEmail) || !usersByEmail.get(toEmail).IsActive) {
throw new IllegalArgumentException('Target user ' + toEmail + ' is missing or inactive');
}
if (!usersByEmail.containsKey(fromEmail)) {
throw new IllegalArgumentException('Source user ' + fromEmail + ' not found');
}
Id fromUserId = usersByEmail.get(fromEmail).Id;
Id toUserId = usersByEmail.get(toEmail).Id;
List<Case> cases = [SELECT Id FROM Case WHERE OwnerId = :fromUserId];
List<Result> results = new List<Result>();
for (Case c : cases) {
c.OwnerId = toUserId;
Result r = new Result();
r.caseId = c.Id;
results.add(r);
}
Database.SaveResult[] saveResults = Database.update(cases, false);
for (Integer i = 0; i < saveResults.size(); i++) {
if (saveResults[i].isSuccess()) {
results[i].success = true;
} else {
results[i].success = false;
results[i].error = saveResults[i].getErrors()[0].getMessage();
}
}
return results;
}
}
The service verifies both source and target users at the start. Any subsequent DML failure (from validation rules, locked records, etc.) is reported per-record without poisoning the batch.
When the inactive user appears in unexpected places
The error sometimes fires for users you didn't directly reference. Common causes:
Workflow rules that set OwnerId. A field update on a workflow rule might assign records to a specific user. If that user becomes inactive, the field update fails on every save that triggers the workflow.
Assignment rules that route to inactive users. Lead and Case assignment rules can route to specific users. Routing to an inactive user fails the assignment, and the lead/case retains its initial owner (often the running user, sometimes a default queue).
Cascade lookups. A custom field on Account references a User; the Account is updated; a trigger on Account does a related operation that references the User. If the User is inactive, the related operation fails.
Each of these requires its own diagnostic. The pattern: when INACTIVE_OWNER_OR_USER fires unexpectedly, audit every User lookup the saved object touches, plus every workflow/process/automation that runs on the save.
The "Update Records with Inactive Owners" permission
A profile-level permission lets users override the active-user check:
- Setup → User Profile → "Update Records with Inactive Owners" → enabled.
Users with this permission can save records owned by inactive users. The permission is meant for migration and cleanup scenarios; granting it broadly defeats the purpose of the check.
If your integration user needs this permission, grant it via a permission set rather than a profile change. The permission set can be assigned only during the migration window and revoked afterward.
A subtle case: the owner is a Queue, not a User
Salesforce supports two kinds of ownership: a User or a Queue. Queue ownership routes the record to a shared pool. OwnerId can hold either type of Id, and the platform distinguishes them.
The INACTIVE_OWNER_OR_USER error is specifically about Users. If you're getting it on a record assigned to a Queue, double-check:
- Is the field actually a Queue id?
OwnerIdstarting with00Gis a Group/Queue; starting with005is a User. - Is the queue assignment routing to an inactive user? The queue itself is active, but its members include an inactive user.
For the second case, audit the queue's member list. Inactive members should be removed.
When the user becomes active mid-transaction
A rare race: a record's owner is being activated by another transaction at the exact moment your code reads them as inactive. The platform's view of activity is up-to-date per transaction; concurrent activation by another transaction commits at its own pace.
This race is too narrow to engineer around for most workflows. If you genuinely need to handle it, add a brief retry: catch INACTIVE_OWNER_OR_USER, re-query the user's status, and retry the assignment once.
Reactivation patterns
When a user leaves the company, the canonical pattern is:
- Reassign their records. Use a transition script to move ownership of every record owned by the departing user to their replacement.
- Update automation references. Audit workflows, processes, flows, and assignment rules for the departing user; update each to reference the replacement.
- Deactivate the user. Setup → User → Edit → uncheck Active.
Skipping step 1 or step 2 leaves orphan records and broken automations. The INACTIVE_OWNER_OR_USER error often surfaces during this transition window if step 1 or 2 wasn't completed.
For organizations with frequent rep changes, build a "Departure Workflow" Flow that prompts the admin through each step. The Flow handles the reassignment, the audit, and the deactivation in sequence; no step gets skipped.
Re-activation undoes the lock
If the inactive user is re-activated (the User record's Active checkbox is re-enabled), records can again be assigned to them. The previously-failing DML succeeds on retry.
This is rare in practice (most inactive users are inactive permanently), but useful to know: the lock is not historical, only current. The platform always checks the User's current status at save time.
Test patterns
A test that confirms the service handles inactive users gracefully:
@isTest
static void reassign_throwsForInactiveTarget() {
User inactive = TestUserFactory.create();
inactive.IsActive = false;
update inactive;
Boolean threw = false;
try {
CaseReassignmentService.reassign('active@example.com', inactive.Email);
} catch (IllegalArgumentException e) {
threw = true;
System.assert(e.getMessage().contains('inactive'));
}
System.assert(threw, 'Should reject inactive target user');
}
The test seeds an inactive user and verifies the service refuses to reassign to them.
Custom User lookups have the same rule
A custom field of type "Lookup (User)" follows the same active-user check. Writing an inactive user's id to that field fails the same way:
account.Custom_Sales_Rep__c = inactiveUser.Id;
update account; // Throws INACTIVE_OWNER_OR_USER
The fix is the same: verify activity before assignment. For custom User fields, also consider whether the field should allow inactive users at all. If the field is for "current rep," reject inactive users. If it's for "historical reference" (the rep who originally owned this), allow inactive users by enabling the permission set described above.
Bulk pattern: check all targets at once
For a bulk reassignment that maps many old users to many new users:
Set<Id> targetUserIds = new Set<Id>();
for (Mapping m : mappings) targetUserIds.add(m.toUserId);
Set<Id> activeUserIds = new Set<Id>();
for (User u : [SELECT Id FROM User WHERE Id IN :targetUserIds AND IsActive = TRUE]) {
activeUserIds.add(u.Id);
}
for (Mapping m : mappings) {
if (!activeUserIds.contains(m.toUserId)) {
// Reject this mapping
}
}
One SOQL fetches the active subset; the loop filters. The pattern handles bulk reassignments efficiently.
A scenario that bites integrations
Integrations often run as a dedicated "Integration User." If that user is accidentally deactivated, every record that the integration tries to assign fails with INACTIVE_OWNER_OR_USER. The errors look like data issues but the root cause is the integration user's own status.
The diagnostic: the failing records' OwnerId is the integration user. The integration user is inactive. Reactivate the user, retry the integration.
The prevention: audit the integration user's status weekly. The Setup → Users page filters by Active=false; if the integration user appears there, reactivate immediately and investigate why they were deactivated.
Frozen vs inactive
Salesforce also supports "Freezing" a user, which is different from deactivating:
- Frozen: the user can't log in but their data ownership remains. Records assigned to a frozen user are unaffected.
- Inactive: the user can't log in, and the platform refuses new record assignment.
The freeze state is a temporary measure (e.g., during an investigation or extended leave). The inactive state is permanent (departure, role change, license reduction).
INACTIVE_OWNER_OR_USER fires only for inactive users, not frozen ones. If you're getting unexpected save failures and the user appears "frozen but not inactive," look elsewhere for the cause.
Org-wide email-suppress patterns
When a user is deactivated, their email address may still receive system-generated mail (workflow notifications, approval requests, etc.) if other users or queues reference them. Cleaning up these references during the offboarding process prevents email bounces and the "Reply All" awkwardness of a departed colleague's mailbox.
A dedicated offboarding playbook (a flow or checklist) ensures each step is consistent across departures.
A subtle case: Process Builder with hard-coded user references
A Process Builder (or its Flow equivalent) that hard-codes a user id in an action will start failing the moment that user is deactivated. The error is INACTIVE_OWNER_OR_USER from the process's perspective, surfacing as a save failure to the user who triggered the process.
The fix: replace hard-coded user references with role-based or queue-based references. Roles and queues survive user deactivation.
Reporting on stale references
A weekly admin report that surfaces orphan references catches issues early:
SELECT Id, Name, OwnerId
FROM Account
WHERE Owner.IsActive = FALSE
LIMIT 200
Run on every object that has User lookups. The output is the work-in-progress list for the offboarding cleanup. Records owned by inactive users are findable and fixable; ignored, they accumulate quietly.
Further reading from Salesforce
Related dictionary terms
Share this fix
Related Validation errors
DUPLICATE_VALUE: duplicate value found
ValidationYou tried to insert or update a record with a value on a unique field (External ID set to "Unique" or one of the unique standard fields like…
ENTITY_IS_DELETED: entity is deleted
ValidationYou tried to update or query a record that's in the recycle bin (soft-deleted). Either undelete it first, query with `ALL ROWS` to include d…
ENTITY_IS_LOCKED: entity is locked for approval
ValidationThe record is currently in an Approval Process and is locked for editing until the approval is granted, rejected, or recalled. The lock is e…
FIELD_CUSTOM_VALIDATION_EXCEPTION: <your validation rule's error message>
ValidationA validation rule on the object evaluated to TRUE for the record being saved, blocking the save. The text after the colon is the rule's own …
INVALID_FIELD_FOR_INSERT_UPDATE
ValidationYou tried to set a field that the platform doesn't let you set in this context — usually a formula field, a system-managed field (CreatedDat…