Leave Localhost logoLeave LocalhostDocs
Observability

Error Handling

The backend utilizes a structured error handling pattern to ensure errors survive the Convex network boundary and arrive at the client with useful metadata.

The backend utilizes a structured error handling pattern to ensure errors survive the Convex network boundary and arrive at the client with useful metadata.

The Problem

By default, if a Convex function throws an Error("Something went wrong"), Convex redacts the message in production for security reasons. The client sees a generic "ConvexError".

The Solution

The starter uses ConvexError to pass a structured payload to the client. The base structure is defined in packages/backend/convex/errors.ts.

Error Factories

Never throw new Error(). Instead, import an error factory:

import { notFound, forbidden, invalidInput, conflict } from "../errors";

// 404 Not Found equivalent
throw notFound("The requested record does not exist.");

// 403 Forbidden equivalent
throw forbidden("Only owners can delete organizations.");

// 400 Bad Request equivalent
throw invalidInput("Workspace name must be alphanumeric.");

// 409 Conflict equivalent
throw conflict("That user is already a member.");

Catching on the Client

When these errors are thrown, the Convex React client catches them. You can inspect the payload:

import { useMutation } from "convex/react";
import { ConvexError } from "convex/values";
import { toast } from "sonner";

function Form() {
  const submit = useMutation(api.example.mutation);

  const handleSubmit = async () => {
    try {
      await submit();
    } catch (error) {
      if (error instanceof ConvexError) {
        // error.data is the structured payload
        const { code, message } = error.data;
        toast.error(`${code}: ${message}`);
      } else {
        toast.error("An unexpected error occurred.");
      }
    }
  };
}

Internal Errors

If a function throws an unhandled exception (e.g. TypeError, ReferenceError, or a generic Error thrown by a third-party library), Convex will redact it in production.

This is correct behavior: you don't want internal stack traces or library errors leaking to the client. Only throw the factory errors (notFound, conflict, etc.) for intentional, client-facing failure states.

On this page