Skip to main content

SF-0336 · Coding · Medium

How to show the error messages on the page or on the field using Trigger. (similar to validation rules error)?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026 · Updated for Spring '26

To block a save from inside a trigger, call addError(message) on the offending record (or one of its fields). The platform aborts the entire transaction, rolls back any pending DML, and renders the message back to the UI just like a validation rule would.

Record-level vs field-level errors

trigger OpportunityTrigger on Opportunity (before update) {
    for (Opportunity opp : Trigger.new) {
        // Record-level: error appears at the top of the page
        if (opp.StageName == 'Closed Won' && opp.Amount == null) {
            opp.addError('Cannot close a won opportunity without an Amount.');
        }
        // Field-level: error appears next to the specific field
        if (opp.CloseDate < Date.today()) {
            opp.CloseDate.addError('Close Date cannot be in the past.');
        }
    }
}
  • opp.addError(msg) attaches the error to the record. It renders at the top of the page layout in classic UI and as a page-level toast in Lightning.
  • opp.CloseDate.addError(msg) attaches the error to the specific field. The field highlights red and the message appears beneath it — the same UX as a validation rule on that field.

Use before triggers for validation

You can call addError in any trigger context, but the right place is almost always a before trigger. By the time an after trigger fires, the row is already in the database; throwing an error rolls back the transaction, which works, but it wastes the DML round-trip.

trigger CaseBefore on Case (before insert, before update) {
    for (Case c : Trigger.new) {
        if (c.Priority == 'High' && String.isBlank(c.Description)) {
            c.Description.addError('Description is required for High-priority cases.');
        }
    }
}

Bulk inserts: error only the bad ones

addError on a single record in Trigger.new lets the rest of the batch succeed if you’re inside a partial-success DML context (the default for Database.insert(records, false)). Errors are surfaced per-row in the Database.SaveResult[].

for (Account a : Trigger.new) {
    if (a.AnnualRevenue != null && a.AnnualRevenue < 0) {
        a.AnnualRevenue.addError('Annual Revenue must be non-negative.');
    }
}

If the call site used insert records (all-or-nothing), any single addError rolls back the entire batch.

addError(Exception) for richer context

addError is overloaded — you can pass an Exception object to preserve the stack and message:

try {
    SomeService.validate(opp);
} catch (Exception e) {
    opp.addError(e); // includes the original exception's message
}

What you cannot do

  • Format with HTML by default. Pass false as the second argument to escape; pass true if you have a curated, safe message and want HTML (opp.addError(msg, false) is the escaped form).
  • Continue execution after addError. The record is marked as failed. The trigger keeps iterating, but DML on this record will not proceed.

Common interview follow-ups

  • What’s the difference between addError and a validation rule? — Validation rules are declarative (formulas), evaluated by the platform before your trigger runs. addError is procedural — use it when the check requires Apex (cross-record, SOQL, custom logic).
  • Can I call addError on a record in Trigger.old? — Yes, in delete contexts: Trigger.old[i].addError('Cannot delete') blocks the delete.
  • Does addError increment governor counts? — No — it just marks the record. The transaction still rolls back cleanly.

Verified against: Apex Developer Guide — addError. Last reviewed 2026-05-17.