The short answer: field history tracking records old and new values for up to twenty fields per object, and those rows are reportable through standard “with history” report types. The data is kept for a limited window, typically eighteen months through the interface and a little longer through the API, unless you have bought Field Audit Trail. History is not a snapshot, it is a list of changes, and building a trend report from it is harder than people expect.
What gets tracked
Turn on history tracking per object, then select the fields. Twenty fields per object. Every change to a selected field writes a row to the object’s history table with the user, the timestamp, the old value and the new value.
Several field types cannot be tracked, and the list is consistent across objects: formula fields, roll-up summary fields, and auto-number fields, because none of them are changed by a user action in a way the platform records. Long text areas and rich text are tracked as a change event with no old or new value stored, which is usually useless for reporting.
The history objects
Standard objects have named history tables: AccountHistory, ContactHistory, CaseHistory, LeadHistory and so on. Custom objects get one automatically when tracking is enabled, named after the object.
Opportunity has two things that sound the same and are not. OpportunityFieldHistory holds field-level changes like any other object. OpportunityHistory is different: it stores a row each time the stage, amount, probability or close date changes, capturing the state of the opportunity at that moment. That second one is a genuine snapshot table and it is the reason opportunity trend reporting works better than trend reporting on anything else.
The standard report types
Enabling tracking makes report types available, usually named in the pattern “Accounts with Account History” or “Opportunity Field History”. They produce one row per change, with columns for the field that changed, the old value, the new value, who did it and when.
Two characteristics shape what you can build:
- Old and new values are text. Regardless of the source field’s type. You cannot sum them, you cannot compute a difference, and you cannot filter numerically. An amount that went from 50,000 to 40,000 gives you two strings.
- The field name is a value, not a column. All tracked fields share one history table, so reporting on changes to one field means filtering on the field name.
Those two together explain why “show me how much the pipeline moved last month” cannot be built from field history. Counting changes works. Measuring them does not.
Retention
Field history is retained for a bounded period, commonly cited as eighteen months in the interface with a somewhat longer window available through the API. After that the rows are deleted by Salesforce and there is no recovery.
Field Audit Trail is the paid add-on that extends this substantially, up to ten years, with its own retention policy configuration per object and field. It is a real product with a real price, and for regulated industries it is usually the only defensible option.
Where the budget does not exist, the practical alternative is to export history periodically to somewhere outside Salesforce, or to capture the values you care about in a reporting snapshot before the history ages out. Both require somebody to set them up before the data disappears, which is the part that goes wrong.
Building something useful from it
History reporting works well for audit questions and badly for analysis. The questions it answers cleanly:
- Who changed this field, and when.
- How many times did records move out of a given status last quarter.
- Which users are editing a sensitive field.
- Did anything change on this record between two dates.
For anything involving a measured quantity over time, use a reporting snapshot instead. Snapshots store typed values you can sum, at intervals you choose, in an object you control. History stores strings describing transitions. Picking the wrong one at the start costs weeks.
Frequently asked
Can I create a custom report type on a history object? Availability varies by object, and where it is offered it lets you add fields from the parent record alongside the change rows. Check the object list when creating the report type rather than assuming.
Why does my history report show blanks for old value? Record creation writes a history row with no old value, and some field types never store the value at all. Filtering out rows with a blank old value removes creation events, which is often what you want.
Can I restore a record from history? Not automatically. You can read what a value used to be and set it back by hand or with a data load. There is no rollback.
