Server Function Errors

By default, errors thrown in server functions are sanitized before reaching the client. The real error message is logged server-side only — the client receives a generic message. This prevents accidental leaks of stack traces, database connection strings, or other sensitive details.

Default Behavior (Sanitized Errors)

When a server function throws a plain Error, the client never sees the original message:

  • Development: the client receives [DEV ONLY] <original message> as a convenience, with a warning that this won't be visible in production.
  • Production: the client receives Internal Server Error [ref: <hash>]. The same hash is logged server-side so you can correlate client reports with server logs.

Client-Safe Errors

To intentionally expose an error to the client, throw or return a ClientSafeError from catmint/server:

import { createServerFn, ClientSafeError } from "catmint/server" export const transferFunds = createServerFn(async (input) => { const balance = await getBalance(input.accountId) if (balance < input.amount) { throw new ClientSafeError("Insufficient funds", { statusCode: 422, data: { available: balance, requested: input.amount }, }) } // ... })

Throw vs Return

The throw/return semantics are preserved across the RPC boundary:

  • A thrown ClientSafeError becomes a thrown ServerFnError on the client (caught via try/catch).
  • A returned ClientSafeError becomes a ServerFnError return value on the client (no throw — useful for form validation patterns).

Handling Errors on the Client

On the client, both cases arrive as a ServerFnError from catmint/error with typed statusCode and data properties:

import { ServerFnError } from "catmint/error" try { await transferFunds({ accountId: "123", amount: 1000 }) } catch (err) { if (err instanceof ServerFnError) { console.log(err.statusCode) // 422 console.log(err.data) // { available: 50, requested: 1000 } } }

Try It

Click each button to see the different error behaviors. The first two use ClientSafeError and expose their message and data. The third throws a plain Error with a fake database connection string — notice how the real message is sanitized.

Key Takeaways

  • Server function errors are sanitized by default — no sensitive data leaks to the client
  • Use ClientSafeError from catmint/server to intentionally expose errors with a status code and structured data
  • On the client, errors arrive as ServerFnError from catmint/error with typed statusCode and data
  • Throw vs return semantics are preserved across the RPC boundary
  • In dev, sanitized errors include the original message (prefixed with [DEV ONLY]); in prod, a correlation hash is used instead
  • Validation errors (from Standard Schema) are automatically wrapped as ClientSafeError with status 422 and structured issues