Examples cookbook: Custom policies
This page is part of the back end customization examples cookbook. Please ensure you've read its introduction.
Out of the box, FoodAdvisor does not use any custom policies or route middlewares that could control access to content type endpoints.
In Strapi, controlling access to a content-type endpoint can be done either with a policy or route middleware:
- policies are read-only and allow a request to pass or return an error,
- while route middlewares can perform additional logic.
In our example, let's use a policy.
Creating a custom policy
💭 Context:
Let's say we would like to customize the backend of FoodAdvisor to prevent restaurant owners from creating fake reviews for their businesses using a form previously created on the front-end website.
🎯 Goals:
- Create a new folder for policies to apply only to the "Reviews" collection type.
- Create a new policy file.
- Use the
findMany()
method from the Entity Service API to get information about the owner of a restaurant when the/reviews
endpoint is reached. - Return an error if the authenticated user is the restaurant's owner, or let the request pass in other cases.
Additional information can be found in the Policies, Routes, and Entity Service API documentation.
🧑💻 Code example:
In the /api
folder of the FoodAdvisor project, create a new src/api/review/policies/is-owner-review.js
file with the following code:
module.exports = async (policyContext, config, { strapi }) => {
const { body } = policyContext.request;
const { user } = policyContext.state;
// Return an error if there is no authenticated user with the request
if (!user) {
return false;
}
/**
* Queries the Restaurants collection type
* using the Entity Service API
* to retrieve information about the restaurant's owner.
*/
const [restaurant] = await strapi.entityService.findMany(
'api::restaurant.restaurant',
{
filters: {
slug: body.restaurant,
},
populate: ['owner'],
}
);
if (!restaurant) {
return false;
}
/**
* If the user submitting the request is the restaurant's owner,
* we don't allow the review creation.
*/
if (user.id === restaurant.owner.id) {
return false;
}
return true;
};
Policies or route middlewares should be declared in the configuration of a route to actually control access. Read more about routes in the reference documentation or see an example in the routes cookbook.
Sending custom errors through policies
💭 Context:
Out of the box, FoodAdvisor sends a default error when a policy refuses access to a route. Let's say we want to customize the error sent when the previously created custom policy does not allow creating a review.
🎯 Goal:
Configure the custom policy to throw a custom error instead of the default error.
Additional information can be found in the Error handling documentation.
🧑💻 Code example:
In the /api
folder of the FoodAdvisor project, update the previously created is-owner-review
custom policy as follows (highlighted lines are the only modified lines):
const { errors } = require('@strapi/utils');
const { PolicyError } = errors;
module.exports = async (policyContext, config, { strapi }) => {
const { body } = policyContext.request;
const { user } = policyContext.state;
// Return an error if there is no authenticated user with the request
if (!user) {
return false;
}
/**
* Queries the Restaurants collection type
* using the Entity Service API
* to retrieve information about the restaurant's owner.
*/
const filteredRestaurants = await strapi.entityService.findMany(
'api::restaurant.restaurant',
{
filters: {
slug: body.restaurant,
},
populate: ['owner'],
}
);
const restaurant = filteredRestaurants[0];
if (!restaurant) {
return false;
}
/**
* If the user submitting the request is the restaurant's owner,
* we don't allow the review creation.
*/
if (user.id === restaurant.owner.id) {
/**
* Throws a custom policy error
* instead of just returning false
* (which would result into a generic Policy Error).
*/
throw new PolicyError('The owner of the restaurant cannot submit reviews', {
errCode: 'RESTAURANT_OWNER_REVIEW', // can be useful for identifying different errors on the front end
});
}
return true;
};
Responses sent with default policy error vs. custom policy error:
Using custom errors on the front end
💭 Context:
Out of the box, the Next.js-powered front-end website provided with FoodAdvisor does not display errors or success messages on the front-end website when accessing content. For instance, the website will not inform the user when adding a new review with a previously created form is not possible.
Let's say we want to customize the front end of FoodAdvisor to catch the custom error thrown by a previously created custom policy and display it to the user with a React Hot Toast notification. As a bonus, another toast notification will be displayed when a review is successfully created.

🎯 Goals:
- Catch the error on the front-end website and display it within a notification.
- Send another notification in case the policy allows the creation of a new review.
🧑💻 Code example:
In the /client
folder of the FoodAdvisor project, you could update the previously created new-review
component as follows (modified lines are highlighted):
Example front-end code to display toast notifications for custom errors or successful review creation:
Learn more about how to configure custom routes to use your custom policies, and how these custom routes can be used to tweak a Strapi-based application.