An error has two audiences, and they need different things.
| Audience | Needs |
|---|---|
| The user | what happened, in plain language, and what to do next |
| The developer | exactly what failed, with context to reproduce it |
For users
// Bad
"TypeError: Cannot read properties of undefined (reading 'map')"
"Error 0x80004005"
"Something went wrong"
// Good
"We could not load your orders. Check your connection and try again."
"That email address is already registered. Try signing in instead."
A good message:
- says what failed, in the user’s terms,
- says what they can do (retry, change a value, contact support),
- is calm and specific, not blaming,
- is in the right place: beside the field, or in a visible message area.
For developers
Log the technical details to the console (or to a logging service):
function loadOrders(userId) {
try {
throw new Error("Orders service returned 503");
} catch (error) {
console.error("loadOrders failed", { userId, message: error.message });
return null;
}
}
loadOrders(42);
Include the context: ids, inputs, the operation. A message like “failed” is useless two weeks later.
Translating errors
Keep a mapping from technical failures to user messages in one place.
function friendlyMessage(error) {
if (error.name === "AbortError") return null; // user cancelled: no message needed
if (error.status === 401) return "Please sign in to continue.";
if (error.status === 404) return "We could not find that page.";
if (error.status >= 500) return "Our servers are having trouble. Please try again soon.";
return "Something unexpected happened. Please try again.";
}
console.log(friendlyMessage({ name: "HttpError", status: 404 }));
console.log(friendlyMessage({ name: "TypeError" }));
Never leak internals
Do not show users stack traces, file paths, SQL, tokens or server details. They are confusing, and they help attackers. Log them privately instead.
Where to show errors
- Field errors next to the field, tied with
aria-describedby. - Page-level errors in a visible area with
role="alert"so they are announced. See Accessible Validation. - Do not use
alert()for normal errors. It blocks the page and is poor for accessibility.
Tone
“Invalid input” → “Use a password with at least 8 characters.” “Error” → “We could not save your changes. Your work is still here, so you can try again.”
Make errors recoverable
Keep what the user typed, offer a retry button, and avoid wiping a form after a failure.