Skip to content

Error Handling

Error handling in oRPC is flexible and consistent. You can use the ORPCError class, define typesafe errors, and adapt custom error classes while still returning meaningful feedback to clients.

ORPCError Class

ORPCError is the standard error type in oRPC. It includes a code, plus optional message and data fields.

DANGER

message and data are sent to the client. Do not include sensitive information in either field.

ts
import { 
ORPCError
,
os
} from '@orpc/server'
const
rateLimitMiddleware
=
os
.
middleware
(async ({
next
}) => {
throw new
ORPCError
('RATE_LIMITED', {
message
: 'You are being rate limited',
data
: {
retryAfter
: 60 }
}) return
next
()
}) const
example
=
os
.
use
(
rateLimitMiddleware
)
.
handler
(async ({
input
}) => {
if (
notFound
) {
throw new
ORPCError
('NOT_FOUND')
} })

Typesafe Errors

For end-to-end type safety, define your errors with .errors or return ORPCError. This lets the client infer each error's shape and handle it safely. You can use any Standard Schema library to validate error data.

DANGER

message and data are sent to the client. Do not include sensitive information in either field.

ts
const 
rateLimitMiddleware
=
os
.
errors
({
RATE_LIMITED
: {
data
:
z
.
object
({
retryAfter
:
z
.
number
(),
}), }, }) .
middleware
(async ({
next
,
errors
}) => {
throw
errors
.
RATE_LIMITED
({
message
: 'You are being rate limited',
data
: {
retryAfter
: 60 }
}) return
next
()
}) const
exampleProcedure
=
os
.
use
(
rateLimitMiddleware
)
.
errors
({
NOT_FOUND
: {
message
: 'The resource was not found', // <- default message
}, }) .
handler
(async ({
input
,
errors
}) => {
if (
notFound
) {
throw
errors
.
NOT_FOUND
()
} })

TIP

You can use typesafe errors across your entire project, but we recommend reserving them for application-specific cases. For common errors like UNAUTHORIZED or RATE_LIMITED, the client usually already understands the meaning. Skipping explicit schemas for those errors can also reduce type complexity and improve TypeScript performance.

ORPCError Compatibility

If you cannot access the errors object, for example in a utility function or another module, you can still throw ORPCError. oRPC will try to convert it to the matching typesafe error when its code and data match a defined error. If no match is found, it is treated as an unknown error.

ts
const exampleProcedure = os
  .errors({
    NOT_FOUND: {
      message: 'The resource was not found',
    },
  })
  .handler(async ({ errors }) => {
    throw errors.NOT_FOUND()

    // Treated as errors.NOT_FOUND because the code and data match
    throw new ORPCError('NOT_FOUND')

    // Treated as an unknown error because it does not match any defined error
    throw new ORPCError('BAD_REQUEST')
  })

Returning an ORPCError

As an alternative to .errors, you can return an ORPCError directly from your handler or middleware to achieve end-to-end type safety.

WARNING

When implementing a contract, returning an ORPCError is equivalent to throwing one.

ts
const exampleProcedure = os
  .handler(async ({ errors }) => {
    if (reachRateLimit) {
      return new ORPCError('RATE_LIMITED', {
        message: 'You are being rate limited',
        data: { retryAfter: 60 }
      })
    }

    return 'Success'
  })

DANGER

message and data are sent to the client. Do not include sensitive information in either field.

ORPC Error Codes

By default, oRPC allows any string as an error code and suggests common HTTP codes like NOT_FOUND and UNAUTHORIZED. You can override this with your own set of allowed error codes for better type safety and consistency.

ts
declare module '@orpc/server' { // or '@orpc/client'
  interface Registry {
    ORPCErrorCode: 'NOT_FOUND' | 'UNAUTHORIZED' | 'RATE_LIMITED' | 'MY_CUSTOM_ERROR' | (string & {})
  }
}

With this configuration, only NOT_FOUND, UNAUTHORIZED, RATE_LIMITED, and MY_CUSTOM_ERROR will be suggested as error codes. The (string & {}) fallback ensures you can still use any string value when needed.

Using Custom Error Classes

You do not have to use ORPCError directly in your business logic. You can throw your own error classes and convert them to ORPCError in middleware or interceptors.

INFO

By default, oRPC can convert non-ORPCError into an ORPCError with code INTERNAL_SERVER_ERROR, or leave them unchanged depending on the client you are using.

ts
class MyCustomError extends Error {
}

const customErrorConverterMiddleware = os.middleware(async ({ next }) => {
  try {
    return await next()
  }
  catch (err) {
    if (err instanceof MyCustomError) {
      throw new ORPCError('MY_CUSTOM_ERROR', { message: err.message, cause: err })
    }

    throw err
  }
})

Client Error Handling

To learn how to handle errors on the client side, see the Client Error Handling documentation.

Released under the MIT License.