Skip to content

createActionError()

Create a structured, typed error to throw from action handlers or middleware. The error is caught by defineAction and returned to the client as a structured ActionResult with success: false.

Type Signature

ts
function createActionError(opts: {
  code: string
  message: string
  statusCode?: number
  fieldErrors?: Record<string, string[]>
}): ActionError

Parameters

A single options object with the following properties.

PropertyTypeRequiredDefaultDescription
codestringYes--A machine-readable error code identifier. Use UPPER_SNAKE_CASE by convention.
messagestringYes--A human-readable error message suitable for display to users.
statusCodenumberNo400The HTTP status code associated with the error.
fieldErrorsRecord<string, string[]>NoundefinedA map of field names to arrays of error messages. Used for form-level validation feedback.

Return Type

ts
interface ActionError {
  code: string
  message: string
  statusCode: number
  fieldErrors?: Record<string, string[]>
}

The returned object also has an internal __isActionError: true marker (not part of the public type). This marker allows defineAction to distinguish action errors from other thrown exceptions and serialize them without leaking internal details.


Usage in Handlers

Throw createActionError inside a defineAction handler or a createActionClient .action() handler to return a structured error response.

ts
export default defineAction({
  input: z.object({ id: z.string() }),
  handler: async ({ input }) => {
    const todo = await db.todo.findUnique({ where: { id: input.id } })
    if (!todo) {
      throw createActionError({
        code: 'NOT_FOUND',
        message: 'Todo not found',
        statusCode: 404,
      })
    }
    return todo
  },
})

The client receives:

json
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Todo not found",
    "statusCode": 404
  }
}

Usage in Middleware

Throw createActionError in middleware to halt the chain and return an error before the handler executes.

ts
export const authMiddleware = defineMiddleware(async ({ event, next }) => {
  const session = await getUserSession(event)
  if (!session) {
    throw createActionError({
      code: 'UNAUTHORIZED',
      message: 'Authentication required',
      statusCode: 401,
    })
  }
  return next({ ctx: { user: session.user } })
})

Usage with Field Errors

Use fieldErrors to provide per-field validation feedback for custom validation logic that goes beyond schema validation (e.g., uniqueness checks against a database).

ts
export default defineAction({
  input: z.object({
    email: z.string().email(),
    username: z.string().min(3),
  }),
  handler: async ({ input }) => {
    const existing = await db.user.findFirst({
      where: {
        OR: [
          { email: input.email },
          { username: input.username },
        ],
      },
    })

    if (existing) {
      const fieldErrors: Record<string, string[]> = {}
      if (existing.email === input.email) {
        fieldErrors.email = ['Email is already taken']
      }
      if (existing.username === input.username) {
        fieldErrors.username = ['Username is already taken']
      }

      throw createActionError({
        code: 'CONFLICT',
        message: 'Duplicate entry',
        statusCode: 409,
        fieldErrors,
      })
    }

    return await db.user.create({ data: input })
  },
})

The client receives:

json
{
  "success": false,
  "error": {
    "code": "CONFLICT",
    "message": "Duplicate entry",
    "statusCode": 409,
    "fieldErrors": {
      "email": ["Email is already taken"],
      "username": ["Username is already taken"]
    }
  }
}

Built-in Error Codes

The following error codes are produced automatically by the defineAction runtime. You do not need to throw these yourself, but you may use them for consistency.

CodeStatus CodeProduced ByDescription
VALIDATION_ERROR422Input schema validationThe request input did not match the input schema. fieldErrors contains per-field details.
OUTPUT_VALIDATION_ERROR500Output schema validationThe handler return value did not match the outputSchema. Indicates a server-side bug.
PARSE_ERROR400Request body parsingThe request body could not be parsed as JSON when Content-Type: application/json was set.
SERVER_ERRORvariesH3 createError()An H3 error was thrown (via createError). The statusCode and statusMessage are preserved.
INTERNAL_ERROR500Unhandled exceptionsAn unexpected error occurred. Internal details are never exposed to the client. In development mode, the full error is logged to the server console.
FETCH_ERROR500useAction / useOptimisticActionA network-level fetch error occurred on the client side.

Custom Error Codes

You are free to define any string as a code. Common conventions:

ts
// Authentication / Authorization
throw createActionError({ code: 'UNAUTHORIZED', message: '...', statusCode: 401 })
throw createActionError({ code: 'FORBIDDEN', message: '...', statusCode: 403 })

// Resource errors
throw createActionError({ code: 'NOT_FOUND', message: '...', statusCode: 404 })
throw createActionError({ code: 'CONFLICT', message: '...', statusCode: 409 })
throw createActionError({ code: 'GONE', message: '...', statusCode: 410 })

// Rate limiting
throw createActionError({ code: 'RATE_LIMITED', message: '...', statusCode: 429 })

// Business logic
throw createActionError({ code: 'INSUFFICIENT_FUNDS', message: '...', statusCode: 422 })
throw createActionError({ code: 'QUOTA_EXCEEDED', message: '...', statusCode: 422 })

// Input (custom validation beyond schema)
throw createActionError({ code: 'VALIDATION_ERROR', message: '...', statusCode: 422, fieldErrors: { ... } })

Examples

Simple Error

ts
throw createActionError({
  code: 'NOT_FOUND',
  message: 'Todo not found',
  statusCode: 404,
})

Default Status Code

When statusCode is omitted, it defaults to 400.

ts
throw createActionError({
  code: 'BAD_REQUEST',
  message: 'Invalid operation',
})
// statusCode will be 400

Error with Field Errors

ts
throw createActionError({
  code: 'VALIDATION_ERROR',
  message: 'Registration failed',
  statusCode: 422,
  fieldErrors: {
    email: ['Email is already taken'],
    username: ['Must be at least 3 characters', 'Cannot contain spaces'],
  },
})

Conditional Error in Handler

ts
export default defineAction({
  input: z.object({ amount: z.number().positive() }),
  middleware: [authMiddleware],
  handler: async ({ input, ctx }) => {
    const balance = await getBalance(ctx.user.id)
    if (balance < input.amount) {
      throw createActionError({
        code: 'INSUFFICIENT_FUNDS',
        message: `Insufficient balance. Available: ${balance}`,
        statusCode: 422,
      })
    }
    return await processPayment(ctx.user.id, input.amount)
  },
})

Handling on the Client

vue
<script setup lang="ts">
const { execute, error } = useAction<{ title: string }, Todo>('/api/todos', {
  method: 'POST',
  onError(err) {
    if (err.code === 'VALIDATION_ERROR' && err.fieldErrors) {
      // Display per-field errors in the form
      formErrors.value = err.fieldErrors
    } else {
      toast.error(err.message)
    }
  },
})
</script>

Auto-Import

createActionError is auto-imported in all server routes (server/) when the nuxt-actions module is installed. It is exported from the same file as defineAction.

See Also

Released under the MIT License.