docs: add re-use error handler section + clarify allowUnregistered (#12780)

* docs: add re-use error handler section + clarify allowUnregistered

* fixes
This commit is contained in:
Shahed Nasser
2025-06-19 19:37:26 +03:00
committed by GitHub
parent 0b9819a7e2
commit b92bdfe7e1
5 changed files with 151 additions and 65 deletions

View File

@@ -35,21 +35,21 @@ export const GET = async (
}
```
The `MedusaError` class accepts in its constructor two parameters:
The `MedusaError` class accepts two parameters in its constructor:
1. The first is the error's type. `MedusaError` has a static property `Types` that you can use. `Types` is an enum whose possible values are explained in the next section.
2. The second is the message to show in the error response.
### Error Object in Response
The error object returned in the response has two properties:
The error object returned in the response has three properties:
- `type`: The error's type.
- `message`: The error message, if available.
- `code`: A common snake-case code. Its values can be:
- `invalid_request_error` for the `DUPLICATE_ERROR` type.
- `api_error`: for the `DB_ERROR` type.
- `invalid_state_error` for `CONFLICT` error type.
- `api_error` for the `DB_ERROR` type.
- `invalid_state_error` for the `CONFLICT` error type.
- `unknown_error` for any unidentified error type.
- For other error types, this property won't be available unless you provide a code as a third parameter to the `MedusaError` constructor.
@@ -191,7 +191,7 @@ The error object returned in the response has two properties:
</Table.Cell>
<Table.Cell>
Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored for a default message.
Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored in favor of a default message.
</Table.Cell>
<Table.Cell>
@@ -276,9 +276,55 @@ export default defineMiddlewares({
The `errorHandler` property's value is a function that accepts four parameters:
1. The error thrown. Its type can be `MedusaError` or any other thrown error type.
1. The error thrown. Its type can be `MedusaError` or any other error type.
2. A request object of type `MedusaRequest`.
3. A response object of type `MedusaResponse`.
4. A function of type MedusaNextFunction that executes the next middleware in the stack.
4. A function of type `MedusaNextFunction` that executes the next middleware in the stack.
This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message.
### Re-Use Default Error Handler
In some use cases, you may not want to override the default error handler but rather perform additional actions as part of the original error handler. For example, you might want to capture the error in a third-party service like Sentry.
In those cases, you can import the default error handler from the Medusa Framework and use it in your custom error handler, along with your custom logic.
For example:
export const defaultErrorHandlerHighlights = [
["3", "errorHandler", "Import the default error handler."],
["12", "originalErrorHandler", "Get the original error handler function."],
["23", "originalErrorHandler", "Re-use the original error handler after your custom logic."],
]
```ts title="src/api/middlewares.ts" highlights={defaultErrorHandlerHighlights}
import {
defineMiddlewares,
errorHandler,
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { MedusaError } from "@medusajs/framework/utils"
// assuming you have Sentry set up in your project
import * as Sentry from "@sentry/node"
const originalErrorHandler = errorHandler()
export default defineMiddlewares({
errorHandler: (
error: MedusaError | any,
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction
) => {
// for example, capture the error in Sentry
Sentry.captureException(error)
return originalErrorHandler(error, req, res, next)
},
})
```
In this example, you import the `errorHandler` function from the Medusa Framework. Then, you call it to get the original error handler function.
Finally, you use it in your custom error handler after performing your custom logic, such as capturing the error in Sentry.

View File

@@ -137,7 +137,7 @@ The `authenticate` middleware function accepts three parameters:
2. An array of types of authentication methods allowed. Both `user` and `customer` scopes support `session` and `bearer`. The `admin` scope also supports the `api-key` authentication method.
3. An optional object of configurations accepting the following properties:
- `allowUnauthenticated`: (default: `false`) A boolean indicating whether authentication is required. For example, you may have an API route where you want to access the logged-in customer if available, but guest customers can still access it too.
- `allowUnregistered` (default: `false`): A boolean indicating if unregistered users should be allowed access. This is useful when you want to allow users who arent registered to access certain routes.
- `allowUnregistered` (default: `false`): A boolean indicating whether users can access this route with a registration token, instead of an authentication token. This is useful if you have a custom actor type, such as `manager`, and you're creating an API route that allows these users to register themselves. Learn more in the [Custom Actor-Type Guide](!resources!/commerce-modules/auth/create-actor-type).
### Example: Custom Actor Type

View File

@@ -149,9 +149,9 @@ export const GET = async (
fields: ["*", "sales_channels.*"],
filters: {
sales_channels: {
id: "sc_123"
}
}
id: "sc_123",
},
},
})
res.json({ products })
@@ -197,7 +197,7 @@ export default defineLink(
{
linkable: BrandModule.linkable.brand,
filterable: ["id", "name"],
},
}
)
```
@@ -238,9 +238,9 @@ export const GET = async (
fields: ["*", "brand.*"],
filters: {
brand: {
name: "Acme"
}
}
name: "Acme",
},
},
})
res.json({ products })
@@ -291,19 +291,19 @@ export const GET = async (
const {
data: products,
metadata
metadata,
} = await query.index({
entity: "product",
fields: ["*", "brand.*"],
filters: {
brand: {
name: "Acme"
}
name: "Acme",
},
},
pagination: {
take: 10,
skip: 0,
}
},
})
res.json({ products, ...metadata })
@@ -370,9 +370,9 @@ const {
filters: {
brand: {
id: {
$ne: null
}
}
$ne: null,
},
},
},
})
```
@@ -390,9 +390,9 @@ const {
filters: {
brand: {
name: {
$like: "Acme%"
}
}
$like: "Acme%",
},
},
},
})
```
@@ -419,14 +419,14 @@ export const GET = async (
const {
data: products,
metadata
metadata,
} = await query.index({
entity: "product",
...req.queryConfig,
filters: {
brand: {
name: "Acme"
}
name: "Acme",
},
},
})
@@ -467,9 +467,9 @@ const retrieveBrandsStep = createStep(
products: {
id: {
$ne: null,
}
}
}
},
},
},
})
return new StepResponse(brands)

