Sign In
More

Errors

Rivet provides robust error handling with security built in by default. Errors are handled differently based on whether they should be exposed to clients or kept private.

There are two types of errors:

  • UserError: Thrown from actors and safely returned to clients with full details
  • Internal errors: All other errors that are converted to a generic error message for security

Throwing and Catching Errors

UserError lets you throw custom errors that will be safely returned to the client.

Throw a UserError with just a message:

import { actor, UserError } from "rivetkit";

const user = actor({
  state: { username: "" },
  actions: {
    updateUsername: (c, username: string) => {
      // Validate username
      if (username.length > 32) {
        throw new UserError("Username is too long");
      }

      // Update username
      c.state.username = username;
    }
  }
});
TypeScript

Error Codes

Use error codes for explicit error matching in try-catch blocks:

import { actor, UserError } from "rivetkit";

const user = actor({
  state: { username: "" },
  actions: {
    updateUsername: (c, username: string) => {
      if (username.length < 3) {
        throw new UserError("Username is too short", {
          code: "username_too_short"
        });
      }

      if (username.length > 32) {
        throw new UserError("Username is too long", {
          code: "username_too_long"
        });
      }

      // Update username
      c.state.username = username;
    }
  }
});
TypeScript

Errors With Metadata

Include metadata to provide additional context for rich error handling:

import { actor, UserError } from "rivetkit";

const api = actor({
  state: { requestCount: 0, lastReset: Date.now() },
  actions: {
    makeRequest: (c) => {
      c.state.requestCount++;

      const limit = 100;
      if (c.state.requestCount > limit) {
        const resetAt = c.state.lastReset + 60_000; // Reset after 1 minute

        throw new UserError("Rate limit exceeded", {
          code: "rate_limited",
          metadata: {
            limit: limit,
            resetAt: resetAt,
            retryAfter: Math.ceil((resetAt - Date.now()) / 1000)
          }
        });
      }

      // Rest of request logic...
    }
  }
});
TypeScript

Internal Errors

All errors that are not UserError instances are automatically converted to a generic "internal error" response. This prevents accidentally leaking sensitive information like stack traces, database details, or internal system information.

import { actor } from "rivetkit";

const payment = actor({
  state: { transactions: [] },
  actions: {
    processPayment: async (c, amount: number) => {
      // This will throw a regular Error (not UserError)
      const result = await fetch("https://payment-api.example.com/charge", {
        method: "POST",
        body: JSON.stringify({ amount })
      });

      if (!result.ok) {
        // This internal error will be hidden from the client
        throw new Error(`Payment API returned ${result.status}: ${await result.text()}`);
      }

      // Rest of payment logic...
    }
  }
});
TypeScript

The original error message and stack trace are logged server-side for debugging. Check your server logs to see the full error details.

API Reference

Suggest changes to this page