# TriggerHandler Base Class and Field-Change Detection

This document explains the `TriggerHandler` base class that ApexTools provides. For an index that includes the other tools, see the [ApexTools Guide](/apex-stem/docs/apex-tools-guide).

## What You Can Do

Simply by extending the `TriggerHandler` base class, the entry-point processing from Triggers settles into a clean shape: "a trigger file that only declares the seven events" plus "a handler class that only writes the hooks you override". A built-in helper for "narrowing down to records where specific fields changed" lets `afterUpdate` filtering be written in a single line.

## Inheritance: seven hooks plus andFinally

Each Handler extends the `TriggerHandler` base class and overrides only the hooks it needs. They are all `protected virtual`, so any hook you don't override does nothing. There are eight in total: seven matching the trigger events, plus `andFinally`, which runs last regardless of context.

| Hook | Signature |
|---|---|
| `beforeInsert` | `(List<SObject> newRecords)` |
| `beforeUpdate` | `(Map<Id, SObject> newMap, Map<Id, SObject> oldMap)` |
| `beforeDelete` | `(Map<Id, SObject> deletedMap)` |
| `afterInsert` | `(Map<Id, SObject> newMap)` |
| `afterUpdate` | `(Map<Id, SObject> newMap, Map<Id, SObject> oldMap)` |
| `afterDelete` | `(Map<Id, SObject> deletedMap)` |
| `afterUndelete` | `(Map<Id, SObject> undeletedMap)` |
| `andFinally` | `()` — always called last (in any context) |

## Integrated With the Fixed Trigger.cls Pattern

The trigger file follows Salesforce convention and uses a fixed form. Declare all seven events and call the Handler in one line.

```apex
trigger Opportunity on Opportunity(
  before insert,
  before update,
  before delete,
  after insert,
  after update,
  after delete,
  after undelete
) {
  (new TriggerOppHandler()).execute();
}
```

> 🚨 **On a custom object you cannot put `__c` straight into the trigger name.** Apex identifiers may not contain a double underscore (Salesforce reserves it), so `trigger SalesActivity__c on SalesActivity__c(...)` fails to deploy with `Invalid character in identifier`. **Give the trigger a different name** (`SalesActivityTrigger`, for instance — match the file name too). Standard objects are fine as `trigger Opportunity on Opportunity(...)`.

The Handler side overrides only the hooks it needs.

```apex
public with sharing class TriggerOppHandler extends TriggerHandler {
  protected override void afterInsert(Map<Id, SObject> newRecordsMap) {
    Set<Id> opportunityIds = newRecordsMap.keySet();
    (new CopyAccountIndustryToOpportunityUsecase(opportunityIds)).invoke();
  }
}
```

## Field-Change Detection Helpers

It's a frequent case to want to narrow `afterUpdate` to "records where specific fields changed". The `TriggerHandler` base class provides helpers for exactly that.

| Method | Return Type | Purpose |
|---|---|---|
| `getUpdatedRecordsWithChangedField(SObjectField field)` | `List<SObject>` | Records where a single field changed |
| `getUpdatedRecordsWithChangedFields(List<SObjectField> fields)` | `List<SObject>` | Records where any of multiple fields changed |
| `getUpdateRecordIdsWithChangedField(SObjectField field)` | `Set<Id>` | Id version of the above |
| `getUpdateRecordIdsWithChangedFields(List<SObjectField> fields)` | `Set<Id>` | Id version of the above |

Example usage:

```apex
public with sharing class TriggerOppHandler extends TriggerHandler {
  protected override void afterUpdate(Map<Id, SObject> newMap, Map<Id, SObject> oldMap) {
    Set<Id> needIds = this.getUpdateRecordIdsWithChangedFields(new List<SObjectField>{
      Opportunity.AccountId,
      Opportunity.StageName
    });
    (new RegenerateCollectionUsecase(needIds)).invoke();
  }
}
```

The logic "do something only when a specific field changed" consolidates into a single line, and the Handler still keeps its slim "only condition checks and Usecase invocation" shape.

## Other Notes

### Hooks You Don't Override Just Do Nothing

The hooks on the `TriggerHandler` base class are all `protected virtual` and do nothing by default. A Handler that only wants to handle `afterInsert` overrides only `afterInsert` — no need to fill other hooks with empty methods.

### When to Use `andFinally`

`andFinally()` is a hook that is **always called last in any context**. Use it for "processing that must always run last, regardless of the before / after type" (finalizing audit logs, wrapping up Trace, etc.). Most Handlers don't need it.

## Read Next

- [ApexTools Guide](/apex-stem/docs/apex-tools-guide): the entry point that includes the other tools in ApexTools
- [Handler-Usecase Architecture](/apex-stem/docs/handler-usecase-architecture): the core Apex Stem architecture where `TriggerHandler` shines
- [Apex Stem Introduction Guide](/apex-stem/docs/apex-stem-full-guide): four steps with working code
