Triggers on Opportunity/Account Contact Role objects – Using Change Data Capture

We all know Contact Role objects have been a pain in the …! Why? Because they are not first class objects, which implies you cannot create workflow rules, process builder or triggers on these objects. There are ideas posted, to vote for, on community for the same.

But then came the Change Data Capture Events! And guess what? You can subscribe to data changes on Opportunity/Account Contact Role objects in CDC!!! And now with Summer’19, we have the ability to create triggers on ChangeEvents!

Now, are you thinking what I am thinking? If yes, give yourself a high five!

So this basically implies that we can execute some logic(code) when there are any updates on Contact Roles object records, and for new insertion record too by subscribing to data changes via a ChangeEvent trigger.
So let’s discuss a simple use case.

Use Case

When a contact is added/updated as a Primary Contact to an Opportunity in Contact Roles related list, it should update a custom Contact Lookup field on Opportunity object with that Contact.


The idea is to create a trigger on OpportunityContactRoleChangeEvent object which passes a collection of OpportunityContactRole record IDs in context, to an Apex class where we handle rest of the logic to update the Primary Contact on Opportunity.


Trigger: UpdateOpptyPrimaryContact.apxt

trigger UpdateOpptyPrimaryContact on OpportunityContactRoleChangeEvent (after insert) {
    set<id> ocrIDs = new set<id>();
    for(OpportunityContactRoleChangeEvent e :{
        EventBus.ChangeEventHeader changeEventHeader = e.ChangeEventHeader;
        //Checking if the if the record is created or updated
        if(changeEventHeader.changetype == 'CREATE' || changeEventHeader.changetype == 'UPDATE'){
    if(ocrIDs.size()>0) //Call the Handler class method and pass the OpportunityContactRole IDs

Handler Class: UpdateOpptyPrimaryContactHandlerHandler.apxc

public class UpdateOpptyPrimaryContactHandler {
    public static void setPrimaryContact(set<id> ocrIDs){
        Opportunity[] oppsToUpdate = new Opportunity[]{};
        list<OpportunityContactRole> ocRoles = new list<OpportunityContactRole>([select id,contactId,OpportunityId,IsPrimary 
                                                                                    from OpportunityContactRole
                                                                                    where Id in :ocrIDs ORDER BY IsPrimary DESC
         * Logic to check if the Opportunity has a primary contact role record or not, and 
         * accordingly set the Primary_Contact__c field to Contact's Id or as NULL.
        set<id> oppIDs = new set<id>(); 
        for(OpportunityContactRole ocr: ocRoles){
                oppsToUpdate.add(new opportunity(id = ocr.OpportunityId, Primary_Contact__c=ocr.ContactId));
            else if(!oppIDs.contains(ocr.OpportunityId)){
                oppsToUpdate.add(new opportunity(id = ocr.OpportunityId, Primary_Contact__c=NULL));
            update oppsToUpdate;

So there’s the code..

Time to play the reel!

My findings(Important stuff!)

  • IsPrimary field is not a part of the Change Event Message Structure for OCR object, which makes it the code a bit lengthy! I posted an Idea on community for the same. Kindly upvote!
  • There can be a situation when a primary OCR record gets deleted. The above code doesn’t account for that. And that’s because, we cannot query a deleted OCR record using ALL Rows clause, since it doesn’t get stored in Recycle Bin. So that’s a limitation too in this approach.


Thank you for being an awesome reader! Subscribe to this blog for receiving all the latest updates straight into your inbox. ?

Source :

One Thought to “Triggers on Opportunity/Account Contact Role objects – Using Change Data Capture”

  1. Mubeen

    Nice! I didn’t knew about this. Keep the good work.

Leave a Comment