View File

@@ -22,7 +22,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/admin/widgets/page.mdx": "2024-12-09T16:43:24.260Z",
"app/learn/fundamentals/data-models/page.mdx": "2025-03-18T07:55:56.252Z",
"app/learn/fundamentals/modules/remote-link/page.mdx": "2024-09-30T08:43:53.127Z",
"app/learn/fundamentals/api-routes/protected-routes/page.mdx": "2025-05-09T07:57:32.929Z",
"app/learn/fundamentals/api-routes/protected-routes/page.mdx": "2025-06-19T16:04:36.064Z",
"app/learn/fundamentals/workflows/add-workflow-hook/page.mdx": "2024-12-09T14:42:39.693Z",
"app/learn/fundamentals/events-and-subscribers/data-payload/page.mdx": "2025-05-01T15:30:08.421Z",
"app/learn/fundamentals/workflows/advanced-example/page.mdx": "2024-09-11T10:46:59.975Z",
@@ -59,7 +59,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/modules/service-constraints/page.mdx": "2025-03-18T15:12:46.006Z",
"app/learn/fundamentals/api-routes/responses/page.mdx": "2024-10-21T13:30:21.367Z",
"app/learn/fundamentals/api-routes/validation/page.mdx": "2025-03-24T06:52:47.896Z",
"app/learn/fundamentals/api-routes/errors/page.mdx": "2024-12-09T16:44:19.781Z",
"app/learn/fundamentals/api-routes/errors/page.mdx": "2025-06-19T16:09:08.563Z",
"app/learn/fundamentals/admin/constraints/page.mdx": "2024-10-21T13:30:21.366Z",
"app/learn/debugging-and-testing/testing-tools/modules-tests/module-example/page.mdx": "2025-03-18T15:07:22.640Z",
"app/learn/debugging-and-testing/testing-tools/modules-tests/page.mdx": "2025-03-24T06:54:21.249Z",
@@ -122,5 +122,5 @@ export const generatedEditDates = {
"app/learn/fundamentals/workflows/errors/page.mdx": "2025-04-25T14:26:25.000Z",
"app/learn/fundamentals/api-routes/override/page.mdx": "2025-05-09T08:01:24.493Z",
"app/learn/fundamentals/module-links/index/page.mdx": "2025-05-23T07:57:58.958Z",
"app/learn/fundamentals/module-links/index-module/page.mdx": "2025-05-23T08:36:13.009Z"
"app/learn/fundamentals/module-links/index-module/page.mdx": "2025-06-19T16:02:05.665Z"
}

View File

