I am writing a trigger to get all the Opportunity competitors into a field on Opportunity, however, getting an error while testing it.

Error: updateCompetitors: execution of AfterUpdate caused by: System.FinalException: Record is read-only: Trigger.updateCompetitors: line 24

Can you please check the below code and suggest me what's wrong with this?

trigger updateCompetitors on Opportunity(after insert, after update){
   List<Opportunity> OppIds = new List<Opportunity>();
    for (Opportunity opp: trigger.new){
 List<OpportunityCompetitor> oppComps = new List<OpportunityCompetitor>();
 oppComps= [select id, CompetitorName, Strengths, Weaknesses from OpportunityCompetitor where OpportunityId in: Trigger.newMap.keySet()];
 List<Opportunity> OppstoUpdate = new List<Opportunity>();

 for (Opportunity opp: OppIds){
    String allcomps = null;
     if( oppComps.size()>0 ) {
      for (integer i = 0; i < oppComps.size(); i++){
         allcomps = allcomps + oppComps[i].CompetitorName+',';
   opp.OpportunityCompetitors__c = allcomps;

  update OppstoUpdate;
  • I think this is because you're updating the same records in an after update trigger. Any reason why this can't be done in the before insert context ? Commented Jan 10, 2014 at 8:26
  • SOQL is the reason why it cant be done in before as Bob suggests in his answer Commented Jan 10, 2014 at 8:40

3 Answers 3


This is because you are in an after insert/update trigger and the records are read only in that context as they have been written, but not committed, to the database.

Unfortunately your trigger is relying on the ids of the records, which means you won't be able to use before insert as the ids won't be populated at that time (as the records haven't been written to the database at that point so while you can write to them, database generated fields aren't populated).

In this instance you'll need to clone the record (or query anew via SOQL) and make changes to the new copy of the record, then execute the update against the new copies.

  • 1
    This is exactly what I needed; thanks! Commented Jun 11, 2019 at 15:03

You cannot update fields on triggered records when you are in an after trigger

Imagine you have updated the opportunity ,again your record will invoke the trigger since an update happened and there will be infinite recursion loop that will happen for your case.

Use a static boolean approach to stop the recursion if you are using the above trigger


I have recently come across the same issue and was able to resolve it by having Two - Part Trigger.new context variables.

First one just to initiate the set or Add all the Opp ids into the set Second one to execute the Database update of the changes you intend.

While the first context variable of trigger.new is being executed the Opportunity record will be saved which gives the required time lapse for the trigger to start the Second Context variable of Trigger.new

trigger mynewtesttrigger on Opportunity (after insert, after update) {

if (trigger.isAfter){
if (trigger.isUpdate || trigger.isInsert){

//Create set of Opportunity Ids based on updated Opportunity/s

Set <String> OppIds = new Set <String>();

for (Opportunity opp : trigger.new){


// First Initiation of Trigger.new is Completed and the Opportunity record is saved and no longer in Read Only mode.

//Compare against reference data

    Map <String, OpportunityCompetitor>matchingoppcomps = new Map <String, OpportunityCompetitor> ();

for (OpportunityCompetitor oppcomp : [Select Id, CompetitorName, Strengths, Weaknesses From OpportunityCompetitor Where OpportunityId IN :OppIds]) 

**// Here you need to put your logic to add all the competitors to the String** 

matchingoppcomps.put(oppcomp.CompetitorName, oppcomp); 

for(Opportunity theopp : trigger.new) {

if (matchingoppcomps.get(theopp.ID)!=null){

//Found the data. Perform the update.

theopp.OpportunityCompetitors__c = matchingoppcomps.get(theopp.ID).Id; 


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .