Use a before-insert or before-update trigger to set or modify fields on the records in Trigger.new directly — changes here are saved without a separate DML call
Use an after-insert or after-update trigger when you need the system-assigned record Id (unavailable before insert) or when writing to related records via a separate DML statement
Never issue DML on the SObject type of the triggering records inside a before trigger — this causes a self-triggering loop and a runtime error
For validation that should prevent save, throw a custom exception or call record.addError() inside a before trigger; addError() in an after context still rolls back the transaction
When both before and after logic is needed, use a single trigger with both contexts delegating to one handler to avoid ordering ambiguity between two separate trigger files
Document the context choice in comments to make it clear to future maintainers why a particular context was selected
Known gotchas
Fields set in a before trigger are visible when after-trigger logic runs in the same transaction, but Trigger.old still reflects the pre-save values regardless of context
In before-update triggers, Trigger.new is writable but Trigger.newMap values are read-only copies — modify via the list, not the map
Calling a @future method from a before trigger is allowed but the future job receives a snapshot of the data at call time, not the final committed values
Give your agent this knowledge — and 200+ more routes
One MCP install gives any agent live access to the full route map, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp