Files
2026-03-07 11:07:45 -03:00

5.3 KiB

Error Handling in Medusa

Medusa provides the MedusaError class for consistent error responses across your API routes and custom code.

Contents

Using MedusaError

Use MedusaError in API routes, workflows, and custom modules to throw errors that Medusa will automatically format and return to clients:

import { MedusaError } from "@medusajs/framework/utils"

// Throw an error
throw new MedusaError(
  MedusaError.Types.NOT_FOUND,
  "Product not found"
)

Error Types

NOT_FOUND

Use when a requested resource doesn't exist:

throw new MedusaError(
  MedusaError.Types.NOT_FOUND,
  "Product with ID 'prod_123' not found"
)

HTTP Status: 404

INVALID_DATA

Use when request data fails validation or is malformed:

throw new MedusaError(
  MedusaError.Types.INVALID_DATA,
  "Email address is invalid"
)

HTTP Status: 400

UNAUTHORIZED

Use when authentication is required but not provided:

throw new MedusaError(
  MedusaError.Types.UNAUTHORIZED,
  "Authentication required to access this resource"
)

HTTP Status: 401

NOT_ALLOWED

Use when the user is authenticated but doesn't have permission:

throw new MedusaError(
  MedusaError.Types.NOT_ALLOWED,
  "You don't have permission to delete this product"
)

HTTP Status: 403

CONFLICT

Use when the operation conflicts with existing data:

throw new MedusaError(
  MedusaError.Types.CONFLICT,
  "A product with this handle already exists"
)

HTTP Status: 409

DUPLICATE_ERROR

Use when trying to create a duplicate resource:

throw new MedusaError(
  MedusaError.Types.DUPLICATE_ERROR,
  "Email address is already registered"
)

HTTP Status: 422

INVALID_STATE

Use when the resource is in an invalid state for the operation:

throw new MedusaError(
  MedusaError.Types.INVALID_STATE,
  "Cannot cancel an order that has already been fulfilled"
)

HTTP Status: 400

Error Response Format

Medusa automatically formats errors into a consistent JSON response:

{
  "type": "not_found",
  "message": "Product with ID 'prod_123' not found"
}

Best Practices

1. Use Specific Error Types

Choose the most appropriate error type for the situation:

// ✅ GOOD: Uses specific error types
export async function GET(req: MedusaRequest, res: MedusaResponse) {
  const { id } = req.params
  const query = req.scope.resolve("query")

  const { data } = await query.graph({
    entity: "product",
    fields: ["id", "title"],
    filters: { id },
  })

  if (!data || data.length === 0) {
    throw new MedusaError(
      MedusaError.Types.NOT_FOUND,
      `Product with ID '${id}' not found`
    )
  }

  return res.json({ product: data[0] })
}

// ❌ BAD: Uses generic error
export async function GET(req: MedusaRequest, res: MedusaResponse) {
  const { id } = req.params
  const query = req.scope.resolve("query")

  const { data } = await query.graph({
    entity: "product",
    fields: ["id", "title"],
    filters: { id },
  })

  if (!data || data.length === 0) {
    throw new Error("Product not found") // Generic error
  }

  return res.json({ product: data[0] })
}

2. Provide Clear Error Messages

Error messages should be descriptive and help users understand what went wrong:

// ✅ GOOD: Clear, specific message
throw new MedusaError(
  MedusaError.Types.INVALID_DATA,
  "Cannot create product: title must be at least 3 characters long"
)

// ❌ BAD: Vague message
throw new MedusaError(
  MedusaError.Types.INVALID_DATA,
  "Invalid input"
)

3. Include Context in Error Messages

// ✅ GOOD: Includes relevant context
throw new MedusaError(
  MedusaError.Types.NOT_FOUND,
  `Product with ID '${productId}' not found`
)

// ✅ GOOD: Includes field name
throw new MedusaError(
  MedusaError.Types.INVALID_DATA,
  `Invalid email format: '${email}'`
)

4. Handle Workflow Errors

When calling workflows from API routes, catch and transform errors:

// ✅ GOOD: Catches and transforms workflow errors
export async function POST(req: MedusaRequest, res: MedusaResponse) {
  const { data } = req.validatedBody

  try {
    const { result } = await myWorkflow(req.scope).run({
      input: { data },
    })

    return res.json({ result })
  } catch (error) {
    // Transform workflow errors into API errors
    throw new MedusaError(
      MedusaError.Types.INVALID_DATA,
      `Failed to create resource: ${error.message}`
    )
  }
}

5. Use Validation Middleware

Let validation middleware handle input validation errors:

// ✅ GOOD: Middleware handles validation
// middlewares.ts
const MySchema = z.object({
  email: z.string().email("Invalid email address"),
  age: z.number().min(18, "Must be at least 18 years old"),
})

export const myMiddlewares: MiddlewareRoute[] = [
  {
    matcher: "/store/my-route",
    method: "POST",
    middlewares: [validateAndTransformBody(MySchema)],
  },
]

// route.ts - No need to validate again
export async function POST(req: MedusaRequest, res: MedusaResponse) {
  const { email, age } = req.validatedBody // Already validated

  // Your logic here
}