Writing Insanely Great Error Messages
An error message is a conversation with someone who is already frustrated. It's some of the most important writing in your product — and usually the least considered.
Great error messages are not cute. They are clear, timely, accountable, and useful. The best ones feel calm because they respect the user's situation: something has gone wrong, and now the product owes them the truth.
What a Good Error Message Does #
A good message answers three questions, fast:
- What happened? In plain language, from the user's point of view — not the system's.
- Why does it matter? What can't they do now, what still works, and is their work safe?
- What can they do? The next step, ideally as an action they can take right there.
Principles (Apple HIG-Inspired) #
- Say what happened in the user's terms. "Couldn't save — your disk is full," not "ENOSPC / errno 28."
- Be specific, not verbose. Precise beats wordy. One clear sentence beats a paragraph.
- Blame the situation, not the user. Avoid "you did X wrong." Describe the state and the fix.
- Offer a way forward. Every message should suggest or provide the next action.
- Don't expose internals. No stack traces, codes, or jargon in the user's face (log those instead).
- Match severity to interruption. Reserve modal alerts for things the user must deal with now. (See Whose Fault Is It Anyway? — no one likes toast flying into their face.)
- Preserve the user's work. Reassure them that what they did is safe whenever it is.
- Update the message when the state changes. If the system reconnects, remove the warning. If a delay becomes an outage, say so.
A Simple Template #
[What happened, plainly]. [Why it matters / is your work safe]. [What to do next].
Example: "Couldn't save your note — you're offline. Your changes are kept and will sync when you reconnect."
Checkout examples:
- "Your card was declined. Try another payment method."
- "Checkout is temporarily unavailable. Your cart is saved. We'll update this page when checkout is back."
- "Your payment went through, but access is taking longer than usual. We're working on it and will email you when it's ready."
- "Your package appears delayed. We're checking with the carrier and will update this order when we know more."
Messages for Operators #
User-facing messages and logs serve different audiences. Do not make the user read your logs, and do not make your logs so vague that support cannot help.
- Give the user a short reference ID when support may need to correlate the failure.
- Log structured fields: request ID, user ID when appropriate, order ID, dependency, failure class, retry count, and final outcome.
- Log decisions, not just crashes: "webhookduplicateignored" is more useful than silence.
- Never log secrets, tokens, full card numbers, or unnecessary personal data.
- Keep internal details in logs and traces, not in user-facing copy.
Observability is communication with future maintainers. A good error message tells the user what they need now. A good log line tells the team what they need later.
Anti-Patterns #
- "An error occurred." (Which? Where? Now what?)
- "Error 0x8007000E." (Meaningful to no one.)
- "Are you sure?" with no consequences stated.
- A wall of technical detail, or a toast for something that already recovered.
- "Payment failed" after the payment actually succeeded but fulfillment is delayed.
Front End vs. Back End Notes #
- Front end: write the message for a stressed human; place it where the problem is; make the recommended action a real button when possible.
- Back end: return a stable machine-readable code and enough context for the client to choose the right message. Keep debug detail in logs, not in the user-facing string.
Related chapters: Whose Fault Is It Anyway? · Keep Informed and Carry On · Test Once, Measure Always