Strapi is a headless content management system for building backend applications. With JavaScript coding knowledge, Strapi allows you to customize your backend with policies, middlewares, controllers, services, models, and webhooks
In this article, you’ll learn more about policies, and how to use them. So, Let’s get started!
Imagine you want to make a deposit to a bank, so you go into the building. As you may know, banks have policies that restrict the things you can bring into the building. Therefore, the bank may have security checkpoints at its entrance to ensure no one violates any policy.
If you violate a policy, the security tells you to exit the building and fix the issue, maybe worse. If you don’t violate a policy, the security allows you into the building.
In computing, requests carry data to the server through routes. The routes are entry points for requests. Strapi allows you to create policies that checks these requests at a route before they get into the server. A request must not violate any policy to be successful.
In technical terms, a policy is a function that defines a rule for all requests entering the server. If the function returns true, the request is allowed into the server. If the function returns false, the request is rejected. You can create policies with your own custom rules.
In Strapi, you can create the following types of policies:
You can initialize a global policy in the following ways:
Initializing a global policy
To initialize a global policy run the yarn strapi generate
command and follow these steps:
$ strapi generate
? Strapi Generators policy - Generate a policy for an API
? Policy name first-policy
? Where do you want to add this policy? Add policy to root of project
✔ ++ /policies/first-policy.js
✨ Done in 28.56s.
I just created a global policy name first-policy
, it is created inside the src/policies
folder.
Here is the example of the file that is automatically created:
1module.exports = (policyContext, config, { strapi }) => {
2 // Add your own logic here.
3 strapi.log.info("In first-policy policy.");
4
5 const canDoSomething = true;
6
7 if (canDoSomething) {
8 return true;
9 }
10
11 return false;
12};
I am also going to add another console.log
in our policy so it is easier to spot.
1strapi.log.info("In first-policy policy.");
2console.log("############### GLOBAL POLICY #################");
Let's create a basic content-type
that we will use later in our real life
example, but let's set it up now so we can test test out the global
policy that we just created.
If you haven't created your Strapi app you can do so now by using the npx create-strapi-app@latest my-project --quickstart
. Once your project is created, follow the steps below.
Step 1: Set up the items content-type
The first thing you need to do is, set up the content-type to store data. The content-type in this project will have name
, code
, and price
fields.
To create the content-type, follow these steps:
Go ahead and add at least one item and publish.
Step 2: Set up the routes
To continue with the project, you need to set up an API that allows external applications access and manipulate the data in Item
. The API will provide routes for getting, adding, modifying, and deleting data.
To set up the routes for Item
, follow the below steps:
Click public to access the configuration for public users.
item
.We can now test our endpoint either using something like Insomnia or Postman.
Or if we are making a GET
request we can test it in the browser locally by navigating to http://localhost:1337/api/items
.
Here is the response we get.
1{
2 "data": [
3 {
4 "id": 1,
5 "attributes": {
6 "name": "Desk",
7 "code": "98932",
8 "price": 10.99,
9 "createdAt": "2023-09-06T17:01:53.934Z",
10 "updatedAt": "2023-09-06T17:01:54.508Z",
11 "publishedAt": "2023-09-06T17:01:54.507Z"
12 }
13 }
14 ],
15 "meta": {
16 "pagination": {
17 "page": 1,
18 "pageSize": 25,
19 "pageCount": 1,
20 "total": 1
21 }
22 }
23}
Registering a global policy to a route Now let's register our policy that we just created to a route. We can do this you in the route’s API configuration file.
Follow these steps to configure a global policy:
./src/api/item/routes/item.js
.createCoreRouter
.1const { createCoreRouter } = require("@strapi/strapi").factories;
2
3module.exports = createCoreRouter("api::item.item", {
4 config: {
5 find: {
6 policies: [
7 // point to a registered policy
8 "global::first-policy",
9
10 // point to a registered policy with custom configuration
11 // { name: "global::policy-name", config: {} },
12
13 // pass policy function directly
14 // (policyContext, config, { strapi }) => {
15 // return true;
16 // },
17 ],
18 },
19 },
20});
Let's make another GET
request to http://localhost:1337/api/items
so we can see if we are getting the console log message from our policy.
We should now see our log from our policy in our console now.
[2023-09-06 12:15:03.337] info: In first-policy policy.
############### GLOBAL POLICY #################
[2023-09-06 12:15:03.341] http: GET /api/items (13 ms) 200
Did you know?
You can use the
yarn strapi console
to start the our Strapi app in an interactive shell environment.
Then you can see all the available policies by running the strapi.policies
.
You should see the following output.
1{
2 'admin::isAuthenticatedAdmin': [Function (anonymous)],
3 'admin::hasPermissions': {
4 name: 'admin::hasPermissions',
5 validator: [Function: wrappedValidator],
6 handler: [Function: handler]
7 },
8 'admin::isTelemetryEnabled': {
9 name: 'admin::isTelemetryEnabled',
10 validator: [Function: wrappedValidator],
11 handler: [Function: handler]
12 },
13 'global::first-policy': [Function (anonymous)],
14 'plugin::content-manager.has-draft-and-publish': [Function (anonymous)],
15 'plugin::content-manager.hasPermissions': {
16 name: 'plugin::content-manager.hasPermissions',
17 validator: [Function: wrappedValidator],
18 handler: [Function: handler]
19 }
20}
You can see our global policy that we created.
Creating a global policy manually
All what the yarn strapi generate
command does is, create ./src/policies/[policy-name].js
file with boilerplate, nothing else.
You can recreate the action manually.
To initialize a policy, create a ./src/policies/[policy-name].js
file. You can optionally paste a boilerplate to get you started quickly. For example:
1"use strict";
2
3module.exports = (policyContext, config, { strapi }) => {
4 // Add your own logic here.
5 strapi.log.info("In the policy.");
6
7 const canDoSomething = true;
8
9 if (canDoSomething) {
10 return true;
11 }
12
13 return false;
14};
Global policies use
global::policy-name
for referencing. Other types of policies have different references.
Let's now look how to create API policies.
Like Global policies you can initialize an API policy in similar ways:
Initializing an API policy with yarn strapi generate
To initialize an API policy run the yarn strapi generate
command and follow these steps:
$ strapi generate
? Strapi Generators policy - Generate a policy for an API
? Policy name second-policy
? Where do you want to add this policy? Add policy to an existing API
? Which API is this for? item
✔ ++ /api/item/policies/second-policy.js
✨ Done in 22.58s.
You can find your newly generate policy in the src/api/item/policies/second-policy.js
.
Here is what the file looks like.
1module.exports = (policyContext, config, { strapi }) => {
2 // Add your own logic here.
3 strapi.log.info("In second-policy policy.");
4
5 const canDoSomething = true;
6
7 if (canDoSomething) {
8 return true;
9 }
10
11 return false;
12};
I am also going to add another console.log
in our policy so it is easier to spot.
1strapi.log.info("In second-policy policy.");
2console.log("############### API POLICY #################");
Registering an API policy in a route The steps to configure an API policy is similar to configuring a global policy:
Open ./src/api/item/routes/item.js
.
We have already added our global policy here. Let's now add our API policy.
If you don't remember the exact name of your policy. You can find it with our previous trick.
Start the Strapi console with yarn strapi console
and type the following strapi.policies
and pres enter.
You should see the following list:
1{
2 'admin::isAuthenticatedAdmin': [Function (anonymous)],
3 'admin::hasPermissions': {
4 name: 'admin::hasPermissions',
5 validator: [Function: wrappedValidator],
6 handler: [Function: handler]
7 },
8 'admin::isTelemetryEnabled': {
9 name: 'admin::isTelemetryEnabled',
10 validator: [Function: wrappedValidator],
11 handler: [Function: handler]
12 },
13 'global::first-policy': [Function (anonymous)],
14 'api::item.second-policy': [Function (anonymous)],
15 'plugin::content-manager.has-draft-and-publish': [Function (anonymous)],
16 'plugin::content-manager.hasPermissions': {
17 name: 'plugin::content-manager.hasPermissions',
18 validator: [Function: wrappedValidator],
19 handler: [Function: handler]
20 }
21}
Nice, you can now see our second policy api::item.second-policy
.
Here is our updated ./src/api/item/routes/item.js
file.
1const { createCoreRouter } = require("@strapi/strapi").factories;
2
3module.exports = createCoreRouter("api::item.item", {
4 config: {
5 find: {
6 policies: [
7 // point to a registered policy
8 "global::first-policy",
9 "api::item.second-policy",
10
11 // point to a registered policy with custom configuration
12 // { name: "global::policy-name", config: {} },
13
14 // pass policy function directly
15 // (policyContext, config, { strapi }) => {
16 // return true;
17 // },
18 ],
19 },
20 },
21});
Let's make another GET
request to http://localhost:1337/api/items
so we can see if we are getting the console log message from our second policy.
We should now see our log from our both policies.
[2023-09-06 12:55:43.107] info: In first-policy policy.
############### GLOBAL POLICY #################
[2023-09-06 12:55:43.108] info: In second-policy policy.
############### API POLICY #################
[2023-09-06 12:55:43.111] http: GET /api/items (19 ms) 200
Routes in scope of an API policy uses
policy-name
for to reference the policy. To reference policies registered in another API, useapi::api-name.policy-name
.
Creating an API policy manually
To manually create a policy, create a .src/api/[api-name]/policies/[policy-name].js
file. Optionally, paste this boilerplate into the file to get started:
1"use strict";
2
3module.exports = (policyContext, config, { strapi }) => {
4 // Add your own logic here.
5 strapi.log.info("In the policy.");
6
7 const canDoSomething = true;
8
9 if (canDoSomething) {
10 return true;
11 }
12
13 return false;
14};
Policies can also be scoped to plugins. These types of policies are called plugin policies. An example of a plugin policy is isAuthenticated
from Users & Permissions plugin. When a route uses the isAuthenticated
policy, any user sending a request needs to be authenticated for the request to be successful.
There are two ways of initialize a plugin policy:
Initializing a plugin policy with strapi generate
Run strapi generate
, follow these steps to initialize a plugin policy:
Creating manually
To manually create a policy, create a ./src/plugins/[plugin-name]/policies/
file. Optionally, you can paste this boilerplate to get you started:
1"use strict";
2
3module.exports = (policyContext, config, { strapi }) => {
4 // Add your own logic here.
5 strapi.log.info("In the policy.");
6
7 const canDoSomething = true;
8
9 if (canDoSomething) {
10 return true;
11 }
12
13 return false;
14};
Configuring in routes Configuring a plugin policy is similar to both API and global policies:
./src/api/[api-name]/routes/router.js
.createCoreRouter
.policies
array.1const { createCoreRouter } = require("@strapi/strapi").factories;
2
3module.exports = createCoreRouter("api::restaurant.restaurant", {
4 config: {
5 find: {
6 policies: [
7 // point to a registered policy
8 "plugin::plugin-name.policy-name",
9
10 // point to a registered policy with custom configuration
11 { name: "plugin::plugin-name.policy-name", config: {} },
12
13 // pass policy function directly
14 (policyContext, config, { strapi }) => {
15 return true;
16 },
17 ],
18 },
19 },
20});
To reference a plugin policy, use
plugin::plugin-name.policy-name
.
You can also add plugin policies to api routes inside your plugin, but this is beyond the scope of this tutorial. Since we would need to have a plugin created with api end point.
Just getting a request error when a policy fails is not enough in some cases. Some you might want to know why the policy failed.
An example is a route with several policies attached to it. By default, all policy failures respond in the same exact way, meaning that tracing the failure will be difficult.
Another example is policies that fail on more than one condition. Without a detailed error message you can’t easily tell which condition made the policy fail.
You can configure a policy to return messages when it fails. The process is simple, and here’s how you can do it:
PolicyError
class into the policy file:1const utils = require("@strapi/utils");
2const { PolicyError } = utils.errors;
PolicyError
object with the error message and error details:1if (error) {
2 throw new PolicyError(error.message, error.details);
3} else {
4 return true;
5}
Imagine you are building a backend application for a store. You are currently working on an API to fetch, create, and modify items on the backend. To create an item, the backend requires the item’s name, code, and price.
The following steps will guide you through building the project and policy. So initialize a new Strapi project and follow the steps that follows.
We are going to use our current Strapi app with our item
collection type.
Build the policy
The policy for this project will make sure that any data sent to Item
's create
route follows a format. The format will be described using a schema.
If you don’t know what a schema is, It is a narration of how data should be structured.
The schema will be built with a JavaScript library called Joi. If you don’t understand how Joi works, you can read this API reference. To install Joi, open the project’s root folder in your terminal, then run any of these commands:
# yarn
$ yarn add joi
# npm
$ npm install joi
Now, you can start working on the the policy.
First, create a new API policy in src/api/item/policies
. You can name the file valid-input.js
.
Now, paste the code below into the policy ./src/api/item/policies/valid-input.js`:
1"use strict";
2const Joi = require("joi");
3const utils = require("@strapi/utils");
4const { PolicyError } = utils.errors;
5
6/**
7 * `valid input` policy
8 */
9
10module.exports = (policyContext, config, { strapi }) => {
11 // Get request body data
12 const requestData = policyContext.request.body["data"];
13
14 // Create a schema to validate request body data
15 const schema = Joi.object({
16 name: Joi.string().required(),
17 code: Joi.string()
18 .pattern(/^([a-zA-Z0-9]{4}-){3}[a-zA-Z0-9]{4}$/) // Test is the format structured as (four groups of alphanumeric characters separated by hyphens)
19 .required(),
20 price: Joi.number().required(),
21 });
22
23 // Validate the request body data
24 const { error } = schema.validate(requestData);
25
26 if (error) {
27 throw new PolicyError("Invalid input data", error.details);
28 } else {
29 // Allow request to proceed if request body data is valid
30 return true;
31 }
32};
The way this policy works is:
error
object is destructured.Let's Register Our Policy
To register the policy follow these steps:
Open the ./src/api/item/routes/item.js
file and add our newly created policy.
Inside the config
object, let's add the following.
1create: {
2 policies: ["api::item.valid-input"],
3}
The item.js
file should look like the following:
1const { createCoreRouter } = require("@strapi/strapi").factories;
2
3module.exports = createCoreRouter("api::item.item", {
4 config: {
5 create: {
6 policies: ["api::item.valid-input"],
7 },
8 find: {
9 policies: [
10 // point to a registered policy
11 "global::first-policy",
12 "api::item.second-policy",
13
14 // point to a registered policy with custom configuration
15 // { name: "global::policy-name", config: {} },
16
17 // pass policy function directly
18 // (policyContext, config, { strapi }) => {
19 // return true;
20 // },
21 ],
22 },
23 },
24});
Let's test our new policy
Since we will be making a POST
request we will need to use Postman or Insomnia to test our endpoint.
I will be using Insomnia.
Inside Insomnia I will make the following request.
Notice how I am leaving all the fields blank.
When we run the following request we will get the following error triggered by our policy.
1{
2 "data": null,
3 "error": {
4 "status": 403,
5 "name": "PolicyError",
6 "message": "Invalid input data",
7 "details": [
8 {
9 "message": "\"name\" is not allowed to be empty",
10 "path": ["name"],
11 "type": "string.empty",
12 "context": {
13 "label": "name",
14 "value": "",
15 "key": "name"
16 }
17 }
18 ]
19 }
20}
Let's now make a request will all fields filled.
We will pass the following data:
1{
2 "data": {
3 "name": "test",
4 "code": "12e3-4r3d-35te-345a",
5 "price": "9.99"
6 }
7}
Now that we pass valid data, we return a 200
status code and our item is created.
Nice. Our policy is working. Great job.
In this article, I gave an overview of Strapi policies, with the different types of policies, and how they work. I also try to give a tutorial on building a real-life policy. If you find anything hard to understand, let me know in the comments.
If you want to learn more about Strapi policies, be sure to check the following links:
You can find the example project here
Chigozie is a technical writer. He started coding since he was young, and entered technical writing recently.