In any CRM-driven business, knowing when to stop pursuing a lead or opportunity is just as important as knowing when to follow up. When an Account is no longer active, keeping open Opportunities tied to it makes little sense and can mislead reports, forecasts, and sales team activities.
In this blog, we explore a practical and high-impact Apex trigger that automatically updates all related Opportunities to “Closed Lost” when an Account’s Active field changes from “Yes” to “No.” The trigger runs in the after update context and ensures that only Opportunities that are not already “Closed Won” or “Closed Lost” are modified.
This automation ensures that your pipeline reflects reality—removing deadweight deals and keeping forecasts clean and actionable.
🧠 Why This Trigger Is Critical
Imagine your sales team continues to chase deals on an Account that is no longer active. It wastes time, pollutes your data, and disrupts performance reviews. Manual cleanup is inconsistent and error-prone. By automating this check:
-
You eliminate open Opportunities for inactive Accounts
-
You keep your pipeline lean and reflective of live deals
-
Sales reps are no longer distracted by outdated Opportunities
-
Reports and dashboards stay clean and meaningful
-
Forecasting becomes more accurate, enabling smarter business decisions
This is the kind of automation that improves data integrity without requiring additional effort from the user.
🔍 What This Blog Covers
-
How to detect a change in a custom field (
Active__c
) usingTrigger.oldMap
-
How to use Parent-to-Child SOQL to access related Opportunities
-
How to filter Opportunities based on Stage
-
How to bulk update child records based on a parent-level field change
-
Why this pattern supports long-term CRM health and sales process compliance
-
How to structure your logic inside a trigger handler class for reusability
The automation is written to be clean, bulk-safe, and production-ready, with logic separated in a handler class for better testing and future maintenance.
🎯 Real-World Use Cases for This Trigger
-
Sales teams avoiding confusion from old or irrelevant Opportunities
-
Sales Ops managers trying to clean up pipeline clutter
-
Revenue leaders looking for clean, real-time forecasting
-
Account managers marking inactive clients in CRM after churn or inactivity
-
Integration-driven orgs syncing data from ERPs or external systems
This automation is ideal for B2B companies, subscription services, or any team where Account status directly impacts deal viability.
👨💻 Developer & Admin Tips
Here’s how the logic works:
-
The trigger runs after update on the Account object.
-
It checks if
Active__c
was changed from “Yes” to “No” usingTrigger.oldMap
. -
For all such Accounts, it collects their IDs in a Set.
-
A single Parent-to-Child SOQL query fetches all related Opportunities.
-
For each Opportunity, if the current Stage is neither “Closed Won” nor “Closed Lost,” the Stage is updated to “Closed Lost.”
-
A single DML update applies the changes to all modified Opportunities.
This logic avoids unnecessary updates, preserves successful (won) deals, and ensures bulk-safe behavior even in large-scale updates. The use of a trigger handler method keeps code modular and testable.
This is also a great pattern to extend for other Account-based field changes—like auto-pausing campaigns, removing user access, or notifying assigned reps.
🎥 Learn By Watching – YouTube Playlist Available
To see this trigger in action, head over to the Salesforce Makes Sense YouTube playlist. The video walks through:
-
The trigger structure
-
How
oldMap
helps detect field changes -
Writing Parent-to-Child queries in Apex
-
Building clean and efficient update loops
-
Testing and deploying the automation in your org
It’s all explained step-by-step in a real-world tone—no jargon, no fluff.
Solution:
trigger AccountTrigger on Account (after update) {
if(Trigger.isUpdate){ if(Trigger.isAfter){
AccountTriggerHandler.updateOpportunityStage(Trigger.New, Trigger.oldMap);
}
}
}
public class AccountTriggerHandler {
public static void updateOpportunityStage(List
accList,Map<Id,Account> oldMap){
List<Opportunity> oppList=new
List<Opportunity>(); Set<Id> idSet= new Set<Id>(); for(Account
acc:accList){ if(acc.Active__c == ‘No’
&& acc.Active__c !=
oldMap.get(acc.Id).Active__c){ idSet.add(acc.Id);
}
}
for(Account a:[SELECT Id,Active__c,(SELECT Id,StageName FROM
Opportunities) FROM Account WHERE Id IN:idSet]){
if(acc.Opportunities!=null){ for(Opportunity opp:a.Opportunities){
if(opp.StageName!=’Closed
Won’&&opp.StageName!=’Closed Lost’){ opp.StageName=’Closed Lost’;
oppList.add(opp);
}
}
}
} if(oppList.size( ) > 0){ update oppList;
}
}
}