Custom Opportunity History Tracking

  • Create Custom Object : OpportunityHistory

  • Fields : Field Name(Text), Old Value(Text) and New Value(Text)

Trigger Logic: 


• When new opportunity is inserted in the system, It creates history record with Field Name = ‘Record Created’.

• When Record is updated, it first identifies which all fields got updated  and then it creates 'OpportunityHistory’ record one for each updated field with Field Name, Old Value and New Value information

I have currently written code just to check the 'NextStep' field. I am not able to proceed further, Unable to fetch and check all fields in Opportunity. Edited the code, just updated trigger is writter in the code. Code:

trigger OpportunityHistory on Opportunity (after update) {
    for(Opportunity opp : Trigger.new) {
        System.debug('NEW MAP' +opp );
        Opportunity oppOld = Trigger.oldMap.get(opp.Id);
        System.debug('OLD MAP' +oppOld );
        OpportunityHistory__c oppHist = new OpportunityHistory__c();
        if(oppOld.NextStep != opp.NextStep) {
            oppHist.Opportunity__c = opp.Id;
            oppHist.Field_Name__c  = 'Record Created 2.0';
                oppHist.Old_Value__c = oppOld.NextStep;
                System.debug('Old Next Step Value: ' +oppHist.Old_Value__c );
                oppHist.New_Value__c  = opp.NextStep;
                System.debug('New Next Step Value: ' +oppHist.New_Value__c );
            insert oppHist;
  • 2
    Hi Pritish, welcome to SFSE. Please take a moment to scroll through the tour and read How to Ask. What have you tried so far? Where are you stuck? Right now this question reads like "Will anyone do my work for me?" and questions of this nature tend to be closed and heavily downvoted. Please note the format here is to ask specific questions about where you are stuck with your implementation. I have closed this question because of this lack of specificity. Please edit it with any clarifications.
    – Adrian Larson
    Mar 28, 2018 at 14:46
  • Hello @Pritesh Kumbhare, Welcome to SFSE, refer the tour on how to ask a good question. Right now you seem to know on what needs to be done, Go through trailhead module Apex Triggers to learn, after it please share what you have tried so far to get better support.
    – Raul
    Mar 28, 2018 at 14:47
  • @Rahul fyi you can link to it by typing [tour] instead.
    – Adrian Larson
    Mar 28, 2018 at 14:47

1 Answer 1


If you want to iterate all fields, you can get them from the describe map:

DescribeSObjectResult describe = SObjectType.Opportunity;
List<SObjectField> opportunityFields = describe.fields.getMap().values();

However, it may be better to just iterate populated fields:

for (Opportunity record : trigger.new)
    Map<String, Object> populatedFields = record.getPopulatedFieldsAsMap();
    // further processing

You should move your logic into separate classes, as it will simplify building out further logic. Your basic class structure would look something like:

public with sharing class OpportunityHistoryService
    public static void trackHistory
        (List<Opportunity> newRecords, Map<Id, Opportunity> oldMap)
        List<OpportunityHistory__c> histories = new List<OpportunityHistory__c>();
        for (Opportunity record : newRecords)
            histories.addAll(getHistories(record, oldMap.get(record.Id));
            // don't neglect error handling
            // naked DML in a trigger should be avoided at all cost
            insert histories;
        catch (DmlException dmx)
            // what should you do if the records fail?
            // DO NOT leave this code block empty (comments/debug don't count)
            // empty catch block is even worse than naked DML
    public static List<OpportunityHistory__c> getHistories
        (Opportunity newRecord, Opportunity oldRecord)
        // implementation

You must log in to answer this question.

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