No error is silent. No action is invisible. Every failure tells the user what happened and what to do next.

Contents

# Section
1 Error Types & Display Locations
2 Field-Level Errors
3 Form API Errors
4 Page-Level Errors
5 API Error Code Mapping
6 Empty States
7 Destructive Action Confirmation
8 Network Errors
9 Auth Errors
10 Message Standards
11 No Silent Failures

1. Error Types & Display Locations

Error Source What It Is Where to Show It
Client-side validation Required field, format, length Below the field (field error)
API field error Duplicate, constraint failure Below the field + form banner
API business rule Permission, state conflict Form banner or inline alert
API server error 500, unexpected Form banner or page error banner
Page load failure Data could not be fetched Full page error state with retry
Network failure No connection, timeout Toast or inline banner
Auth expiry Session expired Redirect to login (not a blank page)

2. Field-Level Errors









3. Form API Errors









4. Page-Level Errors



— Friendly message: "Could not load projects. Please try again."

— Retry button (re-triggers the original fetch)







5. API Error Code Mapping

Map backend error codes to user-facing messages on the frontend. Never show raw codes.

Backend Code User-Facing Message
VALIDATION_ERROR Show the specific field message from the error detail
DUPLICATE "A [entity] with this name already exists."
NOT_FOUND "This item was not found. It may have been deleted."
FORBIDDEN "You don't have permission to do this."
AUTH_REQUIRED Redirect to login
BUSINESS_RULE_VIOLATION Show the specific reason from the error detail
SERVER_ERROR "Something went wrong. Please try again."
Network / no response "Connection lost. Check your internet connection."
// Standard pattern for parsing Supabase/backend error codes
const { data, error } = await supabase.rpc('create_project', params);

if (error) {
  const code = error.message.split(':')[0].trim();
  const msg  = error.message.split(':').slice(1).join(':').trim();

  switch (code) {
    case 'VALIDATION_ERROR':          return showFieldError(msg);
    case 'DUPLICATE':                 return showFormError('A project with this name already exists.');
    case 'FORBIDDEN':                 return showFormError("You don't have permission to do this.");
    case 'NOT_FOUND':                 return showFormError('This item was not found. It may have been deleted.');
    case 'BUSINESS_RULE_VIOLATION':   return showFormError(msg);
    default:                          return showFormError('Something went wrong. Please try again.');
  }
}

6. Empty States

NO DATA EXISTS
[ ] Illustration or icon — not just text
[ ] Specific message: "No projects yet." — not "No data." or "Empty."
[ ] Call to action: "Create your first project" button or link

SEARCH RETURNED NO RESULTS
[ ] "No results for '[search term]'."
[ ] "Try a different search term."
[ ] Clear search button visible

FILTER RETURNED NO RESULTS
[ ] "No [entities] match your current filters."
[ ] Shows what filters are active
[ ] "Clear all filters" button prominent

COMBINATION (search + filters active)
[ ] "No results for '[term]' with these filters."
[ ] Both "Clear search" and "Clear filters" available

RULE: Never show a blank table body or an empty list without a message.

7. Destructive Action Confirmation

Before any irreversible action, show a confirmation dialog.

LOW RISK (Archive, soft actions)
  Title:  "Archive Project?"
  Body:   What happens + whether reversible: "This project will be hidden from your workspace. You can restore it later."
  Buttons: "Cancel" (left), amber or red action button (right): "Archive"

MEDIUM RISK (Delete a record)
  Title:  "Delete Project?"
  Body:   Item name + consequence: "'Marketing Q1' will be permanently deleted along with all its tasks."
  Body:   "This action cannot be undone."
  Buttons: "Cancel" (left), "Delete Project" — red/danger (right)

HIGH RISK (Delete account, all data, bulk delete)
  Same as medium +
  Type-to-confirm input: "Type DELETE to confirm"
  Submit button: disabled until input matches exactly (case-sensitive)
  Buttons: "Cancel" (left), "Delete Everything" — red/danger, disabled until confirmed (right)

8. Network Errors








9. Auth Errors








10. Message Standards

TONE
[ ] Friendly and specific — not technical
[ ] No "Error", "Failure", "Exception" as the full message
[ ] No exclamation marks on error messages
[ ] Past tense for completed: "Project saved." "Changes discarded."
[ ] Present tense for ongoing: "Saving..." "Deleting..."
[ ] No ALL CAPS in messages

SPECIFICITY
Good:  "Password must be at least 8 characters."
Bad:   "Invalid input."

Good:  "A project named 'Website Redesign' already exists in this workspace."
Bad:   "Duplicate error."

Good:  "Could not load team members. Please try again."
Bad:   "Error 500"

NO TECHNICAL DETAILS TO USERS
[ ] No error codes (SQLSTATE, HTTP status codes)
[ ] No stack traces
[ ] No variable names or internal identifiers
[ ] No "undefined", "null", "NaN" in user-facing messages

11. No Silent Failures

These are the most dangerous violations — the user thinks something worked when it didn't.

NEVER:
× console.log(error) without showing anything in the UI
× Catch an error and return undefined / null without showing a message
× Show a success toast after a failed operation
× Let a form submit without feedback (no spinner, no error, no success)
× Show "Saved" when the save actually failed
× Catch errors in a catch block with an empty body: catch (e) {}
× Swallow a network error and show stale data as if fresh
× Not handle the error state of a Promise or async/await call

ALWAYS:
✓ Every try/catch must show an error in the UI if the catch fires
✓ Every async call must have an error handler
✓ Every error handler must update UI state — not just log to console
✓ If you show a loading spinner, you must also handle the error that cancels it

Practice Task

Add/Edit Project Form Build a project form that correctly handles field errors, API errors (duplicate, forbidden), and network failures — with no silent failures.