Error handling
Page summary:
Strapiβs APIs return errors in a consistent structure and let backend code throw custom exceptions for controllers, services, policies, or lifecycles. This documentation lists error classes, context helpers, and examples for crafting meaningful responses.
Strapi is natively handling errors with a standard format.
There are 2 use cases for error handling:
- As a developer querying content through the REST or GraphQL APIs, you might receive errors in response to the requests.
- As a developer customizing the backend of your Strapi application, you could use controllers and services to throw errors.
Receiving errorsβ
Errors are included in the response object with the error
key and include information such as the HTTP status code, the name of the error, and additional information.
REST errorsβ
Errors thrown by the REST API are included in the response that has the following format:
{
"data": null,
"error": {
"status": "", // HTTP status
"name": "", // Strapi error name ('ApplicationError' or 'ValidationError')
"message": "", // A human readable error message
"details": {
// error info specific to the error type
}
}
}
GraphQL errorsβ
Errors thrown by the GraphQL API are included in the response that has the following format:
{ "errors": [
{
"message": "", // A human reable error message
"extensions": {
"error": {
"name": "", // Strapi error name ('ApplicationError' or 'ValidationError'),
"message": "", // A human reable error message (same one as above);
"details": {}, // Error info specific to the error type
},
"code": "" // GraphQL error code (ex: BAD_USER_INPUT)
}
}
],
"data": {
"graphQLQueryName": null
}
}
Throwing errorsβ
Controllers and middlewaresβ
The recommended way to throw errors when developing any custom logic with Strapi is to have the controller or middleware respond with the correct status and body.
This can be done by calling an error function on the context (i.e. ctx
). Available error functions are listed in the http-errors documentation but their name should be lower camel-cased to be used by Strapi (e.g. badRequest
).
Error functions accept 2 parameters that correspond to the error.message
and error.details
attributes received by a developer querying the API:
- the first parameter of the function is the error
message
- and the second one is the object that will be set as
details
in the response received
- JavaScript
- TypeScript
// path: ./src/api/[api-name]/controllers/my-controller.js
module.exports = {
renameDog: async (ctx, next) => {
const newName = ctx.request.body.name;
if (!newName) {
return ctx.badRequest('name is missing', { foo: 'bar' })
}
ctx.body = strapi.service('api::dog.dog').rename(newName);
}
}
// path: ./src/api/[api-name]/middlewares/my-middleware.js
module.exports = async (ctx, next) => {
const newName = ctx.request.body.name;
if (!newName) {
return ctx.badRequest('name is missing', { foo: 'bar' })
}
await next();
}
// path: ./src/api/[api-name]/controllers/my-controller.ts
export default {
renameDog: async (ctx, next) => {
const newName = ctx.request.body.name;
if (!newName) {
return ctx.badRequest('name is missing', { foo: 'bar' })
}
ctx.body = strapi.service('api::dog.dog').rename(newName);
}
}
// path: ./src/api/[api-name]/middlewares/my-middleware.ts
export default async (ctx, next) => {
const newName = ctx.request.body.name;
if (!newName) {
return ctx.badRequest('name is missing', { foo: 'bar' })
}
await next();
}
Services and models lifecyclesβ
Once you are working at a deeper layer than the controllers or middlewares there are dedicated error classes that can be used to throw errors. These classes are extensions of Node `Error` class and are specifically targeted for certain use-cases.
These error classes are imported through the @strapi/utils
package and can be called from several different layers. The following examples use the service layer but error classes are not just limited to services and model lifecycles. When throwing errors in the model lifecycle layer, it's recommended to use the ApplicationError
class so that proper error messages are shown in the admin panel.
See the default error classes section for more information on the error classes provided by Strapi.
Example: Throwing an error in a service**β
This example shows wrapping a core service and doing a custom validation on the create
method:
- JavaScript
- TypeScript
const { errors } = require('@strapi/utils');
const { ApplicationError } = errors;
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
async create(params) {
let okay = false;
// Throwing an error will prevent the restaurant from being created
if (!okay) {
throw new errors.ApplicationError('Something went wrong', { foo: 'bar' });
}
const result = await super.create(params);
return result;
}
});
import { errors } from '@strapi/utils';
import { factories } from '@strapi/strapi';
const { ApplicationError } = errors;
export default factories.createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
async create(params) {
let okay = false;
// Throwing an error will prevent the restaurant from being created
if (!okay) {
throw new errors.ApplicationError('Something went wrong', { foo: 'bar' });
}
const result = await super.create(params);
return result;
}
}));
Example: Throwing an error in a model lifecycle**β
This example shows building a custom model lifecycle and being able to throw an error that stops the request and will return proper error messages to the admin panel. Generally you should only throw an error in beforeX
lifecycles, not afterX
lifecycles.
- JavaScript
- TypeScript
const { errors } = require('@strapi/utils');
const { ApplicationError } = errors;
module.exports = {
beforeCreate(event) {
let okay = false;
// Throwing an error will prevent the entity from being created
if (!okay) {
throw new errors.ApplicationError('Something went wrong', { foo: 'bar' });
}
},
};
import { errors } from '@strapi/utils';
const { ApplicationError } = errors;
export default {
beforeCreate(event) {
let okay = false;
// Throwing an error will prevent the entity from being created
if (!okay) {
throw new errors.ApplicationError('Something went wrong', { foo: 'bar' });
}
},
};
Policiesβ
Policies are a special type of middleware that are executed before a controller. They are used to check if the user is allowed to perform the action or not. If the user is not allowed to perform the action and a return false
is used then a generic error will be thrown. As an alternative, you can throw a custom error message using a nested class extensions from the Strapi ForbiddenError
class, ApplicationError
class (see Default error classes for both classes), and finally the Node `Error` class.
The PolicyError
class is available from @strapi/utils
package and accepts 2 parameters:
- the first parameter of the function is the error
message
- (optional) the second parameter is the object that will be set as
details
in the response received; a best practice is to set apolicy
key with the name of the policy that threw the error.
Example: Throwing a PolicyError in a custom policyβ
This example shows building a custom policy that will throw a custom error message and stop the request.
- JavaScript
- TypeScript
const { errors } = require('@strapi/utils');
const { PolicyError } = errors;
module.exports = (policyContext, config, { strapi }) => {
let isAllowed = false;
if (isAllowed) {
return true;
} else {
throw new errors.PolicyError('You are not allowed to perform this action', {
policy: 'my-policy',
myCustomKey: 'myCustomValue',
});
}
}
import { errors } from '@strapi/utils';
const { PolicyError } = errors;
export default (policyContext, config, { strapi }) => {
let isAllowed = false;
if (isAllowed) {
return true;
} else {
throw new errors.PolicyError('You are not allowed to perform this action', {
policy: 'my-policy',
myCustomKey: 'myCustomValue',
});
}
};