@@ -6231,21 +6231,21 @@ export const GET = async (
}
```
The `MedusaError` class accepts in its constructor two parameters:
The `MedusaError` class accepts two parameters in its constructor:
1. The first is the error's type. `MedusaError` has a static property `Types` that you can use. `Types` is an enum whose possible values are explained in the next section.
2. The second is the message to show in the error response.
### Error Object in Response
The error object returned in the response has two properties:
The error object returned in the response has three properties:
- `type`: The error's type.
- `message`: The error message, if available.
- `code`: A common snake-case code. Its values can be:
- `invalid_request_error` for the `DUPLICATE_ERROR` type.
- `api_error`: for the `DB_ERROR` type.
- `invalid_state_error` for `CONFLICT` error type.
- `api_error` for the `DB_ERROR` type.
- `invalid_state_error` for the `CONFLICT` error type.
- `unknown_error` for any unidentified error type.
- For other error types, this property won't be available unless you provide a code as a third parameter to the `MedusaError` constructor.
@@ -6260,7 +6260,7 @@ The error object returned in the response has two properties:
|\`UNAUTHORIZED\`|Indicates that a user is not authorized to perform an action or access a route.|\`401\`|
|\`NOT\_FOUND\`|Indicates that the requested resource, such as a route or a record, isn't found.|\`404\`|
|\`NOT\_ALLOWED\`|Indicates that an operation isn't allowed.|\`400\`|
|\`CONFLICT\`|Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored for a default message.|\`409\`|
|\`CONFLICT\`|Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored in favor of a default message.|\`409\`|
|\`PAYMENT\_AUTHORIZATION\_ERROR\`|Indicates an error has occurred while authorizing a payment.|\`422\`|
|Other error types|Any other error type results in an |\`500\`|
@@ -6299,13 +6299,53 @@ export default defineMiddlewares({
The `errorHandler` property's value is a function that accepts four parameters:
1. The error thrown. Its type can be `MedusaError` or any other thrown error type.
1. The error thrown. Its type can be `MedusaError` or any other error type.
2. A request object of type `MedusaRequest`.
3. A response object of type `MedusaResponse`.
4. A function of type MedusaNextFunction that executes the next middleware in the stack.
4. A function of type `MedusaNextFunction` that executes the next middleware in the stack.
This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message.
### Re-Use Default Error Handler
In some use cases, you may not want to override the default error handler but rather perform additional actions as part of the original error handler. For example, you might want to capture the error in a third-party service like Sentry.
In those cases, you can import the default error handler from the Medusa Framework and use it in your custom error handler, along with your custom logic.
For example:
```ts title="src/api/middlewares.ts" highlights={defaultErrorHandlerHighlights}
import {
defineMiddlewares,
errorHandler,
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { MedusaError } from "@medusajs/framework/utils"
// assuming you have Sentry set up in your project
import * as Sentry from "@sentry/node"
const originalErrorHandler = errorHandler()
export default defineMiddlewares({
errorHandler: (
error: MedusaError | any,
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction
) => {
// for example, capture the error in Sentry
Sentry.captureException(error)
return originalErrorHandler(error, req, res, next)
},
})
```
In this example, you import the `errorHandler` function from the Medusa Framework. Then, you call it to get the original error handler function.
Finally, you use it in your custom error handler after performing your custom logic, such as capturing the error in Sentry.
# HTTP Methods
@@ -7246,7 +7286,7 @@ The `authenticate` middleware function accepts three parameters:
2. An array of types of authentication methods allowed. Both `user` and `customer` scopes support `session` and `bearer`. The `admin` scope also supports the `api-key` authentication method.
3. An optional object of configurations accepting the following properties:
- `allowUnauthenticated`: (default: `false`) A boolean indicating whether authentication is required. For example, you may have an API route where you want to access the logged-in customer if available, but guest customers can still access it too.
- `allowUnregistered` (default: `false`): A boolean indicating if unregistered users should be allowed access. This is useful when you want to allow users who arent registered to access certain routes.
- `allowUnregistered` (default: `false`): A boolean indicating whether users can access this route with a registration token, instead of an authentication token. This is useful if you have a custom actor type, such as `manager`, and you're creating an API route that allows these users to register themselves. Learn more in the [Custom Actor-Type Guide](https://docs.medusajs.com/resources/commerce-modules/auth/create-actor-type/index.html.md).
### Example: Custom Actor Type
@@ -11102,9 +11142,9 @@ export const GET = async (
fields: ["*", "sales_channels.*"],
filters: {
sales_channels: {
id: "sc_123"
}
}
id: "sc_123",
},
},
})
res.json({ products })
@@ -11142,7 +11182,7 @@ export default defineLink(
{
linkable: BrandModule.linkable.brand,
filterable: ["id", "name"],
},
}
)
```
@@ -11183,9 +11223,9 @@ export const GET = async (
fields: ["*", "brand.*"],
filters: {
brand: {
name: "Acme"
}
}
name: "Acme",
},
},
})
res.json({ products })
@@ -11231,19 +11271,19 @@ export const GET = async (
const {
data: products,
metadata
metadata,
} = await query.index({
entity: "product",
fields: ["*", "brand.*"],
filters: {
brand: {
name: "Acme"
}
name: "Acme",
},
},
pagination: {
take: 10,
skip: 0,
}
},
})
res.json({ products, ...metadata })
@@ -11310,9 +11350,9 @@ const {
filters: {
brand: {
id: {
$ne: null
}
}
$ne: null,
},
},
},
})
```
@@ -11330,9 +11370,9 @@ const {
filters: {
brand: {
name: {
$like: "Acme%"
}
}
$like: "Acme%",
},
},
},
})
```
@@ -11359,14 +11399,14 @@ export const GET = async (
const {
data: products,
metadata
metadata,
} = await query.index({
entity: "product",
...req.queryConfig,
filters: {
brand: {
name: "Acme"
}
name: "Acme",
},
},
})
@@ -11402,9 +11442,9 @@ const retrieveBrandsStep = createStep(
products: {
id: {
$ne: null,
}
}
}
},
},
},
})
return new StepResponse(brands)