Feel like I am missing the point of Field History Tracking.

Before going any further, here are the Salesforce Limits from the Salesforce Limits Quick Reference Guide date June 2, 2014:

Edition (Fields per object, Field History Tracking per object)

  • Developer (500, 20)
  • Unlimited and Performance (800, 20)
  • Enterprise (500, 20)
  • Professional (100, 20)
  • Group (100, 20)
  • Contact Manager (25, 20)
  • Personal (5, NA)

And here are all the help pages I'm able to find too:

Reviewing the main "idea" page requesting 20 field limit be increased, one user comments:

If you open a case with support, you can get this increased!

Appears that the common increase stated was only to 50, which is more than double the current amount, but still seems like a small amount if you've got 500-800 fields.

Is this the only solution, and if so, are there any third party products or solutions that are free and exceed these limits?

Seems to me that this limit should at the very least be in total, not per object, since currently, if that was the case I'd have more than enough; meaning on the Enterprise version, with the 200 custom objects with 20 tracking fields per object, that would be 4,000 fields being track, yet I have zero plans to create 200 custom objects; the Unlimited and Performance Editions get 2,000 custom objects with 20 tracking fields per object, that would be 40,000 fields being track.

Seems backwards to me, and unable to see a solution without a hack like having a shadow fields that are concatenations, shadow objects with 20 fields per object, asking for a bump to 50 fields via the support request, or using a third-party/custom solution off platform.

Any suggestions, or an I missing the point of field tracking?


3 Answers 3


Create an object called AccountHistoryTracking and fields like APIName__c,OldValue__c,NewValue__c then create a fieldset on Account called'HistoryTracking' with the fields you want to track on Account and then write below code to track history on account.This way you don't need to worry about history tracking limit per object.

trigger AccountHistoryTracker on Account (after update) {

    final List<Schema.FieldSetMember> trackedFields = 

    if (trackedFields.isEmpty()) return;

    final List<AccountHistoryTracking__c> fieldChanges = 
        new List<AccountHistoryTracking__c>();


    for (Account newAccount : trigger.new) {

        final Account oldAccount = trigger.oldmap.get(newAccount.Id);

        for (Schema.FieldSetMember fsm : trackedFields) {

            String fieldName  = fsm.getFieldPath();
            String fieldLabel = fsm.getLabel();

            if (newAccount.get(fieldName) == oldAccount.get(fieldName))

            String oldValue = String.valueOf(oldAccount.get(fieldName));
            String newValue = String.valueOf(newAccount.get(fieldName));

            if (oldValue != null && oldValue.length()>255) 
                oldValue = oldValue.substring(0,255);

            if (newValue != null && newValue.length()>255) 
                newValue = newValue.substring(0,255); 

            final AccountHistoryTracking__c accountHistory = 
                new AccountHistoryTracking__c();

            accountHistory.name         = fieldLabel;
            accountHistory.apiName__c   = fieldName;
            accountHistory.User__c      = newAccount.Id;
            accountHistory.ChangedBy__c = UserInfo.getUserId();
            accountHistory.OldValue__c  = oldValue;
            accountHistory.NewValue__c  = newValue;


    if (!fieldChanges.isEmpty()) {
        insert fieldChanges;
  • Thank you, that's awesome, and able to confirm it works as promised! (In case it's not clear, very happy this issue is now resolved.)
    – blunders
    Commented Jun 9, 2014 at 13:20
  • In case it is of use to anyone, here's how I deployed the trigger; note this deployment was to contacts, not accounts, but the code above works fine, only changes I made was to manually replace "account" references in the code to "contact" where needed. Also, the custom fields I added were, name, apiName, User, ChangedBy, OldValue, NewValue; note: if the fields are marked as required, the code won't work. Lastly, in the code I changed aht.name to aht.name__c before loading it to production via the progress I linked to above.
    – blunders
    Commented Jun 9, 2014 at 13:37
  • 1
    I have to track deletions here also. Can someone suggest any way out? Commented Sep 10, 2014 at 10:56
  • 1
    This is great! But why do you need ChangedBy? Isn't that the same as CreatedById? Commented Oct 9, 2015 at 3:49
  • 3
    Also, I took this and added a Date__c field since CreatedDate and LastModifiedDate were only displaying to the user on the related list as Dates not DateTimes. Commented Oct 9, 2015 at 16:09

@sfdc's method will consume a lot of Data Storage -- and that's expensive!

Do what @sfdc said but store the data in JSON in a Long Text Area Field on the record. Then build a Visualforce Page that displays the JSON in a readable way.

  • 2
    I really like the idea :) But one wouldn't be able to run standard reports on this then since it's a single long text area field? Commented Jan 5, 2017 at 10:05
  • 1
    Correct, that's the catch to using JSON to store data. Commented Jan 5, 2017 at 16:16
  • 1
    I like the idea of using JSON but it won't store historical field history, only the most recent version. I think this proposed solution is most useful for creating an undo function
    – sunny
    Commented Oct 1, 2018 at 12:57

Here is a Trigger + handlerclass version I've implemented account team history tracking using a similar to the method above. I have a custom object Account_Team_Member_History__c. The standard name field is set to autonumber.


trigger AccountTeamMemberTrigger on AccountTeamMember (after update)
    AccountTeamHandler.handleTrigger(Trigger.new, Trigger.oldMap, Trigger.operationType);

The class currently only covers updates, (not insert or delete)

public with sharing class AccountTeamHandler {
public static void handleTrigger(
    List<AccountTeamMember> newRecords,
    Map<ID, AccountTeamMember> oldRecordMap,
    System.TriggerOperation triggerEvent
  ) {
    switch on triggerEvent {
when AFTER_UPDATE { onUpdateAccountTeamMemberHistory(newRecords, oldRecordMap);}

public static void onUpdateAccountTeamMemberHistory(List<AccountTeamMember> newRecords,Map<ID, AccountTeamMember> oldRecordMap){

final List<Schema.FieldSetMember> trackedFields = SObjectType.AccountTeamMember.FieldSets.HistoryTracking.getFields();

if (trackedFields.isEmpty()) {
    'No data found in AccountTeamMember.FieldSets.HistoryTracking'

final List<Account_Team_Member_History__c> fieldChanges = new List<Account_Team_Member_History__c>();

  for (AccountTeamMember newAccountTeamMember : newRecords) {
    final AccountTeamMember oldAccountTeamMember = oldRecordMap.get(

    for (Schema.FieldSetMember fsm : trackedFields) {
      String fieldName = fsm.getFieldPath();
      String fieldLabel = fsm.getLabel();

      if (
        newAccountTeamMember.get(fieldName) ==
      ) {
      String oldValue = String.valueOf(oldAccountTeamMember.get(fieldName));
      String newValue = String.valueOf(newAccountTeamMember.get(fieldName));

      if (oldValue != null && oldValue.length() > 255) {
        oldValue = oldValue.substring(0, 255);

      if (newValue != null && newValue.length() > 255) {
        newValue = newValue.substring(0, 255);

      final Account_Team_Member_History__c accountTeamMemberHistory = new Account_Team_Member_History__c();

      accountTeamMemberHistory.Field_Label__c = fieldLabel; 
      accountTeamMemberHistory.apiName__c = fieldName;
      accountTeamMemberHistory.User__c = newaccountTeamMember.Id;
      accountTeamMemberHistory.ChangedBy__c = UserInfo.getUserId();
      accountTeamMemberHistory.OldValue__c = oldValue;
      accountTeamMemberHistory.NewValue__c = newValue;


if (!fieldChanges.isEmpty()) {
  insert fieldChanges;


You must log in to answer this question.

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