0

I'm getting an error on my trigger that it cannot change the value of a field because it is read-only. The problem is, it is not Read-Only. Is there a setting somewhere or some information about the field I should direct my attention to that I'm missing? It seems pretty straight-forward, but it's been a "bug", if that's what it is, for a few days now. I don't want to delete the field because I would have to sift through a few triggers and classes to get rid of the field and then add it back in. I'm hoping there's something more to this that I'm missing rather than a bug.

4
  • Where did you check that the field is not read-only? Jan 17, 2017 at 14:46
  • it will be also nice to see actual error message
    – kurunve
    Jan 17, 2017 at 14:48
  • I didn't think it'd be necessary, it's a very straight forward error. System.FinalException: Record is read-only. I checked that it is not read-only by clicking on the field (where you see an object's list of fields and relationships) and going into "Set Field-Level Security" Jan 17, 2017 at 14:54
  • when you encounter a problem and ask on stackexchange, please, provide more information - in this case - what field is it, your trigger code(better not the whole code, but the part/method where the error appears), error message and so on.
    – EvAzi
    Jan 17, 2017 at 14:54

1 Answer 1

5

This sounds like you're trying to modify a record contained in a trigger context variable (possibly in an After trigger).

Taking a look at the documentation on trigger context variables, there is this tidbit (emphasis mine):

  • new --- Returns a list of the new versions of the sObject records.
    • This sObject list is only available in insert, update, and undelete triggers, and the records can only be modified in before triggers.

So if you have a trigger that looks like this, you will run into an error

trigger myTrigger on MyObject__c (before update, after update){
    for(MyObject__c myRec :trigger.new){
        // This line will result in a 'record is read-only' error because
        //   it can be executed in an 'after' trigger context and attempts
        //   to update a record which is read-only at that point
        myRec.someField__c = 'newvalue';
    }
}

There are two main ways to overcome this issue:

  • split the trigger into different blocks that only run in certain trigger contexts using if statements and other trigger context variables like Trigger.isAfter and Trigger.isUpdate

  • perform a separate query for the records contained in trigger.new, and modify those records instead of the copies stored in the trigger context variables

Which strategy you end up using is up to you, and depends on the situation. My preference is for the first solution. I'll give an example using a combination of both, though trying to use this example directly would not make much sense.

trigger myTrigger on MyObject__c (before update, after update){
    if(Trigger.isBefore){
        for(MyObject__c myRec :trigger.new){
            // Since this line is now only executed in before trigger contexts,
            //   you will be able to execute this line of code without issue
            myRec.someField__c = 'newvalue';
        }
    } else if(Trigger.isAfter && Trigger.isUpdate){
        // It is possible to update a record that appears in a trigger context variable
        //   in an after trigger, but there are risks involved.
        // It is very easy to get yourself into a situation where the trigger
        //   gets into an infinite loop.
        // The general rule of thumb is that if you are updating an sObject record 
        //   in the trigger for that same sObject, it should be done in a 'before' trigger

        List<MyObject__c> recordsToUpdate = [SELECT Id FROM MyObject__c WHERE Id IN :Trigger.new];

        for(MyObject__c myRec :recordsToUpdate){
            myRec.someField__c = 'otherNewValue';
        }

        // In any after trigger, DML is required to update records.
        // We only get to skip the DML in 'before' triggers when directly 
        //   modifying a record in trigger.new or trigger.newMap
        update recordsToUpdate;
    }
}
5
  • Thanks! So, why exactly can't I update this field in an After, but I can in a before? That perplexes me that it would throw an error having to do with its security, when in reality it has to do with.....what exactly? I guess if I better understand why, I will better approach similar issues in the future Jan 17, 2017 at 14:56
  • 1
    This makes sense. Apex Triggers run in System mode and can thus do any CRUD operations independently from the context of the User that is causing the trigger to fire.
    – Dan Jones
    Jan 17, 2017 at 14:56
  • 1
    @NatalieSpatharakis In an after DML trigger, the record has already been saved to the database. You should always use a before trigger to modify records in Trigger.new.
    – sfdcfox
    Jan 17, 2017 at 15:01
  • 1
    @NatalieSpatharakis If this is indeed the issue that you're running into, then it has nothing to do with FLS. If you take a look at the error, it should mention that the record itself is read-only, not the specific field. sfdcfox covers why trigger context variables are read-only in after triggers.
    – Derek F
    Jan 17, 2017 at 15:02
  • 1
    See also: Triggers, which astutely recommends all field updates occur in a before trigger.
    – Adrian Larson
    Jan 17, 2017 at 17:42

You must log in to answer this question.

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