);
};
export default EnhancedLogin;
```
# Custom middlewares
Source: https://docs.strapi.io/cms/backend-customization/examples/middlewares
## Populating an analytics dashboard in Google Sheets with a custom middleware
Description: Additional information can be found in the official .
(Source: https://docs.strapi.io/cms/backend-customization/examples/middlewares#populating-an-analytics-dashboard-in-google-sheets-with-a-custom-middleware)
Language: JavaScript
File path: src/api/restaurant/middlewares/utils.js
```js
const { google } = require('googleapis');
const createGoogleSheetClient = async ({
keyFile,
sheetId,
tabName,
range,
}) => {
async function getGoogleSheetClient() {
const auth = new google.auth.GoogleAuth({
keyFile,
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});
const authClient = await auth.getClient();
return google.sheets({
version: 'v4',
auth: authClient,
});
}
const googleSheetClient = await getGoogleSheetClient();
const writeGoogleSheet = async (data) => {
googleSheetClient.spreadsheets.values.append({
spreadsheetId: sheetId,
range: `${tabName}!${range}`,
valueInputOption: 'USER_ENTERED',
insertDataOption: 'INSERT_ROWS',
resource: {
majorDimension: 'ROWS',
values: data,
},
});
};
const updateoogleSheet = async (cell, data) => {
googleSheetClient.spreadsheets.values.update({
spreadsheetId: sheetId,
range: `${tabName}!${cell}`,
valueInputOption: 'USER_ENTERED',
resource: {
majorDimension: 'ROWS',
values: data,
},
});
};
const readGoogleSheet = async () => {
const res = await googleSheetClient.spreadsheets.values.get({
spreadsheetId: sheetId,
range: `${tabName}!${range}`,
});
return res.data.values;
};
return {
writeGoogleSheet,
updateoogleSheet,
readGoogleSheet,
};
};
module.exports = {
createGoogleSheetClient,
};
```
Language: JavaScript
File path: src/api/restaurant/middlewares/analytics.js
```js
'use strict';
const { createGoogleSheetClient } = require('./utils');
const serviceAccountKeyFile = './gs-keys.json';
// Replace the sheetId value with the corresponding id found in your own URL
const sheetId = '1P7Oeh84c18NlHp1Zy-5kXD8zgpoA1WmvYL62T4GWpfk';
const tabName = 'Restaurants';
const range = 'A2:C';
const VIEWS_CELL = 'C';
const transformGSheetToObject = (response) =>
response.reduce(
(acc, restaurant) => ({
...acc,
[restaurant[0]]: {
id: restaurant[0],
name: restaurant[1],
views: restaurant[2],
cellNum: Object.keys(acc).length + 2 // + 2 because we need to consider the header and that the initial length is 0, so our first real row would be 2,
},
}),
{}
);
module.exports = (config, { strapi }) => {
return async (context, next) => {
// Generating google sheet client
const { readGoogleSheet, updateoogleSheet, writeGoogleSheet } =
await createGoogleSheetClient({
keyFile: serviceAccountKeyFile,
range,
sheetId,
tabName,
});
// Get the restaurant ID from the params in the URL
const restaurantId = context.params.id;
const restaurant = await strapi.entityService.findOne(
'api::restaurant.restaurant',
restaurantId
);
// Read the spreadsheet to get the current data
const restaurantAnalytics = await readGoogleSheet();
/**
* The returned data comes in the shape [1, "Mint Lounge", 23],
* and we need to transform it into an object: {id: 1, name: "Mint Lounge", views: 23, cellNum: 2}
*/
const requestedRestaurant =
transformGSheetToObject(restaurantAnalytics)[restaurantId];
if (requestedRestaurant) {
await updateoogleSheet(
`${VIEWS_CELL}${requestedRestaurant.cellNum}:${VIEWS_CELL}${requestedRestaurant.cellNum}`,
[[Number(requestedRestaurant.views) + 1]]
);
} else {
/** If we don't have the restaurant in the spreadsheet already,
* we create it with 1 view.
*/
const newRestaurant = [[restaurant.id, restaurant.name, 1]];
await writeGoogleSheet(newRestaurant);
}
// Call next to continue with the flow and get to the controller
await next();
};
};
```
Language: JavaScript
File path: src/api/restaurant/routes/restaurant.js
```js
'use strict';
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::restaurant.restaurant', {
config: {
findOne: {
auth: false,
policies: [],
middlewares: ['api::restaurant.analytics'],
},
},
});
```
# Custom policies
Source: https://docs.strapi.io/cms/backend-customization/examples/policies
## Creating a custom policy
Description: In the /api folder of the project, create a new src/api/review/policies/is-owner-review.js file with the following code:
(Source: https://docs.strapi.io/cms/backend-customization/examples/policies#creating-a-custom-policy)
Language: JavaScript
File path: src/api/review/policies/is-owner-review.js
```js
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;
};
```
## Sending custom errors through policies
Description: In the /api folder of the project, update the previously created is-owner-review custom policy as follows (highlighted lines are the only modified lines):
(Source: https://docs.strapi.io/cms/backend-customization/examples/policies#sending-custom-errors-through-policies)
Language: JSON
File path: /api/review/policies/is-owner-review.js
```json
{
"data": null,
"error": {
"status": 403,
"name": "PolicyError",
"message": "Policy Failed",
"details": {}
}
}
```
---
Language: JSON
File path: N/A
```json
{
"data": null,
"error": {
"status": 403,
"name": "PolicyError",
"message": "The owner of the restaurant cannot submit reviews",
"details": {
"policy": "is-owner-review",
"errCode": "RESTAURANT_OWNER_REVIEW"
}
}
}
```
Language: JavaScript
File path: src/api/review/policies/is-owner-review.js
```js
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) {
// highlight-start
/**
* 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
});
// highlight-end
}
return true;
};
```
## Using custom errors on the front end
Description: Example front-end code to display toast notifications for custom errors or successful review creation:
(Source: https://docs.strapi.io/cms/backend-customization/examples/policies#using-custom-errors-on-the-front-end)
Language: JavaScript
File path: /client/components/pages/restaurant/RestaurantContent/Reviews/new-review.js
```js
import { Button, Input, Textarea } from '@nextui-org/react';
import { useFormik } from 'formik';
import { useRouter } from 'next/router';
import React from 'react';
import { getStrapiURL } from '../../../../../utils';
// highlight-start
/**
* A notification will be displayed on the front-end using React Hot Toast
* (See https://github.com/timolins/react-hot-toast).
* React Hot Toast should be added to your project's dependencies;
* Use yarn or npm to install it and it will be added to your package.json file.
*/
import toast from 'react-hot-toast';
class UnauthorizedError extends Error {
constructor(message) {
super(message);
}
}
// highlight-end
const NewReview = () => {
const router = useRouter();
const { handleSubmit, handleChange, values } = useFormik({
initialValues: {
note: '',
content: '',
},
onSubmit: async (values) => {
// highlight-start
/**
* The previously added code is wrapped in a try/catch block.
*/
try {
// highlight-end
const res = await fetch(getStrapiURL('/reviews'), {
method: 'POST',
body: JSON.stringify({
restaurant: router.query.slug,
...values,
}),
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
'Content-Type': 'application/json',
},
});
// highlight-start
const { data, error } = await res.json();
/**
* If the Strapi backend server returns an error,
* we use the custom error message to throw a custom error.
* If the request is a success, we display a success message.
* In both cases, a toast notification is displayed on the front-end.
*/
if (error) {
throw new UnauthorizedError(error.message);
}
toast.success('Review created!');
return data;
} catch (err) {
toast.error(err.message);
console.error(err);
}
},
// highlight-end
});
return (
Write your review
);
};
export default NewReview;
```
# Custom routes
Source: https://docs.strapi.io/cms/backend-customization/examples/routes
## Examples cookbook: Custom routes
Description: In the /api folder of the project, replace the content of the api/src/api/review/routes/review.js file with the following code:
(Source: https://docs.strapi.io/cms/backend-customization/examples/routes#examples-cookbook-custom-routes)
Language: JavaScript
File path: src/api/review/routes/review.js
```js
'use strict';
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::review.review', {
config: {
create: {
auth: false, // set the route to bypass the normal Strapi authentication system
policies: ['is-owner-review'], // set the route to use a custom policy
middlewares: [],
},
},
});
```
# Custom services and controllers
Source: https://docs.strapi.io/cms/backend-customization/examples/services-and-controllers
## REST API queries from the front end
Description: Create a new file in the /client folder to add a new component for writing reviews with the following code:
(Source: https://docs.strapi.io/cms/backend-customization/examples/services-and-controllers#rest-api-queries-from-the-front-end)
Language: JavaScript
File path: /client/components/pages/restaurant/RestaurantContent/Reviews/new-review.js
```js
import { Button, Input, Textarea } from '@nextui-org/react';
import { useFormik } from 'formik';
import { useRouter } from 'next/router';
import React from 'react';
import { getStrapiURL } from '../../../../../utils';
const NewReview = () => {
const router = useRouter();
const { handleSubmit, handleChange, values } = useFormik({
initialValues: {
note: '',
content: '',
},
onSubmit: async (values) => {
/**
* Queries Strapi REST API to reach the reviews endpoint
* using the JWT previously stored in localStorage to authenticate
*/
const res = await fetch(getStrapiURL('/reviews'), {
method: 'POST',
body: JSON.stringify({
restaurant: router.query.slug,
...values,
}),
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
'Content-Type': 'application/json',
},
});
},
});
/**
* Renders the form
*/
return (
Write your review
);
};
export default NewReview;
```
Language: JavaScript
File path: /client/components/pages/restaurant/RestaurantContent/Reviews/reviews.js
```js
import React from 'react';
import delve from 'dlv';
import { formatDistance } from 'date-fns';
import { getStrapiMedia } from '../../../../../utils';
// highlight-start
import { Textarea } from '@nextui-org/react';
import NewReview from './new-review';
// highlight-end
const Reviews = ({ reviews }) => {
return (
// highlight-next-line
{reviews &&
reviews.map((review, index) => (
// …
```
## Custom service: Creating a review
Description: To create such a service, in the /api folder of the project, replace the content of the src/api/review/services/review.js file with the following code:
(Source: https://docs.strapi.io/cms/backend-customization/examples/services-and-controllers#custom-service-creating-a-review)
Language: JavaScript
File path: src/api/review/services/review.js
```js
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::review.review', ({ strapi }) => ({
async create(ctx) {
const user = ctx.state.user;
const { body } = ctx.request;
/**
* Queries the Restaurants collection type
* using the Entity Service API
* to retrieve information about the restaurant.
*/
const restaurants = await strapi.entityService.findMany(
'api::restaurant.restaurant',
{
filters: {
slug: body.restaurant,
},
}
);
/**
* Creates a new entry for the Reviews collection type
* and populates data with information about the restaurant's owner
* using the Entity Service API.
*/
const newReview = await strapi.entityService.create('api::review.review', {
data: {
note: body.note,
content: body.content,
restaurant: restaurants[0].id,
author: user.id,
},
populate: ['restaurant.owner'],
});
return newReview;
},
}));
```
## Custom Service: Sending an email to the restaurant owner
Description: To create such a service, in the /api folder of the project, create a new src/api/email/services/email.js file with the following code:
(Source: https://docs.strapi.io/cms/backend-customization/examples/services-and-controllers#custom-service-sending-an-email-to-the-restaurant-owner)
Language: JavaScript
File path: src/api/email/services/email.js
```js
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::email.email', ({ strapi }) => ({
async send({ to, subject, html }) {
/**
* Retrieves email configuration data
* stored in the Email single type
* using the Entity Service API.
*/
const emailConfig = await strapi.entityService.findOne(
'api::email.email',
1
);
/**
* Sends an email using:
* - parameters to pass when invoking the service
* - the 'from' address previously retrieved with the email configuration
*/
await strapi.plugins['email'].services.email.send({
to,
subject,
html,
from: emailConfig.from,
});
},
}));
```
## Custom controller
Description: In the /api folder of the project, replace the content of the src/api/review/controllers/review.js file with one of the following code examples, depending on whether you previously created just one custom service or both custom services for the review creation and the email notification:
(Source: https://docs.strapi.io/cms/backend-customization/examples/services-and-controllers#custom-controller)
Language: JavaScript
File path: src/api/review/controllers/review.js
```js
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::review.review', ({ strapi }) => ({
/**
* As the controller action is named
* exactly like the original `create` action provided by the core controller,
* it overwrites it.
*/
async create(ctx) {
// Creates the new review using a service
const newReview = await strapi.service('api::review.review').create(ctx);
const sanitizedReview = await this.sanitizeOutput(newReview, ctx);
ctx.body = sanitizedReview;
},
}));
```
---
Language: JavaScript
File path: src/api/review/controllers/review.js
```js
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::review.review', ({ strapi }) => ({
/**
* As the controller action is named
* exactly like the original `create` action provided by the core controller,
* it overwrites it.
*/
async create(ctx) {
// Creates the new review using a service
const newReview = await strapi.service('api::review.review').create(ctx);
// Sends an email to the restaurant's owner, using another service
if (newReview.restaurant?.owner) {
await strapi.service('api::email.email').send({
to: newReview.restaurant.owner.email,
subject: 'You have a new review!',
html: `You've received a ${newReview.note} star review: ${newReview.content}`,
});
}
const sanitizedReview = await this.sanitizeOutput(newReview, ctx);
ctx.body = sanitizedReview;
},
}));
```
# Middlewares
Source: https://docs.strapi.io/cms/backend-customization/middlewares
## Implementation
Description: Middlewares working with the REST API are functions like the following:
(Source: https://docs.strapi.io/cms/backend-customization/middlewares#implementation)
Language: JavaScript
File path: ./src/middlewares/my-middleware.js
```js
module.exports = (config, { strapi })=> {
return (context, next) => {};
};
```
---
Language: JavaScript
File path: ./src/middlewares/my-middleware.js
```js
export default (config, { strapi })=> {
return (context, next) => {};
};
```
Language: JavaScript
File path: /config/middlewares.js
```js
module.exports = () => {
return async (ctx, next) => {
const start = Date.now();
await next();
const delta = Math.ceil(Date.now() - start);
ctx.set('X-Response-Time', delta + 'ms');
};
};
```
---
Language: TypeScript
File path: /config/middlewares.ts
```ts
export default () => {
return async (ctx, next) => {
const start = Date.now();
await next();
const delta = Math.ceil(Date.now() - start);
ctx.set('X-Response-Time', delta + 'ms');
};
};
```
Language: JavaScript
File path: ./src/api/[api-name]/routes/[collection-name].js
```js
module.exports = {
routes: [
{
method: "GET",
path: "/[collection-name]",
handler: "[controller].find",
config: {
middlewares: ["[middleware-name]"],
// See the usage section below for middleware naming conventions
},
},
],
};
```
## Restricting content access with an "is-owner policy"
Description: Select middleware from the list, using keyboard arrows, and press Enter.
(Source: https://docs.strapi.io/cms/backend-customization/middlewares#restricting-content-access-with-an-is-owner-policy)
Language: JavaScript
File path: src/api/blog-post/middlewares/isOwner.js
```js
"use strict";
/**
* `isOwner` middleware
*/
module.exports = (config, { strapi }) => {
// Add your own logic here.
return async (ctx, next) => {
const user = ctx.state.user;
const entryId = ctx.params.id ? ctx.params.id : undefined;
let entry = {};
/**
* Gets all information about a given entry,
* populating every relations to ensure
* the response includes author-related information
*/
if (entryId) {
entry = await strapi.documents('api::restaurant.restaurant').findOne(
entryId,
{ populate: "*" }
);
}
/**
* Compares user id and entry author id
* to decide whether the request can be fulfilled
* by going forward in the Strapi backend server
*/
if (user.id !== entry.author.id) {
return ctx.unauthorized("This action is unauthorized.");
} else {
return next();
}
};
};
```
Language: JavaScript
File path: src/api/restaurant/routes/restaurant.js
```js
/**
* restaurant router
*/
const { createCoreRouter } = require("@strapi/strapi").factories;
module.exports = createCoreRouter("api::restaurant.restaurant", {
config: {
update: {
middlewares: ["api::restaurant.is-owner"],
},
delete: {
middlewares: ["api::restaurant.is-owner"],
},
},
});
```
# Models
Source: https://docs.strapi.io/cms/backend-customization/models
## Model settings
Description: | Parameter | Type | Description | | -------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | | collectionName | String | Database table name in which the data should be stored | | kind Optional, only for content-types | String | Defines if the content-type is: a collection type (collectionType) or a single type (singleType) |
(Source: https://docs.strapi.io/cms/backend-customization/models#model-settings)
Language: JSON
File path: N/A
```json
// ./src/api/[api-name]/content-types/restaurant/schema.json
{
"kind": "collectionType",
"collectionName": "Restaurants_v1",
}
```
## Model information
Description: | -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | displayName | String | Default name to use in the admin panel | | singularName | String | Singular form of the content-type name.
(Source: https://docs.strapi.io/cms/backend-customization/models#model-information)
Language: JSON
File path: ./src/api/[api-name]/content-types/restaurant/schema.json
```json
"info": {
"displayName": "Restaurant",
"singularName": "restaurant",
"pluralName": "restaurants",
"description": ""
},
```
## Validations
Description: | min | Integer | Checks if the value is less than or equal to the given minimum | - | | minLength | Integer | Minimum number of characters for a field input value | - | | maxLength | Integer | Maximum number of characters for a field input value | - | | private | Boolean | If true, the attribute will be removed from the server response.
(Source: https://docs.strapi.io/cms/backend-customization/models#validations)
Language: JSON
File path: ./src/api/[api-name]/content-types/restaurant/schema.json
```json
{
// ...
"attributes": {
"title": {
"type": "string",
"minLength": 3,
"maxLength": 99,
"unique": true
},
"description": {
"default": "My description",
"type": "text",
"required": true
},
"slug": {
"type": "uid",
"targetField": "title"
}
// ...
}
}
```
## Database validations and settings
Description: disable Draft & Publish on content-types that must stay globally unique, - or add custom validation (e.g.
(Source: https://docs.strapi.io/cms/backend-customization/models#database-validations-and-settings)
Language: JSON
File path: ./src/api/[api-name]/content-types/restaurant/schema.json
```json
{
// ...
"attributes": {
"title": {
"type": "string",
"minLength": 3,
"maxLength": 99,
"unique": true,
"column": {
"unique": true // enforce database unique also
}
},
"description": {
"default": "My description",
"type": "text",
"required": true,
"column": {
"defaultTo": "My description", // set database level default
"notNullable": true // enforce required at database level, even for drafts
}
},
"rating": {
"type": "decimal",
"default": 0,
"column": {
"defaultTo": 0,
"type": "decimal", // using the native decimal type but allowing for custom precision
"args": [
6,1 // using custom precision and scale
]
}
}
// ...
}
}
```
## Relations
Description: A blog article belongs to a category.
(Source: https://docs.strapi.io/cms/backend-customization/models#relations)
Language: JSON
File path: ./src/api/[api-name]/content-types/article/schema.json
```json
// …
attributes: {
category: {
type: 'relation',
relation: 'oneToOne',
target: 'category',
},
},
// …
```
---
Language: JSON
File path: ./src/api/[api-name]/content-types/article/schema.json
```json
// …
attributes: {
category: {
type: 'relation',
relation: 'oneToOne',
target: 'category',
inversedBy: 'article',
},
},
// …
```
---
Language: JSON
File path: ./src/api/[api-name]/content-types/category/schema.json
```json
// …
attributes: {
article: {
type: 'relation',
relation: 'oneToOne',
target: 'article',
mappedBy: 'category',
},
},
// …
```
---
Language: JSON
File path: ./src/api/[api-name]/content-types/plant/schema.json
```json
// …
attributes: {
owner: {
type: 'relation',
relation: 'manyToOne',
target: 'api::person.person',
inversedBy: 'plants',
},
},
// …
```
---
Language: JSON
File path: ./src/api/person/models/schema.json
```json
// …
attributes: {
plants: {
type: 'relation',
relation: 'oneToMany',
target: 'api::plant.plant',
mappedBy: 'owner',
},
},
// …
```
---
Language: JSON
File path: ./src/api/[api-name]/content-types/book/schema.json
```json
// …
attributes: {
author: {
type: 'relation',
relation: 'manyToOne',
target: 'author',
},
},
// …
```
---
Language: JSON
File path: ./src/api/[api-name]/content-types/article/schema.json
```json
// …
attributes: {
author: {
type: 'relation',
relation: 'manyToOne',
target: 'category',
inversedBy: 'article',
},
},
// …
```
---
Language: JSON
File path: ./src/api/[api-name]/content-types/category/schema.json
```json
// …
attributes: {
books: {
type: 'relation',
relation: 'oneToMany',
target: 'article',
mappedBy: 'category',
},
},
// …
```
---
Language: JSON
File path: N/A
```json
// …
attributes: {
categories: {
type: 'relation',
relation: 'manyToMany',
target: 'category',
},
},
// …
```
---
Language: JSON
File path: /src/api/[api-name]/content-types/article/schema.json
```json
// …
attributes: {
tags: {
type: 'relation',
relation: 'manyToMany',
target: 'tag',
inversedBy: 'articles',
},
},
// …
```
---
Language: JSON
File path: ./src/api/[api-name]/content-types/tag/schema.json
```json
// …
attributes: {
articles: {
type: 'relation',
relation: 'manyToMany',
target: 'article',
mappedBy: 'tag',
},
},
// …
```
---
Language: JSON
File path: ./src/api/category/models/Category.settings.js
```json
{
"attributes": {
"products": {
"collection": "product",
"via": "categories"
}
}
}
```
## Custom fields
Description: a customField attribute whose value acts as a unique identifier to indicate which registered custom field should be used.
(Source: https://docs.strapi.io/cms/backend-customization/models#custom-fields)
Language: JSON
File path: ./src/api/[apiName]/[content-type-name]/content-types/schema.json
```json
{
// …
"attributes": {
"attributeName": { // attributeName would be replaced by the actual attribute name
"type": "customField",
"customField": "plugin::color-picker.color",
"options": {
"format": "hex"
}
}
}
// …
}
```
## Components
Description: | Parameter | Type | Description | | ------------ | ------- | ---------------------------------------------------------------------------------------- | | repeatable | Boolean | Could be true or false depending on whether the component is repeatable or not | | component | String | Define the corresponding component, following this format: .
(Source: https://docs.strapi.io/cms/backend-customization/models#components-json)
Language: JSON
File path: ./src/api/[apiName]/restaurant/content-types/schema.json
```json
{
"attributes": {
"openinghours": {
"type": "component",
"repeatable": true,
"component": "restaurant.openinghours"
}
}
}
```
## Dynamic zones
Description: Dynamic zones are explicitly defined in the attributes of a model with type: 'dynamiczone'.
(Source: https://docs.strapi.io/cms/backend-customization/models#dynamic-zones)
Language: JSON
File path: ./src/api/[api-name]/content-types/article/schema.json
```json
{
"attributes": {
"body": {
"type": "dynamiczone",
"components": ["article.slider", "article.content"]
}
}
}
```
## Model options
Description: | Parameter | Type | Description | |---------------------|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | privateAttributes | Array of strings | Allows treating a set of attributes as private, even if they're not actually defined as attributes in the model.
(Source: https://docs.strapi.io/cms/backend-customization/models#model-options)
Language: JSON
File path: ./src/api/[api-name]/content-types/restaurant/schema.json
```json
{
"options": {
"privateAttributes": ["id", "createdAt"],
"draftAndPublish": true
}
}
```
## Plugin options
Description: | Key | Value | Description | |---------------------------|-------------------------------|--------------------------------------------------------| | i18n | localized: true | Enables localization.
(Source: https://docs.strapi.io/cms/backend-customization/models#plugin-options)
Language: JSON
File path: ./src/api/[api-name]/content-types/[content-type-name]/schema.json
```json
{
"attributes": {
"name": {
"pluginOptions": {
"i18n": {
"localized": true
}
},
"type": "string",
"required": true
},
"slug": {
"pluginOptions": {
"i18n": {
"localized": true
}
},
"type": "uid",
"targetField": "name",
"required": true
}
// …additional attributes
}
}
```
## Declarative and programmatic usage
Description: Each event listener is called sequentially.
(Source: https://docs.strapi.io/cms/backend-customization/models#declarative-and-programmatic-usage)
Language: JavaScript
File path: ./src/api/[api-name]/content-types/[content-type-name]/lifecycles.js
```js
module.exports = {
beforeCreate(event) {
const { data, where, select, populate } = event.params;
// let's do a 20% discount everytime
event.params.data.price = event.params.data.price * 0.8;
},
afterCreate(event) {
const { result, params } = event;
// do something to the result;
},
};
```
---
Language: TypeScript
File path: ./src/api/[api-name]/content-types/[content-type-name]/lifecycles.ts
```ts
export default {
beforeCreate(event) {
const { data, where, select, populate } = event.params;
// let's do a 20% discount everytime
event.params.data.price = event.params.data.price * 0.8;
},
afterCreate(event) {
const { result, params } = event;
// do something to the result;
},
};
```
Language: JavaScript
File path: ./src/index.js
```js
module.exports = {
async bootstrap({ strapi }) {
// registering a subscriber
strapi.db.lifecycles.subscribe({
models: [], // optional;
beforeCreate(event) {
const { data, where, select, populate } = event.params;
event.state = 'doStuffAfterWards';
},
afterCreate(event) {
if (event.state === 'doStuffAfterWards') {
}
const { result, params } = event;
// do something to the result
},
});
// generic subscribe for generic handling
strapi.db.lifecycles.subscribe((event) => {
if (event.action === 'beforeCreate') {
// do something
}
});
}
}
```
# Policies
Source: https://docs.strapi.io/cms/backend-customization/policies
## Implementation
Description: Global policy implementation example:
(Source: https://docs.strapi.io/cms/backend-customization/policies#implementation)
Language: JavaScript
File path: ./src/policies/is-authenticated.js
```js
module.exports = (policyContext, config, { strapi }) => {
if (policyContext.state.user) { // if a session is open
// go to next policy or reach the controller's action
return true;
}
return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass
};
```
---
Language: TypeScript
File path: ./src/policies/is-authenticated.ts
```ts
export default (policyContext, config, { strapi }) => {
if (policyContext.state.user) { // if a session is open
// go to next policy or reach the controller's action
return true;
}
return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass
};
```
Language: JavaScript
File path: ./src/api/[api-name]/policies/my-policy.js
```js
module.exports = (policyContext, config, { strapi }) => {
if (policyContext.state.user.role.code === config.role) { // if user's role is the same as the one described in configuration
return true;
}
return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass
};
```
---
Language: TypeScript
File path: ./src/api/[api-name]/policies/my-policy.ts
```ts
export default (policyContext, config, { strapi }) => {
if (policyContext.state.user.role.code === config.role) { // if user's role is the same as the one described in configuration
return true;
}
return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass
};
```
## Global policies
Description: Global policies can be associated to any route in a project.
(Source: https://docs.strapi.io/cms/backend-customization/policies#global-policies)
Language: JavaScript
File path: ./src/api/restaurant/routes/custom-restaurant.js
```js
module.exports = {
routes: [
{
method: 'GET',
path: '/restaurants',
handler: 'Restaurant.find',
config: {
/**
Before executing the find action in the Restaurant.js controller,
we call the global 'is-authenticated' policy,
found at ./src/policies/is-authenticated.js.
*/
policies: ['global::is-authenticated']
}
}
]
}
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/custom-restaurant.ts
```ts
export default {
routes: [
{
method: 'GET',
path: '/restaurants',
handler: 'Restaurant.find',
config: {
/**
Before executing the find action in the Restaurant.js controller,
we call the global 'is-authenticated' policy,
found at ./src/policies/is-authenticated.js.
*/
policies: ['global::is-authenticated']
}
}
]
}
```
## Plugin policies
Description: Plugins can add and expose policies to an application.
(Source: https://docs.strapi.io/cms/backend-customization/policies#plugin-policies)
Language: JavaScript
File path: ./src/api/restaurant/routes/custom-restaurant.js
```js
module.exports = {
routes: [
{
method: 'GET',
path: '/restaurants',
handler: 'Restaurant.find',
config: {
/**
The `isAuthenticated` policy prodived with the `users-permissions` plugin
is executed before the `find` action in the `Restaurant.js` controller.
*/
policies: ['plugin::users-permissions.isAuthenticated']
}
}
]
}
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/custom-restaurant.ts
```ts
export default {
routes: [
{
method: 'GET',
path: '/restaurants',
handler: 'Restaurant.find',
config: {
/**
The `isAuthenticated` policy prodived with the `users-permissions` plugin
is executed before the `find` action in the `Restaurant.js` controller.
*/
policies: ['plugin::users-permissions.isAuthenticated']
}
}
]
}
```
## API policies
Description: API policies are associated to the routes defined in the API where they have been declared.
(Source: https://docs.strapi.io/cms/backend-customization/policies#api-policies)
Language: JavaScript
File path: ./src/api/restaurant/policies/is-admin.js.
```js
module.exports = async (policyContext, config, { strapi }) => {
if (policyContext.state.user.role.name === 'Administrator') {
// Go to next policy or will reach the controller's action.
return true;
}
return false;
};
```
---
Language: JavaScript
File path: ./src/api/restaurant/routes/custom-restaurant.js
```js
module.exports = {
routes: [
{
method: 'GET',
path: '/restaurants',
handler: 'Restaurant.find',
config: {
/**
The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.js`
is executed before the `find` action in the `Restaurant.js` controller.
*/
policies: ['is-admin']
}
}
]
}
```
---
Language: TypeScript
File path: ./src/api/restaurant/policies/is-admin.ts
```ts
export default (policyContext, config, { strapi }) => {
if (policyContext.state.user.role.name === 'Administrator') {
// Go to next policy or will reach the controller's action.
return true;
}
return false;
};
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/custom-restaurant.ts
```ts
export default {
routes: [
{
method: 'GET',
path: '/restaurants',
handler: 'Restaurant.find',
config: {
/**
The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.js`
is executed before the `find` action in the `Restaurant.ts` controller.
*/
policies: ['is-admin']
}
}
]
}
```
Language: JavaScript
File path: ./src/api/category/routes/custom-category.js
```js
module.exports = {
routes: [
{
method: 'GET',
path: '/categories',
handler: 'Category.find',
config: {
/**
The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.js`
is executed before the `find` action in the `Restaurant.js` controller.
*/
policies: ['api::restaurant.is-admin']
}
}
]
}
```
---
Language: TypeScript
File path: ./src/api/category/routes/custom-category.ts
```ts
export default {
routes: [
{
method: 'GET',
path: '/categories',
handler: 'Category.find',
config: {
/**
The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.ts`
is executed before the `find` action in the `Restaurant.js` controller.
*/
policies: ['api::restaurant.is-admin']
}
}
]
}
```
# Requests and Responses
Source: https://docs.strapi.io/cms/backend-customization/requests-responses
## Accessing the request context anywhere
Description: You can access the request as follows:
(Source: https://docs.strapi.io/cms/backend-customization/requests-responses#accessing-the-request-context-anywhere)
Language: JavaScript
File path: N/A
```js
const ctx = strapi.requestContext.get();
```
Language: JavaScript
File path: N/A
```js
// correct
const service = {
myFunction() {
const ctx = strapi.requestContext.get();
console.log(ctx.state.user);
},
};
// incorrect
const ctx = strapi.requestContext.get();
const service = {
myFunction() {
console.log(ctx.state.user);
},
};
```
Language: JavaScript
File path: ./api/test/content-types/article/lifecycles.js
```js
module.exports = {
beforeUpdate() {
const ctx = strapi.requestContext.get();
console.log('User info in service: ', ctx.state.user);
},
};
```
# Routes
Source: https://docs.strapi.io/cms/backend-customization/routes
## Configuring core routers
Description: Generic implementation example:
(Source: https://docs.strapi.io/cms/backend-customization/routes#configuring-core-routers)
Language: JavaScript
File path: ./src/api/[apiName]/routes/[routerName].js
```js
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::restaurant.restaurant', {
prefix: '',
only: ['find', 'findOne'],
except: [],
config: {
find: {
auth: false,
policies: [],
middlewares: [],
},
findOne: {},
create: {},
update: {},
delete: {},
},
});
```
---
Language: TypeScript
File path: ./src/api/[apiName]/routes/[routerName].ts
```ts
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::restaurant.restaurant', {
prefix: '',
only: ['find', 'findOne'],
except: [],
config: {
find: {
auth: false,
policies: [],
middlewares: [],
},
findOne: {},
create: {},
update: {},
delete: {},
},
});
```
Language: JavaScript
File path: ./src/api/restaurant/routes/restaurant.js
```js
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::restaurant.restaurant', {
only: ['find'],
config: {
find: {
auth: false,
policies: [],
middlewares: [],
}
}
});
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/restaurant.ts
```ts
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::restaurant.restaurant', {
only: ['find'],
config: {
find: {
auth: false,
policies: [],
middlewares: [],
}
}
});
```
## Creating custom routers
Description: In the following example, the custom routes file name is prefixed with 01- to make sure the route is reached before the core routes.
(Source: https://docs.strapi.io/cms/backend-customization/routes#creating-custom-routers)
Language: JavaScript
File path: ./src/api/restaurant/routes/01-custom-restaurant.js
```js
/** @type {import('@strapi/strapi').Core.RouterConfig} */
const config = {
type: 'content-api',
routes: [
{ // Path defined with an URL parameter
method: 'POST',
path: '/restaurants/:id/review',
handler: 'api::restaurant.restaurant.review',
},
{ // Path defined with a regular expression
method: 'GET',
path: '/restaurants/:category([a-z]+)', // Only match when the URL parameter is composed of lowercase letters
handler: 'api::restaurant.restaurant.findByCategory',
}
]
}
module.exports = config
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/01-custom-restaurant.ts
```ts
import type { Core } from '@strapi/strapi';
const config: Core.RouterConfig = {
type: 'content-api',
routes: [
{ // Path defined with a URL parameter
method: 'GET',
path: '/restaurants/:category/:id',
handler: 'api::restaurant.restaurant.findOneByCategory',
},
{ // Path defined with a regular expression
method: 'GET',
path: '/restaurants/:region(\\d{2}|\\d{3})/:id', // Only match when the first parameter contains 2 or 3 digits.
handler: 'api::restaurant.restaurant.findOneByRegion',
}
]
}
export default config
```
## Policies
Description: by pointing to a policy registered in ./src/policies, with or without passing a custom configuration - or by declaring the policy implementation directly, as a function that takes policyContext to extend (ctx) and the strapi instance as arguments (see policies documentation)
(Source: https://docs.strapi.io/cms/backend-customization/routes#policies)
Language: JavaScript
File path: ./src/api/restaurant/routes/restaurant.js
```js
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::restaurant.restaurant', {
config: {
find: {
policies: [
// point to a registered policy
'policy-name',
// point to a registered policy with some custom configuration
{ name: 'policy-name', config: {} },
// pass a policy implementation directly
(policyContext, config, { strapi }) => {
return true;
},
]
}
}
});
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/restaurant.ts
```ts
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::restaurant.restaurant', {
config: {
find: {
policies: [
// point to a registered policy
'policy-name',
// point to a registered policy with some custom configuration
{ name: 'policy-name', config: {} },
// pass a policy implementation directly
(policyContext, config, { strapi }) => {
return true;
},
]
}
}
});
```
Language: JavaScript
File path: ./src/api/restaurant/routes/custom-restaurant.js
```js
module.exports = {
routes: [
{
method: 'GET',
path: '/articles/customRoute',
handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller
config: {
policies: [
// point to a registered policy
'policy-name',
// point to a registered policy with some custom configuration
{ name: 'policy-name', config: {} },
// pass a policy implementation directly
(policyContext, config, { strapi }) => {
return true;
},
]
},
},
],
};
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/custom-restaurant.ts
```ts
export default {
routes: [
{
method: 'GET',
path: '/articles/customRoute',
handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller
config: {
policies: [
// point to a registered policy
'policy-name',
// point to a registered policy with some custom configuration
{ name: 'policy-name', config: {} },
// pass a policy implementation directly
(policyContext, config, { strapi }) => {
return true;
},
]
},
},
],
};
```
## Middlewares
Description: by pointing to a middleware registered in ./src/middlewares, with or without passing a custom configuration - or by declaring the middleware implementation directly, as a function that takes (ctx) and the strapi instance as arguments:
(Source: https://docs.strapi.io/cms/backend-customization/routes#middlewares)
Language: JavaScript
File path: ./src/api/restaurant/routes/restaurant.js
```js
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::restaurant.restaurant', {
config: {
find: {
middlewares: [
// point to a registered middleware
'middleware-name',
// point to a registered middleware with some custom configuration
{ name: 'middleware-name', config: {} },
// pass a middleware implementation directly
(ctx, next) => {
return next();
},
]
}
}
});
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/restaurant.ts
```ts
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::restaurant.restaurant', {
config: {
find: {
middlewares: [
// point to a registered middleware
'middleware-name',
// point to a registered middleware with some custom configuration
{ name: 'middleware-name', config: {} },
// pass a middleware implementation directly
(ctx, next) => {
return next();
},
]
}
}
});
```
Language: JavaScript
File path: ./src/api/restaurant/routes/custom-restaurant.js
```js
module.exports = {
routes: [
{
method: 'GET',
path: '/articles/customRoute',
handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller
config: {
middlewares: [
// point to a registered middleware
'middleware-name',
// point to a registered middleware with some custom configuration
{ name: 'middleware-name', config: {} },
// pass a middleware implementation directly
(ctx, next) => {
return next();
},
],
},
},
],
};
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/custom-restaurant.ts
```ts
export default {
routes: [
{
method: 'GET',
path: '/articles/customRoute',
handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller
config: {
middlewares: [
// point to a registered middleware
'middleware-name',
// point to a registered middleware with some custom configuration
{ name: 'middleware-name', config: {} },
// pass a middleware implementation directly
(ctx, next) => {
return next();
},
],
},
},
],
};
```
## Public routes
Description: In some scenarios, it can be useful to have a route publicly available and control the access outside of the normal Strapi authentication system.
(Source: https://docs.strapi.io/cms/backend-customization/routes#public-routes)
Language: JavaScript
File path: ./src/api/restaurant/routes/restaurant.js
```js
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::restaurant.restaurant', {
config: {
find: {
auth: false
}
}
});
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/restaurant.ts
```ts
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::restaurant.restaurant', {
config: {
find: {
auth: false
}
}
});
```
Language: JavaScript
File path: ./src/api/restaurant/routes/custom-restaurant.js
```js
module.exports = {
routes: [
{
method: 'GET',
path: '/articles/customRoute',
handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller
config: {
auth: false,
},
},
],
};
```
---
Language: TypeScript
File path: ./src/api/restaurant/routes/custom-restaurant.ts
```ts
export default {
routes: [
{
method: 'GET',
path: '/articles/customRoute',
handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller
config: {
auth: false,
},
},
],
};
```
## matchRoute
Description: For example, to target only GET routes, use matchRoute: (route) => route.method === 'GET'.
(Source: https://docs.strapi.io/cms/backend-customization/routes#matchroute)
Language: JavaScript
File path: ./src/index.js
```js
module.exports = {
register({ strapi }) {
strapi.contentAPI.addQueryParams({
search: {
schema: (z) => z.string().max(200).optional(),
matchRoute: (route) => route.path.includes('articles'),
},
});
strapi.contentAPI.addInputParams({
clientMutationId: {
schema: (z) => z.string().max(100).optional(),
},
});
},
};
```
---
Language: TypeScript
File path: ./src/index.ts
```ts
export default {
register({ strapi }) {
strapi.contentAPI.addQueryParams({
search: {
schema: (z) => z.string().max(200).optional(),
matchRoute: (route) => route.path.includes('articles'),
},
});
strapi.contentAPI.addInputParams({
clientMutationId: {
schema: (z) => z.string().max(100).optional(),
},
});
},
};
```
# Services
Source: https://docs.strapi.io/cms/backend-customization/services
## Adding a new service
Description: To manually create a service, export a factory function that returns the service implementation (i.e.
(Source: https://docs.strapi.io/cms/backend-customization/services#adding-a-new-service)
Language: JavaScript
File path: ./src/api/restaurant/services/restaurant.js
```js
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
// Method 1: Creating an entirely new custom service
async exampleService(...args) {
let response = { okay: true }
if (response.okay === false) {
return { response, error: true }
}
return response
},
// Method 2: Wrapping a core service (leaves core logic in place)
async find(...args) {
// Calling the default core controller
const { results, pagination } = await super.find(...args);
// some custom logic
results.forEach(result => {
result.counter = 1;
});
return { results, pagination };
},
// Method 3: Replacing a core service
async findOne(documentId, params = {}) {
return strapi.documents('api::restaurant.restaurant').findOne({
documentId,
// Use super to keep core fetch parameter formatting
...super.getFetchParams(params),
});
}
}));
```
---
Language: TypeScript
File path: ./src/api/restaurant/services/restaurant.ts
```ts
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
// Method 1: Creating an entirely custom service
async exampleService(...args) {
let response = { okay: true }
if (response.okay === false) {
return { response, error: true }
}
return response
},
// Method 2: Wrapping a core service (leaves core logic in place)
async find(...args) {
// Calling the default core controller
const { results, pagination } = await super.find(...args);
// some custom logic
results.forEach(result => {
result.counter = 1;
});
return { results, pagination };
},
// Method 3: Replacing a core service
async findOne(documentId, params = {}) {
return strapi.documents('api::restaurant.restaurant').findOne({
documentId,
// Use super to keep core fetch parameter formatting
...super.getFetchParams(params) }) as any;
}
}));
```
Language: DOCKERFILE
File path: ./src/api/restaurant/services/restaurant.js
```dockerfile
const { createCoreService } = require('@strapi/strapi').factories;
const nodemailer = require('nodemailer'); // Requires nodemailer to be installed (npm install nodemailer)
// Create reusable transporter object using SMTP transport.
const transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: 'user@gmail.com',
pass: 'password',
},
});
module.exports = createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
sendNewsletter(from, to, subject, text) {
// Setup e-mail data.
const options = {
from,
to,
subject,
text,
};
// Return a promise of the function that sends the email.
return transporter.sendMail(options);
},
}));
```
---
Language: DOCKERFILE
File path: ./src/api/restaurant/services/restaurant.ts
```dockerfile
import { factories } from '@strapi/strapi';
const nodemailer = require('nodemailer'); // Requires nodemailer to be installed (npm install nodemailer)
// Create reusable transporter object using SMTP transport.
const transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: 'user@gmail.com',
pass: 'password',
},
});
export default factories.createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
sendNewsletter(from, to, subject, text) {
// Setup e-mail data.
const options = {
from,
to,
subject,
text,
};
// Return a promise of the function that sends the email.
return transporter.sendMail(options);
},
}));
```
Language: JavaScript
File path: ./src/api/restaurant/controllers/restaurant.js
```js
module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({
// GET /hello
async signup(ctx) {
const { userData } = ctx.body;
// Store the new user in database.
const user = await strapi.service('plugin::users-permissions.user').add(userData);
// Send an email to validate his subscriptions.
strapi.service('api::restaurant.restaurant').sendNewsletter('welcome@mysite.com', user.email, 'Welcome', '...');
// Send response to the server.
ctx.send({
ok: true,
});
},
}));
```
---
Language: TypeScript
File path: ./src/api/restaurant/controllers/restaurant.ts
```ts
export default factories.createCoreController('api::restaurant.restaurant', ({ strapi }) => ({
// GET /hello
async signup(ctx) {
const { userData } = ctx.body;
// Store the new user in database.
const user = await strapi.service('plugin::users-permissions.user').add(userData);
// Send an email to validate his subscriptions.
strapi.service('api::restaurant.restaurant').sendNewsletter('welcome@mysite.com', user.email, 'Welcome', '...');
// Send response to the server.
ctx.send({
ok: true,
});
},
}));
```
## Extending core services
Description: Collection type examples
(Source: https://docs.strapi.io/cms/backend-customization/services#extending-core-services)
Language: JavaScript
File path: N/A
```js
async find(params) {
// some logic here
const { results, pagination } = await super.find(params);
// some more logic
return { results, pagination };
}
```
---
Language: JavaScript
File path: N/A
```js
async findOne(documentId, params) {
// some logic here
const result = await super.findOne(documentId, params);
// some more logic
return result;
}
```
---
Language: JavaScript
File path: N/A
```js
async create(params) {
// some logic here
const result = await super.create(params);
// some more logic
return result;
}
```
---
Language: JavaScript
File path: N/A
```js
async update(documentId, params) {
// some logic here
const result = await super.update(documentId, params);
// some more logic
return result;
}
```
---
Language: JavaScript
File path: N/A
```js
async delete(documentId, params) {
// some logic here
const result = await super.delete(documentId, params);
// some more logic
return result;
}
```
Language: JavaScript
File path: N/A
```js
async find(params) {
// some logic here
const document = await super.find(params);
// some more logic
return document;
}
```
---
Language: JavaScript
File path: N/A
```js
async createOrUpdate({ data, ...params }) {
// some logic here
const document = await super.createOrUpdate({ data, ...params });
// some more logic
return document;
}
```
---
Language: JavaScript
File path: N/A
```js
async delete(params) {
// some logic here
const document = await super.delete(params);
// some more logic
return document;
}
```
## Usage
Description: Once a service is created, it's accessible from controllers or from other services:
(Source: https://docs.strapi.io/cms/backend-customization/services#usage)
Language: JavaScript
File path: N/A
```js
// access an API service
strapi.service('api::apiName.serviceName').FunctionName();
// access a plugin service
strapi.service('plugin::pluginName.serviceName').FunctionName();
```
# Webhooks
Source: https://docs.strapi.io/cms/backend-customization/webhooks
## Available configurations
Description: Example configuration
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#available-configurations)
Language: JavaScript
File path: ./config/server.js
```js
module.exports = {
webhooks: {
defaultHeaders: {
"Custom-Header": "my-custom-header",
},
},
};
```
---
Language: TypeScript
File path: ./config/server.ts
```ts
export default {
webhooks: {
defaultHeaders: {
"Custom-Header": "my-custom-header",
},
},
};
```
## Webhooks security
Description: You can configure these global headers by updating the file at ./config/server:
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#webhooks-security)
Language: JavaScript
File path: ./config/server.js
```js
module.exports = {
webhooks: {
defaultHeaders: {
Authorization: "Bearer my-very-secured-token",
},
},
};
```
---
Language: TypeScript
File path: ./config/server.ts
```ts
export default {
webhooks: {
defaultHeaders: {
Authorization: "Bearer my-very-secured-token",
},
},
};
```
Language: JavaScript
File path: ./config/server.js
```js
module.exports = {
webhooks: {
defaultHeaders: {
Authorization: `Bearer ${process.env.WEBHOOK_TOKEN}`,
},
},
};
```
---
Language: TypeScript
File path: ./config/server.ts
```ts
export default {
webhooks: {
defaultHeaders: {
Authorization: `Bearer ${process.env.WEBHOOK_TOKEN}`,
},
},
};
```
## Verifying signatures
Description: Here is a minimal Node.js middleware example (pseudo‑code) showing verification:
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#verifying-signatures)
Language: JavaScript
File path: /src/middlewares/verify-webhook.js
```js
const crypto = require("crypto");
module.exports = (config, { strapi }) => {
const secret = process.env.WEBHOOK_SECRET;
return async (ctx, next) => {
const signature = ctx.get("X-Webhook-Signature");
const timestamp = ctx.get("X-Webhook-Timestamp");
if (!signature || !timestamp) return ctx.unauthorized("Missing signature");
// Compute HMAC over raw body + timestamp
const raw = ctx.request.rawBody || (ctx.request.body and JSON.stringify(ctx.request.body)) || "";
const hmac = crypto.createHmac("sha256", secret);
hmac.update(timestamp + "." + raw);
const expected = "sha256=" + hmac.digest("hex");
// Constant-time compare + basic replay protection
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
const skew = Math.abs(Date.now() - Number(timestamp));
if (!ok or skew > 5 * 60 * 1000) {
return ctx.unauthorized("Invalid or expired signature");
}
await next();
};
};
```
---
Language: TypeScript
File path: /src/middlewares/verify-webhook.ts
```ts
import crypto from "node:crypto"
export default (config: unknown, { strapi }: any) => {
const secret = process.env.WEBHOOK_SECRET as string;
return async (ctx: any, next: any) => {
const signature = ctx.get("X-Webhook-Signature") as string;
const timestamp = ctx.get("X-Webhook-Timestamp") as string;
if (!signature || !timestamp) return ctx.unauthorized("Missing signature");
// Compute HMAC over raw body + timestamp
const raw: string = ctx.request.rawBody || (ctx.request.body && JSON.stringify(ctx.request.body)) || "";
const hmac = crypto.createHmac("sha256", secret);
hmac.update(`${timestamp}.${raw}`);
const expected = `sha256=${hmac.digest("hex")}`;
// Constant-time compare + basic replay protection
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
const skew = Math.abs(Date.now() - Number(timestamp));
if (!ok || skew > 5 * 60 * 1000) {
return ctx.unauthorized("Invalid or expired signature");
}
await next();
};
};
```
## entry.create
Description: Example payload
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#entry-create)
Language: JSON
File path: N/A
```json
{
"event": "entry.create",
"createdAt": "2020-01-10T08:47:36.649Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"createdAt": "2020-01-10T08:47:36.264Z",
"updatedAt": "2020-01-10T08:47:36.264Z",
"cover": null,
"images": []
}
}
```
## entry.update
Description: Example payload
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#entry-update)
Language: JSON
File path: N/A
```json
{
"event": "entry.update",
"createdAt": "2020-01-10T08:58:26.563Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"createdAt": "2020-01-10T08:47:36.264Z",
"updatedAt": "2020-01-10T08:58:26.210Z",
"cover": null,
"images": []
}
}
```
## entry.delete
Description: Example payload
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#entry-delete)
Language: JSON
File path: N/A
```json
{
"event": "entry.delete",
"createdAt": "2020-01-10T08:59:35.796Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"createdAt": "2020-01-10T08:47:36.264Z",
"updatedAt": "2020-01-10T08:58:26.210Z",
"cover": null,
"images": []
}
}
```
## entry.publish
Description: Example payload
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#entry-publish)
Language: JSON
File path: N/A
```json
{
"event": "entry.publish",
"createdAt": "2020-01-10T08:59:35.796Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"createdAt": "2020-01-10T08:47:36.264Z",
"updatedAt": "2020-01-10T08:58:26.210Z",
"publishedAt": "2020-08-29T14:20:12.134Z",
"cover": null,
"images": []
}
}
```
## entry.unpublish
Description: Example payload
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#entry-unpublish)
Language: JSON
File path: N/A
```json
{
"event": "entry.unpublish",
"createdAt": "2020-01-10T08:59:35.796Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"createdAt": "2020-01-10T08:47:36.264Z",
"updatedAt": "2020-01-10T08:58:26.210Z",
"publishedAt": null,
"cover": null,
"images": []
}
}
```
## media.create
Description: Example payload
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#media-create)
Language: JSON
File path: N/A
```json
{
"event": "media.create",
"createdAt": "2020-01-10T10:58:41.115Z",
"media": {
"id": 1,
"name": "image.png",
"hash": "353fc98a19e44da9acf61d71b11895f9",
"sha256": "huGUaFJhmcZRHLcxeQNKblh53vtSUXYaB16WSOe0Bdc",
"ext": ".png",
"mime": "image/png",
"size": 228.19,
"url": "/uploads/353fc98a19e44da9acf61d71b11895f9.png",
"provider": "local",
"provider_metadata": null,
"createdAt": "2020-01-10T10:58:41.095Z",
"updatedAt": "2020-01-10T10:58:41.095Z",
"related": []
}
}
```
## media.update
Description: Example payload
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#media-update)
Language: JSON
File path: N/A
```json
{
"event": "media.update",
"createdAt": "2020-01-10T10:58:41.115Z",
"media": {
"id": 1,
"name": "image.png",
"hash": "353fc98a19e44da9acf61d71b11895f9",
"sha256": "huGUaFJhmcZRHLcxeQNKblh53vtSUXYaB16WSOe0Bdc",
"ext": ".png",
"mime": "image/png",
"size": 228.19,
"url": "/uploads/353fc98a19e44da9acf61d71b11895f9.png",
"provider": "local",
"provider_metadata": null,
"createdAt": "2020-01-10T10:58:41.095Z",
"updatedAt": "2020-01-10T10:58:41.095Z",
"related": []
}
}
```
## media.delete
Description: Example payload
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#media-delete)
Language: JSON
File path: N/A
```json
{
"event": "media.delete",
"createdAt": "2020-01-10T11:02:46.232Z",
"media": {
"id": 11,
"name": "photo.png",
"hash": "43761478513a4c47a5fd4a03178cfccb",
"sha256": "HrpDOKLFoSocilA6B0_icA9XXTSPR9heekt2SsHTZZE",
"ext": ".png",
"mime": "image/png",
"size": 4947.76,
"url": "/uploads/43761478513a4c47a5fd4a03178cfccb.png",
"provider": "local",
"provider_metadata": null,
"createdAt": "2020-01-07T19:34:32.168Z",
"updatedAt": "2020-01-07T19:34:32.168Z",
"related": []
}
}
```
## review-workflows.updateEntryStage
Description: Example payload
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#review-workflows-updateentrystage)
Language: JSON
File path: N/A
```json
{
"event": "review-workflows.updateEntryStage",
"createdAt": "2023-06-26T15:46:35.664Z",
"model": "model",
"uid": "uid",
"entity": {
"id": 2
},
"workflow": {
"id": 1,
"stages": {
"from": {
"id": 1,
"name": "Stage 1"
},
"to": {
"id": 2,
"name": "Stage 2"
}
}
}
}
```
## releases.publish
Description: Example payload
(Source: https://docs.strapi.io/cms/backend-customization/webhooks#releases-publish)
Language: JSON
File path: N/A
```json
{
"event": "releases.publish",
"createdAt": "2024-02-21T16:45:36.877Z",
"isPublished": true,
"release": {
"id": 2,
"name": "Fall Winter highlights",
"releasedAt": "2024-02-21T16:45:36.873Z",
"scheduledAt": null,
"timezone": null,
"createdAt": "2024-02-21T15:16:22.555Z",
"updatedAt": "2024-02-21T16:45:36.875Z",
"actions": {
"count": 1
}
}
}
```
# Command Line Interface
Source: https://docs.strapi.io/cms/cli
## strapi develop
Description: Strapi also adds middlewares to support HMR (Hot Module Replacement) for the administration panel.
(Source: https://docs.strapi.io/cms/cli#strapi-develop)
Language: SHELL
File path: N/A
```shell
strapi develop
options: [--bundler | --open | --no-watch-admin | --no-build-admin | --polling | --debug | --silent]
```
## strapi build
Description: Builds your admin panel.
(Source: https://docs.strapi.io/cms/cli#strapi-build)
Language: Bash
File path: N/A
```bash
strapi build
```
## strapi console
Description: Start the server and evaluate commands in your application in real time.
(Source: https://docs.strapi.io/cms/cli#strapi-console)
Language: Bash
File path: N/A
```bash
strapi console
```
## Available helpers
Description: strapi.middlewares and strapi.middleware(name) for middlewares - strapi.plugins and strapi.plugin(name) for plugins - strapi.hooks and strapi.hook(name) for hooks - strapi.apis and strapi.api(name) for APIs - strapi.db to directly query the database through the Query Engine API, for example as follows:
(Source: https://docs.strapi.io/cms/cli#available-helpers)
Language: JavaScript
File path: N/A
```js
await strapi.db.query('api::article.article').findMany();
```
## strapi export
Description: Exports your project data.
(Source: https://docs.strapi.io/cms/cli#strapi-export)
Language: Bash
File path: N/A
```bash
strapi export
```
Language: Bash
File path: N/A
```bash
strapi export -f myData
```
Language: Bash
File path: N/A
```bash
strapi export --no-encrypt
```
Language: Bash
File path: N/A
```bash
strapi export --format dir --no-encrypt -f my-export
```
## strapi import
Description: Imports data into your project.
(Source: https://docs.strapi.io/cms/cli#strapi-import)
Language: Bash
File path: N/A
```bash
strapi import
```
Language: Bash
File path: N/A
```bash
strapi import -f your-filepath-and-filename --key my-key
```
Language: Bash
File path: N/A
```bash
strapi import -f ./my-export
```
## strapi transfer
Description: Example
(Source: https://docs.strapi.io/cms/cli#strapi-transfer)
Language: Bash
File path: N/A
```bash
strapi transfer --to http://example.com/admin --to-token my-transfer-token
```
## strapi report
Description: To include the project UUID and dependencies in the output:
(Source: https://docs.strapi.io/cms/cli#strapi-report)
Language: Bash
File path: N/A
```bash
strapi report --uuid --dependencies
```
Language: Bash
File path: N/A
```bash
strapi report --all
```
## strapi telemetry:disable
Description: Disable data collection for the project (see Usage Information).
(Source: https://docs.strapi.io/cms/cli#strapi-telemetry-disable)
Language: Bash
File path: N/A
```bash
strapi telemetry:disable
```
## strapi telemetry:enable
Description: Re-enable data collection for the project after it was disabled (see Usage Information).
(Source: https://docs.strapi.io/cms/cli#strapi-telemetry-enable)
Language: Bash
File path: N/A
```bash
strapi telemetry:enable
```
## strapi version
Description: Print the currently installed Strapi version.
(Source: https://docs.strapi.io/cms/cli#strapi-version)
Language: Bash
File path: N/A
```bash
strapi version
```
## strapi help
Description: List CLI commands.
(Source: https://docs.strapi.io/cms/cli#strapi-help)
Language: Bash
File path: N/A
```bash
strapi help
```
## strapi configuration:dump
Description: The dump format will be a JSON array.
(Source: https://docs.strapi.io/cms/cli#strapi-configuration-dump)
Language: Bash
File path: strapi
```bash
Options:
-f, --file Output file, default output is stdout
-p, --pretty Format the output JSON with indentation and line breaks (default: false)
```
## strapi configuration:restore
Description: The input format must be a JSON array.
(Source: https://docs.strapi.io/cms/cli#strapi-configuration-restore)
Language: Bash
File path: N/A
```bash
strapi configuration:restore
Options:
-f, --file Input file, default input is stdin
-s, --strategy Strategy name, one of: "replace", "merge", "keep". Defaults to: "replace"
```
## strapi admin:create-user
Description: Example
(Source: https://docs.strapi.io/cms/cli#strapi-admin-create-user)
Language: Bash
File path: N/A
```bash
strapi admin:create-user --firstname=Kai --lastname=Doe --email=chef@strapi.io --password=Gourmet1234
```
## strapi admin:reset-user-password
Description: Example
(Source: https://docs.strapi.io/cms/cli#strapi-admin-reset-user-password)
Language: Bash
File path: N/A
```bash
strapi admin:reset-user-password --email=chef@strapi.io --password=Gourmet1234
```
## strapi admin:list-users
Description: Displays a table listing all administrator accounts, including their ID, email, first and last name, active status, blocked status, and assigned roles.
(Source: https://docs.strapi.io/cms/cli#strapi-admin-list-users)
Language: Bash
File path: N/A
```bash
strapi admin:list-users
```
## strapi admin:active-user
Description: Example
(Source: https://docs.strapi.io/cms/cli#strapi-admin-active-user)
Language: Bash
File path: N/A
```bash
strapi admin:active-user --email=chef@strapi.io --active=true
```
## strapi admin:block-user
Description: Example
(Source: https://docs.strapi.io/cms/cli#strapi-admin-block-user)
Language: Bash
File path: N/A
```bash
strapi admin:block-user --email=chef@strapi.io --block=true
```
## strapi admin:delete-user
Description: Example
(Source: https://docs.strapi.io/cms/cli#strapi-admin-delete-user)
Language: Bash
File path: N/A
```bash
strapi admin:delete-user --email=chef@strapi.io
```
## strapi generate
Description: Generate APIs, controllers, content-types, policies, middlewares, services, and migrations.
(Source: https://docs.strapi.io/cms/cli#strapi-generate)
Language: Bash
File path: N/A
```bash
strapi generate
```
## strapi openapi generate
Description: Generate OpenAPI specifications for your Strapi application.
(Source: https://docs.strapi.io/cms/cli#strapi-openapi-generate)
Language: Bash
File path: /migrations/[timestamp].[name].js
```bash
strapi openapi generate
```
## Examples
Description: | Option | Type | Default | Description | | ---------- | -------- | --------------------- | ------------------------------------------------ | | --output | string | ./openapi-spec.json | Output file path for the generated specification |
(Source: https://docs.strapi.io/cms/cli#examples)
Language: Bash
File path: ./openapi-spec.js
```bash
# Generate OpenAPI specification (default)
yarn strapi openapi generate
# Generate with custom output path
yarn strapi openapi generate --output ./docs/api-spec.json
```
---
Language: Bash
File path: ./openapi-spec.js
```bash
# Generate OpenAPI specification (default)
npm run strapi openapi generate
# Generate with custom output path
npm run strapi openapi generate -- --output ./docs/api-spec.json
```
## strapi templates:generate
Description: Create a template from the current Strapi project.
(Source: https://docs.strapi.io/cms/cli#strapi-templates-generate)
Language: Bash
File path: ./openapi-spec.js
```bash
strapi templates:generate
```
## strapi ts:generate-types
Description: Generate TypeScript typings for the project schemas.
(Source: https://docs.strapi.io/cms/cli#strapi-ts-generate-types)
Language: Bash
File path: ./openapi-spec.js
```bash
strapi ts:generate-types
```
## strapi routes:list
Description: Display a list of all the available routes.
(Source: https://docs.strapi.io/cms/cli#strapi-routes-list)
Language: Bash
File path: ./openapi-spec.js
```bash
strapi routes:list
```
## strapi policies:list
Description: Display a list of all the registered policies.
(Source: https://docs.strapi.io/cms/cli#strapi-policies-list)
Language: Bash
File path: ./openapi-spec.js
```bash
strapi policies:list
```
## strapi middlewares:list
Description: Display a list of all the registered middlewares.
(Source: https://docs.strapi.io/cms/cli#strapi-middlewares-list)
Language: Bash
File path: N/A
```bash
strapi middlewares:list
```
## strapi content-types:list
Description: Display a list of all the existing content-types.
(Source: https://docs.strapi.io/cms/cli#strapi-content-types-list)
Language: Bash
File path: N/A
```bash
strapi content-types:list
```
## strapi hooks:list
Description: Display a list of all the available hooks.
(Source: https://docs.strapi.io/cms/cli#strapi-hooks-list)
Language: Bash
File path: N/A
```bash
strapi hooks:list
```
## strapi controllers:list
Description: Display a list of all the registered controllers.
(Source: https://docs.strapi.io/cms/cli#strapi-controllers-list)
Language: Bash
File path: N/A
```bash
strapi controllers:list
```
## strapi services:list
Description: Display a list of all the registered services.
(Source: https://docs.strapi.io/cms/cli#strapi-services-list)
Language: Bash
File path: N/A
```bash
strapi services:list
```
# Admin panel configuration
Source: https://docs.strapi.io/cms/configurations/admin-panel
## Admin panel behavior
Description: Releases notifications To disable notifications about new Strapi releases, set the config.notifications.releases key to false.
(Source: https://docs.strapi.io/cms/configurations/admin-panel#admin-panel-behavior)
Language: JavaScript
File path: /src/admin/app.js
```js
const config = {
// … other customization options go here
tutorials: false,
notifications: { releases: false },
};
export default {
config,
};
```
## Update the admin panel's path only
Description: To make the admin panel accessible at another path, for instance at http://localhost:1337/dashboard, define or update the url property:
(Source: https://docs.strapi.io/cms/configurations/admin-panel#update-the-admin-panel-s-path-only)
Language: JavaScript
File path: /config/admin.js
```js
module.exports = ({ env }) => ({
// … other configuration properties
url: "/dashboard",
});
```
## Update the admin panel's host and port
Description: If the admin panel server and the back-end server are not hosted on the same server, you will need to update the host and port of the admin panel.
(Source: https://docs.strapi.io/cms/configurations/admin-panel#update-the-admin-panel-s-host-and-port)
Language: JavaScript
File path: /config/admin.js
```js
module.exports = ({ env }) => ({
host: "my-host.com",
port: 3000,
// Additionally you can define another path instead of the default /admin one 👇
// url: '/dashboard'
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
export default ({ env }) => ({
host: "my-host.com",
port: 3000,
// Additionally you can define another path instead of the default /admin one 👇
// url: '/dashboard'
});
```
## Allow additional hosts
Description: Create ./src/admin/vite.config.ts (or .js) if it does not already exist and extend the dev-server configuration.
(Source: https://docs.strapi.io/cms/configurations/admin-panel#allow-additional-hosts)
Language: JavaScript
File path: src/admin/vite.config.js
```js
import { mergeConfig } from 'vite';
export default (config) => {
return mergeConfig(config, {
server: {
allowedHosts: ['preview.my-app.test', '.example-proxy.internal'],
},
});
};
```
---
Language: TypeScript
File path: src/admin/vite.config.ts
```ts
import { mergeConfig, type UserConfig } from 'vite';
export default (config: UserConfig) => {
return mergeConfig(config, {
server: {
allowedHosts: ['preview.my-app.test', '.example-proxy.internal'],
},
});
};
```
## Deploy on different servers
Description: The following example setup allows you to serve the admin panel from one domain while the API runs on another:
(Source: https://docs.strapi.io/cms/configurations/admin-panel#deploy-on-different-servers)
Language: JavaScript
File path: /config/server.js
```js
module.exports = ({ env }) => ({
host: env("HOST", "0.0.0.0"),
port: env.int("PORT", 1337),
url: "http://yourbackend.com",
});
```
---
Language: JavaScript
File path: /config/admin.js
```js
module.exports = ({ env }) => ({
/**
* Note: The administration will be accessible from the root of the domain
* (ex: http://yourfrontend.com/)
*/
url: "/",
serveAdminPanel: false, // http://yourbackend.com will not serve any static admin files
});
```
---
Language: TypeScript
File path: /config/server.ts
```ts
export default ({ env }) => ({
host: env("HOST", "0.0.0.0"),
port: env.int("PORT", 1337),
url: "http://yourbackend.com",
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
export default ({ env }) => ({
/**
* Note: The administration will be accessible from the root of the domain
* (ex: http://yourfrontend.com/)
*/
url: "/",
serveAdminPanel: false, // http://yourbackend.com will not serve any static admin files
});
```
## Configuration examples
Description: The default configuration created with any new project should at least include the following:
(Source: https://docs.strapi.io/cms/configurations/admin-panel#configuration-examples)
Language: JavaScript
File path: /config/admin.js
```js
module.exports = ({ env }) => ({
apiToken: {
salt: env('API_TOKEN_SALT', 'someRandomLongString'),
},
auditLogs: { // only accessible with an Enterprise plan
enabled: env.bool('AUDIT_LOGS_ENABLED', true),
},
auth: {
secret: env('ADMIN_JWT_SECRET', 'someSecretKey'),
},
transfer: {
token: {
salt: env('TRANSFER_TOKEN_SALT', 'anotherRandomLongString'),
}
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
export default ({ env }) => ({
apiToken: {
salt: env('API_TOKEN_SALT', 'someRandomLongString'),
},
auditLogs: { // only accessible with an Enterprise plan
enabled: env.bool('AUDIT_LOGS_ENABLED', true),
},
auth: {
secret: env('ADMIN_JWT_SECRET', 'someSecretKey'),
},
transfer: {
token: {
salt: env('TRANSFER_TOKEN_SALT', 'anotherRandomLongString'),
}
},
});
```
Language: JavaScript
File path: /config/admin.js
```js
module.exports = ({ env }) => ({
apiToken: {
salt: env('API_TOKEN_SALT', 'someRandomLongString'),
secrets: {
encryptionKey: env('ENCRYPTION_KEY'),
},
},
ai: {
enabled: false, // use this to disable Strapi AI
},
auditLogs: { // only accessible with an Enterprise plan
enabled: env.bool('AUDIT_LOGS_ENABLED', true),
retentionDays: 120,
},
auth: {
events: {
onConnectionSuccess(e) {
console.log(e.user, e.provider);
},
onConnectionError(e) {
console.error(e.error, e.provider);
},
},
options: {
expiresIn: '7d',
},
secret: env('ADMIN_JWT_SECRET', 'someSecretKey'),
sessions: {
accessTokenLifespan: 1800, // 30 minutes
maxRefreshTokenLifespan: 2592000, // 30 days
idleRefreshTokenLifespan: 604800, // 7 days
maxSessionLifespan: 604800, // 7 days
idleSessionLifespan: 3600, // 1 hour
},
cookie: {
domain: env('ADMIN_COOKIE_DOMAIN'),
path: '/admin',
sameSite: 'lax',
},
},
url: env('PUBLIC_ADMIN_URL', '/dashboard'),
autoOpen: false,
watchIgnoreFiles: [
'./my-custom-folder', // Folder
'./scripts/someScript.sh', // File
],
host: 'localhost',
port: 8003,
serveAdminPanel: env.bool('SERVE_ADMIN', true),
forgotPassword: {
from: 'no-reply@example.com',
replyTo: 'no-reply@example.com',
},
rateLimit: {
interval: { hour: 1, min: 30 },
timeWait: 3*1000,
max: 10,
},
transfer: {
token: {
salt: env('TRANSFER_TOKEN_SALT', 'anotherRandomLongString'),
}
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
export default ({ env }) => ({
apiToken: {
salt: env('API_TOKEN_SALT', 'someRandomLongString'),
secrets: {
encryptionKey: env('ENCRYPTION_KEY'),
},
},
ai: {
enabled: false, // use this to disable Strapi AI
},
auditLogs: { // only accessible with an Enterprise plan
enabled: env.bool('AUDIT_LOGS_ENABLED', true),
retentionDays: 120,
},
auth: {
events: {
onConnectionSuccess(e) {
console.log(e.user, e.provider);
},
onConnectionError(e) {
console.error(e.error, e.provider);
},
},
options: {
expiresIn: '7d',
},
secret: env('ADMIN_JWT_SECRET', 'someSecretKey'),
sessions: {
accessTokenLifespan: 1800, // 30 minutes
maxRefreshTokenLifespan: 2592000, // 30 days
idleRefreshTokenLifespan: 604800, // 7 days
maxSessionLifespan: 604800, // 7 days
idleSessionLifespan: 3600, // 1 hour
},
cookie: {
domain: env('ADMIN_COOKIE_DOMAIN'),
path: '/admin',
sameSite: 'lax',
},
},
url: env('PUBLIC_ADMIN_URL', '/dashboard'),
autoOpen: false,
watchIgnoreFiles: [
'./my-custom-folder', // Folder
'./scripts/someScript.sh', // File
],
host: 'localhost',
port: 8003,
serveAdminPanel: env.bool('SERVE_ADMIN', true),
forgotPassword: {
from: 'no-reply@example.com',
replyTo: 'no-reply@example.com',
},
rateLimit: {
interval: { hour: 1, min: 30 },
timeWait: 3*1000,
max: 10,
},
transfer: {
token: {
salt: env('TRANSFER_TOKEN_SALT', 'anotherRandomLongString'),
}
},
});
```
# API calls configuration
Source: https://docs.strapi.io/cms/configurations/api
## API configuration
Description: Example:
(Source: https://docs.strapi.io/cms/configurations/api#api-configuration)
Language: JavaScript
File path: ./config/api.js
```js
module.exports = ({ env }) => ({
responses: {
privateAttributes: ['_v', 'id', 'created_at'],
},
rest: {
prefix: '/v1',
defaultLimit: 100,
maxLimit: 250,
strictParams: true, // only allow parameters defined on routes or added via contentAPI.addQueryParams/addInputParams
},
documents: {
strictParams: true, // reject unrecognized root-level parameters in strapi.documents() calls
},
});
```
---
Language: TypeScript
File path: ./config/api.ts
```ts
export default ({ env }) => ({
responses: {
privateAttributes: ['_v', 'id', 'created_at'],
},
rest: {
prefix: '/v1',
defaultLimit: 100,
maxLimit: 250,
strictParams: true, // only allow parameters defined on routes or added via contentAPI.addQueryParams/addInputParams
},
documents: {
strictParams: true, // reject unrecognized root-level parameters in strapi.documents() calls
},
});
```
# CRON jobs
Source: https://docs.strapi.io/cms/configurations/cron
## Cron jobs
Description: The cron format consists of:
(Source: https://docs.strapi.io/cms/configurations/cron#cron-jobs)
Language: JavaScript
File path: ./config/server.js
```js
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ |
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, OPTIONAL)
```
## Using the object format
Description: To define a cron job with the object format, create a file with the following structure:
(Source: https://docs.strapi.io/cms/configurations/cron#using-the-object-format)
Language: JavaScript
File path: ./config/cron-tasks.js
```js
module.exports = {
/**
* Simple example.
* Every monday at 1am.
*/
myJob: {
task: ({ strapi }) => {
// Add your own logic here (e.g. send a queue of email, create a database backup, etc.).
},
options: {
rule: "0 0 1 * * 1",
},
},
};
```
---
Language: TypeScript
File path: ./config/cron-tasks.ts
```ts
export default {
/**
* Simple example.
* Every monday at 1am.
*/
myJob: {
task: ({ strapi }) => {
// Add your own logic here (e.g. send a queue of email, create a database backup, etc.).
},
options: {
rule: "0 0 1 * * 1",
},
},
};
```
Language: JavaScript
File path: ./config/cron-tasks.js
```js
module.exports = {
/**
* Cron job with timezone example.
* Every Monday at 1am for Asia/Dhaka timezone.
* List of valid timezones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
*/
myJob: {
task: ({ strapi }) => {
/* Add your own logic here */
},
options: {
rule: "0 0 1 * * 1",
tz: "Asia/Dhaka",
},
},
};
```
---
Language: TypeScript
File path: ./config/cron-tasks.ts
```ts
export default {
/**
* Cron job with timezone example.
* Every Monday at 1am for Asia/Dhaka timezone.
* List of valid timezones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
*/
myJob: {
task: ({ strapi }) => {
/* Add your own logic here */
},
options: {
rule: "0 0 1 * * 1",
tz: "Asia/Dhaka",
},
},
};
```
Language: JavaScript
File path: ./config/cron-tasks.js
```js
module.exports = {
myJob: {
task: ({ strapi }) => {
/* Add your own logic here */
},
// only run once after 10 seconds
options: new Date(Date.now() + 10000),
},
};
```
---
Language: TypeScript
File path: ./config/cron-tasks.ts
```ts
export default {
myJob: {
task: ({ strapi }) => {
/* Add your own logic here */
},
// only run once after 10 seconds
options: new Date(Date.now() + 10000),
},
};
```
Language: JavaScript
File path: ./config/cron-tasks.js
```js
module.exports = {
myJob: {
task: ({ strapi }) => {
/* Add your own logic here */
},
options: {
rule: "* * * * * *",
// start 10 seconds from now
start: new Date(Date.now() + 10000),
// end 20 seconds from now
end: new Date(Date.now() + 20000),
},
},
};
```
---
Language: TypeScript
File path: ./config/cron-tasks.ts
```ts
export default {
myJob: {
task: ({ strapi }) => {
/* Add your own logic here */
},
// only run once after 10 seconds
options: {
rule: "* * * * * *",
// start 10 seconds from now
start: new Date(Date.now() + 10000),
// end 20 seconds from now
end: new Date(Date.now() + 20000),
},
},
};
```
## Using the key format
Description: To define a cron job with the key format, create a file with the following structure:
(Source: https://docs.strapi.io/cms/configurations/cron#using-the-key-format)
Language: JavaScript
File path: ./config/cron-tasks.js
```js
module.exports = {
/**
* Simple example.
* Every monday at 1am.
*/
"0 0 1 * * 1": ({ strapi }) => {
// Add your own logic here (e.g. send a queue of email, create a database backup, etc.).
},
};
```
---
Language: TypeScript
File path: ./config/cron-tasks.ts
```ts
export default {
/**
* Simple example.
* Every monday at 1am.
*/
"0 0 1 * * 1": ({ strapi }) => {
// Add your own logic here (e.g. send a queue of email, create a database backup, etc.).
},
};
```
## Enabling cron jobs
Description: To enable cron jobs, set cron.enabled to true in the server configuration file and declare the jobs:
(Source: https://docs.strapi.io/cms/configurations/cron#enabling-cron-jobs)
Language: JavaScript
File path: ./config/server.js
```js
const cronTasks = require("./cron-tasks");
module.exports = ({ env }) => ({
host: env("HOST", "0.0.0.0"),
port: env.int("PORT", 1337),
cron: {
enabled: true,
tasks: cronTasks,
},
});
```
---
Language: TypeScript
File path: ./config/server.ts
```ts
import cronTasks from "./cron-tasks";
export default ({ env }) => ({
host: env("HOST", "0.0.0.0"),
port: env.int("PORT", 1337),
cron: {
enabled: true,
tasks: cronTasks,
},
});
```
## Adding or removing cron jobs
Description: Use strapi.cron.add anywhere in your custom code add CRON jobs to the Strapi instance:
(Source: https://docs.strapi.io/cms/configurations/cron#adding-or-removing-cron-jobs)
Language: JavaScript
File path: ./src/plugins/my-plugin/strapi-server.js
```js
module.exports = () => ({
bootstrap({ strapi }) {
strapi.cron.add({
// runs every second
myJob: {
task: ({ strapi }) => {
console.log("hello from plugin");
},
options: {
rule: "* * * * * *",
},
},
});
},
});
```
Language: Bash
File path: N/A
```bash
strapi.cron.remove("myJob");
```
## Listing cron jobs
Description: Use strapi.cron.jobs anywhere in your custom code to list all the cron jobs that are currently running:
(Source: https://docs.strapi.io/cms/configurations/cron#listing-cron-jobs)
Language: Bash
File path: N/A
```bash
strapi.cron.jobs
```
# Database configuration
Source: https://docs.strapi.io/cms/configurations/database
## Configuration examples
Description: | Parameter | Description | Type | Default | | ---------------- | --------------------------------------------------------------- | --------- | ------- | | forceMigration | Enable or disable the forced database migration.
(Source: https://docs.strapi.io/cms/configurations/database#configuration-examples)
Language: DOCKERFILE
File path: ./config/database.js
```dockerfile
module.exports = ({ env }) => ({
connection: {
client: 'postgres',
connection: {
connectionString: env('DATABASE_URL'),
host: env('DATABASE_HOST', '127.0.0.1'),
port: env.int('DATABASE_PORT', 5432),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD', 'strapi'),
schema: env('DATABASE_SCHEMA', 'public'), // Not required
ssl: env.bool('DATABASE_SSL', false) && {
rejectUnauthorized: env.bool('DATABASE_SSL_SELF', false), // For self-signed certificates
},
},
pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
debug: false,
},
});
```
---
Language: JavaScript
File path: ./config/database.js
```js
module.exports = ({ env }) => ({
connection: {
client: 'postgres',
connection: {
...
ssl: env('DATABASE_SSL', false)
},
},
});
```
---
Language: JavaScript
File path: ./config/database.js
```js
const fs = require('fs');
module.exports = ({ env }) => ({
connection: {
client: 'postgres',
connection: {
...
ssl: {
ca: fs.readFileSync(`${__dirname}/path/to/your/ca-certificate.crt`).toString(),
},
},
},
});
```
---
Language: DOCKERFILE
File path: ./config/database.js
```dockerfile
module.exports = ({ env }) => ({
connection: {
client: 'mysql',
connection: {
host: env('DATABASE_HOST', '127.0.0.1'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD', 'strapi'),
ssl: {
rejectUnauthorized: env.bool('DATABASE_SSL_SELF', false), // For self-signed certificates
},
},
debug: false,
},
});
```
Language: JavaScript
File path: ./config/database.js
```js
module.exports = ({ env }) => ({
connection: {
client: 'sqlite',
connection: {
filename: env('DATABASE_FILENAME', '.tmp/data.db'),
},
useNullAsDefault: true,
debug: false,
},
});
```
---
Language: DOCKERFILE
File path: ./config/database.ts
```dockerfile
import path from 'path';
export default ({ env }) => ({
connection: {
client: 'sqlite',
connection: {
filename: path.join(
__dirname,
'..',
'..',
env('DATABASE_FILENAME', path.join('.tmp', 'data.db'))
),
},
useNullAsDefault: true,
},
});
```
## Get settings
Description: environment (string): Sets the environment you want to store the data in.
(Source: https://docs.strapi.io/cms/configurations/database#get-settings)
Language: JavaScript
File path: ./config/database.js
```js
// strapi.store(object).get(object);
// create reusable plugin store variable
const pluginStore = strapi.store({
environment: strapi.config.environment,
type: 'plugin',
name: 'users-permissions',
});
await pluginStore.get({ key: 'grant' });
```
## Set settings
Description: value (any, required): The value you want to store.
(Source: https://docs.strapi.io/cms/configurations/database#set-settings)
Language: JavaScript
File path: N/A
```js
// strapi.store(object).set(object);
// create reusable plugin store variable
const pluginStore = strapi.store({
environment: strapi.config.environment,
type: 'plugin',
name: 'users-permissions'
});
await pluginStore.set({
key: 'grant',
value: {
...
}
});
```
## Environment variables in database configurations
Description: Strapi version v4.6.2 and higher includes the database configuration options in the ./config/database.js or ./config/database.ts file.
(Source: https://docs.strapi.io/cms/configurations/database#environment-variables-in-database-configurations)
Language: JavaScript
File path: ./config/database.js
```js
const path = require('path');
module.exports = ({ env }) => {
const client = env('DATABASE_CLIENT', 'sqlite');
const connections = {
mysql: {
connection: {
connectionString: env('DATABASE_URL'),
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD', 'strapi'),
ssl: env.bool('DATABASE_SSL', false) && {
key: env('DATABASE_SSL_KEY', undefined),
cert: env('DATABASE_SSL_CERT', undefined),
ca: env('DATABASE_SSL_CA', undefined),
capath: env('DATABASE_SSL_CAPATH', undefined),
cipher: env('DATABASE_SSL_CIPHER', undefined),
rejectUnauthorized: env.bool(
'DATABASE_SSL_REJECT_UNAUTHORIZED',
true
),
},
},
pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
},
postgres: {
connection: {
connectionString: env('DATABASE_URL'),
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD', 'strapi'),
ssl: env.bool('DATABASE_SSL', false) && {
key: env('DATABASE_SSL_KEY', undefined),
cert: env('DATABASE_SSL_CERT', undefined),
ca: env('DATABASE_SSL_CA', undefined),
capath: env('DATABASE_SSL_CAPATH', undefined),
cipher: env('DATABASE_SSL_CIPHER', undefined),
rejectUnauthorized: env.bool(
'DATABASE_SSL_REJECT_UNAUTHORIZED',
true
),
},
schema: env('DATABASE_SCHEMA', 'public'),
},
pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
},
sqlite: {
connection: {
filename: path.join(
__dirname,
'..',
env('DATABASE_FILENAME', 'data.db')
),
},
useNullAsDefault: true,
},
};
return {
connection: {
client,
...connections[client],
acquireConnectionTimeout: env.int('DATABASE_CONNECTION_TIMEOUT', 60000),
},
};
};
```
---
Language: JavaScript
File path: ./config/database.js
```js
import path from 'path';
export default = ({ env }) => {
const client = env('DATABASE_CLIENT', 'sqlite');
const connections = {
mysql: {
connection: {
connectionString: env('DATABASE_URL'),
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD', 'strapi'),
ssl: env.bool('DATABASE_SSL', false) && {
key: env('DATABASE_SSL_KEY', undefined),
cert: env('DATABASE_SSL_CERT', undefined),
ca: env('DATABASE_SSL_CA', undefined),
capath: env('DATABASE_SSL_CAPATH', undefined),
cipher: env('DATABASE_SSL_CIPHER', undefined),
rejectUnauthorized: env.bool(
'DATABASE_SSL_REJECT_UNAUTHORIZED',
true
),
},
},
pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
},
postgres: {
connection: {
connectionString: env('DATABASE_URL'),
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD', 'strapi'),
ssl: env.bool('DATABASE_SSL', false) && {
key: env('DATABASE_SSL_KEY', undefined),
cert: env('DATABASE_SSL_CERT', undefined),
ca: env('DATABASE_SSL_CA', undefined),
capath: env('DATABASE_SSL_CAPATH', undefined),
cipher: env('DATABASE_SSL_CIPHER', undefined),
rejectUnauthorized: env.bool(
'DATABASE_SSL_REJECT_UNAUTHORIZED',
true
),
},
schema: env('DATABASE_SCHEMA', 'public'),
},
pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
},
sqlite: {
connection: {
filename: path.join(
__dirname,
'..',
env('DATABASE_FILENAME', 'data.db')
),
},
useNullAsDefault: true,
},
};
return {
connection: {
client,
...connections[client],
acquireConnectionTimeout: env.int('DATABASE_CONNECTION_TIMEOUT', 60000),
},
};
};
```
Language: JavaScript
File path: ./config/database.js
```js
# Database
DATABASE_CLIENT=mysql
DATABASE_HOST=127.0.0.1
DATABASE_PORT=3306
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=strap1
DATABASE_SSL=false
```
---
Language: JavaScript
File path: ./config/database.js
```js
# Database
DATABASE_CLIENT=postgres
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=strapi
DATABASE_SSL=false
```
---
Language: JavaScript
File path: ./config/database.js
```js
# Database
DATABASE_CLIENT=sqlite
DATABASE_FILENAME=.tmp/data.db
```
## Install SQLite during application creation
Description: Use one of the following commands:
(Source: https://docs.strapi.io/cms/configurations/database#install-sqlite-during-application-creation)
Language: Bash
File path: .config.js
```bash
yarn create strapi-app my-project --quickstart
```
---
Language: Bash
File path: .config.js
```bash
npx create-strapi-app@latest my-project --quickstart
```
## Install SQLite manually
Description: In a terminal, run the following command:
(Source: https://docs.strapi.io/cms/configurations/database#install-sqlite-manually)
Language: Bash
File path: .config.js
```bash
yarn add better-sqlite3
```
---
Language: Bash
File path: .config.js
```bash
npm install better-sqlite3
```
Language: JavaScript
File path: ./config/database.js
```js
module.exports = ({ env }) => ({
connection: {
client: 'sqlite',
connection: {
filename: path.join(__dirname, '..', env('DATABASE_FILENAME', '.tmp/data.db')),
},
useNullAsDefault: true,
},
});
```
---
Language: TypeScript
File path: ./config/database.ts
```ts
import path from 'path';
export default ({ env }) => ({
connection: {
client: 'sqlite',
connection: {
filename: path.join(__dirname, '..', '..', env('DATABASE_FILENAME', '.tmp/data.db')),
},
useNullAsDefault: true,
},
});
```
## PostgreSQL
Description: To create a new PostgreSQL user with the SCHEMA permission, use the following steps:
(Source: https://docs.strapi.io/cms/configurations/database#postgresql)
Language: TypeScript
File path: /config/database.ts
```ts
# Create a new database user with a secure password
$ CREATE USER my_strapi_db_user WITH PASSWORD 'password';
# Connect to the database as the PostgreSQL admin
$ \c my_strapi_db_name admin_user
# Grant schema privileges to the user
$ GRANT ALL ON SCHEMA public TO my_strapi_db_user;
```
# Creating custom email providers
Source: https://docs.strapi.io/cms/configurations/email-custom-providers
## Provider interface
Description: To implement a custom provider you must .
(Source: https://docs.strapi.io/cms/configurations/email-custom-providers#provider-interface)
Language: JavaScript
File path: N/A
```js
module.exports = {
init: (providerOptions = {}, settings = {}) => {
return {
send: async options => {},
};
},
};
```
---
Language: JavaScript
File path: N/A
```js
export default {
init: (providerOptions = {}, settings = {}) => {
return {
send: async options => {},
};
},
};
```
## Using a local provider
Description: Create a providers folder at the root of your Strapi application.
(Source: https://docs.strapi.io/cms/configurations/email-custom-providers#using-a-local-provider)
Language: JSON
File path: /config/plugins.js
```json
{
"dependencies": {
"strapi-provider-email-custom": "file:providers/strapi-provider-email-custom"
}
}
```
# Advanced Nodemailer configuration
Source: https://docs.strapi.io/cms/configurations/email-nodemailer
## OAuth2 authentication
Description: For services like Gmail or Outlook that require OAuth2 instead of a password:
(Source: https://docs.strapi.io/cms/configurations/email-nodemailer#oauth2-authentication)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: 'smtp.gmail.com',
port: 465,
secure: true,
// highlight-start
auth: {
type: 'OAuth2',
user: env('SMTP_USER'),
clientId: env('OAUTH_CLIENT_ID'),
clientSecret: env('OAUTH_CLIENT_SECRET'),
refreshToken: env('OAUTH_REFRESH_TOKEN'),
},
// highlight-end
},
settings: {
defaultFrom: env('SMTP_USER'),
defaultReplyTo: env('SMTP_USER'),
},
},
},
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: 'smtp.gmail.com',
port: 465,
secure: true,
// highlight-start
auth: {
type: 'OAuth2',
user: env('SMTP_USER'),
clientId: env('OAUTH_CLIENT_ID'),
clientSecret: env('OAUTH_CLIENT_SECRET'),
refreshToken: env('OAUTH_REFRESH_TOKEN'),
},
// highlight-end
},
settings: {
defaultFrom: env('SMTP_USER'),
defaultReplyTo: env('SMTP_USER'),
},
},
},
});
```
## Connection pooling
Description: Use connection pooling to reuse SMTP connections and improve throughput when sending many emails:
(Source: https://docs.strapi.io/cms/configurations/email-nodemailer#connection-pooling)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: env('SMTP_HOST'),
port: 465,
secure: true,
// highlight-start
pool: true,
maxConnections: 5,
maxMessages: 100,
// highlight-end
auth: {
user: env('SMTP_USERNAME'),
pass: env('SMTP_PASSWORD'),
},
},
settings: {
defaultFrom: 'hello@example.com',
defaultReplyTo: 'hello@example.com',
},
},
},
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: env('SMTP_HOST'),
port: 465,
secure: true,
// highlight-start
pool: true,
maxConnections: 5,
maxMessages: 100,
// highlight-end
auth: {
user: env('SMTP_USERNAME'),
pass: env('SMTP_PASSWORD'),
},
},
settings: {
defaultFrom: 'hello@example.com',
defaultReplyTo: 'hello@example.com',
},
},
},
});
```
## DKIM signing
Description: Add DKIM signatures to improve deliverability and authenticate outbound mail:
(Source: https://docs.strapi.io/cms/configurations/email-nodemailer#dkim-signing)
Language: DOCKERFILE
File path: /config/plugins.js
```dockerfile
module.exports = ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: env('SMTP_HOST'),
port: 587,
auth: {
user: env('SMTP_USERNAME'),
pass: env('SMTP_PASSWORD'),
},
// highlight-start
dkim: {
domainName: 'example.com',
keySelector: 'mail',
privateKey: env('DKIM_PRIVATE_KEY'),
},
// highlight-end
},
settings: {
defaultFrom: 'hello@example.com',
defaultReplyTo: 'hello@example.com',
},
},
},
});
```
---
Language: DOCKERFILE
File path: /config/plugins.ts
```dockerfile
export default ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: env('SMTP_HOST'),
port: 587,
auth: {
user: env('SMTP_USERNAME'),
pass: env('SMTP_PASSWORD'),
},
// highlight-start
dkim: {
domainName: 'example.com',
keySelector: 'mail',
privateKey: env('DKIM_PRIVATE_KEY'),
},
// highlight-end
},
settings: {
defaultFrom: 'hello@example.com',
defaultReplyTo: 'hello@example.com',
},
},
},
});
```
## Rate limiting
Description: Limit the number of messages sent per time interval to avoid triggering spam filters:
(Source: https://docs.strapi.io/cms/configurations/email-nodemailer#rate-limiting)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: env('SMTP_HOST'),
port: 465,
secure: true,
pool: true,
// highlight-start
rateLimit: 5, // max messages per rateDelta
rateDelta: 1000, // time interval in ms (1 second)
// highlight-end
auth: {
user: env('SMTP_USERNAME'),
pass: env('SMTP_PASSWORD'),
},
},
settings: {
defaultFrom: 'hello@example.com',
defaultReplyTo: 'hello@example.com',
},
},
},
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: env('SMTP_HOST'),
port: 465,
secure: true,
pool: true,
// highlight-start
rateLimit: 5,
rateDelta: 1000,
// highlight-end
auth: {
user: env('SMTP_USERNAME'),
pass: env('SMTP_PASSWORD'),
},
},
settings: {
defaultFrom: 'hello@example.com',
defaultReplyTo: 'hello@example.com',
},
},
},
});
```
# Environment variables configuration
Source: https://docs.strapi.io/cms/configurations/environment
## Example .env file
Description: Set these environment variables for secure authentication with sessions management configuration:
(Source: https://docs.strapi.io/cms/configurations/environment#example-env-file)
Language: DOTENV
File path: .env
```dotenv
# Admin authentication
ADMIN_JWT_SECRET=your-admin-secret-key
# Cookie domain (optional)
ADMIN_COOKIE_DOMAIN=yourdomain.com
# Users & Permissions JWT secret
JWT_SECRET=your-content-api-secret-key
# Users & Permissions session management
UP_JWT_MANAGEMENT=refresh # or 'legacy-support'
UP_SESSIONS_ACCESS_TTL=604800 # 1 week in seconds
UP_SESSIONS_MAX_REFRESH_TTL=2592000 # 30 days in seconds
UP_SESSIONS_IDLE_REFRESH_TTL=604800 # 7 days in seconds
UP_SESSIONS_HTTPONLY=false # true for HTTP-only cookies
UP_SESSIONS_COOKIE_NAME=strapi_up_refresh
UP_SESSIONS_COOKIE_SAMESITE=lax
UP_SESSIONS_COOKIE_PATH=/
UP_SESSIONS_COOKIE_SECURE=false # true in production
```
## Environment configurations
Description: For instance, using the following configuration files will give you various options to start the server:
(Source: https://docs.strapi.io/cms/configurations/environment#environment-configurations)
Language: JavaScript
File path: ./config/server.js
```js
module.exports = {
host: '127.0.0.1',
};
```
---
Language: JavaScript
File path: ./config/env/production/server.js
```js
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
});
```
---
Language: TypeScript
File path: ./config/server.ts
```ts
export default ({ env }) => ({
host: '127.0.0.1',
});
```
---
Language: TypeScript
File path: ./config/env/production/server.ts
```ts
export default ({ env }) => ({
host: env('HOST', '0.0.0.0'),
});
```
Language: Bash
File path: N/A
```bash
yarn start # uses host 127.0.0.1
NODE_ENV=production yarn start # uses host defined in .env. If not defined, uses 0.0.0.0
HOST=10.0.0.1 NODE_ENV=production yarn start # uses host 10.0.0.1
```
# Features configuration
Source: https://docs.strapi.io/cms/configurations/features
## Enabling a future flag
Description: (optional) If the server is running, stop it with Ctrl-C.
(Source: https://docs.strapi.io/cms/configurations/features#enabling-a-future-flag)
Language: TypeScript
File path: /config/features.ts
```ts
module.exports = ({ env }) => ({
future: {
experimental_firstPublishedAt: env.bool('STRAPI_FUTURE_EXPERIMENTAL_FIRST_PUBLISHED_AT', false),
},
})
```
---
Language: DOTENV
File path: .env
```dotenv
STRAPI_FUTURE_EXPERIMENTAL_FIRST_PUBLISHED_AT=true
```
---
Language: TypeScript
File path: /config/features.ts
```ts
export default {
future: {
experimental_firstPublishedAt: env.bool('STRAPI_FUTURE_EXPERIMENTAL_FIRST_PUBLISHED_AT', false),
},
};
```
---
Language: DOTENV
File path: .env
```dotenv
STRAPI_FUTURE_EXPERIMENTAL_FIRST_PUBLISHED_AT=true
```
Language: Bash
File path: /features.js
```bash
yarn develop
```
---
Language: Bash
File path: /features.js
```bash
npm run develop
```
# Lifecycle functions
Source: https://docs.strapi.io/cms/configurations/functions
## Synchronous
Description: Synchronous functions run logic that completes immediately without awaiting other asynchronous tasks.
(Source: https://docs.strapi.io/cms/configurations/functions#synchronous)
Language: JavaScript
File path: ./src/index.js
```js
module.exports = {
register({ strapi }) {
strapi.log.info('Registering static configuration');
},
bootstrap({ strapi }) {
strapi.log.info('Bootstrap finished without awaiting tasks');
},
destroy({ strapi }) {
strapi.log.info('Server shutdown started');
}
};
```
---
Language: JavaScript
File path: ./src/index.js
```js
export default {
register({ strapi }) {
strapi.log.info('Registering static configuration');
},
bootstrap({ strapi }) {
strapi.log.info('Bootstrap finished without awaiting tasks');
},
destroy({ strapi }) {
strapi.log.info('Server shutdown started');
}
};
```
## Asynchronous
Description: Asynchronous functions use the async keyword to await tasks such as API calls or database queries before Strapi continues.
(Source: https://docs.strapi.io/cms/configurations/functions#asynchronous)
Language: JavaScript
File path: ./src/index.js
```js
module.exports = {
async register({ strapi }) {
await new Promise((resolve) => setTimeout(resolve, 200));
strapi.log.info('Async register finished after a short delay');
},
async bootstrap({ strapi }) {
const { results: articles } = await strapi
.documents('api::article.article')
.findMany({
filters: { publishedAt: { $notNull: true } },
fields: ['id'],
});
strapi.log.info(`Indexed ${articles.length} published articles`);
},
async destroy({ strapi }) {
await strapi.documents('api::temporary-cache.temporary-cache').deleteMany({
filters: {},
});
}
};
```
---
Language: JavaScript
File path: ./src/index.js
```js
export default {
async register({ strapi }) {
await new Promise((resolve) => setTimeout(resolve, 200));
strapi.log.info('Async register finished after a short delay');
},
async bootstrap({ strapi }) {
const { results: articles } = await strapi
.documents('api::article.article')
.findMany({
filters: { publishedAt: { $notNull: true } },
fields: ['id'],
});
strapi.log.info(`Indexed ${articles.length} published articles`);
},
async destroy({ strapi }) {
await strapi.documents('api::temporary-cache.temporary-cache').deleteMany({
filters: {},
});
}
};
```
## Returning a promise
Description: Promise-returning functions hand back a promise so Strapi can wait for its resolution before continuing.
(Source: https://docs.strapi.io/cms/configurations/functions#returning-a-promise)
Language: JavaScript
File path: ./src/index.js
```js
module.exports = {
register({ strapi }) {
return new Promise((resolve) => {
strapi.log.info('Registering with a delayed startup task');
setTimeout(resolve, 200);
});
},
bootstrap({ strapi }) {
return new Promise((resolve, reject) => {
strapi
.documents('api::category.category')
.findMany({ filters: { slug: 'general' }, pageSize: 1 })
.then(({ results }) => {
if (results.length === 0) {
return strapi.documents('api::category.category').create({
data: { name: 'General', slug: 'general' },
});
}
return results[0];
})
.then(() => {
strapi.log.info('Ensured default category exists');
resolve();
})
.catch(reject);
});
},
destroy({ strapi }) {
return new Promise((resolve, reject) => {
strapi
.documents('api::temporary-cache.temporary-cache')
.deleteMany({ filters: {} })
.then(() => {
strapi.log.info('Cleared temporary cache before shutdown');
resolve();
})
.catch(reject);
});
}
};
```
---
Language: JavaScript
File path: N/A
```js
export default {
register({ strapi }) {
return new Promise((resolve) => {
strapi.log.info('Registering with a delayed startup task');
setTimeout(resolve, 200);
});
},
bootstrap({ strapi }) {
return new Promise((resolve, reject) => {
strapi
.documents('api::category.category')
.findMany({ filters: { slug: 'general' }, pageSize: 1 })
.then(({ results }) => {
if (results.length === 0) {
return strapi.documents('api::category.category').create({
data: { name: 'General', slug: 'general' },
});
}
return results[0];
})
.then(() => {
strapi.log.info('Ensured default category exists');
resolve();
})
.catch(reject);
});
},
destroy({ strapi }) {
return new Promise((resolve, reject) => {
strapi
.documents('api::temporary-cache.temporary-cache')
.deleteMany({ filters: {} })
.then(() => {
strapi.log.info('Cleared temporary cache before shutdown');
resolve();
})
.catch(reject);
});
}
};
```
## Register
Description: More specifically, typical use-cases for register() include front-load security tasks such as loading secrets, rotating API keys, or registering authentication providers before the app finishes initializing.
(Source: https://docs.strapi.io/cms/configurations/functions#register)
Language: JavaScript
File path: ./src/index.js
```js
module.exports = {
register({ strapi }) {
strapi.customFields.register({
name: 'color',
plugin: 'my-color-picker',
type: 'string',
});
},
};
```
---
Language: JavaScript
File path: ./src/index.js
```js
export default {
register({ strapi }) {
strapi.customFields.register({
name: 'color',
plugin: 'my-color-picker',
type: 'string',
});
},
};
```
## Bootstrap
Description: :::tip You can run yarn strapi console (or npm run strapi console) in the terminal and interact with the strapi object.
(Source: https://docs.strapi.io/cms/configurations/functions#bootstrap)
Language: JavaScript
File path: ./src/index.js
```js
module.exports = {
async bootstrap({ strapi }) {
const { results } = await strapi
.documents('api::category.category')
.findMany({ filters: { slug: 'general' }, pageSize: 1 });
if (results.length === 0) {
await strapi.documents('api::category.category').create({
data: { name: 'General', slug: 'general' },
});
strapi.log.info('Created default category');
}
},
};
```
---
Language: JavaScript
File path: ./src/index.js
```js
export default {
async bootstrap({ strapi }) {
const { results } = await strapi
.documents('api::category.category')
.findMany({ filters: { slug: 'general' }, pageSize: 1 });
if (results.length === 0) {
await strapi.documents('api::category.category').create({
data: { name: 'General', slug: 'general' },
});
strapi.log.info('Created default category');
}
},
};
```
## Destroy
Description: More specifically, a typical use-case for destroy() is to handle operational clean-up, such as closing database or queue connections and removing listeners so the application can shut down cleanly.
(Source: https://docs.strapi.io/cms/configurations/functions#destroy)
Language: JavaScript
File path: ./src/index.js
```js
let heartbeat;
module.exports = {
async bootstrap({ strapi }) {
heartbeat = setInterval(() => {
strapi.log.debug('Heartbeat interval running');
}, 60_000);
},
async destroy() {
clearInterval(heartbeat);
},
};
```
---
Language: JavaScript
File path: ./src/index.js
```js
let heartbeat: ReturnType;
export default {
async bootstrap({ strapi }) {
heartbeat = setInterval(() => {
strapi.log.debug('Heartbeat interval running');
}, 60_000);
},
async destroy() {
clearInterval(heartbeat);
},
};
```
## Combined usage
Description: Add initialization-only tasks (e.g.
(Source: https://docs.strapi.io/cms/configurations/functions#combined-usage)
Language: TypeScript
File path: src/index.ts
```ts
let cronJobKey: string | undefined;
export default {
register({ strapi }) {
strapi.customFields.register({
name: 'color',
type: 'string',
plugin: 'color-picker',
});
},
async bootstrap({ strapi }) {
cronJobKey = 'log-reminders';
strapi.cron.add({
[cronJobKey]: {
rule: '0 */6 * * *', // every 6 hours
job: async () => {
strapi.log.info('Remember to review new content in the admin panel.');
},
},
});
},
async destroy({ strapi }) {
if (cronJobKey) {
strapi.cron.remove(cronJobKey);
}
},
};
```
# Access and cast env variables
Source: https://docs.strapi.io/cms/configurations/guides/access-cast-environment-variables
## How to access and cast environment variables
Description: Instead of writing those credentials into configuration files, variables can be defined in a .env file at the root of the application:
(Source: https://docs.strapi.io/cms/configurations/guides/access-cast-environment-variables#how-to-access-and-cast-environment-variables)
Language: Bash
File path: N/A
```sh
# path: .env
DATABASE_PASSWORD=acme
```
Language: Bash
File path: N/A
```sh
ENV_PATH=/absolute/path/to/.env npm run start
```
## Accessing environment variables
Description: In configuration files, a env() utility allows defining defaults and casting values:
(Source: https://docs.strapi.io/cms/configurations/guides/access-cast-environment-variables#accessing-environment-variables)
Language: JavaScript
File path: ./config/database.js
```js
module.exports = ({ env }) => ({
connections: {
default: {
settings: {
password: env('DATABASE_PASSWORD'),
},
},
},
});
```
---
Language: TypeScript
File path: ./config/database.ts
```ts
export default ({ env }) => ({
connections: {
default: {
settings: {
password: env('DATABASE_PASSWORD'),
},
},
},
});
```
## Casting environment variables
Description: The env() utility can be used to cast environment variables to different types:
(Source: https://docs.strapi.io/cms/configurations/guides/access-cast-environment-variables#casting-environment-variables)
Language: DOCKERFILE
File path: N/A
```dockerfile
// Returns the env if defined without casting it
env('VAR', 'default');
// Cast to integer (using parseInt)
env.int('VAR', 0);
// Cast to float (using parseFloat)
env.float('VAR', 3.14);
// Cast to boolean (check if the value is equal to 'true')
env.bool('VAR', true);
// Cast to JS object (using JSON.parse)
env.json('VAR', { key: 'value' });
// Cast to array (syntax: ENV_VAR=[value1, value2, value3] | ENV_VAR=["value1", "value2", "value3"])
env.array('VAR', [1, 2, 3]);
// Cast to date (using new Date(value))
env.date('VAR', new Date());
// Returns the env matching oneOf union types
env.oneOf('UPLOAD_PROVIDER', ['local', 'aws'], 'local')
```
# Access configuration values
Source: https://docs.strapi.io/cms/configurations/guides/access-configuration-values
## How to access to configuration values from the code
Description: If the /config/server.ts|js file has the following configuration:
(Source: https://docs.strapi.io/cms/configurations/guides/access-configuration-values#how-to-access-to-configuration-values-from-the-code)
Language: TypeScript
File path: /config/server.ts
```ts
module.exports = {
host: '0.0.0.0',
};
```
---
Language: TypeScript
File path: /config/server.ts
```ts
export default {
host: '0.0.0.0',
};
```
Language: TypeScript
File path: /config/server.ts
```ts
module.exports = {
mySecret: 'someValue',
};
```
---
Language: TypeScript
File path: /config/server.ts
```ts
export default {
mySecret: 'someValue',
};
```
Language: TypeScript
File path: /config/server.ts
```ts
module.exports = ({ env }) => {
return {
mySecret: env('MY_SECRET_KEY', 'defaultSecretValue'),
};
};
```
---
Language: TypeScript
File path: /config/server.ts
```ts
export default ({ env }) => {
return {
mySecret: env('MY_SECRET_KEY', 'defaultSecretValue'),
};
};
```
Language: Bash
File path: /config/server.ts
```bash
strapi.config.get('server.host', 'defaultValueIfUndefined');
```
# SSO configuration
Source: https://docs.strapi.io/cms/configurations/guides/configure-sso
## Accessing the configuration
Description: The providers' configuration should be written in the auth.providers path of the admin panel as an array of provider configurations:
(Source: https://docs.strapi.io/cms/configurations/guides/configure-sso#accessing-the-configuration)
Language: JavaScript
File path: /config/admin.js
```js
module.exports = ({ env }) => ({
// ...
auth: {
providers: [], // The providers' configuration lives there
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
export default ({ env }) => ({
// ...
auth: {
providers: [], // The providers' configuration lives there
},
});
```
## Displaying providers logos
Description: By default, Strapi security policy does not allow loading images from external URLs, so provider logos will not show up on the login screen of the admin panel unless a security exception is added through middlewares configuration, as in the following example:
(Source: https://docs.strapi.io/cms/configurations/guides/configure-sso#displaying-providers-logos)
Language: JavaScript
File path: /config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // Base URL of the provider's logo
],
'media-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // Base URL of the provider's logo
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
```
---
Language: TypeScript
File path: /config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // Base URL of the provider's logo
],
'media-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // Base URL of the provider's logo
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
```
## Setting common domain for cookies
Description: When deploying the admin panel to a different location or on a different subdomain, an additional configuration is required to set the common domain for the cookies.
(Source: https://docs.strapi.io/cms/configurations/guides/configure-sso#setting-common-domain-for-cookies)
Language: JavaScript
File path: /config/admin.js
```js
module.exports = ({ env }) => ({
auth: {
domain: env("ADMIN_SSO_DOMAIN", ".test.example.com"),
providers: [
// ...
],
},
url: env("ADMIN_URL", "http://admin.test.example.com"),
// ...
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
export default ({ env }) => ({
auth: {
domain: env("ADMIN_SSO_DOMAIN", ".test.example.com"),
providers: [
// ...
],
},
url: env("ADMIN_URL", "http://admin.test.example.com"),
// ...
});
```
## Custom logic
Description: For example, if you want to allow only people with an official strapi.io email address, you can instantiate your strategy like follows:
(Source: https://docs.strapi.io/cms/configurations/guides/configure-sso#custom-logic)
Language: JavaScript
File path: /config/admin.js
```js
const strategyInstance = new Strategy(configuration, ({ email, username }, done) => {
// If the email ends with @strapi.io
if (email.endsWith('@strapi.io')) {
// then we continue with the data given by the provider
return done(null, { email, username });
}
// Otherwise, we continue by sending an error to the done function
done(new Error('Forbidden email address'));
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
const strategyInstance = new Strategy(configuration, ({ email, username }, done) => {
// If the email ends with @strapi.io
if (email.endsWith('@strapi.io')) {
// then we continue with the data given by the provider
return done(null, { email, username });
}
// Otherwise, we continue by sending an error to the done function
done(new Error('Forbidden email address'));
});
```
## Authentication events
Description: This event is triggered whenever a user is created using the auto-register feature added by SSO.
(Source: https://docs.strapi.io/cms/configurations/guides/configure-sso#authentication-events)
Language: JavaScript
File path: /config/admin.js
```js
module.exports = () => ({
auth: {
// ...
events: {
onConnectionSuccess(e) {},
onConnectionError(e) {},
// ...
onSSOAutoRegistration(e) {
const { user, provider } = e;
console.log(
`A new user (${user.id}) has been automatically registered using ${provider}`
);
},
},
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
export default () => ({
auth: {
// ...
events: {
onConnectionSuccess(e) {},
onConnectionError(e) {},
// ...
onSSOAutoRegistration(e) {
const { user, provider } = e;
console.log(
`A new user (${user.id}) has been automatically registered using ${provider}`
);
},
},
},
});
```
# Create new Role-Based Access Control (RBAC) conditions
Source: https://docs.strapi.io/cms/configurations/guides/rbac
## Using the condition handler
Description: Returning true or false is useful to verify an external condition or a condition on the authenticated user.
(Source: https://docs.strapi.io/cms/configurations/guides/rbac#using-the-condition-handler)
Language: JavaScript
File path: /github.com/crcn/sift.js
```js
handler: () => new Date().getHours() === 17;
```
Language: JavaScript
File path: /github.com/crcn/sift.js
```js
const condition = {
displayName: 'Email address from strapi.io',
name: 'email-strapi-dot-io',
async handler(user) {
return user.email.includes('@strapi.io');
},
};
```
Language: JavaScript
File path: /github.com/crcn/sift.js
```js
const condition = {
displayName: 'price greater than 50',
name: 'price-gt-50',
async handler(user) {
return { price: { $gt: 50 } };
},
};
```
## Registering conditions
Description: To be available in the admin panel, conditions should be declared and registered in the global bootstrap function found in /src/index.
(Source: https://docs.strapi.io/cms/configurations/guides/rbac#registering-conditions)
Language: JavaScript
File path: /src/index.js
```js
module.exports = async () => {
await strapi.admin.services.permission.conditionProvider.register({
displayName: 'Billing amount under 10K',
name: 'billing-amount-under-10k',
plugin: 'admin',
handler: { amount: { $lt: 10000 } },
});
};
```
---
Language: TypeScript
File path: /src/index.ts
```ts
export default async () => {
await strapi.admin.services.permission.conditionProvider.register({
displayName: 'Billing amount under 10K',
name: 'billing-amount-under-10k',
plugin: 'admin',
handler: { amount: { $lt: 10000 } },
});
};
```
Language: JavaScript
File path: /src/index.js
```js
const conditions = [
{
displayName: "Entity has same name as user",
name: "same-name-as-user",
plugin: "name of a plugin if created in a plugin",
handler: (user) => {
return { name: user.name };
},
},
{
displayName: "Email address from strapi.io",
name: "email-strapi-dot-io",
async handler(user) {
return user.email.includes('@strapi.io');
},
}
];
module.exports = {
async bootstrap(/*{ strapi }*/) {
// do your boostrap
await strapi.admin.services.permission.conditionProvider.registerMany(conditions);
},
};
```
---
Language: TypeScript
File path: /src/index.ts
```ts
const conditions = [
{
displayName: "Entity has same name as user",
name: "same-name-as-user",
plugin: "name of a plugin if created in a plugin"
handler: (user) => {
return { name: user.name };
},
},
{
displayName: "Email address from strapi.io",
name: "email-strapi-dot-io",
async handler(user) {
return user.email.includes('@strapi.io');
},
}
];
export default async () => {
// do your boostrap
await strapi.admin.services.permission.conditionProvider.registerMany(conditions);
};
```
# Media Library providers
Source: https://docs.strapi.io/cms/configurations/media-library-providers
## Installing providers
Description: For example, to install the AWS S3 provider for the Media Library feature:
(Source: https://docs.strapi.io/cms/configurations/media-library-providers#installing-providers)
Language: Bash
File path: N/A
```bash
yarn add @strapi/provider-upload-aws-s3
```
---
Language: Bash
File path: N/A
```bash
npm install @strapi/provider-upload-aws-s3 --save
```
## Creating providers
Description: The interface that must be exported depends on the plugin you are developing the provider for.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers#creating-providers)
Language: JavaScript
File path: /config/env/{env}/plugins.js
```js
module.exports = {
init(providerOptions) {
// init your provider if necessary
return {
upload(file) {
// upload the file in the provider
// file content is accessible by `file.buffer`
},
uploadStream(file) {
// upload the file in the provider
// file content is accessible by `file.stream`
},
delete(file) {
// delete the file in the provider
},
checkFileSize(file, { sizeLimit }) {
// (optional)
// implement your own file size limit logic
},
getSignedUrl(file) {
// (optional)
// Generate a signed URL for the given file.
// The signed URL allows secure access to the file.
// Only Content Manager assets will be signed.
// Returns an object {url: string}.
},
isPrivate() {
// (optional)
// if it is private, file urls will be signed
// Returns a boolean
},
};
},
};
```
---
Language: JavaScript
File path: /config/env/{env}/plugins.js
```js
export default {
init(providerOptions) {
// init your provider if necessary
return {
upload(file) {
// upload the file in the provider
// file content is accessible by `file.buffer`
},
uploadStream(file) {
// upload the file in the provider
// file content is accessible by `file.stream`
},
delete(file) {
// delete the file in the provider
},
checkFileSize(file, { sizeLimit }) {
// (optional)
// implement your own file size limit logic
},
getSignedUrl(file) {
// (optional)
// Generate a signed URL for the given file.
// The signed URL allows secure access to the file.
// Only Content Manager assets will be signed.
// Returns an object {url: string}.
},
isPrivate() {
// (optional)
// if it is private, file urls will be signed
// Returns a boolean
},
};
},
};
```
## Local providers
Description: Create a providers folder in your application.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers#local-providers)
Language: JSON
File path: /docs.npmjs.com/files/package.js
```json
{
...
"dependencies": {
...
"strapi-provider--": "file:providers/strapi-provider--",
...
}
}
```
# Amazon S3 provider
Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3
## Installation
Description: To install the official Strapi-maintained AWS S3 provider, run the following command in a terminal:
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#installation)
Language: Bash
File path: N/A
```bash
yarn add @strapi/provider-upload-aws-s3
```
---
Language: Bash
File path: N/A
```bash
npm install @strapi/provider-upload-aws-s3 --save
```
## Basic example
Description: provider to define the provider name (i.e., amazon-s3) providerOptions to define options that are passed down during the construction of the provider (see for the full list of options; some examples are given in the dedicated extended provider options section) * actionOptions to define options that are passed directly to the parameters to each method respectively.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#basic-example)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ...
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
baseUrl: env('CDN_URL'),
rootPath: env('CDN_ROOT_PATH'),
s3Options: {
credentials: {
accessKeyId: env('AWS_ACCESS_KEY_ID'),
secretAccessKey: env('AWS_ACCESS_SECRET'),
},
region: env('AWS_REGION'),
params: {
ACL: env('AWS_ACL', 'public-read'),
signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60),
Bucket: env('AWS_BUCKET'),
},
},
},
actionOptions: {
upload: {},
uploadStream: {},
delete: {},
},
},
},
// ...
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// ...
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
baseUrl: env('CDN_URL'),
rootPath: env('CDN_ROOT_PATH'),
s3Options: {
credentials: {
accessKeyId: env('AWS_ACCESS_KEY_ID'),
secretAccessKey: env('AWS_ACCESS_SECRET'),
},
region: env('AWS_REGION'),
params: {
ACL: env('AWS_ACL', 'public-read'),
signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60),
Bucket: env('AWS_BUCKET'),
},
},
},
actionOptions: {
upload: {},
uploadStream: {},
delete: {},
},
},
},
// ...
});
```
Language: JavaScript
File path: /config/env/${yourEnvironment}/plugins.js
```js
baseUrl: `https://s3.${process.env.AWS_REGION}.amazonaws.com/${process.env.AWS_BUCKET}`,
```
## Private bucket and signed URLs
Description: :::note If you are using a CDN, the URLs will not be signed.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#private-bucket-and-signed-urls)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ...
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
s3Options: {
credentials: {
accessKeyId: env('AWS_ACCESS_KEY_ID'),
secretAccessKey: env('AWS_ACCESS_SECRET'),
},
region: env('AWS_REGION'),
params: {
ACL: 'private', // <== set ACL to private
signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60),
Bucket: env('AWS_BUCKET'),
},
},
},
actionOptions: {
upload: {},
uploadStream: {},
delete: {},
},
},
},
// ...
});
```
## IAM policy actions
Description: The following are the minimum amount of permissions needed for the AWS S3 provider to work:
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#iam-policy-actions)
Language: JSON
File path: N/A
```json
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject",
"s3:PutObjectAcl"
],
```
## Bucket CORS configuration
Description: Open your bucket in the AWS console.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#bucket-cors-configuration)
Language: JSON
File path: N/A
```json
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET"],
"AllowedOrigins": ["YOUR STRAPI URL"],
"ExposeHeaders": [],
"MaxAgeSeconds": 3000
}
]
```
## Security middleware configuration
Description: The default Strapi security middleware settings block S3 thumbnail previews in the Media Library.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#security-middleware-configuration)
Language: JavaScript
File path: /config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::security',
config: {
// highlight-start
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'market-assets.strapi.io',
'yourBucketName.s3.yourRegion.amazonaws.com',
],
'media-src': [
"'self'",
'data:',
'blob:',
'market-assets.strapi.io',
'yourBucketName.s3.yourRegion.amazonaws.com',
],
upgradeInsecureRequests: null,
},
},
// highlight-end
},
},
// ...
];
```
## Scaleway
Description: | Wasabi | Not needed | Supported | - | | Scaleway | Not needed | Supported | Returns correct virtual-hosted URLs | | Vultr | Not needed | Supported | - | | Backblaze B2 | Not needed | Supported | Returns correct virtual-hosted URLs | | Cloudflare R2 | Not needed | Not supported | Omit ACL from parameters |
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#scaleway)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ...
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
// highlight-start
s3Options: {
credentials: {
accessKeyId: env('SCALEWAY_ACCESS_KEY_ID'),
secretAccessKey: env('SCALEWAY_ACCESS_SECRET'),
},
region: env('SCALEWAY_REGION'), // e.g "fr-par"
endpoint: env('SCALEWAY_ENDPOINT'), // e.g. "https://s3.fr-par.scw.cloud"
params: {
Bucket: env('SCALEWAY_BUCKET'),
},
},
// highlight-end
},
},
},
// ...
});
```
## IONOS / MinIO / Contabo
Description: These providers require forcePathStyle: true because they use path-style URLs instead of virtual-hosted-style URLs.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#ionos-minio-contabo)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
// highlight-start
s3Options: {
credentials: {
accessKeyId: env('S3_ACCESS_KEY_ID'),
secretAccessKey: env('S3_ACCESS_SECRET'),
},
region: env('S3_REGION'),
endpoint: env('S3_ENDPOINT'),
forcePathStyle: true, // Required for these providers
params: {
Bucket: env('S3_BUCKET'),
},
},
// highlight-end
},
},
},
});
```
## Cloudflare R2
Description: Cloudflare R2 does not support ACLs.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#cloudflare-r2)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
// highlight-start
s3Options: {
credentials: {
accessKeyId: env('R2_ACCESS_KEY_ID'),
secretAccessKey: env('R2_ACCESS_SECRET'),
},
region: 'auto',
endpoint: env('R2_ENDPOINT'), // e.g. "https://.r2.cloudflarestorage.com"
params: {
Bucket: env('R2_BUCKET'),
// Do NOT set ACL - R2 does not support ACLs
},
},
// highlight-end
},
},
},
});
```
## Checksum validation
Description: Enable automatic checksum calculation to ensure data integrity during uploads.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#checksum-validation)
Language: JavaScript
File path: N/A
```js
providerOptions: {
s3Options: { /* ... */ },
providerConfig: {
checksumAlgorithm: 'CRC64NVME', // Options: 'CRC32', 'CRC32C', 'SHA1', 'SHA256', 'CRC64NVME'
},
},
```
## Conditional writes (prevent overwrites)
Description: Prevent accidental file overwrites due to race conditions by enabling conditional writes.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#conditional-writes-prevent-overwrites)
Language: JavaScript
File path: N/A
```js
providerConfig: {
preventOverwrite: true,
},
```
## Storage class (AWS S3 only)
Description: :::note Storage classes are AWS S3-specific.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#storage-class-aws-s3-only)
Language: JavaScript
File path: N/A
```js
providerConfig: {
storageClass: 'INTELLIGENT_TIERING', // Auto-optimizes costs
},
```
## Server-side encryption
Description: Configure server-side encryption for compliance requirements (GDPR, HIPAA, etc.).
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#server-side-encryption)
Language: JavaScript
File path: N/A
```js
providerConfig: {
encryption: {
type: 'AES256', // S3-managed encryption
},
},
```
Language: JavaScript
File path: N/A
```js
providerConfig: {
encryption: {
type: 'aws:kms',
kmsKeyId: env('AWS_KMS_KEY_ID'),
},
},
```
## Object tagging
Description: Apply tags to uploaded objects for cost allocation, lifecycle policies, and organization.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#object-tagging)
Language: JavaScript
File path: N/A
```js
providerConfig: {
tags: {
project: 'website',
environment: 'production',
team: 'backend',
},
},
```
## Multipart upload configuration
Description: Configure multipart upload behavior for large files.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#multipart-upload-configuration)
Language: JavaScript
File path: N/A
```js
providerConfig: {
multipart: {
partSize: 10 * 1024 * 1024, // 10MB per part
queueSize: 4, // Number of parallel uploads
leavePartsOnError: false, // Clean up on failure
},
},
```
## Complete configuration example
Description: Configure multipart upload behavior for large files.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#complete-configuration-example)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
baseUrl: env('CDN_URL'),
rootPath: env('CDN_ROOT_PATH'),
s3Options: {
credentials: {
accessKeyId: env('AWS_ACCESS_KEY_ID'),
secretAccessKey: env('AWS_ACCESS_SECRET'),
},
region: env('AWS_REGION'),
params: {
ACL: 'private',
signedUrlExpires: 15 * 60,
Bucket: env('AWS_BUCKET'),
},
},
providerConfig: {
checksumAlgorithm: 'CRC64NVME',
preventOverwrite: true,
storageClass: 'INTELLIGENT_TIERING',
encryption: {
type: 'aws:kms',
kmsKeyId: env('AWS_KMS_KEY_ID'),
},
tags: {
application: 'strapi',
environment: env('NODE_ENV'),
},
multipart: {
partSize: 10 * 1024 * 1024,
queueSize: 4,
},
},
},
},
},
});
```
## Custom provider override (private S3 provider)
Description: Create a /providers/aws-s3 folder in your application (see local providers for more information).
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/amazon-s3#private-aws-s3-provider)
Language: JavaScript
File path: /providers/aws-s3/index.js
```js
module.exports = {
init: (config) => {
const s3 = new AWS.S3(config);
return {
async upload(file) {
// code to upload file to S3
},
async delete(file) {
// code to delete file from S3
},
async isPrivate() {
return true;
},
async getSignedUrl(file) {
const params = {
Bucket: config.params.Bucket,
Key: file.path,
Expires: 60, // URL expiration time in seconds
};
const signedUrl = await s3.getSignedUrlPromise("getObject", params);
return { url: signedUrl };
},
};
},
};
```
---
Language: TypeScript
File path: /providers/aws-s3/index.ts
```ts
export = {
init: (config) => {
const s3 = new AWS.S3(config);
return {
async upload(file) {
// code to upload file to S3
},
async delete(file) {
// code to delete file from S3
},
async isPrivate() {
return true;
},
async getSignedUrl(file) {
const params = {
Bucket: config.params.Bucket,
Key: file.path,
Expires: 60, // URL expiration time in seconds
};
const signedUrl = await s3.getSignedUrlPromise("getObject", params);
return { url: signedUrl };
},
};
},
};
```
# Cloudinary provider
Source: https://docs.strapi.io/cms/configurations/media-library-providers/cloudinary
## Installation
Description: To install the official Strapi-maintained Cloudinary provider, run the following command in a terminal:
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/cloudinary#installation)
Language: Bash
File path: N/A
```bash
yarn add @strapi/provider-upload-cloudinary
```
---
Language: Bash
File path: N/A
```bash
npm install @strapi/provider-upload-cloudinary --save
```
## Configuration
Description: provider to define the provider name (i.e., cloudinary) providerOptions to define options that are passed down during the construction of the provider (see for the full list of options) * actionOptions to define options that are passed directly to the parameters to each method respectively.
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/cloudinary#configuration)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ...
upload: {
config: {
provider: 'cloudinary',
providerOptions: {
cloud_name: env('CLOUDINARY_NAME'),
api_key: env('CLOUDINARY_KEY'),
api_secret: env('CLOUDINARY_SECRET'),
},
actionOptions: {
upload: {},
uploadStream: {},
delete: {},
},
},
},
// ...
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// ...
upload: {
config: {
provider: 'cloudinary',
providerOptions: {
cloud_name: env('CLOUDINARY_NAME'),
api_key: env('CLOUDINARY_KEY'),
api_secret: env('CLOUDINARY_SECRET'),
},
actionOptions: {
upload: {},
uploadStream: {},
delete: {},
},
},
},
// ...
});
```
# Local upload provider
Source: https://docs.strapi.io/cms/configurations/media-library-providers/local-upload
## Installation
Description: To install the provider, run the following command in a terminal:
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/local-upload#installation)
Language: Bash
File path: N/A
```bash
yarn add @strapi/provider-upload-local
```
---
Language: Bash
File path: N/A
```bash
npm install @strapi/provider-upload-local --save
```
## Configuration
Description: The following is an example configuration:
(Source: https://docs.strapi.io/cms/configurations/media-library-providers/local-upload#configuration)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ...
upload: {
config: {
provider: 'local',
providerOptions: {
sizeLimit: 100000,
},
},
},
// ...
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// ...
upload: {
config: {
provider: 'local',
providerOptions: {
sizeLimit: 100000,
},
},
},
// ...
});
```
# Middlewares configuration
Source: https://docs.strapi.io/cms/configurations/middlewares
## Loading order
Description: The ./config/middlewares.js file exports an array, where order matters and controls the execution order of the middleware stack:
(Source: https://docs.strapi.io/cms/configurations/middlewares#loading-order)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// The array is pre-populated with internal, built-in middlewares, prefixed by `strapi::`
'strapi::logger',
'strapi::errors',
'strapi::security',
'strapi::cors',
// custom middleware that does not require any configuration
'global::my-custom-node-module',
// custom name to find a package or a path
{
name: 'my-custom-node-module',
config: {
foo: 'bar',
},
},
// custom resolve to find a package or a path
{
resolve: '../some-dir/custom-middleware',
config: {
foo: 'bar',
},
},
// custom configuration for internal middleware
{
name: 'strapi::poweredBy',
config: {
poweredBy: 'Some awesome company',
},
},
// remaining internal & built-in middlewares
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
];
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// The array is pre-populated with internal, built-in middlewares, prefixed by `strapi::`
'strapi::logger',
'strapi::cors',
'strapi::body',
'strapi::errors',
// ...
'my-custom-node-module', // custom middleware that does not require any configuration
{
// custom name to find a package or a path
name: 'my-custom-node-module',
config: {
foo: 'bar',
},
},
{
// custom resolve to find a package or a path
resolve: '../some-dir/custom-middleware',
config: {
foo: 'bar',
},
},
];
```
## body
Description: Example: Custom configuration for the body middleware
(Source: https://docs.strapi.io/cms/configurations/middlewares#body)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::body',
config: {
jsonLimit: '3mb',
formLimit: '10mb',
textLimit: '256kb',
encoding: 'gbk',
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::body',
config: {
jsonLimit: '3mb',
formLimit: '10mb',
textLimit: '256kb',
encoding: 'gbk',
},
},
// ...
]
```
## compression
Description: Example: Custom configuration for the compression middleware
(Source: https://docs.strapi.io/cms/configurations/middlewares#compression)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::compression',
config: {
br: false
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::compression',
config: {
br: false
},
},
// ...
]
```
## cors
Description: Example: Custom configuration for the cors middleware
(Source: https://docs.strapi.io/cms/configurations/middlewares#cors)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::cors',
config: {
origin: ['https://example.com', 'https://subdomain.example.com', 'https://someotherwebsite.org'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],
keepHeaderOnError: true,
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::cors',
config: {
origin: ['https://example.com', 'https://subdomain.example.com', 'https://someotherwebsite.org'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],
keepHeaderOnError: true,
},
},
// ...
]
```
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::cors',
config: {
origin: (ctx): string | string[] => {
const origin = ctx.request.header.origin;
if (origin === 'http://localhost:3000') {
return origin; // The returns will be part of the Access-Control-Allow-Origin header
}
return ''; // Fail cors check
}
},
},
// ...
]
```
## favicon
Description: Example: Custom configuration for the favicon middleware
(Source: https://docs.strapi.io/cms/configurations/middlewares#favicon)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::favicon',
config: {
path: './public/uploads/custom-fav-abc123.ico'
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::favicon',
config: {
path: './public/uploads/custom-fav-abc123.ico'
},
},
// ...
]
```
## ip
Description: Example: Custom configuration for the ip middleware
(Source: https://docs.strapi.io/cms/configurations/middlewares#ip)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::ip',
config: {
whitelist: ['192.168.0.*', '192.168.1.*', '123.123.123.123'],
blacklist: ['1.116.*.*', '103.54.*.*'],
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::ip',
config: {
whitelist: ['192.168.0.*', '192.168.1.*', '123.123.123.123'],
blacklist: ['1.116.*.*', '103.54.*.*'],
},
},
// ...
]
```
## logger
Description: Example: Custom configuration for the logger middleware
(Source: https://docs.strapi.io/cms/configurations/middlewares#logger)
Language: JavaScript
File path: ./config/logger.js
```js
'use strict';
const {
winston,
formats: { prettyPrint, levelFilter },
} = require('@strapi/logger');
module.exports = {
transports: [
new winston.transports.Console({
level: 'http',
format: winston.format.combine(
levelFilter('http'),
prettyPrint({ timestamps: 'YYYY-MM-DD hh:mm:ss.SSS' })
),
}),
],
};
```
---
Language: TypeScript
File path: ./config/logger.ts
```ts
'use strict';
const {
winston,
formats: { prettyPrint, levelFilter },
} = require('@strapi/logger');
export default {
transports: [
new winston.transports.Console({
level: 'http',
format: winston.format.combine(
levelFilter('http'),
prettyPrint({ timestamps: 'YYYY-MM-DD hh:mm:ss.SSS' })
),
}),
],
};
```
## poweredBy
Description: details Example: Custom configuration for the poweredBy middleware
(Source: https://docs.strapi.io/cms/configurations/middlewares#poweredby)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::poweredBy',
config: {
poweredBy: 'Some Awesome Company '
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::poweredBy',
config: {
poweredBy: 'Some Awesome Company '
},
},
// ...
]
```
## query
Description: Example: Custom configuration for the query middleware
(Source: https://docs.strapi.io/cms/configurations/middlewares#query)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::query',
config: {
arrayLimit: 50,
depth: 10,
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::query',
config: {
arrayLimit: 50,
depth: 10,
},
},
// ...
]
```
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::query',
config: {
arrayLimit: 200,
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::query',
config: {
arrayLimit: 200,
},
},
// ...
]
```
## public
Description: Example: Custom configuration for the public middleware
(Source: https://docs.strapi.io/cms/configurations/middlewares#public)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::public',
config: {
defer: true,
index: env('INDEX_PATH', 'index-dev.html')
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::public',
config: {
defer: true,
index: env('INDEX_PATH', 'index-dev.html')
},
},
// ...
]
```
## security
Description: Example: Custom configuration for the security middleware for using the AWS-S3 provider
(Source: https://docs.strapi.io/cms/configurations/middlewares#security)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'market-assets.strapi.io',
'yourBucketName.s3.yourRegion.amazonaws.com',
],
'media-src': [
"'self'",
'data:',
'blob:',
'market-assets.strapi.io',
'yourBucketName.s3.yourRegion.amazonaws.com',
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'market-assets.strapi.io',
'yourBucketName.s3.yourRegion.amazonaws.com',
],
'media-src': [
"'self'",
'data:',
'blob:',
'market-assets.strapi.io',
'yourBucketName.s3.yourRegion.amazonaws.com',
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
```
## session
Description: Example: Custom configuration for the session middleware
(Source: https://docs.strapi.io/cms/configurations/middlewares#session)
Language: JavaScript
File path: ./config/middlewares.js
```js
module.exports = [
// ...
{
name: 'strapi::session',
config: {
rolling: true,
renew: true
},
},
// ...
]
```
---
Language: TypeScript
File path: ./config/middlewares.ts
```ts
export default [
// ...
{
name: 'strapi::session',
config: {
rolling: true,
renew: true
},
},
// ...
]
```
# Plugins configuration
Source: https://docs.strapi.io/cms/configurations/plugins
## Plugins configuration
Description: Basic example custom configuration for plugins:
(Source: https://docs.strapi.io/cms/configurations/plugins#plugins-configuration)
Language: JavaScript
File path: ./config/plugins.js
```js
module.exports = ({ env }) => ({
// enable a plugin that doesn't require any configuration
i18n: true,
// enable a custom plugin
myplugin: {
// my-plugin is going to be the internal name used for this plugin
enabled: true,
resolve: './src/plugins/my-local-plugin',
config: {
// user plugin config goes here
},
},
// disable a plugin
'my-other-plugin': {
enabled: false, // plugin installed but disabled
},
});
```
---
Language: TypeScript
File path: ./config/plugins.ts
```ts
export default ({ env }) => ({
// enable a plugin that doesn't require any configuration
i18n: true,
// enable a custom plugin
myplugin: {
// my-plugin is going to be the internal name used for this plugin
enabled: true,
resolve: './src/plugins/my-local-plugin',
config: {
// user plugin config goes here
},
},
// disable a plugin
'my-other-plugin': {
enabled: false, // plugin installed but disabled
},
});
```
# Server configuration
Source: https://docs.strapi.io/cms/configurations/server
## Available options
Description: For outgoing HTTP calls, you can pass a keep-alive agent to your HTTP client.
(Source: https://docs.strapi.io/cms/configurations/server#available-options)
Language: TypeScript
File path: /github.com/nodejs/undici/blob/main/types/proxy-agent.d.ts
```ts
const { HttpsAgent } = require('agentkeepalive');
const axios = require('axios');
const agent = new HttpsAgent();
axios.get('https://example.com', { httpsAgent: agent });
```
## Configurations
Description: The default configuration created with any new project should at least include the following:
(Source: https://docs.strapi.io/cms/configurations/server#configurations)
Language: JavaScript
File path: ./config/server.js
```js
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
});
```
---
Language: TypeScript
File path: ./config/server.ts
```ts
export default ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
});
```
Language: JavaScript
File path: ./config/server.js
```js
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
socket: '/tmp/nginx.socket', // only use if absolutely required
emitErrors: false,
url: env('PUBLIC_URL', 'https://api.example.com'),
proxy: { koa: env.bool('IS_PROXIED', true) },
cron: {
enabled: env.bool('CRON_ENABLED', false),
},
transfer: {
remote: {
enabled: false,
},
},
logger: {
updates: {
enabled: false,
},
startup: {
enabled: false,
},
},
});
```
---
Language: TypeScript
File path: ./config/server.ts
```ts
export default ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
socket: '/tmp/nginx.socket', // only use if absolutely required
emitErrors: false,
url: env('PUBLIC_URL', 'https://api.example.com'),
proxy: { koa: env.bool('IS_PROXIED', true) },
cron: {
enabled: env.bool('CRON_ENABLED', false),
},
transfer: {
remote: {
enabled: false,
},
},
logger: {
updates: {
enabled: false,
},
startup: {
enabled: false,
},
},
});
```
# Discord SSO provider
Source: https://docs.strapi.io/cms/configurations/sso-providers/discord
## Installation
Description: Install :
(Source: https://docs.strapi.io/cms/configurations/sso-providers/discord#installation)
Language: Bash
File path: N/A
```bash
yarn add passport-discord
```
---
Language: Bash
File path: N/A
```bash
npm install --save passport-discord
```
## Configuration example
Description: The Discord SSO provider is configured in the auth.providers array of the config/admin file:
(Source: https://docs.strapi.io/cms/configurations/sso-providers/discord#configuration-example)
Language: JavaScript
File path: /config/admin.js
```js
const DiscordStrategy = require("passport-discord");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "discord",
displayName: "Discord",
icon: "https://cdn0.iconfinder.com/data/icons/free-social-media-set/24/discord-512.png",
createStrategy: (strapi) =>
new DiscordStrategy(
{
clientID: env("DISCORD_CLIENT_ID"),
clientSecret: env("DISCORD_SECRET"),
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL(
"discord"
),
scope: ["identify", "email"],
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: `${profile.username}#${profile.discriminator}`,
});
}
),
},
],
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
import { Strategy as DiscordStrategy } from "passport-discord";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "discord",
displayName: "Discord",
icon: "https://cdn0.iconfinder.com/data/icons/free-social-media-set/24/discord-512.png",
createStrategy: (strapi) =>
new DiscordStrategy(
{
clientID: env("DISCORD_CLIENT_ID"),
clientSecret: env("DISCORD_SECRET"),
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL(
"discord"
),
scope: ["identify", "email"],
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: `${profile.username}#${profile.discriminator}`,
});
}
),
},
],
},
});
```
# GitHub SSO provider
Source: https://docs.strapi.io/cms/configurations/sso-providers/github
## Installation
Description: Install :
(Source: https://docs.strapi.io/cms/configurations/sso-providers/github#installation)
Language: Bash
File path: N/A
```bash
yarn add passport-github2
```
---
Language: Bash
File path: N/A
```bash
npm install --save passport-github2
```
## Configuration example
Description: The GitHub SSO provider is configured in the auth.providers array of the config/admin file:
(Source: https://docs.strapi.io/cms/configurations/sso-providers/github#configuration-example)
Language: JavaScript
File path: /config/admin.js
```js
const GithubStrategy = require("passport-github2");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "github",
displayName: "Github",
icon: "https://cdn1.iconfinder.com/data/icons/logotypes/32/github-512.png",
createStrategy: (strapi) =>
new GithubStrategy(
{
clientID: env("GITHUB_CLIENT_ID"),
clientSecret: env("GITHUB_CLIENT_SECRET"),
scope: ["user:email"],
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL("github"),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.emails[0].value,
username: profile.username,
});
}
),
},
],
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
import { Strategy as GithubStrategy } from "passport-github2";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "github",
displayName: "Github",
icon: "https://cdn1.iconfinder.com/data/icons/logotypes/32/github-512.png",
createStrategy: (strapi) =>
new GithubStrategy(
{
clientID: env("GITHUB_CLIENT_ID"),
clientSecret: env("GITHUB_CLIENT_SECRET"),
scope: ["user:email"],
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL("github"),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.emails[0].value,
username: profile.username,
});
}
),
},
],
},
});
```
# Google SSO provider
Source: https://docs.strapi.io/cms/configurations/sso-providers/google
## Installation
Description: Install :
(Source: https://docs.strapi.io/cms/configurations/sso-providers/google#installation)
Language: Bash
File path: N/A
```bash
yarn add passport-google-oauth2
```
---
Language: Bash
File path: N/A
```bash
npm install --save passport-google-oauth2
```
## Configuration example
Description: The Google SSO provider is configured in the auth.providers array of the config/admin file:
(Source: https://docs.strapi.io/cms/configurations/sso-providers/google#configuration-example)
Language: JavaScript
File path: /config/admin.js
```js
const GoogleStrategy = require("passport-google-oauth2");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "google",
displayName: "Google",
icon: "https://cdn2.iconfinder.com/data/icons/social-icons-33/128/Google-512.png",
createStrategy: (strapi) =>
new GoogleStrategy(
{
clientID: env("GOOGLE_CLIENT_ID"),
clientSecret: env("GOOGLE_CLIENT_SECRET"),
scope: [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
],
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL("google"),
},
(request, accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
firstname: profile.given_name,
lastname: profile.family_name,
});
}
),
},
],
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
import {Strategy as GoogleStrategy } from "passport-google-oauth2";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "google",
displayName: "Google",
icon: "https://cdn2.iconfinder.com/data/icons/social-icons-33/128/Google-512.png",
createStrategy: (strapi) =>
new GoogleStrategy(
{
clientID: env("GOOGLE_CLIENT_ID"),
clientSecret: env("GOOGLE_CLIENT_SECRET"),
scope: [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
],
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL("google"),
},
(request, accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
firstname: profile.given_name,
lastname: profile.family_name,
});
}
),
},
],
},
});
```
# Keycloak SSO provider
Source: https://docs.strapi.io/cms/configurations/sso-providers/keycloak
## Installation
Description: Install :
(Source: https://docs.strapi.io/cms/configurations/sso-providers/keycloak#installation)
Language: Bash
File path: N/A
```bash
yarn add passport-keycloak-oauth2-oidc
```
---
Language: Bash
File path: N/A
```bash
npm install --save passport-keycloak-oauth2-oidc
```
## Configuration example
Description: The Keycloak SSO provider is configured in the auth.providers array of the config/admin file:
(Source: https://docs.strapi.io/cms/configurations/sso-providers/keycloak#configuration-example)
Language: JavaScript
File path: /config/admin.js
```js
const KeyCloakStrategy = require("passport-keycloak-oauth2-oidc");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "keycloak",
displayName: "Keycloak",
icon: "https://raw.githubusercontent.com/keycloak/keycloak-admin-ui/main/themes/keycloak/logo.svg",
createStrategy: (strapi) =>
new KeyCloakStrategy(
{
clientID: env("KEYCLOAK_CLIENT_ID", ""),
realm: env("KEYCLOAK_REALM", ""),
publicClient: env.bool("KEYCLOAK_PUBLIC_CLIENT", false),
clientSecret: env("KEYCLOAK_CLIENT_SECRET", ""),
sslRequired: env("KEYCLOAK_SSL_REQUIRED", "external"),
authServerURL: env("KEYCLOAK_AUTH_SERVER_URL", ""),
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL(
"keycloak"
),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: profile.username,
});
}
),
},
],
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
import { Strategy as KeyCloakStrategy } from "passport-keycloak-oauth2-oidc";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "keycloak",
displayName: "Keycloak",
icon: "https://raw.githubusercontent.com/keycloak/keycloak-admin-ui/main/themes/keycloak/logo.svg",
createStrategy: (strapi) =>
new KeyCloakStrategy(
{
clientID: env("KEYCLOAK_CLIENT_ID", ""),
realm: env("KEYCLOAK_REALM", ""),
publicClient: env.bool("KEYCLOAK_PUBLIC_CLIENT", false),
clientSecret: env("KEYCLOAK_CLIENT_SECRET", ""),
sslRequired: env("KEYCLOAK_SSL_REQUIRED", "external"),
authServerURL: env("KEYCLOAK_AUTH_SERVER_URL", ""),
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL(
"keycloak"
),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: profile.username,
});
}
),
},
],
},
});
```
# Microsoft SSO provider
Source: https://docs.strapi.io/cms/configurations/sso-providers/microsoft
## Installation
Description: Install :
(Source: https://docs.strapi.io/cms/configurations/sso-providers/microsoft#installation)
Language: Bash
File path: N/A
```bash
yarn add passport-azure-ad-oauth2 jsonwebtoken
```
---
Language: Bash
File path: N/A
```bash
npm install --save passport-azure-ad-oauth2 jsonwebtoken
```
## Configuration example
Description: The Microsoft SSO provider is configured in the auth.providers array of the config/admin file:
(Source: https://docs.strapi.io/cms/configurations/sso-providers/microsoft#configuration-example)
Language: JavaScript
File path: /config/admin.js
```js
const AzureAdOAuth2Strategy = require("passport-azure-ad-oauth2");
const jwt = require("jsonwebtoken");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "azure_ad_oauth2",
displayName: "Microsoft",
icon: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Microsoft_logo_%282012%29.svg/320px-Microsoft_logo_%282012%29.svg.png",
createStrategy: (strapi) =>
new AzureAdOAuth2Strategy(
{
clientID: env("MICROSOFT_CLIENT_ID", ""),
clientSecret: env("MICROSOFT_CLIENT_SECRET", ""),
scope: ["user:email"],
tenant: env("MICROSOFT_TENANT_ID", ""),
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL(
"azure_ad_oauth2"
),
},
(accessToken, refreshToken, params, profile, done) => {
let waadProfile = jwt.decode(params.id_token, "", true);
done(null, {
email: waadProfile.email,
username: waadProfile.email,
firstname: waadProfile.given_name, // optional if email and username exist
lastname: waadProfile.family_name, // optional if email and username exist
});
}
),
},
],
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
import { Strategy as AzureAdOAuth2Strategy} from "passport-azure-ad-oauth2";
import jwt from "jsonwebtoken";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "azure_ad_oauth2",
displayName: "Microsoft",
icon: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Microsoft_logo_%282012%29.svg/320px-Microsoft_logo_%282012%29.svg.png",
createStrategy: (strapi) =>
new AzureAdOAuth2Strategy(
{
clientID: env("MICROSOFT_CLIENT_ID", ""),
clientSecret: env("MICROSOFT_CLIENT_SECRET", ""),
scope: ["user:email"],
tenant: env("MICROSOFT_TENANT_ID", ""),
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL(
"azure_ad_oauth2"
),
},
(accessToken, refreshToken, params, profile, done) => {
let waadProfile = jwt.decode(params.id_token, "", true);
done(null, {
email: waadProfile.email,
username: waadProfile.email,
firstname: waadProfile.given_name, // optional if email and username exist
lastname: waadProfile.family_name, // optional if email and username exist
});
}
),
},
],
},
});
```
# Okta SSO provider
Source: https://docs.strapi.io/cms/configurations/sso-providers/okta
## Installation
Description: Install :
(Source: https://docs.strapi.io/cms/configurations/sso-providers/okta#installation)
Language: Bash
File path: N/A
```bash
yarn add passport-okta-oauth20
```
---
Language: Bash
File path: N/A
```bash
npm install --save passport-okta-oauth20
```
## Configuration example
Description: :::caution When setting the OKTA_DOMAIN environment variable, make sure to include the protocol (e.g., https://example.okta.com).
(Source: https://docs.strapi.io/cms/configurations/sso-providers/okta#configuration-example)
Language: JavaScript
File path: /config/admin.js
```js
const OktaOAuth2Strategy = require("passport-okta-oauth20").Strategy;
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "okta",
displayName: "Okta",
icon: "https://www.okta.com/sites/default/files/Okta_Logo_BrightBlue_Medium-thumbnail.png",
createStrategy: (strapi) =>
new OktaOAuth2Strategy(
{
clientID: env("OKTA_CLIENT_ID"),
clientSecret: env("OKTA_CLIENT_SECRET"),
audience: env("OKTA_DOMAIN"),
scope: ["openid", "email", "profile"],
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL("okta"),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: profile.username,
});
}
),
},
],
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
import { Strategy as OktaOAuth2Strategy } from "passport-okta-oauth20";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "okta",
displayName: "Okta",
icon: "https://www.okta.com/sites/default/files/Okta_Logo_BrightBlue_Medium-thumbnail.png",
createStrategy: (strapi) =>
new OktaOAuth2Strategy(
{
clientID: env("OKTA_CLIENT_ID"),
clientSecret: env("OKTA_CLIENT_SECRET"),
audience: env("OKTA_DOMAIN"),
scope: ["openid", "email", "profile"],
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL("okta"),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: profile.username,
});
}
),
},
],
},
});
```
# TypeScript configuration
Source: https://docs.strapi.io/cms/configurations/typescript
## Strapi-specific configuration for TypeScript
Description: Example:
(Source: https://docs.strapi.io/cms/configurations/typescript#strapi-specific-configuration-for-typescript)
Language: JavaScript
File path: ./config/typescript.js
```js
module.exports = ({ env }) => ({
autogenerate: true,
});
```
---
Language: TypeScript
File path: ./config/typescript.ts
```ts
export default ({ env }) => ({
autogenerate: true,
});
```
# Users & Permissions Providers
Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers
## Setting up the server URL
Description: Before setting up a provider you must specify the absolute URL of your backend in /config/server:
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers#setting-up-the-server-url)
Language: JavaScript
File path: /config/server.js
```js
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
url: env('', 'http://localhost:1337'),
});
```
---
Language: TypeScript
File path: /config/server.ts
```ts
export default ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
url: env('', 'http://localhost:1337'),
});
```
# CAS provider setup for Users & Permissions
Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/cas
## CAS configuration
Description: could already be used by your company or organization or you can setup a local CAS server by cloning the project or using the newer to create an overlay project.
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/cas#cas-configuration)
Language: JSON
File path: N/A
```json
{
"@class": "org.apereo.cas.services.OidcRegisteredService",
"clientId": "thestrapiclientid",
"clientSecret": "thestrapiclientsecret",
"bypassApprovalPrompt": true,
"serviceId": "^http(|s)://localhost:1337/.*",
"name": "Local Strapi",
"id": 20201103,
"evaluationOrder": 50,
"attributeReleasePolicy": {
"@class": "org.apereo.cas.services.ReturnMappedAttributeReleasePolicy",
"allowedAttributes": {
"@class": "java.util.TreeMap",
"strapiemail": "groovy { return attributes['mail'].get(0) }",
"strapiusername": "groovy { return attributes['username'].get(0) }"
}
}
}
```
## Strapi configuration
Description: Enable: ON - Client ID: thestrapiclientid - Client Secret: thestrapiclientsecret - The redirect URL to your front-end app: http://localhost:1337/api/connect/cas/redirect - The Provider Subdomain such that the following URLs are correct for the CAS deployment you are targeting:
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/cas#strapi-configuration)
Language: YAML
File path: N/A
```yaml
authorize_url: https://[subdomain]/oidc/authorize
access_url: https://[subdomain]/oidc/token
profile_url: https://[subdomain]/oidc/profile
```
# Facebook provider setup for Users & Permissions
Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/facebook
## Facebook configuration
Description: Facebook doesn't accept localhost urls.
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/facebook#facebook-configuration)
Language: JavaScript
File path: N/A
```
ngrok http 1337
```
# GitHub provider setup for Users & Permissions
Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/github
## GitHub configuration
Description: :::note Github doesn't accept localhost urls.
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/github#github-configuration)
Language: JavaScript
File path: N/A
```
ngrok http 1337
```
# Instagram provider setup for Users & Permissions
Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/instagram
## Instagram configuration
Description: :::note Facebook doesn't accept localhost urls.
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/instagram#instagram-configuration)
Language: JavaScript
File path: N/A
```
ngrok http 1337
```
# Creating and adding a custom Users & Permissions provider
Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/new-provider-guide
## Creating a custom provider
Description: You can use the register lifecycle function to create your own custom provider in the src/index.js|ts file of your Strapi application.
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/new-provider-guide#creating-a-custom-provider)
Language: JavaScript
File path: /src/index.js
```js
module.exports = {
register({ strapi }) {
strapi
.plugin("users-permissions")
.service("providers-registry")
.add("example-provider-name", {
icon: "",
enabled: true,
grantConfig: {
key: "",
secret: "",
callback: `${strapi.config.server.url}/auth/example-provider-name/callback`,
scope: ["email"],
authorize_url: "https://awesome.com/authorize",
access_url: "https://awesome.com/token",
oauth: 2,
},
async authCallback({ accessToken, providers, purest }) {
// use whatever you want here to get the user info
return {
username: "test",
email: "test",
};
},
});
},
};
```
## Forgotten password: ask for the reset password link
Description: Then, your forgotten password page has to make the following request to your backend:
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/new-provider-guide#forgotten-password-ask-for-the-reset-password-link)
Language: JavaScript
File path: /config.js
```js
import axios from 'axios';
// Request API.
axios
.post('http://localhost:1337/api/auth/forgot-password', {
email: 'user@strapi.io', // user's email
})
.then(response => {
console.log('Your user received an email');
})
.catch(error => {
console.log('An error occurred:', error.response);
});
```
## Reset Password: Send the new password
Description: Your reset password page has to make the following request to your backend:
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/new-provider-guide#reset-password-send-the-new-password)
Language: JavaScript
File path: /config.js
```js
import axios from 'axios';
// Request API.
axios.post(
'http://localhost:1337/api/auth/change-password',
{
currentPassword: 'currentPassword',
password: 'userNewPassword',
passwordConfirmation: 'userNewPassword',
},
{
headers: {
Authorization: 'Bearer ',
},
}
);
```
Language: JavaScript
File path: /config.js
```js
import axios from 'axios';
// Request API.
axios
.post('http://localhost:1337/api/auth/reset-password', {
code: 'privateCode', // code contained in the reset link of step 3.
password: 'userNewPassword',
passwordConfirmation: 'userNewPassword',
})
.then(response => {
console.log("Your user's password has been reset.");
})
.catch(error => {
console.log('An error occurred:', error.response);
});
```
## Email validation
Description: If needed you can re-send the confirmation email by making the following request:
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/new-provider-guide#email-validation)
Language: JavaScript
File path: N/A
```js
import axios from 'axios';
// Request API.
axios
.post(`http://localhost:1337/api/auth/send-email-confirmation`, {
email: 'user@strapi.io', // user's email
})
.then(response => {
console.log('Your user received an email');
})
.catch(error => {
console.error('An error occurred:', error.response);
});
```
## Prepare your files
Description: To add a new provider on Strapi, you will need to perform changes to the following files:
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/new-provider-guide#prepare-your-files)
Language: JavaScript
File path: N/A
```
extensions/users-permissions/services/Providers.js
extensions/users-permissions/config/functions/bootstrap.js
```
## Configure your OAuth generic information
Description: Here is an example that uses the discord provider.
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/new-provider-guide#configure-your-oauth-generic-information)
Language: JavaScript
File path: N/A
```js
case 'discord': {
const discord = new Purest({
provider: 'discord',
config: {
'discord': {
'https://discordapp.com/api/': {
'__domain': {
'auth': {
'auth': {'bearer': '[0]'}
}
},
'{endpoint}': {
'__path': {
'alias': '__default'
}
}
}
}
}
});
}
```
## Retrieve your user's information
Description: For our Discord provider it will look like the following:
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/new-provider-guide#retrieve-your-user-s-information)
Language: JavaScript
File path: /github.com/simov/purest-providers/blob/master/config/providers.js
```js
discord.query().get('users/@me').auth(access_token).request((err, res, body) => {
if (err) {
callback(err);
} else {
// Combine username and discriminator because discord username is not unique
const username = `${body.username}#${body.discriminator}`;
callback(null, {
username,
email: body.email
});
}
});
break;
}
```
## Configure the new provider model onto database
Description: Add the fields your provider needs into the grantConfig object.
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/new-provider-guide#configure-the-new-provider-model-onto-database)
Language: JavaScript
File path: /strapi-plugin-users-permissions/config/functions/bootstrap.js
```js
discord: {
enabled: false, // make this provider disabled by default
icon: 'comments', // The icon to use on the UI
key: '', // our provider app id (leave it blank, you will fill it with the Content Manager)
secret: '', // our provider secret key (leave it blank, you will fill it with the Content Manager)
callback: '/auth/discord/callback', // the callback endpoint of our provider
scope: [ // the scope that we need from our user to retrieve information
'identify',
'email'
]
},
```
# Patreon provider setup for Users & Permissions
Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/patreon
## Patreon configuration
Description: :::note Patreon does not accept localhost urls.
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/patreon#patreon-configuration)
Language: Bash
File path: N/A
```bash
ngrok http 1337
```
# Twitter provider setup for Users & Permissions
Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/twitter
## Twitter configuration
Description: :::note Twitter doesn't accept localhost urls.
(Source: https://docs.strapi.io/cms/configurations/users-and-permissions-providers/twitter#twitter-configuration)
Language: JavaScript
File path: N/A
```
ngrok http 1337
```
# Data export
Source: https://docs.strapi.io/cms/data-management/export
## Understand the exported archive
Description: The exported .tar archive contains a flat structure of numbered files arranged in folders for each exported resource:
(Source: https://docs.strapi.io/cms/data-management/export#understand-the-exported-archive)
Language: Bash
File path: N/A
```bash
yarn strapi export --no-encrypt --no-compress -f my-export
```
---
Language: Bash
File path: N/A
```bash
npm run strapi export -- --no-encrypt --no-compress -f my-export
```
Language: TEXT
File path: N/A
```text
export_202401011230.tar
├── metadata.json
├── configuration/
│ └── configuration_00001.jsonl
├── entities/
│ └── entities_00001.jsonl
├── links/
│ └── links_00001.jsonl
└── schemas/
└── schemas_00001.jsonl
```
## Export to a directory
Description: Directory exports do not support encryption or compression.
(Source: https://docs.strapi.io/cms/data-management/export#export-to-a-directory)
Language: Bash
File path: N/A
```bash
yarn strapi export --format dir --no-encrypt -f my-export
```
---
Language: Bash
File path: N/A
```bash
npm run strapi export -- --format dir --no-encrypt -f my-export
```
## Example: Export data with a custom filename
Description: Exported data are contained in a .tar file (or a directory when using --format dir) that is automatically named using the format export_YYYYMMDDHHMMSS.
(Source: https://docs.strapi.io/cms/data-management/export#example-export-data-with-a-custom-filename)
Language: Bash
File path: N/A
```bash
yarn strapi export --file my-strapi-export
```
---
Language: Bash
File path: N/A
```bash
npm run strapi export -- --file my-strapi-export
```
## Configure data encryption
Description: :::tip Encryption keys Strong encryption keys are encouraged to protect sensitive data in your project.
(Source: https://docs.strapi.io/cms/data-management/export#configure-data-encryption)
Language: Bash
File path: N/A
```bash
openssl rand -base64 48
```
---
Language: JavaScript
File path: N/A
```js
node -p "require('crypto').randomBytes(48).toString('base64');"
```
## Example: Export data without encryption
Description: To disable encryption, pass the --no-encrypt option with the strapi export command.
(Source: https://docs.strapi.io/cms/data-management/export#example-export-data-without-encryption)
Language: Bash
File path: N/A
```bash
yarn strapi export --no-encrypt
```
---
Language: Bash
File path: N/A
```bash
npm run strapi export -- --no-encrypt
```
## Example: Export data with the encryption --key option
Description: To disable encryption, pass the --no-encrypt option with the strapi export command.
(Source: https://docs.strapi.io/cms/data-management/export#example-export-data-with-the-encryption-key-option)
Language: Bash
File path: N/A
```bash
yarn strapi export --key my-encryption-key
```
---
Language: Bash
File path: N/A
```bash
npm run strapi export -- --key my-encryption-key
```
## Example: Export data without compression
Description: To disable compression, pass the --no-compress option with the strapi export command.
(Source: https://docs.strapi.io/cms/data-management/export#example-export-data-without-compression)
Language: Bash
File path: N/A
```bash
yarn strapi export --no-compress
```
---
Language: Bash
File path: N/A
```bash
npm run strapi export -- --no-compress
```
## Example: Export only entities and relations
Description: Code example from "Example: Export only entities and relations"
(Source: https://docs.strapi.io/cms/data-management/export#example-export-only-entities-and-relations)
Language: Bash
File path: N/A
```bash
yarn strapi export --only content
```
---
Language: Bash
File path: N/A
```bash
npm run strapi export -- --only content
```
## Example: Export data excluding assets, entities, and relations
Description: :::note Media such as images consist of the file (asset) and the entity in the database.
(Source: https://docs.strapi.io/cms/data-management/export#example-export-data-excluding-assets-entities-and-relations)
Language: Bash
File path: N/A
```bash
yarn strapi export --exclude files,content
```
---
Language: Bash
File path: N/A
```bash
npm run strapi export -- --exclude files,content
```
# Data import
Source: https://docs.strapi.io/cms/data-management/import
## Understand the import archive
Description: To prepare an archive for manual review or modification:
(Source: https://docs.strapi.io/cms/data-management/import#understand-the-import-archive)
Language: JavaScript
File path: N/A
```js
yarn strapi export --no-encrypt --no-compress -f my-export
tar -xf my-export.tar
```
---
Language: JavaScript
File path: N/A
```js
npm run strapi export -- --no-encrypt --no-compress -f my-export
tar -xf my-export.tar
```
## Import from a directory
Description: Instead of a .tar archive, you can pass the path to an unpacked export directory.
(Source: https://docs.strapi.io/cms/data-management/import#import-from-a-directory)
Language: Bash
File path: N/A
```bash
yarn strapi import -f ./my-export
```
---
Language: Bash
File path: N/A
```bash
npm run strapi import -- -f ./my-export
```
## Example: Minimum command to import data from a file in the Strapi project root
Description: To import data into a Strapi instance, use the strapi import command in the destination project root directory.
(Source: https://docs.strapi.io/cms/data-management/import#example-minimum-command-to-import-data-from-a-file-in-the-strapi-project-root)
Language: Bash
File path: N/A
```bash
yarn strapi import -f /path/to/my/file/export_20221213105643.tar.gz.enc
```
---
Language: Bash
File path: N/A
```bash
npm run strapi import -- -f /path/to/my/file/export_20221213105643.tar.gz.enc
```
## Example: Import a plain .tar archive
Description: To import data into a Strapi instance, use the strapi import command in the destination project root directory.
(Source: https://docs.strapi.io/cms/data-management/import#example-import-a-plain-tar-archive)
Language: Bash
File path: N/A
```bash
yarn strapi import -f /path/to/my/file/backup.tar
```
---
Language: Bash
File path: N/A
```bash
npm run strapi import -- -f /path/to/my/file/backup.tar
```
## Example: Pass the encryption key with the strapi import command
Description: If you are importing data from an encrypted file the encryption key can be passed with the strapi import command by using the -k or --key option.
(Source: https://docs.strapi.io/cms/data-management/import#example-pass-the-encryption-key-with-the-strapi-import-command)
Language: Bash
File path: N/A
```bash
yarn strapi import -f /path/to/my/file/export_20221213105643.tar.gz.enc --key my-encryption-key
```
---
Language: Bash
File path: N/A
```bash
npm run strapi import -- -f /path/to/my/file/export_20221213105643.tar.gz.enc --key my-encryption-key
```
## Example of the --force option
Description: When using the strapi import command, you are required to confirm that the import will delete the existing database contents.
(Source: https://docs.strapi.io/cms/data-management/import#example-of-the-force-option)
Language: Bash
File path: N/A
```bash
yarn strapi import -f /path/to/my/file/export_20221213105643.tar.gz.enc --force --key my-encryption-key
```
---
Language: Bash
File path: N/A
```bash
npm run strapi import -- -f /path/to/my/file/export_20221213105643.tar.gz.enc --force --key my-encryption-key
```
## Example: exclude assets from an import
Description: :::note Media such as images consist of the file (asset) and the entity in the database.
(Source: https://docs.strapi.io/cms/data-management/import#example-exclude-assets-from-an-import)
Language: Bash
File path: N/A
```bash
yarn strapi import -f /path/to/my/file/export_20221213105643.tar.gz.enc --exclude files
```
---
Language: Bash
File path: N/A
```bash
npm strapi import -- -f /path/to/my/file/export_20221213105643.tar.gz.enc --exclude files
```
## Example: import only the project configuration
Description: :::note Media such as images consist of the file (asset) and the entity in the database.
(Source: https://docs.strapi.io/cms/data-management/import#example-import-only-the-project-configuration)
Language: Bash
File path: N/A
```bash
yarn strapi import -f /path/to/my/file/export_20221213105643.tar.gz.enc --only config
```
---
Language: Bash
File path: N/A
```bash
npm strapi import -- -f /path/to/my/file/export_20221213105643.tar.gz.enc --only config
```
# Data transfer
Source: https://docs.strapi.io/cms/data-management/transfer
## Data transfer
Description: :::warning When using nginx and a server that proxies requests into a localhost, issues might occur.
(Source: https://docs.strapi.io/cms/data-management/transfer#data-transfer)
Language: JavaScript
File path: N/A
```
server {
listen 80;
server_name ;
location / {
proxy_pass http://localhost:1337;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
include proxy_params;
}
}
```
## Setup and run the data transfer
Description: Start the Strapi server for the destination instance.
(Source: https://docs.strapi.io/cms/data-management/transfer#setup-and-run-the-data-transfer)
Language: Bash
File path: N/A
```bash
yarn strapi transfer --to destinationURL
```
---
Language: Bash
File path: N/A
```bash
npm run strapi transfer -- --to destinationURL
```
Language: Bash
File path: N/A
```bash
yarn strapi transfer --from remoteURL
```
---
Language: Bash
File path: N/A
```bash
npm run strapi transfer -- --from remoteURL
```
## Example: bypass the transfer command line prompts with --force
Description: :::caution The --force option bypasses all warnings about content deletion.
(Source: https://docs.strapi.io/cms/data-management/transfer#example-bypass-the-transfer-command-line-prompts-with-force)
Language: Bash
File path: N/A
```bash
yarn strapi transfer --to https://example.com/admin --to-token my-transfer-token --force
```
---
Language: Bash
File path: N/A
```bash
npm run strapi transfer -- --to https://example.com/admin --to-token my-transfer-token --force
```
## Example: only transfer files
Description: The default strapi transfer command transfers your content (entities and relations), files (assets), project configuration, and schemas.
(Source: https://docs.strapi.io/cms/data-management/transfer#example-only-transfer-files)
Language: Bash
File path: N/A
```bash
yarn strapi transfer --to https://example.com/admin --only files
```
---
Language: Bash
File path: N/A
```bash
npm run strapi transfer -- --to https://example.com/admin --only files
```
## Example: exclude files from transfer
Description: The default strapi transfer command transfers your content (entities and relations), files (assets), project configuration, and schemas.
(Source: https://docs.strapi.io/cms/data-management/transfer#example-exclude-files-from-transfer)
Language: Bash
File path: N/A
```bash
yarn strapi transfer --to https://example.com/admin --exclude files
```
---
Language: Bash
File path: N/A
```bash
npm run strapi transfer -- --to https://example.com/admin --exclude files
```
## Manage data transfer with environment variables
Description: The environment variable STRAPIDISABLEREMOTEDATATRANSFER is available to disable remote data transfer.
(Source: https://docs.strapi.io/cms/data-management/transfer#manage-data-transfer-with-environment-variables)
Language: Bash
File path: N/A
```bash
STRAPI_DISABLE_REMOTE_DATA_TRANSFER=true yarn start
```
## Create and clone a new Strapi project
Description: Create a new Strapi project using the installation command:
(Source: https://docs.strapi.io/cms/data-management/transfer#create-and-clone-a-new-strapi-project)
Language: Bash
File path: N/A
```bash
npx create-strapi-app@latest --quickstart
```
Language: Bash
File path: N/A
```bash
git init
git add .
git commit -m "first commit"
```
Language: Bash
File path: N/A
```bash
cd .. # move to the parent directory
git clone .git/
```
## Create a transfer token
Description: Navigate to the second Strapi instance and run the build and start commands in the root directory:
(Source: https://docs.strapi.io/cms/data-management/transfer#create-a-transfer-token)
Language: Bash
File path: N/A
```bash
yarn build && yarn start
```
---
Language: Bash
File path: N/A
```bash
npm run build && npm run start
```
## Transfer your data
Description: Return the the first Strapi instance.
(Source: https://docs.strapi.io/cms/data-management/transfer#transfer-your-data)
Language: Bash
File path: N/A
```bash
yarn strapi transfer --to http://localhost:1337/admin
```
---
Language: Bash
File path: N/A
```bash
npm run strapi transfer -- --to http://localhost:1337/admin
```
# Database migrations
Source: https://docs.strapi.io/cms/database-migrations
## Creating a migration file
Description: Copy and paste the following template in the previously created file:
(Source: https://docs.strapi.io/cms/database-migrations#creating-a-migration-file)
Language: JavaScript
File path: .05.10T00.00.00.name-of-my-migration.js
```js
'use strict'
async function up(knex) {}
module.exports = { up };
```
Language: JavaScript
File path: ./database/migrations/2022.05.10T00.00.00.name-of-my-migration.js
```js
module.exports = {
async up(knex) {
// You have full access to the Knex.js API with an already initialized connection to the database
// Example: renaming a table
await knex.schema.renameTable('oldName', 'newName');
// Example: renaming a column
await knex.schema.table('someTable', table => {
table.renameColumn('oldName', 'newName');
});
// Example: updating data
await knex.from('someTable').update({ columnName: 'newValue' }).where({ columnName: 'oldValue' });
},
};
```
## Using Strapi Instance for migrations
Description: Example of migration file with Strapi instance
(Source: https://docs.strapi.io/cms/database-migrations#using-strapi-instance-for-migrations)
Language: JavaScript
File path: ./database/migrations/2022.05.10T00.00.00.name-of-my-migration.js
```js
module.exports = {
async up() {
await strapi.db.transaction(async () => {
// Your migration code here
// Example: creating new entries
await strapi.entityService.create('api::article.article', {
data: {
title: 'My Article',
},
});
// Example: custom service method
await strapi.service('api::article.article').updateRelatedArticles();
});
},
};
```
## Handling migrations with TypeScript code
Description: Here's how to configure it in your database settings:
(Source: https://docs.strapi.io/cms/database-migrations#handling-migrations-with-typescript-code)
Language: JavaScript
File path: /config/database.js
```js
module.exports = ({ env }) => ({
connection: {
// Your database connection settings
},
settings: {
useTypescriptMigrations: true
}
});
```
---
Language: TypeScript
File path: /config/database.ts
```ts
export default ({ env }) => ({
connection: {
// Your database connection settings
},
settings: {
useTypescriptMigrations: true
}
});
```
# Database transactions
Source: https://docs.strapi.io/cms/database-transactions
## Usage
Description: Transactions are handled by passing a handler function into strapi.db.transaction:
(Source: https://docs.strapi.io/cms/database-transactions#usage)
Language: JavaScript
File path: N/A
```js
await strapi.db.transaction(async ({ trx, rollback, commit, onCommit, onRollback }) => {
// It will implicitly use the transaction
await strapi.entityService.create();
await strapi.entityService.create();
});
```
## Nested transactions
Description: Transactions can be nested.
(Source: https://docs.strapi.io/cms/database-transactions#nested-transactions)
Language: JavaScript
File path: N/A
```js
await strapi.db.transaction(async () => {
// It will implicitly use the transaction
await strapi.entityService.create();
// Nested transactions will implicitly use the outer transaction
await strapi.db.transaction(async ({}) => {
await strapi.entityService.create();
});
});
```
## onCommit and onRollback
Description: The onCommit and onRollback hooks can be used to execute code after the transaction is committed or rolled back.
(Source: https://docs.strapi.io/cms/database-transactions#oncommit-and-onrollback)
Language: JavaScript
File path: N/A
```js
await strapi.db.transaction(async ({ onCommit, onRollback }) => {
// It will implicitly use the transaction
await strapi.entityService.create();
await strapi.entityService.create();
onCommit(() => {
// This will be executed after the transaction is committed
});
onRollback(() => {
// This will be executed after the transaction is rolled back
});
});
```
## Using knex queries
Description: Transactions can also be used with knex queries, but in those cases .transacting(trx) must be explicitly called.
(Source: https://docs.strapi.io/cms/database-transactions#using-knex-queries)
Language: JavaScript
File path: N/A
```js
await strapi.db.transaction(async ({ trx, rollback, commit }) => {
await knex('users').where('id', 1).update({ name: 'foo' }).transacting(trx);
});
```
# Deployment
Source: https://docs.strapi.io/cms/deployment
## 1. Configure
Description: We recommend using environment variables to configure your application based on the environment, for example:
(Source: https://docs.strapi.io/cms/deployment#1-configure)
Language: JavaScript
File path: /config/server.js
```js
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
});
```
Language: JavaScript
File path: N/A
```
HOST=10.0.0.1
PORT=1338
```
## 2. Launch the server
Description: Before running your server in production you need to build your admin panel for production:
(Source: https://docs.strapi.io/cms/deployment#2-launch-the-server)
Language: Bash
File path: N/A
```bash
NODE_ENV=production yarn build
```
---
Language: Bash
File path: N/A
```bash
NODE_ENV=production npm run build
```
---
Language: Bash
File path: N/A
```bash
npm install cross-env
```
---
Language: Bash
File path: N/A
```bash
"build:win": "cross-env NODE_ENV=production npm run build",
```
---
Language: Bash
File path: N/A
```bash
npm run build:win
```
Language: Bash
File path: N/A
```bash
NODE_ENV=production yarn start
```
---
Language: Bash
File path: N/A
```bash
NODE_ENV=production npm run start
```
---
Language: Bash
File path: N/A
```bash
npm install cross-env
```
---
Language: Bash
File path: N/A
```bash
"start:win": "cross-env NODE_ENV=production npm start",
```
---
Language: Bash
File path: N/A
```bash
npm run start:win
```
Language: JavaScript
File path: ./server.js
```js
const strapi = require('@strapi/strapi');
strapi.createStrapi(/* {...} */).start();
```
# Error handling
Source: https://docs.strapi.io/cms/error-handling
## REST errors
Description: Errors thrown by the REST API are included in the response that has the following format:
(Source: https://docs.strapi.io/cms/error-handling#rest-errors)
Language: JSON
File path: N/A
```json
{
"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
Description: Errors thrown by the GraphQL API are included in the response that has the following format:
(Source: https://docs.strapi.io/cms/error-handling#graphql-errors)
Language: JSON
File path: N/A
```json
{ "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
}
}
```
## Controllers and middlewares
Description: 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
(Source: https://docs.strapi.io/cms/error-handling#controllers-and-middlewares)
Language: JavaScript
File path: N/A
```js
// 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();
}
```
---
Language: JavaScript
File path: N/A
```js
// 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();
}
```
## Example: Throwing an error in a service**
Description: This example shows wrapping a core service and doing a custom validation on the create method:
(Source: https://docs.strapi.io/cms/error-handling#example-throwing-an-error-in-a-service)
Language: JavaScript
File path: ./src/api/restaurant/services/restaurant.js
```js
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;
}
});
```
---
Language: TypeScript
File path: ./src/api/[api-name]/policies/my-policy.ts
```ts
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**
Description: 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.
(Source: https://docs.strapi.io/cms/error-handling#example-throwing-an-error-in-a-model-lifecycle)
Language: JavaScript
File path: ./src/api/[api-name]/content-types/[api-name]/lifecycles.js
```js
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' });
}
},
};
```
---
Language: TypeScript
File path: ./src/api/[api-name]/content-types/[api-name]/lifecycles.ts
```ts
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' });
}
},
};
```
## Example: Throwing a PolicyError in a custom policy
Description: This example shows building a custom policy that will throw a custom error message and stop the request.
(Source: https://docs.strapi.io/cms/error-handling#example-throwing-a-policyerror-in-a-custom-policy)
Language: JavaScript
File path: ./src/api/[api-name]/policies/my-policy.js
```js
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',
});
}
}
```
---
Language: TypeScript
File path: ./src/api/[api-name]/policies/my-policy.ts
```ts
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',
});
}
};
```
## Default error classes
Description: | Parameter | Type | Description | Default | | --- | --- | --- | --- | | message | string | The error message | An application error occurred | | details | object | Object to define additional details | {} |
(Source: https://docs.strapi.io/cms/error-handling#default-error-classes)
Language: JavaScript
File path: N/A
```js
throw new errors.ApplicationError('Something went wrong', { foo: 'bar' });
```
---
Language: JavaScript
File path: N/A
```
::: -->
The `PaginationError` class is a specific error class that is typically used when parsing the pagination information from [REST](/cms/api/rest/sort-pagination#pagination), [GraphQL](/cms/api/graphql#pagination), or the [Document Service](/cms/api/document-service). It accepts the following parameters:
| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Invalid pagination` |
```
---
Language: JavaScript
File path: N/A
```
The `NotFoundError` class is a generic error class for throwing `404` status code errors. It accepts the following parameters:
| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Entity not found` |
```
---
Language: JavaScript
File path: N/A
```
The `ForbiddenError` class is a specific error class used when a user either doesn't provide any or the correct authentication credentials. It accepts the following parameters:
| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Forbidden access` |
```
---
Language: JavaScript
File path: N/A
```
The `UnauthorizedError` class is a specific error class used when a user doesn't have the proper role or permissions to perform a specific action, but has properly authenticated. It accepts the following parameters:
| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Unauthorized` |
```
---
Language: JavaScript
File path: N/A
```
The `NotImplementedError` class is a specific error class used when the incoming request is attempting to use a feature that is not currently implemented or configured. It accepts the following parameters:
| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `This feature is not implemented yet` |
```
---
Language: JavaScript
File path: N/A
```
The `PayloadTooLargeError` class is a specific error class used when the incoming request body or attached files exceed the limits of the server. It accepts the following parameters:
| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Entity too large` |
```
---
Language: JavaScript
File path: N/A
```
The `PolicyError` class is a specific error designed to be used with [route policies](/cms/backend-customization/policies). The best practice recommendation is to ensure the name of the policy is passed in the `details` parameter. It accepts the following parameters:
| Parameter | Type | Description | Default |
| --- | --- | --- | --- |
| `message` | `string` | The error message | `Policy Failed` |
| `details` | `object` | Object to define additional details | `{}` |
```
# API Tokens
Source: https://docs.strapi.io/cms/features/api-tokens
## Ensuring API tokens are visible in the admin panel
Description: To allow persistent visibility of API tokens in the admin panel, an encryption key must be provided in your /config/admin file under secrets.encryptionKey:
(Source: https://docs.strapi.io/cms/features/api-tokens#ensuring-api-tokens-are-visible-in-the-admin-panel)
Language: JavaScript
File path: /config/admin.js
```js
module.exports = ({ env }) => ({
// other config parameters
secrets: {
encryptionKey: env('ENCRYPTION_KEY'),
}
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
export default ({ env }) => ({
// other config parameters
secrets: {
encryptionKey: env('ENCRYPTION_KEY'),
}
});
```
# Content-type Builder
Source: https://docs.strapi.io/cms/features/content-type-builder
## Relation
Description: Example
(Source: https://docs.strapi.io/cms/features/content-type-builder#relation)
Language: JSON
File path: Populate
```json
{
populate: {
children: {
fields: ['title', 'slug'],
populate: {
children: {
fields: ['title', 'slug'],
},
},
},
},
}
```
# Custom Fields
Source: https://docs.strapi.io/cms/features/custom-fields
## Registering a custom field on the server
Description: In the following example, the color-picker plugin was created using the CLI generator (see plugins development):
(Source: https://docs.strapi.io/cms/features/custom-fields#registering-a-custom-field-on-the-server)
Language: JavaScript
File path: /src/plugins/color-picker/server/register.js
```js
module.exports = ({ strapi }) => {
strapi.customFields.register({
name: "color",
plugin: "color-picker",
type: "string",
inputSize: {
// optional
default: 4,
isResizable: true,
},
});
};
```
---
Language: TypeScript
File path: /src/plugins/color-picker/server/register.ts
```ts
export default ({ strapi }: { strapi: any }) => {
strapi.customFields.register({
name: "color",
plugin: "color-picker",
type: "string",
inputSize: {
// optional
default: 4,
isResizable: true,
},
});
};
```
Language: JavaScript
File path: /src/plugins/color-picker/strapi-server.js
```js
module.exports = {
register({ strapi }) {
strapi.customFields.register({
name: "color",
plugin: "color-picker",
type: "text",
inputSize: {
// optional
default: 4,
isResizable: true,
},
});
},
};
```
---
Language: TypeScript
File path: /src/plugins/color-picker/strapi-server.ts
```ts
export default {
register({ strapi }: { strapi: any }) {
strapi.customFields.register({
name: "color",
plugin: "color-picker",
type: "text",
inputSize: {
// optional
default: 4,
isResizable: true,
},
});
},
};
```
## Registering a custom field in the admin panel
Description: In the following example, the color-picker plugin was created using the CLI generator (see plugins development):
(Source: https://docs.strapi.io/cms/features/custom-fields#registering-a-custom-field-in-the-admin-panel)
Language: JavaScript
File path: /src/plugins/color-picker/admin/src/index.js
```js
import ColorPickerIcon from "./components/ColorPicker/ColorPickerIcon";
export default {
register(app) {
// ... app.addMenuLink() goes here
// ... app.registerPlugin() goes here
app.customFields.register({
name: "color",
pluginId: "color-picker", // the custom field is created by a color-picker plugin
type: "string", // the color will be stored as a string
intlLabel: {
id: "color-picker.color.label",
defaultMessage: "Color",
},
intlDescription: {
id: "color-picker.color.description",
defaultMessage: "Select any color",
},
icon: ColorPickerIcon, // don't forget to create/import your icon component
components: {
Input: async () =>
import('./components/Input').then((module) => ({
default: module.Input,
})),
},
options: {
// declare options here
},
});
},
// ... bootstrap() goes here
};
```
---
Language: TypeScript
File path: /src/plugins/color-picker/admin/src/index.ts
```ts
import ColorPickerIcon from "./components/ColorPicker/ColorPickerIcon";
export default {
register(app) {
// ... app.addMenuLink() goes here
// ... app.registerPlugin() goes here
app.customFields.register({
name: "color",
pluginId: "color-picker", // the custom field is created by a color-picker plugin
type: "string", // the color will be stored as a string
intlLabel: {
id: "color-picker.color.label",
defaultMessage: "Color",
},
intlDescription: {
id: "color-picker.color.description",
defaultMessage: "Select any color",
},
icon: ColorPickerIcon, // don't forget to create/import your icon component
components: {
Input: async () =>
import('./components/Input').then((module) => ({
default: module.Input,
})),
},
options: {
// declare options here
},
});
},
// ... bootstrap() goes here
};
```
## Components
Description: In the following example, the color-picker plugin was created using the CLI generator (see plugins development):
(Source: https://docs.strapi.io/cms/features/custom-fields#components)
Language: JavaScript
File path: /src/plugins/color-picker/admin/src/index.js
```js
export default {
register(app) {
app.customFields.register({
// …
components: {
Input: async () =>
import('./components/Input').then((module) => ({
default: module.Input,
})),
},
// …
});
},
};
```
---
Language: JavaScript
File path: /src/plugins/color-picker/admin/src/index.js
```js
export default {
register(app) {
app.customFields.register({
// …
components: {
Input: async () =>
import('./components/Input').then((module) => ({
default: module.Input,
})),
},
// …
});
},
};
```
Language: JavaScript
File path: /src/plugins//admin/src/components/Input.js
```js
import * as React from "react";
import { useIntl } from "react-intl";
const Input = React.forwardRef((props, ref) => {
const { attribute, disabled, intlLabel, name, onChange, required, value } =
props; // these are just some of the props passed by the content-manager
const { formatMessage } = useIntl();
const handleChange = (e) => {
onChange({
target: { name, type: attribute.type, value: e.currentTarget.value },
});
};
return (
);
});
export default Input;
```
---
Language: TypeScript
File path: /src/plugins//admin/src/components/Input.ts
```ts
import * as React from "react";
import { useIntl } from "react-intl";
const Input = React.forwardRef((props, ref) => {
const { attribute, disabled, intlLabel, name, onChange, required, value } =
props; // these are just some of the props passed by the content-manager
const { formatMessage } = useIntl();
const handleChange = (e) => {
onChange({
target: { name, type: attribute.type, value: e.currentTarget.value },
});
};
return (
);
});
export default Input;
```
## Options
Description: In the following example, the color-picker plugin was created using the CLI generator (see plugins development):
(Source: https://docs.strapi.io/cms/features/custom-fields#options)
Language: JavaScript
File path: /src/plugins/color-picker/admin/src/index.js
```js
// imports go here (ColorPickerIcon, pluginId, yup package…)
export default {
register(app) {
// ... app.addMenuLink() goes here
// ... app.registerPlugin() goes here
app.customFields.register({
// …
options: {
base: [
/*
Declare settings to be added to the "Base settings" section
of the field in the Content-Type Builder
*/
{
sectionTitle: {
// Add a "Format" settings section
id: "color-picker.color.section.format",
defaultMessage: "Format",
},
items: [
// Add settings items to the section
{
/*
Add a "Color format" dropdown
to choose between 2 different format options
for the color value: hexadecimal or RGBA
*/
intlLabel: {
id: "color-picker.color.format.label",
defaultMessage: "Color format",
},
name: "options.format",
type: "select",
value: "hex", // option selected by default
options: [
// List all available "Color format" options
{
key: "hex",
defaultValue: "hex",
value: "hex",
metadatas: {
intlLabel: {
id: "color-picker.color.format.hex",
defaultMessage: "Hexadecimal",
},
},
},
{
key: "rgba",
value: "rgba",
metadatas: {
intlLabel: {
id: "color-picker.color.format.rgba",
defaultMessage: "RGBA",
},
},
},
],
},
],
},
],
advanced: [
/*
Declare settings to be added to the "Advanced settings" section
of the field in the Content-Type Builder
*/
],
validator: (args) => ({
format: yup.string().required({
id: "options.color-picker.format.error",
defaultMessage: "The color format is required",
}),
}),
},
});
},
};
```
---
Language: TypeScript
File path: /src/plugins/color-picker/admin/src/index.ts
```ts
// imports go here (ColorPickerIcon, pluginId, yup package…)
export default {
register(app) {
// ... app.addMenuLink() goes here
// ... app.registerPlugin() goes here
app.customFields.register({
// …
options: {
base: [
/*
Declare settings to be added to the "Base settings" section
of the field in the Content-Type Builder
*/
{
sectionTitle: {
// Add a "Format" settings section
id: "color-picker.color.section.format",
defaultMessage: "Format",
},
items: [
// Add settings items to the section
{
/*
Add a "Color format" dropdown
to choose between 2 different format options
for the color value: hexadecimal or RGBA
*/
intlLabel: {
id: "color-picker.color.format.label",
defaultMessage: "Color format",
},
name: "options.format",
type: "select",
value: "hex", // option selected by default
options: [
// List all available "Color format" options
{
key: "hex",
defaultValue: "hex",
value: "hex",
metadatas: {
intlLabel: {
id: "color-picker.color.format.hex",
defaultMessage: "Hexadecimal",
},
},
},
{
key: "rgba",
value: "rgba",
metadatas: {
intlLabel: {
id: "color-picker.color.format.rgba",
defaultMessage: "RGBA",
},
},
},
],
},
],
},
],
advanced: [
/*
Declare settings to be added to the "Advanced settings" section
of the field in the Content-Type Builder
*/
],
validator: (args) => ({
format: yup.string().required({
id: "options.color-picker.format.error",
defaultMessage: "The color format is required",
}),
}),
},
});
},
};
```
## In the code
Description: Example: A simple color custom field model definition:
(Source: https://docs.strapi.io/cms/features/custom-fields#in-the-code)
Language: JSON
File path: /src/api/[apiName]/[content-type-name]/content-types/schema.json
```json
{
// …
"attributes": {
"color": { // name of the custom field defined in the Content-Type Builder
"type": "customField",
"customField": "plugin::color-picker.color",
"options": {
"format": "hex"
}
}
}
// …
}
```
# Data Management
Source: https://docs.strapi.io/cms/features/data-management
## Code-based configuration
Description: A transfer.token.salt value must be defined in the config/admin file so that transfer tokens can be properly generated.
(Source: https://docs.strapi.io/cms/features/data-management#code-based-configuration)
Language: JavaScript
File path: /config/admin.js
```js
module.exports = ({ env }) => ({
// …
transfer: {
token: {
salt: env('TRANSFER_TOKEN_SALT', 'anotherRandomLongString'),
}
},
});
```
---
Language: TypeScript
File path: /config/admin.ts
```ts
export default ({ env }) => ({
// …
transfer: {
token: {
salt: env('TRANSFER_TOKEN_SALT', 'anotherRandomLongString'),
}
},
});
```
# Email
Source: https://docs.strapi.io/cms/features/email
## Installing providers
Description: For example, to install the Sendgrid provider:
(Source: https://docs.strapi.io/cms/features/email#installing-providers)
Language: Bash
File path: /plugins.js
```bash
yarn add @strapi/provider-email-sendgrid
```
---
Language: Bash
File path: /plugins.js
```bash
npm install @strapi/provider-email-sendgrid --save
```
## Configuring providers
Description: The following is an example configuration for the Sendgrid provider:
(Source: https://docs.strapi.io/cms/features/email#configuring-providers)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ...
email: {
config: {
provider: 'sendgrid', // For community providers pass the full package name (e.g. provider: 'strapi-provider-email-mandrill')
providerOptions: {
apiKey: env('SENDGRID_API_KEY'),
},
settings: {
defaultFrom: 'juliasedefdjian@strapi.io',
defaultReplyTo: 'juliasedefdjian@strapi.io',
testAddress: 'juliasedefdjian@strapi.io',
},
},
},
// ...
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// ...
email: {
config: {
provider: 'sendgrid', // For community providers pass the full package name (e.g. provider: 'strapi-provider-email-mandrill')
providerOptions: {
apiKey: env('SENDGRID_API_KEY'),
},
settings: {
defaultFrom: 'juliasedefdjian@strapi.io',
defaultReplyTo: 'juliasedefdjian@strapi.io',
testAddress: 'juliasedefdjian@strapi.io',
},
},
},
// ...
});
```
## Per-environment configuration
Description: Some providers expose SMTP-style connection details instead of (or in addition to) an API key.
(Source: https://docs.strapi.io/cms/features/email#per-environment-configuration)
Language: DOCKERFILE
File path: /config/plugins.js
```dockerfile
module.exports = ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: env('SMTP_HOST'),
port: env.int('SMTP_PORT', 587),
secure: false, // Use `true` for port 465
auth: {
user: env('SMTP_USERNAME'),
pass: env('SMTP_PASSWORD'),
},
},
settings: {
defaultFrom: 'no-reply@example.com',
defaultReplyTo: 'support@example.com',
},
},
},
});
```
---
Language: DOCKERFILE
File path: /config/plugins.ts
```dockerfile
export default ({ env }) => ({
email: {
config: {
provider: 'nodemailer',
providerOptions: {
host: env('SMTP_HOST'),
port: 587,
secure: false, // Use `true` for port 465
auth: {
user: env('SMTP_USERNAME'),
pass: env('SMTP_PASSWORD'),
},
},
settings: {
defaultFrom: 'no-reply@example.com',
defaultReplyTo: 'support@example.com',
},
},
},
});
```
## Using the send() function
Description: The following code example can be used in a controller or a service:
(Source: https://docs.strapi.io/cms/features/email#using-the-send-function)
Language: JavaScript
File path: /src/api/my-api-name/controllers/my-api-name.ts|js
```js
await strapi.plugins['email'].services.email.send({
to: 'valid email address',
from: 'your verified email address', //e.g. single sender verification in SendGrid
cc: 'valid email address',
bcc: 'valid email address',
replyTo: 'valid email address',
subject: 'The Strapi Email feature worked successfully',
text: 'Hello world!',
html: 'Hello world!',
}),
```
## Using the sendTemplatedEmail() function
Description: The following code example can be used in a controller or a service:
(Source: https://docs.strapi.io/cms/features/email#using-the-sendtemplatedemail-function)
Language: JavaScript
File path: /src/api/my-api-name/controllers/my-api-name.js
```js
const emailTemplate = {
subject: 'Welcome <%= user.firstname %>',
text: `Welcome to mywebsite.fr!
Your account is now linked with: <%= user.email %>.`,
html: `
Welcome to mywebsite.fr!
Your account is now linked with: <%= user.email %>.
`,
};
await strapi.plugins['email'].services.email.sendTemplatedEmail(
{
to: user.email,
// from: is not specified, the defaultFrom is used.
},
emailTemplate,
{
user: _.pick(user, ['username', 'email', 'firstname', 'lastname']),
}
);
```
## Sending emails from a lifecycle hook
Description: The following example illustrates how to send an email each time a new content entry is added in the Content Manager use the afterCreate lifecycle hook:
(Source: https://docs.strapi.io/cms/features/email#lifecycle-hook)
Language: JavaScript
File path: /src/api/my-api-name/content-types/my-content-type-name/lifecycles.js
```js
module.exports = {
async afterCreate(event) { // Connected to "Save" button in admin panel
const { result } = event;
try{
await strapi.plugin('email').service('email').send({ // you could also do: await strapi.service('plugin:email.email').send({
to: 'valid email address',
from: 'your verified email address', // e.g. single sender verification in SendGrid
cc: 'valid email address',
bcc: 'valid email address',
replyTo: 'valid email address',
subject: 'The Strapi Email feature worked successfully',
text: '${fieldName}', // Replace with a valid field ID
html: 'Hello world!',
})
} catch(err) {
console.log(err);
}
}
}
```
---
Language: TypeScript
File path: /src/api/my-api-name/content-types/my-content-type-name/lifecycles.ts
```ts
export default {
async afterCreate(event) { // Connected to "Save" button in admin panel
const { result } = event;
try{
await strapi.plugins['email'].services.email.send({
to: 'valid email address',
from: 'your verified email address', // e.g. single sender verification in SendGrid
cc: 'valid email address',
bcc: 'valid email address',
replyTo: 'valid email address',
subject: 'The Strapi Email feature worked successfully',
text: '${fieldName}', // Replace with a valid field ID
html: 'Hello world!',
})
} catch(err) {
console.log(err);
}
}
}
```
# Media Library
Source: https://docs.strapi.io/cms/features/media-library
## Example custom configuration
Description: The following is an example of a custom configuration for the Upload plugin when using the default upload provider:
(Source: https://docs.strapi.io/cms/features/media-library#example-custom-configuration)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env })=>({
upload: {
config: {
providerOptions: {
localServer: {
maxage: 300000
},
},
sizeLimit: 250 * 1024 * 1024, // 256mb in bytes
breakpoints: {
xlarge: 1920,
large: 1000,
medium: 750,
small: 500,
xsmall: 64
},
security: {
allowedTypes: ['image/*', 'application/*'],
deniedTypes: ['application/x-sh', 'application/x-dosexec']
},
},
},
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default () => ({
upload: {
config: {
providerOptions: {
localServer: {
maxage: 300000
},
},
sizeLimit: 250 * 1024 * 1024, // 256mb in bytes
breakpoints: {
xlarge: 1920,
large: 1000,
medium: 750,
small: 500,
xsmall: 64
},
security: {
allowedTypes: ['image/*', 'application/*'],
deniedTypes: ['application/x-sh', 'application/x-dosexec']
},
},
},
})
```
## Local server
Description: You can provide them by creating or editing the /config/plugins file.
(Source: https://docs.strapi.io/cms/features/media-library#local-server)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env })=>({
upload: {
config: {
providerOptions: {
localServer: {
maxage: 300000
},
},
},
},
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
upload: {
config: {
providerOptions: {
localServer: {
maxage: 300000
},
},
},
},
});
```
## Max file size
Description: The middleware used by the Upload package is the body middleware.
(Source: https://docs.strapi.io/cms/features/media-library#max-file-size)
Language: JavaScript
File path: /config/middlewares.js
```js
module.exports = [
// ...
{
name: "strapi::body",
config: {
formLimit: "256mb", // modify form body
jsonLimit: "256mb", // modify JSON body
textLimit: "256mb", // modify text body
formidable: {
maxFileSize: 250 * 1024 * 1024, // multipart data, modify here limit of uploaded file size
},
},
},
// ...
];
```
---
Language: TypeScript
File path: /config/middlewares.ts
```ts
export default [
// ...
{
name: "strapi::body",
config: {
formLimit: "256mb", // modify form body
jsonLimit: "256mb", // modify JSON body
textLimit: "256mb", // modify text body
formidable: {
maxFileSize: 250 * 1024 * 1024, // multipart data, modify here limit of uploaded file size
},
},
},
// ...
];
```
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = {
// ...
upload: {
config: {
sizeLimit: 250 * 1024 * 1024 // 256mb in bytes
}
}
};
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default {
// ...
upload: {
config: {
sizeLimit: 250 * 1024 * 1024 // 256mb in bytes
}
}
};
```
## Security
Description: You can provide them by creating or editing the /config/plugins file.
(Source: https://docs.strapi.io/cms/features/media-library#security)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = {
// ...
upload: {
config: {
security: {
allowedTypes: ['image/*', 'application/*'],
deniedTypes: ['application/x-sh', 'application/x-dosexec']
},
}
}
};
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default {
// ...
upload: {
config: {
security: {
allowedTypes: ['image/*', 'application/*'],
deniedTypes: ['application/x-sh', 'application/x-dosexec']
},
}
}
};
```
## Upload request timeout
Description: An alternate method is to set the requestTimeout value in the bootstrap function that runs before Strapi gets started.
(Source: https://docs.strapi.io/cms/features/media-library#upload-request-timeout)
Language: JavaScript
File path: /index.js
```js
module.exports = {
//...
bootstrap({ strapi }) {
// Set the requestTimeout to 1,800,000 milliseconds (30 minutes):
strapi.server.httpServer.requestTimeout = 30 * 60 * 1000;
},
};
```
---
Language: TypeScript
File path: /index.ts
```ts
export default {
//...
bootstrap({ strapi }) {
// Set the requestTimeout to 1,800,000 milliseconds (30 minutes):
strapi.server.httpServer.requestTimeout = 30 * 60 * 1000;
},
};
```
## Responsive Images
Description: These sizes can be overridden in /config/plugins:
(Source: https://docs.strapi.io/cms/features/media-library#responsive-images)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
upload: {
config: {
breakpoints: {
xlarge: 1920,
large: 1000,
medium: 750,
small: 500,
xsmall: 64
},
},
},
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
upload: {
config: {
breakpoints: {
xlarge: 1920,
large: 1000,
medium: 750,
small: 500,
xsmall: 64
},
},
},
});
```
# Preview
Source: https://docs.strapi.io/cms/features/preview
## Configuration
Description: :::note Notes * The following environment variables must be defined in your .env file, replacing example values with appropriate values:
(Source: https://docs.strapi.io/cms/features/preview#configuration)
Language: Bash
File path: N/A
```bash
CLIENT_URL=https://your-frontend-app.com
PREVIEW_SECRET=your-secret-key
```
## Activation flag
Description: Enables or disables the preview feature:
(Source: https://docs.strapi.io/cms/features/preview#activation-flag)
Language: JavaScript
File path: config/admin.ts|js
```javascript
// …
preview: {
enabled: true,
// …
}
// …
```
## Allowed origins
Description: Controls which domains can access previews:
(Source: https://docs.strapi.io/cms/features/preview#allowed-origins)
Language: JavaScript
File path: config/admin.ts|js
```javascript
// …
preview: {
enabled: true,
config: {
allowedOrigins: env("CLIENT_URL"), // Usually your frontend application URL
// …
}
}
// …
```
## Preview handler
Description: Manages the preview logic and URL generation, as in the following basic example where uid is the content-type identifier (e.g., api::article.article or plugin::my-api.my-content-type):
(Source: https://docs.strapi.io/cms/features/preview#preview-handler)
Language: JSX
File path: config/admin.ts|js
```jsx
// …
preview: {
enabled: true,
config: {
// …
async handler(uid, { documentId, locale, status }) {
const document = await strapi.documents(uid).findOne({ documentId });
const pathname = getPreviewPathname(uid, { locale, document });
return `${env('PREVIEW_URL')}${pathname}`
},
}
}
// …
```
## Previewing draft entries
Description: When Draft & Publish is enabled for your content-type, you can also directly leverage Strapi's status parameter to handle the logic within the Preview handler, using the following generic approach:
(Source: https://docs.strapi.io/cms/features/preview#previewing-draft-entries)
Language: JavaScript
File path: N/A
```javascript
async handler(uid, { documentId, locale, status }) {
const document = await strapi.documents(uid).findOne({ documentId });
const pathname = getPreviewPathname(uid, { locale, document });
if (status === 'published') {
// return the published version
}
// return the draft version
},
```
## 1. [Strapi] Create the Preview configuration
Description: Create a new file /config/admin.ts (or update it if it exists) with the following basic structure:
(Source: https://docs.strapi.io/cms/features/preview#1-create-config)
Language: TypeScript
File path: config/admin.ts
```ts
export default ({ env }) => ({
// Other admin-related configurations go here
// (see docs.strapi.io/cms/configurations/admin-panel)
preview: {
enabled: true,
config: {
allowedOrigins: env('CLIENT_URL'),
async handler (uid, { documentId, locale, status }) => {
// Handler implementation coming in step 3
},
},
},
});
```
## 2. [Strapi] Add URL generation logic
Description: Add the URL generation logic with a getPreviewPathname function.
(Source: https://docs.strapi.io/cms/features/preview#2-add-url-generation)
Language: TypeScript
File path: config/admin.ts
```ts
// Function to generate preview pathname based on content type and document
const getPreviewPathname = (uid, { locale, document }): string => {
const { slug } = document;
// Handle different content types with their specific URL patterns
switch (uid) {
// Handle pages with predefined routes
case "api::page.page":
switch (slug) {
case "homepage":
return `/${locale}`; // Localized homepage
case "pricing":
return "/pricing"; // Pricing page
case "contact":
return "/contact"; // Contact page
case "faq":
return "/faq"; // FAQ page
}
// Handle product pages
case "api::product.product": {
if (!slug) {
return "/products"; // Products listing page
}
return `/products/${slug}`; // Individual product page
}
// Handle blog articles
case "api::article.article": {
if (!slug) {
return "/blog"; // Blog listing page
}
return `/blog/${slug}`; // Individual article page
}
default: {
return null;
}
}
};
// … main export (see step 3)
```
## 3. [Strapi] Add handler logic
Description: Create the complete configuration, expanding the basic configuration created in step 1.
(Source: https://docs.strapi.io/cms/features/preview#3-add-handler)
Language: TypeScript
File path: config/admin.ts
```ts
const getPreviewPathname = (uid, { locale, document }): string => {
// … as defined in step 2
};
// Main configuration export
export default ({ env }) => {
// Get environment variables
const clientUrl = env("CLIENT_URL"); // Frontend application URL
const previewSecret = env("PREVIEW_SECRET"); // Secret key for preview authentication
return {
// Other admin-related configurations go here
// (see docs.strapi.io/cms/configurations/admin-panel)
preview: {
enabled: true, // Enable preview functionality
config: {
allowedOrigins: clientUrl, // Restrict preview access to specific domain
async handler(uid, { documentId, locale, status }) {
// Fetch the complete document from Strapi
const document = await strapi.documents(uid).findOne({ documentId });
// Generate the preview pathname based on content type and document
const pathname = getPreviewPathname(uid, { locale, document });
// Disable preview if the pathname is not found
if (!pathname) {
return null;
}
// Use Next.js draft mode passing it a secret key and the content-type status
const urlSearchParams = new URLSearchParams({
url: pathname,
secret: previewSecret,
status,
});
return `${clientUrl}/api/preview?${urlSearchParams}`;
},
},
},
};
};
```
## 4. [Front end] Set up the front-end preview route
Description: If using Next.js, a basic implementation could be like in the following example taken from the Strapi demo application:
(Source: https://docs.strapi.io/cms/features/preview#4-setup-frontend-route)
Language: TypeScript
File path: /next/api/preview/route.ts
```ts
import { draftMode } from "next/headers";
import { redirect } from "next/navigation";
export async function GET(request: Request) {
// Parse query string parameters
const { searchParams } = new URL(request.url);
const secret = searchParams.get("secret");
const url = searchParams.get("url");
const status = searchParams.get("status");
// Check the secret and next parameters
// This secret should only be known to this route handler and the CMS
if (secret !== process.env.PREVIEW_SECRET) {
return new Response("Invalid token", { status: 401 });
}
// Enable Draft Mode by setting the cookie
if (status === "published") {
draftMode().disable();
} else {
draftMode().enable();
}
// Redirect to the path from the fetched post
// We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities
redirect(url || "/");
}
```
## 6. [Front end] Adapt data fetching for draft content
Description: The following, taken from the Strapi demo application, is an example of how to implement draft-aware data fetching in your Next.js front-end application:
(Source: https://docs.strapi.io/cms/features/preview#6-fetch-draft-content)
Language: TypeScript
File path: /config/admin.ts
```ts
import { draftMode } from "next/headers";
import qs from "qs";
export default async function fetchContentType(
contentType: string,
params: Record = {}
): Promise {
// Check if Next.js draft mode is enabled
const { isEnabled: isDraftMode } = await draftMode();
try {
const queryParams = { ...params };
// Add status=draft parameter when draft mode is enabled
if (isDraftMode) {
queryParams.status = "draft";
}
const url = `${baseURL}/${contentType}?${qs.stringify(queryParams)}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`Failed to fetch data from Strapi (url=${url}, status=${response.status})`
);
}
return await response.json();
} catch (error) {
console.error("Error fetching content:", error);
throw error;
}
}
```
Language: TypeScript
File path: /config/admin.ts
```ts
// In your page component:
const pageData = await fetchContentType('api::page.page', {
// Your other query parameters
});
```
## Window messages
Description: When putting it all together, a component ready to be added to your global layout could look like:
(Source: https://docs.strapi.io/cms/features/preview#window-messages)
Language: JavaScript
File path: next/app/path/to/your/front/end/logic.jsx
```js
'use client';
export default function LivePreview() {
// …
const router = useRouter();
useEffect(() => {
const handleMessage = async (message) => {
const { origin, data } = message;
if (origin !== process.env.NEXT_PUBLIC_API_URL) {
return;
}
if (data.type === 'strapiUpdate') {
router.refresh();
} else if (data.type === 'strapiScript') {
const script = window.document.createElement('script');
script.textContent = data.payload.script;
window.document.head.appendChild(script);
}
};
// Add the event listener
window.addEventListener('message', handleMessage);
// Let Strapi know we're ready to receive the script
window.parent?.postMessage({ type: 'previewReady' }, '*');
// Remove the event listener on unmount
return () => {
window.removeEventListener('message', handleMessage);
};
}, [router]);
return null;
}
```
---
Language: TypeScript
File path: next/app/path/to/your/front/end/logic.tsx
```ts
'use client';
export default function LivePreview() {
// …
const router = useRouter();
useEffect(() => {
const handleMessage = async (message: MessageEvent) => {
const { origin, data } = message;
if (origin !== process.env.NEXT_PUBLIC_API_URL) {
return;
}
if (data.type === 'strapiUpdate') {
router.refresh();
} else if (data.type === 'strapiScript') {
const script = window.document.createElement('script');
script.textContent = data.payload.script;
window.document.head.appendChild(script);
}
};
// Add the event listener
window.addEventListener('message', handleMessage);
// Let Strapi know we're ready to receive the script
window.parent?.postMessage({ type: 'previewReady' }, '*');
// Remove the event listener on unmount
return () => {
window.removeEventListener('message', handleMessage);
};
}, [router]);
return null;
}
```
## Content source maps
Description: For a Next.js application, you may use the draftMode() method from next/headers to detect if draft mode is enabled, and set the header accordingly in all your API calls:
(Source: https://docs.strapi.io/cms/features/preview#content-source-maps)
Language: JavaScript
File path: N/A
```js
import { draftMode } from "next/headers";
import qs from "qs";
export default async function fetchContentType(
contentType: string,
params: Record = {}
): Promise {
// Check if Next.js draft mode is enabled
const { isEnabled: isDraftMode } = await draftMode();
try {
const queryParams = { ...params };
// Add status=draft parameter when draft mode is enabled
if (isDraftMode) {
queryParams.status = "draft";
}
const url = `${baseURL}/${contentType}?${qs.stringify(queryParams)}`;
const response = await fetch(url, {
headers: {
// Enable content source maps in preview mode
"strapi-encode-source-maps": isDraftMode ? "true" : "false",
},
});
if (!response.ok) {
throw new Error(
`Failed to fetch data from Strapi (url=${url}, status=${response.status})`
);
}
return await response.json();
} catch (error) {
console.error("Error fetching content:", error);
throw error;
}
}
```
# Users & Permissions
Source: https://docs.strapi.io/cms/features/users-permissions
## JWT management modes
Description: For backwards compatibility, the Users & Permission feature defaults to legacy mode:
(Source: https://docs.strapi.io/cms/features/users-permissions#jwt-management-modes)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// …
'users-permissions': {
config: {
jwtManagement: 'refresh',
sessions: {
accessTokenLifespan: 604800, // 1 week (default)
maxRefreshTokenLifespan: 2592000, // 30 days
idleRefreshTokenLifespan: 604800, // 7 days
httpOnly: false, // Set to true for HTTP-only cookies
cookie: {
name: 'strapi_up_refresh',
sameSite: 'lax',
path: '/',
secure: false, // true in production
},
},
},
},
// ...
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// …
'users-permissions': {
config: {
jwtManagement: 'refresh',
sessions: {
accessTokenLifespan: 604800, // 1 week (default)
maxRefreshTokenLifespan: 2592000, // 30 days
idleRefreshTokenLifespan: 604800, // 7 days
httpOnly: false, // Set to true for HTTP-only cookies
cookie: {
name: 'strapi_up_refresh',
sameSite: 'lax',
path: '/',
secure: false, // true in production
},
},
},
},
// ...
});
```
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
'users-permissions': {
config: {
jwtManagement: 'legacy-support',
jwt: {
expiresIn: '7d', // Traditional JWT expiry
},
},
},
});
```
## Registration configuration
Description: The following example shows how to ensure a field called "nickname" is accepted by the API on user registration:
(Source: https://docs.strapi.io/cms/features/users-permissions#registration-configuration)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ...
"users-permissions": {
config: {
register: {
allowedFields: ["nickname"],
},
},
},
// ...
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// ...
"users-permissions": {
config: {
register: {
allowedFields: ["nickname"],
},
},
},
// ...
});
```
## Rate limiting configuration
Description: | ratelimit | Settings to customize the rate limiting of the authentications and registration endpoints | object | {} | | ratelimit.enabled | Enable or disable the rate limiter | boolean | true | | ratelimit.interval | Time window for requests to be considered as part of the same rate limiting bucket | object | { min: 5 } | | ratelimit.max | Maximum number of requests allowed in the time window | integer | 5 | | ratelimit.prefixKey | Prefix for the rate limiting key | string | ${userIdentifier}:${requestPath}:${ctx.request.ip} |
(Source: https://docs.strapi.io/cms/features/users-permissions#rate-limiting-configuration)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ... other plugins configuration ...
// Users & Permissions configuration
'users-permissions': {
config: {
ratelimit: {
enabled: true,
interval: { min: 5 },
max: 5,
},
},
},
// ...
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// ... other plugins configuration ...
// Users & Permissions configuration
'users-permissions': {
config: {
ratelimit: {
enabled: true,
interval: { min: 5 },
max: 5,
},
},
},
// ...
});
```
## Security configuration
Description: By default you can set a JWT_SECRET environment variable and it will be used as secret.
(Source: https://docs.strapi.io/cms/features/users-permissions#security-configuration)
Language: JavaScript
File path: /extensions/users-permissions/config/jwt.js
```js
module.exports = {
jwtSecret: process.env.SOME_ENV_VAR,
};
```
---
Language: TypeScript
File path: /extensions/users-permissions/config/jwt.ts
```ts
export default {
jwtSecret: process.env.SOME_ENV_VAR,
};
```
## Creating a custom callback validator
Description: If you need to configure a custom handler to accept other URLs, you can create a callback validate function in your plugins.js for the users-permissions plugin.
(Source: https://docs.strapi.io/cms/features/users-permissions#creating-a-custom-password-validation)
Language: TSX
File path: /config/plugins.js|ts
```tsx
// ... other plugins configuration ...
// Users & Permissions configuration
'users-permissions': {
enabled: true,
config: {
callback: {
validate: (cbUrl, options) => {
// cbUrl is where Strapi is being asked to redirect the auth info
// that was received from the provider to
// in this case, we will only validate that the
// if using a base url, you should always include the trailing slash
// although in real-world usage you should also include the full paths
if (cbUrl.startsWith('https://myproxy.mysite.com/') ||
cbUrl.startsWith('https://mysite.com/')) {
return;
}
// Note that you MUST throw an error to fail validation
// return values are not checked
throw new Error('Invalid callback url');
},
},
},
},
```
## Session management endpoints
Description: Code example from "Session management endpoints"
(Source: https://docs.strapi.io/cms/features/users-permissions#session-management-endpoints)
Language: JavaScript
File path: N/A
```
curl -X POST http://localhost:1337/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "your-refresh-token"
}'
```
---
Language: JSON
File path: N/A
```json
{
"jwt": "your-new-access-token"
}
```
---
Language: Bash
File path: N/A
```bash
curl -X POST http://localhost:1337/api/auth/logout \
-H "Authorization: Bearer your-access-token"
```
## Identifier
Description: The identifier parameter sent with requests can be an email or username, as in the following examples:
(Source: https://docs.strapi.io/cms/features/users-permissions#identifier)
Language: JavaScript
File path: N/A
```js
import axios from 'axios';
// Request API.
axios
.post('http://localhost:1337/api/auth/local', {
identifier: 'user@strapi.io',
password: 'strapiPassword',
})
.then(response => {
// Handle success.
console.log('Well done!');
console.log('User profile', response.data.user);
console.log('User token', response.data.jwt);
})
.catch(error => {
// Handle error.
console.log('An error occurred:', error.response);
});
```
---
Language: JSON
File path: N/A
```json
{
"identifier": "user@strapi.io",
"password": "strapiPassword"
}
```
---
Language: JSON
File path: N/A
```json
{
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTc2OTM4MTUwLCJleHAiOjE1Nzk1MzAxNTB9.UgsjjXkAZ-anD257BF7y1hbjuY3ogNceKfTAQtzDEsU",
"user": {
"id": 1,
"username": "user",
...
}
}
```
---
Language: JSON
File path: N/A
```json
{
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // Short-lived access token
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // Long-lived refresh token
"user": {
"id": 1,
"username": "user",
...
}
}
```
## Token usage
Description: The token variable is the data.jwt received when logging in or registering.
(Source: https://docs.strapi.io/cms/features/users-permissions#token-usage)
Language: JavaScript
File path: N/A
```js
import axios from 'axios';
const token = 'YOUR_TOKEN_HERE';
// Request API.
axios
.get('http://localhost:1337/posts', {
headers: {
Authorization: `Bearer ${token}`,
},
})
.then(response => {
// Handle success.
console.log('Data: ', response.data);
})
.catch(error => {
// Handle error.
console.log('An error occurred:', error.response);
});
```
## User registration
Description: Creating a new user in the database with a default role as 'registered' can be done like in the following example:
(Source: https://docs.strapi.io/cms/features/users-permissions#user-registration)
Language: JavaScript
File path: N/A
```js
import axios from 'axios';
// Request API.
// Add your own code here to customize or restrict how the public can register new users.
axios
.post('http://localhost:1337/api/auth/local/register', {
username: 'Strapi user',
email: 'user@strapi.io',
password: 'strapiPassword',
})
.then(response => {
// Handle success.
console.log('Well done!');
console.log('User profile', response.data.user);
console.log('User token', response.data.jwt);
})
.catch(error => {
// Handle error.
console.log('An error occurred:', error.response);
});
```
## User object in Strapi context
Description: The authenticated user object is a property of ctx.state.
(Source: https://docs.strapi.io/cms/features/users-permissions#user-object-in-strapi-context)
Language: JavaScript
File path: N/A
```js
create: async ctx => {
const { id } = ctx.state.user;
const depositObj = {
...ctx.request.body,
depositor: id,
};
const data = await strapi.services.deposit.add(depositObj);
// Send 201 `created`
ctx.created(data);
};
```
# CLI
Source: https://docs.strapi.io/cms/installation/cli
## Creating a Strapi project
Description: In a terminal, run the following command:
(Source: https://docs.strapi.io/cms/installation/cli#creating-a-strapi-project)
Language: Bash
File path: N/A
```bash
npx create-strapi@latest
```
---
Language: Bash
File path: N/A
```bash
pnpm create strapi
```
## Running Strapi
Description: Code example from "Running Strapi"
(Source: https://docs.strapi.io/cms/installation/cli#running-strapi)
Language: Bash
File path: N/A
```bash
pnpm develop
```
---
Language: Bash
File path: N/A
```bash
npm run develop
```
# Docker
Source: https://docs.strapi.io/cms/installation/docker
## Development Dockerfile
Description: Sample Dockerfile:
(Source: https://docs.strapi.io/cms/installation/docker#development-dockerfile)
Language: DOCKERFILE
File path: ./Dockerfile
```dockerfile
FROM node:22-alpine
# Installing libvips-dev for sharp Compatibility
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev git
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY package.json yarn.lock ./
RUN yarn global add node-gyp
RUN yarn config set network-timeout 600000 -g && yarn install --frozen-lockfile
ENV PATH=/opt/node_modules/.bin:$PATH
WORKDIR /opt/app
COPY . .
RUN chown -R node:node /opt/app
USER node
RUN ["yarn", "build"]
EXPOSE 1337
CMD ["yarn", "develop"]
```
---
Language: DOCKERFILE
File path: ./Dockerfile
```dockerfile
FROM node:22-alpine
# Installing libvips-dev for sharp Compatibility
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev git
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY package.json package-lock.json ./
RUN npm install -g node-gyp
RUN npm config set fetch-retry-maxtimeout 600000 -g && npm ci
ENV PATH=/opt/node_modules/.bin:$PATH
WORKDIR /opt/app
COPY . .
RUN chown -R node:node /opt/app
USER node
RUN ["npm", "run", "build"]
EXPOSE 1337
CMD ["npm", "run", "develop"]
```
Language: DOCKERFILE
File path: N/A
```dockerfile
FROM node:22-slim
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
```
## (Optional) Docker Compose
Description: Sample docker-compose.yml:
(Source: https://docs.strapi.io/cms/installation/docker#optional-docker-compose)
Language: YAML
File path: ./docker-compose.yml
```yaml
services:
strapi:
container_name: strapi
build: .
image: strapi:latest
restart: unless-stopped
env_file: .env
environment:
DATABASE_CLIENT: ${DATABASE_CLIENT}
DATABASE_HOST: strapiDB
DATABASE_PORT: ${DATABASE_PORT}
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
APP_KEYS: ${APP_KEYS}
NODE_ENV: ${NODE_ENV}
volumes:
- ./config:/opt/app/config
- ./src:/opt/app/src
- ./package.json:/opt/package.json
- ./yarn.lock:/opt/yarn.lock
- ./.env:/opt/app/.env
- ./public/uploads:/opt/app/public/uploads
ports:
- "1337:1337"
networks:
- strapi
depends_on:
- strapiDB
strapiDB:
container_name: strapiDB
platform: linux/amd64 #for platform error on Apple M1 chips
restart: unless-stopped
env_file: .env
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_USER: ${DATABASE_USERNAME}
MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD}
MYSQL_PASSWORD: ${DATABASE_PASSWORD}
MYSQL_DATABASE: ${DATABASE_NAME}
volumes:
- strapi-data:/var/lib/mysql
#- ./data:/var/lib/mysql # if you want to use a bind folder
ports:
- "3306:3306"
networks:
- strapi
volumes:
strapi-data:
networks:
strapi:
name: strapi
driver: bridge
```
---
Language: YAML
File path: ./docker-compose.yml
```yaml
version: "3"
services:
strapi:
container_name: strapi
build: .
image: strapi:latest
restart: unless-stopped
env_file: .env
environment:
DATABASE_CLIENT: ${DATABASE_CLIENT}
DATABASE_HOST: strapiDB
DATABASE_PORT: ${DATABASE_PORT}
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
APP_KEYS: ${APP_KEYS}
NODE_ENV: ${NODE_ENV}
volumes:
- ./config:/opt/app/config
- ./src:/opt/app/src
- ./package.json:/opt/package.json
- ./yarn.lock:/opt/yarn.lock
- ./.env:/opt/app/.env
- ./public/uploads:/opt/app/public/uploads
ports:
- "1337:1337"
networks:
- strapi
depends_on:
- strapiDB
strapiDB:
container_name: strapiDB
platform: linux/amd64 #for platform error on Apple M1 chips
restart: unless-stopped
env_file: .env
image: mariadb:latest
environment:
MYSQL_USER: ${DATABASE_USERNAME}
MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD}
MYSQL_PASSWORD: ${DATABASE_PASSWORD}
MYSQL_DATABASE: ${DATABASE_NAME}
volumes:
- strapi-data:/var/lib/mysql
#- ./data:/var/lib/mysql # if you want to use a bind folder
ports:
- "3306:3306"
networks:
- strapi
volumes:
strapi-data:
networks:
strapi:
name: strapi
driver: bridge
```
---
Language: YAML
File path: ./docker-compose.yml
```yaml
version: "3"
services:
strapi:
container_name: strapi
build: .
image: strapi:latest
restart: unless-stopped
env_file: .env
environment:
DATABASE_CLIENT: ${DATABASE_CLIENT}
DATABASE_HOST: strapiDB
DATABASE_PORT: ${DATABASE_PORT}
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
APP_KEYS: ${APP_KEYS}
NODE_ENV: ${NODE_ENV}
volumes:
- ./config:/opt/app/config
- ./src:/opt/app/src
- ./package.json:/opt/package.json
- ./yarn.lock:/opt/yarn.lock
- ./.env:/opt/app/.env
- ./public/uploads:/opt/app/public/uploads
ports:
- "1337:1337"
networks:
- strapi
depends_on:
- strapiDB
strapiDB:
container_name: strapiDB
platform: linux/amd64 #for platform error on Apple M1 chips
restart: unless-stopped
env_file: .env
image: postgres:16.0-alpine
environment:
POSTGRES_USER: ${DATABASE_USERNAME}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: ${DATABASE_NAME}
volumes:
- strapi-data:/var/lib/postgresql/data/ #using a volume
#- ./data:/var/lib/postgresql/data/ # if you want to use a bind folder
ports:
- "5432:5432"
networks:
- strapi
volumes:
strapi-data:
networks:
strapi:
name: strapi
driver: bridge
```
## Production Dockerfile
Description: The following Dockerfile can be used to build a production Docker image for a Strapi project.
(Source: https://docs.strapi.io/cms/installation/docker#production-dockerfile)
Language: DOCKERFILE
File path: ./Dockerfile.prod
```dockerfile
# Creating multi-stage build for production
FROM node:22-alpine AS build
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev git > /dev/null 2>&1
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY package.json yarn.lock ./
RUN yarn global add node-gyp
RUN yarn config set network-timeout 600000 -g && yarn install --frozen-lockfile --production
ENV PATH=/opt/node_modules/.bin:$PATH
WORKDIR /opt/app
COPY . .
RUN yarn build
# Creating final production image
FROM node:22-alpine
RUN apk add --no-cache vips-dev
ENV NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/app
COPY --from=build /opt/node_modules ./node_modules
COPY --from=build /opt/app ./
ENV PATH=/opt/node_modules/.bin:$PATH
RUN chown -R node:node /opt/app
USER node
EXPOSE 1337
CMD ["yarn", "start"]
```
---
Language: DOCKERFILE
File path: ./Dockerfile.prod
```dockerfile
# Creating multi-stage build for production
FROM node:22-alpine AS build
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev git > /dev/null 2>&1
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY package.json package-lock.json ./
RUN npm install -g node-gyp
RUN npm config set fetch-retry-maxtimeout 600000 -g && npm ci --only=production
ENV PATH=/opt/node_modules/.bin:$PATH
WORKDIR /opt/app
COPY . .
RUN npm run build
# Creating final production image
FROM node:22-alpine
RUN apk add --no-cache vips-dev
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/app
COPY --from=build /opt/node_modules ./node_modules
COPY --from=build /opt/app ./
ENV PATH=/opt/node_modules/.bin:$PATH
RUN chown -R node:node /opt/app
USER node
EXPOSE 1337
CMD ["npm", "run", "start"]
```
## Building the production container
Description: To build a production Docker image for a Strapi project, run the following command:
(Source: https://docs.strapi.io/cms/installation/docker#building-the-production-container)
Language: Bash
File path: N/A
```bash
docker build \
--build-arg NODE_ENV=production \
# --build-arg STRAPI_URL=https://api.example.com \ # Uncomment to set the Strapi Server URL
-t mystrapiapp:latest \ # Replace with your image name
-f Dockerfile.prod .
```
# From Entity Service API to Document Service API
Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service
## findOne
Description: Before:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service#findone)
Language: Bash
File path: N/A
```bash
strapi.entityService.findOne(uid, entityId);
```
Language: TSX
File path: N/A
```tsx
strapi.documents(uid).findOne({
documentId: "__TODO__"
});
```
## findMany
Description: Before:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service#findmany)
Language: TSX
File path: N/A
```tsx
strapi.entityService.findMany(uid, {
fields: ["id", "name", "description"],
populate: ["author", "comments"],
publicationState: "preview",
});
```
Language: TSX
File path: N/A
```tsx
strapi.documents(uid).findMany({
fields: ["id", "name", "description"],
populate: ["author", "comments"],
status: "draft",
});
```
## create
Description: Before:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service#create)
Language: TSX
File path: N/A
```tsx
strapi.entityService.create(uid, {
data: {
name: "John Doe",
age: 30,
},
});
```
Language: TSX
File path: N/A
```tsx
strapi.documents(uid).create({
data: {
name: "John Doe",
age: 30,
},
});
```
## update
Description: Before:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service#update)
Language: TSX
File path: N/A
```tsx
strapi.entityService.update(uid, entityId, {
data: {
name: "John Doe",
age: 30,
}
});
```
Language: TSX
File path: N/A
```tsx
strapi.documents(uid).update({
documentId: "__TODO__",
data: {
name: "John Doe",
age: 30,
}
});
```
## delete
Description: Before:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service#delete)
Language: Bash
File path: N/A
```bash
strapi.entityService.delete(uid, entityId);
```
Language: TSX
File path: N/A
```tsx
strapi.documents(uid).delete({
documentId: "__TODO__"
});
```
## count
Description: Before:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service#count)
Language: Bash
File path: N/A
```bash
strapi.entityService.count(uid);
```
Language: Bash
File path: N/A
```bash
strapi.documents(uid).count();
```
## Manual migration
Description: In Strapi v4:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service#manual-migration)
Language: TSX
File path: N/A
```tsx
strapi.entityService.decorate((service) => {
return Object.assign(service, {
findOne(entityId, params = {}) {
// e.g., exclude soft deleted content
params.filters = { ...params.filters, deletedAt: { $notNull: true } }
return service.findOne(entityId, params)
}
});
})
```
Language: TSX
File path: N/A
```tsx
strapi.documents.use((ctx, next) => {
if (ctx.uid !== "api::my-content-type.my-content-type") {
return next();
}
if (ctx.action === 'findOne') {
// customization
ctx.params.filters = { ...params.filters, deletedAt: { $notNull: true } }
const res = await next();
// do something with the response if you want
return res;
}
return next();
});
```
# Helper-plugin migration reference
Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin
## AnErrorOccurred
Description: This component has been removed and refactored to be part of the Page component exported from @strapi/strapi/admin.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#anerroroccurred)
Language: JavaScript
File path: N/A
```js
// Before
import { AnErrorOccurred } from '@strapi/helper-plugin';
const MyPage = () => {
// ...
if (error) {
return ;
}
// ...
};
// After
import { Page } from '@strapi/strapi/admin';
const MyPage = () => {
// ...
if (error) {
return ;
}
// ...
};
```
## CheckPagePermissions
Description: This component has been removed and refactored to be part of the Page component exported from @strapi/strapi/admin.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#checkpagepermissions)
Language: JavaScript
File path: N/A
```js
// Before
import { CheckPagePermissions } from '@strapi/helper-plugin';
const MyProtectedPage = () => {
return (
);
};
// After
import { Page } from '@strapi/strapi/admin';
const MyProtectedPage = () => {
return (
);
};
```
## ConfirmDialog
Description: This component has been moved and refactored.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#confirmdialog)
Language: JavaScript
File path: N/A
```js
// Before
import { ConfirmDialog } from '@strapi/helper-plugin';
// After
import { ConfirmDialog } from '@strapi/strapi/admin';
```
## DateTimePicker
Description: This was aliasing the design-system.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#datetimepicker)
Language: JavaScript
File path: N/A
```js
// Before
import { DateTimePicker } from '@strapi/helper-plugin';
// After
import { DateTimePicker } from '@strapi/design-system';
```
## EmptyStateLayout
Description: This component has been removed and not replaced.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#emptystatelayout)
Language: JavaScript
File path: N/A
```js
// Before
import { EmptyStateLayout } from '@strapi/helper-plugin';
// After
import { EmptyStateLayout } from '@strapi/design-system';
```
## FilterListURLQuery
Description: This component was moved to the admin package and can now be imported via the @strapi/strapi package as part of the composite component Filters:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#filterlisturlquery)
Language: JavaScript
File path: N/A
```js
// Before
import { FilterListURLQuery } from '@strapi/helper-plugin';
const MyComponent = () => {
return (
);
};
// After
import { Filters } from '@strapi/strapi/admin';
const MyComponent = () => {
return (
);
};
```
## FilterPopoverURLQueryProps
Description: This component was moved to the admin package and can now be imported via the @strapi/strapi package as part of the composite component Filters:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#filterpopoverurlqueryprops)
Language: JavaScript
File path: N/A
```js
// Before
import { FilterPopoverURLQueryProps } from '@strapi/helper-plugin';
// After
import { Filters } from '@strapi/strapi/admin';
const MyComponent = () => {
return (
);
};
```
## Form
Description: This component aliased Formik, something we're working towards removing.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#form)
Language: JavaScript
File path: N/A
```js
// Before
import { Form } from '@strapi/helper-plugin';
// After
import { Form } from '@strapi/strapi/admin';
```
## GenericInput
Description: This component has been removed and refactored to become the InputRenderer component exported from @strapi/strapi/admin.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#genericinput)
Language: JavaScript
File path: N/A
```js
// Before
import { GenericInput } from '@strapi/helper-plugin';
const MyComponent = () => {
return (
);
};
// After
import { InputRenderer } from '@strapi/strapi/admin';
```
## InjectionZone
Description: This component has been removed and not replaced.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#injectionzone)
Language: JavaScript
File path: N/A
```js
// Before
import { InjectionZone } from '@strapi/helper-plugin';
;
// After
const MyComponent = ({ area, ...compProps }) => {
const getPlugin = useStrapiApp('MyComponent', (state) => state.getPlugin);
const [pluginName, page, position] = area.split('.');
const plugin = getPlugin(pluginName);
const components = plugin?.getInjectedComponents(page, position);
if (!plugin || !components) {
return null;
}
return components.map(({ name, Component }) => (
));
};
```
## Link
Description: This was aliasing the design-system and using the as prop with react-router-dom.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#link)
Language: JavaScript
File path: N/A
```js
// Before
import { Link } from '@strapi/helper-plugin';
// After
import { Link } from '@strapi/design-system/v2';
import { NavLink } from 'react-router-dom';
const MyLink = () => {
return (
My Link
);
};
```
## LinkButton
Description: This was aliasing the design-system and using the as prop with react-router-dom.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#linkbutton)
Language: JavaScript
File path: N/A
```js
// Before
import { LinkButton } from '@strapi/helper-plugin';
// After
import { LinkButton } from '@strapi/design-system/v2';
import { NavLink } from 'react-router-dom';
const MyLink = () => {
return (
My Link
);
};
```
## LoadingIndicatorPage
Description: This component has been removed and refactored to be part of the Page component exported from @strapi/strapi/admin.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#loadingindicatorpage)
Language: JavaScript
File path: N/A
```js
// Before
import { LoadingIndicatorPage } from '@strapi/helper-plugin';
const MyPage = () => {
// ...
if (isLoading) {
return ;
}
// ...
};
// After
import { Page } from '@strapi/strapi/admin';
const MyPage = () => {
// ...
if (isLoading) {
return ;
}
// ...
};
```
## NoContent
Description: This component has been removed and not replaced, you should use the EmptyStateLayout component from @strapi/design-system.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#nocontent)
Language: JavaScript
File path: N/A
```js
// Before
import { NoContent } from '@strapi/helper-plugin';
;
// After
import { EmptyStateLayout } from '@strapi/design-system';
;
```
## NoPermissions
Description: This component has been removed and refactored to be part of the Page component exported from @strapi/strapi/admin.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#nopermissions)
Language: JavaScript
File path: N/A
```js
// Before
import { NoPermissions } from '@strapi/helper-plugin';
const MyPage = () => {
// ...
if (!canRead) {
return ;
}
// ...
};
// After
import { Page } from '@strapi/strapi/admin';
const MyPage = () => {
// ...
if (!canRead) {
return ;
}
// ...
};
```
## NotAllowedInput
Description: This component has been removed and not replaced.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#notallowedinput)
Language: JavaScript
File path: N/A
```js
import { TextInput } from '@strapi/design-system';
const MyComponent = (props) => {
return (
);
};
```
## PageSizeURLQuery
Description: This component was moved to the admin package and can now be imported via the @strapi/strapi package as part of the composite component Pagination:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#pagesizeurlquery)
Language: JavaScript
File path: N/A
```js
// Before
import { PageSizeURLQuery } from '@strapi/helper-plugin';
const MyComponent = () => {
return (
);
};
// After
import { Pagination } from '@strapi/strapi/admin';
const MyComponent = () => {
return (
);
};
```
## PaginationURLQueryProps
Description: This component was moved to the admin package and can now be imported via the @strapi/strapi package as part of the composite component Pagination:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#paginationurlqueryprops)
Language: JavaScript
File path: N/A
```js
// Before
import { PaginationURLQueryProps } from '@strapi/helper-plugin';
// After
import { Pagination } from '@strapi/strapi/admin';
const MyComponent = () => {
return (
);
};
```
## SearchURLQuery
Description: This component was removed and renamed to SearchInput and can now be imported by the @strapi/strapi package:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#searchurlquery)
Language: JavaScript
File path: N/A
```js
// Before
import { SearchURLQuery } from '@strapi/helper-plugin';
// After
import { SearchInput } from '@strapi/strapi/admin';
```
## Status
Description: This component should be imported from the @strapi/design-system package:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#status)
Language: JavaScript
File path: N/A
```js
// Before
import { Status } from '@strapi/helper-plugin';
const MyComponent = () => {
return (
{stateMessage[status]}
);
};
// After
import { Status } from '@strapi/design-system';
```
## Table
Description: This component should be imported from the @strapi/strapi/admin package:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#table)
Language: JavaScript
File path: N/A
```js
// Before
import { Table } from '@strapi/helper-plugin';
const MyComponent = () => {
return (
{`Name`}
{`Description`}
{data?.map(({ name, description }) => {
return (
{name}
{description}
);
})}
);
};
// After
import { Table } from '@strapi/strapi/admin';
```
## useCMEditViewDataManager
Description: A lot of the internals have been reworked and split.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#usecmeditviewdatamanager)
Language: JavaScript
File path: N/A
```js
// Before
import { useCMEditViewDataManager } from '@strapi/helper-plugin';
// After
import { unstable_useContentManagerContext as useContentManagerContext } from '@strapi/strapi/admin';
```
Language: TSX
File path: N/A
```tsx
// Before
const { slug, isSingleType, isCreatingEntry, hasDraftAndPublished } =
useCMEditViewDataManager();
// After
const {
model,
collectionType,
id,
slug,
isCreatingEntry,
isSingleType,
hasDraftAndPublish,
} = useContentManagerContext();
```
---
Language: TSX
File path: N/A
```tsx
// Before
// 'allLayoutData' has been removed. It contained 'components' and 'contentType' which can be extracted from the 'useContentManagerContext' hook as seen below.
const { allLayoutData } = useCMEditViewDataManager();
// After
const { components, contentType } = useContentManagerContext();
```
---
Language: TSX
File path: N/A
```tsx
// Before
const { layout } = useCMEditViewDataManager();
// After
const { layout } = useContentManagerContext();
const {
edit: { layout, components },
list: { layout },
} = layout;
```
---
Language: TSX
File path: N/A
```tsx
// Before
const { initialData, modifiedData, onChange } = useCMEditViewDataManager();
// After
const { form } = useContentManagerContext();
// Here 'initialData' and 'modifiedData' correspond to 'initialValues' and 'values'.
const { initialValues, values, onChange } = form;
```
---
Language: TSX
File path: N/A
```tsx
// Before
const { onPublish, onUnpublish } = useCMEditViewDataManager();
// After
const { publish, unpublish } = useDocumentActions();
```
## Notifications
Description: This feature has been moved to the @strapi/admin package and only the useNotifications hook is exported.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#notifications)
Language: JavaScript
File path: N/A
```js
// Before
import { useNotification } from '@strapi/helper-plugin';
const toggleNotification = useNotification();
toggleNotification({
type: 'warning',
message: {
id: 'my.message.id',
defaultMessage: 'My message',
},
});
// After
import { useNotification } from '@strapi/strapi/admin';
const { toggleNotification } = useNotification();
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'my.message.id',
defaultMessage: 'My message',
}),
});
```
## RBAC
Description: This feature has removed and not replaced.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#rbac)
Language: JavaScript
File path: N/A
```js
// Before
import { useRBACProvider } from '@strapi/helper-plugin';
const { allPermission, refetchPermissions } = useRBACProvider();
// After
import { useAuth } from '@strapi/strapi/admin';
const permissions = useAuth('COMPONENT_NAME', (state) => state.permissions);
const refetchPermission = useAuth(
'COMPONENT_NAME',
(state) => state.refetchPermission
);
```
## useAPIErrorHandler
Description: This hook has been removed.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#useapierrorhandler)
Language: JavaScript
File path: N/A
```js
// Before
import { useAPIErrorHandler } from '@strapi/helper-plugin';
// After
import { useAPIErrorHandler } from '@strapi/strapi/admin';
```
## useCallbackRef
Description: This hook has been removed.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#usecallbackref)
Language: JavaScript
File path: N/A
```js
// Before
import { useCallbackRef } from '@strapi/helper-plugin';
// After
import { useCallbackRef } from '@strapi/design-system';
```
## useCollator
Description: This hook has been removed.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#usecollator)
Language: JavaScript
File path: N/A
```js
// Before
import { useCollator } from '@strapi/helper-plugin';
// After
import { useCollator } from '@strapi/design-system';
```
## useFetchClient
Description: This hook has been removed.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#usefetchclient)
Language: JavaScript
File path: N/A
```js
// Before
import { useFetchClient } from '@strapi/helper-plugin';
// After
import { useFetchClient } from '@strapi/strapi/admin';
```
## useFilter
Description: This hook has been removed.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#usefilter)
Language: JavaScript
File path: N/A
```js
// Before
import { useFilter } from '@strapi/helper-plugin';
// After
import { useFilter } from '@strapi/design-system';
```
## useQueryParams
Description: This hook has been moved.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#usequeryparams)
Language: JavaScript
File path: N/A
```js
// Before
import { useQueryParams } from '@strapi/helper-plugin';
// After
import { useQueryParams } from '@strapi/strapi/admin';
```
## useRBAC
Description: This hook has been moved.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#userbac)
Language: JavaScript
File path: N/A
```js
// Before
import { useRBAC } from '@strapi/helper-plugin';
// After
import { useRBAC } from '@strapi/strapi/admin';
```
## getFetchClient
Description: This util has been removed.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#getfetchclient)
Language: JavaScript
File path: N/A
```js
// Before
import { getFetchClient } from '@strapi/helper-plugin';
// After
import { getFetchClient } from '@strapi/strapi/admin';
```
## hasPermissions
Description: This util has been removed.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#haspermissions)
Language: JavaScript
File path: N/A
```js
// Before
import { hasPermissions } from '@strapi/helper-plugin';
const permissions = await Promise.all(
generalSectionRawLinks.map(({ permissions }) =>
hasPermissions(userPermissions, permissions)
)
);
// After
import { useAuth } from '@strapi/strapi/admin';
const { checkUserHasPermissions } = useAuth(
'COMPONENT_NAME',
(state) => state.checkUserHasPermissions
);
```
## prefixPluginTranslations
Description: This util has been removed and not replaced.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#prefixplugintranslations)
Language: TSX
File path: N/A
```tsx
type TradOptions = Record;
const prefixPluginTranslations = (
trad: TradOptions,
pluginId: string
): TradOptions => {
if (!pluginId) {
throw new TypeError("pluginId can't be empty");
}
return Object.keys(trad).reduce((acc, current) => {
acc[`${pluginId}.${current}`] = trad[current];
return acc;
}, {} as TradOptions);
};
```
## pxToRem
Description: This util has been removed and not replaced.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#pxtorem)
Language: TSX
File path: N/A
```tsx
// Before
pxToRem(
32
) // After
`${32 / 16}rem`;
// or
('2rem');
```
## request
Description: This util has been removed and not replaced.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#request)
Language: JavaScript
File path: N/A
```js
// Before
import { request } from '@strapi/helper-plugin';
request(`/${pluginId}/settings/config`, { method: 'GET' });
// After
import { useFetchClient } from '@strapi/strapi/admin';
const { get } = useFetchClient();
get(`/${pluginId}/settings/config`);
```
Language: TSX
File path: N/A
```tsx
const { get } = useFetchClient();
const { data } = await get(requestURL);
```
## translatedErrors
Description: This utils has been removed.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/additional-resources/helper-plugin#translatederrors)
Language: JavaScript
File path: N/A
```js
// Before
import { translatedErrors } from '@strapi/helper-plugin';
// After
import { translatedErrors } from '@strapi/strapi/admin';
```
# The admin panel RBAC system has been updated
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/admin-panel-rbac-store-updated
## Breaking change description
Description: Permissions are handled with the content-manager_rbacManager section of the redux store, like in the following generic example:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/admin-panel-rbac-store-updated#breaking-change-description)
Language: TSX
File path: N/A
```tsx
const cmPermissions useSelector(state => state['content-manager_rbacManager'])
```
---
Language: TSX
File path: N/A
```tsx
const { allowedActions } = useRBAC({
main: [{ action: 'admin::something.main', subject: null }]
})
const canMain = allowedActions.canMain
```
Language: TSX
File path: N/A
```tsx
const { allowedActions } = useRBAC([
{ action: 'admin::something.main', subject: null }
])
const canMain = allowedActions.canMain
```
# Components and dynamic zones do not return an id
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/components-and-dynamic-zones-do-not-return-id
## Notes
Description: In Strapi v4, you could do the following send requests that would return a component's id:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/components-and-dynamic-zones-do-not-return-id#notes)
Language: TSX
File path: N/A
```tsx
// 1. GET /category/:id
category = fetch(...)
// 2. PUT /category/:id
{
data: {
name: 'category-name',
// Update component by its id
component: {
id: category.component.id // Use the id received in 1.
value: 'value'
}
}
}
```
# Core service methods use the Document Service API
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/core-service-methods-use-document-service
## Notes
Description: In Strapi v4:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/core-service-methods-use-document-service#notes)
Language: JavaScript
File path: /src/api/my-api-name/services/my-service.js
```js
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::address.address', {
findOne(entityId, params) {
// customization
super.findOne(entityId, params);
// or to show a bit more context
strapi.entityService.findOne(uid, entityId, params);
},
update(entityId, params) {
// customization
super.update(entityId, params);
},
delete(entityId, params) {
// customization
super.delete(entityId, params)
}
});
```
Language: JavaScript
File path: /src/api/my-api-name/services/my-service.js
```js
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::address.address', {
findOne(documentId, params) {
// customization
super.findOne(documentId, params);
// or to show a bit more context
strapi.documents(uid).findOne(documentId, params);
},
update(documentId, params) {
// customization
super.update(documentId, params);
},
delete(documentId, params) {
// customization
super.delete(documentId, params)
}
});
```
# Design System Updates in Strapi 5
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/design-system
## Import structure changes
Description: Key changes to be aware of include the following:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/design-system#import-structure-changes)
Language: JavaScript
File path: N/A
```js
- import { Combobox } from '@strapi/design-system/Combobox';
+ import { Combobox } from '@strapi/design-system';
```
## ThemeProvider migration
Description: Key changes to be aware of include the following:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/design-system#themeprovider-migration)
Language: JavaScript
File path: N/A
```js
- import { ThemeProvider } from '@strapi/design-system';
+ import { DesignSystemProvider } from '@strapi/design-system';
```
## Field components API changes
Description: Key changes to be aware of include the following:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/design-system#field-components-api-changes)
Language: DIFF
File path: N/A
```diff
- return ;
+ return (
+
+ {label}
+
+
+
+
+ );
```
## Grid component structure
Description: Key changes to be aware of include the following:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/design-system#grid-component-structure)
Language: JavaScript
File path: N/A
```js
- import { Grid, GridItem } from '@strapi/design-system';
+ import { Grid } from '@strapi/design-system';
-
- 1
- 2
-
+
+ 1
+ 2
+
```
# defaultIndex removed
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/do-not-update-repeatable-components-with-document-service-api
## Notes
Description: In Strapi 5, draft components and published components have different ids, and you could fetch data like follows:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/do-not-update-repeatable-components-with-document-service-api#notes)
Language: JavaScript
File path: N/A
```js
// Request
GET /articles // -> returns published article
// Response
{
documentId …
component: [
{ id: 2, name: 'component-1' },
{ id: 4, name: 'component-2' },
]
}
```
Language: JavaScript
File path: N/A
```js
PUT /articles/{documentId} // <== Update draft article
{
documentId …
component: [
{ id: 2, name: 'component-1-updated' }, // <== Update component 1
]
}
```
# strapi.fetch uses native fetch() API
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/fetch
## Manual procedure
Description: In Strapi v4
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/fetch#manual-procedure)
Language: TSX
File path: N/A
```tsx
strapi.fetch(url, {
method: 'POST',
body,
headers,
timeout: 1000,
}); // accepts the type RequestInit from node-fetch
```
Language: TSX
File path: N/A
```tsx
strapi.fetch(url, {
method: 'POST',
body,
headers,
signal: AbortSignal.timeout(1000)
}); // accepts the type RequestInit native to Node
```
# The getWhere() method for permission provider instances has been removed
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/get-where-removed
## Breaking change description
Description: The query was an object where keys and values were matched with the provider entries:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/get-where-removed#breaking-change-description)
Language: JavaScript
File path: N/A
```js
const values = provider.getWhere({ foo: 42, bar: 'baz' });
```
Language: JavaScript
File path: N/A
```js
const values = provider.values().filter(value => value.foo === 42 && value.bar === 'baz');
```
## Manual procedure
Description: In Strapi v4
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/get-where-removed#manual-procedure)
Language: TSX
File path: N/A
```tsx
const values = provider.getWhere({ foo: 42, bar: 'baz' });
```
Language: TSX
File path: N/A
```tsx
const values = provider.values().filter(
value => value.foo === 42 && value.bar === 'baz'
);
```
# The GraphQL API has been updated
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/graphql-api-updated
## Migration
Description: Enable the v4CompatibilityMode retro-compatibility header so queries can continue to rely on data.attributes.* while you refactor clients.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/graphql-api-updated#migration)
Language: JavaScript
File path: config/plugins.js
```js
module.exports = {
graphql: {
config: {
v4CompatibilityMode: true,
},
},
};
```
Language: GRAPHQL
File path: N/A
```graphql
{
restaurants {
data {
id
attributes {
title
image {
data {
id
attributes {
url
}
}
}
images {
data {
id
attributes {
url
}
}
}
xToOneRelation {
data {
id
attributes {
field
}
}
}
xToManyRelation {
data {
id
attributes {
field
}
}
}
}
}
meta {
pagination {
page
pageSize
}
}
}
}
```
Language: GRAPHQL
File path: N/A
```graphql
{
restaurants {
data {
documentId
attributes {
title
image {
data {
documentId
attributes {
url
}
}
}
images {
data {
documentId
attributes {
url
}
}
}
xToOneRelation {
data {
documentId
attributes {
field
}
}
}
xToManyRelation {
data {
documentId
attributes {
field
}
}
}
}
}
}
}
```
---
Language: GRAPHQL
File path: N/A
```graphql
mutation UpdateRestaurant {
updateRestaurant(
documentId: "some-doc-id",
data: { title: "My great restaurant" }
) {
data {
documentId
attributes {
title
image {
data {
documentId
attributes {
url
}
}
}
}
}
}
}
```
Language: GRAPHQL
File path: N/A
```graphql
{
# collection fields can be renamed to _connection to get a v4 compat response
restaurants_connection {
data {
id
attributes {
title
image {
data {
id
attributes {
url
}
}
}
# collection fields can be renamed to _connection to get a v4 compat response
images_connection {
data {
id
attributes {
url
}
}
}
xToOneRelation {
data {
id
attributes {
field
}
}
}
# collection fields can be renamed to _connection to get a v4 compat response
xToManyRelation_connection {
data {
id
attributes {
field
}
}
}
}
}
meta {
pagination {
page
pageSize
}
}
}
}
```
Language: GRAPHQL
File path: N/A
```graphql
{
# collection fields can be renamed to _connection to get a v4 compat response
restaurants_connection {
data {
id
title
image {
data {
id
url
}
}
# collection fields can be renamed to _connection to get a v4 compat response
images_connection {
data {
id
url
}
}
xToOneRelation {
data {
id
field
}
}
# collection fields can be renamed to _connection to get a v4 compat response
xToManyRelation_connection {
data {
id
field
}
}
}
meta {
pagination {
page
pageSize
}
}
}
}
```
Language: GRAPHQL
File path: N/A
```graphql
{
# Rename data to nodes & meta.pagination to pageInfo
restaurants_connection {
nodes {
id
title
image {
id
url
}
images_connection {
nodes {
id
url
}
}
xToOneRelation {
id
field
}
xToManyRelation_connection {
nodes {
id
field
}
}
}
pageInfo {
page
pageSize
}
}
}
```
---
Language: GRAPHQL
File path: N/A
```graphql
{
# remove _connection & data if you don't need pagination att all
restaurants {
id
title
image {
id
url
}
images {
id
url
}
xToOneRelation {
id
field
}
xToManyRelation {
id
field
}
}
}
```
# injectContentManagerComponent() removed
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/inject-content-manager-component
## Breaking change description
Description: A component is injected into the Content Manager as follows:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/inject-content-manager-component#breaking-change-description)
Language: TSX
File path: N/A
```tsx
app.injectContentManagerComponent('editView', 'right-links', {
name: 'PreviewButton',
Component: () => (
),
});
```
---
Language: TSX
File path: N/A
```tsx
app.getPlugin('content-manager').injectComponent('editView', 'right-links', {
name: 'PreviewButton',
Component: () => (
),
});
```
## Migration steps
Description: Change your plugin index.ts file from:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/inject-content-manager-component#migration-steps)
Language: JavaScript
File path: N/A
```js
app.injectContentManagerComponent()
```
Language: TSX
File path: N/A
```tsx
app.getPlugin('content-manager').injectComponent()
```
# Strapi 5 uses koa-body v6
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/koa-body-v6
## Breaking change description
Description: A user might create custom endpoints and handle files with the ctx object:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/koa-body-v6#breaking-change-description)
Language: JavaScript
File path: N/A
```js
const endpoint = (ctx) => {
ctx.request.files.fileName.path
ctx.request.files.fileName.name
ctx.request.files.fileName.type
}
```
Language: JavaScript
File path: N/A
```js
const endpoint = (ctx) => {
ctx.request.files.fileName.filepath
ctx.request.files.fileName.originalFilename
ctx.request.files.fileName.mimetype
}
```
# Some Mailgun provider legacy variables are not supported
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/mailgun-provider-variables
## Notes
Description: A Mailgun provider configuration in the plugins configuration file could look like the following example in Strapi 5:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/mailgun-provider-variables#notes)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ...
email: {
config: {
provider: 'mailgun',
providerOptions: {
key: env('MAILGUN_API_KEY'), // Required
domain: env('MAILGUN_DOMAIN'), // Required
url: env('MAILGUN_URL', 'https://api.mailgun.net'), //Optional. If domain region is Europe use 'https://api.eu.mailgun.net'
},
settings: {
defaultFrom: 'myemail@protonmail.com',
defaultReplyTo: 'myemail@protonmail.com',
},
},
},
// ...
});
```
# Model config path uses uid instead of dot notation
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/model-config-path-uses-uid
## Breaking change description
Description: Models are added to the configuration using .
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/model-config-path-uses-uid#breaking-change-description)
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/use-uid-for-config-namespace.code.ts
```ts
strapi.config.get('plugin.upload.somesetting');
if ( strapi.config.has('plugin.upload.somesetting') ) {
strapi.config.set('plugin.upload.somesetting', false);
}
```
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/use-uid-for-config-namespace.code.ts
```ts
strapi.config.get('plugin::upload.somesetting');
if ( strapi.config.has('plugin::upload.somesetting') ) {
strapi.config.set('plugin::upload.somesetting', false);
}
```
# Strapi 5 has a new, flattened response format for API calls
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/new-response-format
## Breaking change description
Description: The Content API returns all the attributes of requested content wrapped inside an attributes parameter:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/new-response-format#breaking-change-description)
Language: JSON
File path: N/A
```json
{
"data": {
// system fields
"id": 14,
"attributes": {
// user fields
"title": "Article A"
"relation": {
"data": {
"id": "clkgylw7d000108lc4rw1bb6s"
"name": "Category A"
}
}
}
}
"meta": {
"pagination": {
"page": 1,
"pageSize": 10
}
}
}
```
Language: JSON
File path: N/A
```json
{
"data": {
// system fields
"documentId": "clkgylmcc000008lcdd868feh",
"locale": "en",
// user fields
"title": "Article A"
"relation": {
// system fields
"documentId": "clkgylw7d000108lc4rw1bb6s"
// user fields
"name": "Category A"
}
}
"meta": {
"pagination": {
"page": 1,
"pageSize": 10
}
}
}
```
## Notes
Description: Examples
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/new-response-format#notes)
Language: Bash
File path: curl
```bash
curl \
-H 'Authorization: Bearer ' \
-H 'Strapi-Response-Format: v4' \
'https://api.example.com/api/articles?populate=category'
```
Language: JavaScript
File path: fetch
```js
await fetch('https://api.example.com/api/articles', {
headers: {
Authorization: `Bearer ${token}`,
'Strapi-Response-Format': 'v4',
},
});
```
Language: JavaScript
File path: Axios
```js
const client = axios.create({
baseURL: 'https://api.example.com/api',
headers: {
Authorization: `Bearer ${token}`,
'Strapi-Response-Format': 'v4',
},
});
const articles = await client.get('/articles', { params: { populate: '*'} });
```
# No findPage() in Document Service API
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/no-find-page-in-document-service
## Breaking change description
Description: In Strapi v4 you could use the findPage() method from the Entity Service API, for instance as follows:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/no-find-page-in-document-service#breaking-change-description)
Language: JSX
File path: N/A
```jsx
strapi.entityService.findPage('api::article.article', {
start: 10,
limit: 15,
});
```
Language: JSX
File path: N/A
```jsx
strapi.documents("api::article.article").findMany({
limit: 10,
start: 0,
});
```
# The ContentManagerAppState redux is modified
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/redux-content-manager-app-state
## Breaking change description
Description: The payload nests attributes inside the data object.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/redux-content-manager-app-state#breaking-change-description)
Language: JSON
File path: N/A
```json
data: {
authorizedCollectionTypeLinks: ContentManagerAppState['collectionTypeLinks'];
authorizedSingleTypeLinks: ContentManagerAppState['singleTypeLinks'];
components: ContentManagerAppState['components'];
contentTypeSchemas: ContentManagerAppState['models'];
fieldSizes: ContentManagerAppState['fieldSizes'];
};
```
Language: JSON
File path: N/A
```json
{
authorizedCollectionTypeLinks: ContentManagerAppState['collectionTypeLinks'];
authorizedSingleTypeLinks: ContentManagerAppState['singleTypeLinks'];
components: ContentManagerAppState['components'];
contentTypeSchemas: ContentManagerAppState['models'];
fieldSizes: ContentManagerAppState['fieldSizes'];
}
```
# Some env-only configuration options are handled by the server configuration
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/removed-support-for-some-env-options
## Migration steps
Description: If you previously disabled one of the listed environment variables in your environment, update the /config/server.js by adding the appropriate values:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/removed-support-for-some-env-options#migration-steps)
Language: JavaScript
File path: /config/server.js
```js
module.exports = ({ env }) => ({
// … other configuration options
transfer: {
remote: {
enabled: false, // disable remote data transfers instead of STRAPI_DISABLE_REMOTE_DATA_TRANSFER
},
},
logger: {
updates: {
enabled: false, // disable update notification logging instead of STRAPI_DISABLE_UPDATE_NOTIFICATION
},
startup: {
enabled: false, // disable startup message instead of STRAPI_HIDE_STARTUP_MESSAGE
},
},
});
```
---
Language: TypeScript
File path: /config/server.ts
```ts
export default ({ env }) => ({
// … other configuration options
transfer: {
remote: {
enabled: false, // disable remote data transfers instead of STRAPI_DISABLE_REMOTE_DATA_TRANSFER
},
},
logger: {
updates: {
enabled: false, // disable update notification logging instead of STRAPI_DISABLE_UPDATE_NOTIFICATION
},
startup: {
enabled: false, // disable startup message instead of STRAPI_HIDE_STARTUP_MESSAGE
},
},
});
```
# Sorting by id is no longer possible to sort by chronological order
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/sort-by-id
## Breaking change description
Description: In Strapi v4, using the Entity Service API, you could do the following to sort entries by chronological order:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/sort-by-id#breaking-change-description)
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/entity-service-document-service.code.ts
```ts
strapi.entityService.findMany('api::article.article', {
sort: 'id',
});
```
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/entity-service-document-service.code.ts
```ts
strapi.documentService.findMany('api::article.article', {
sort: 'createdAt',
});
```
# Strapi is a subclass of Container
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/strapi-container
## Breaking change description
Description: Container methods are accessed like follows:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/strapi-container#breaking-change-description)
Language: Bash
File path: N/A
```bash
strapi.container.register(....)
strapi.container.get(...)
```
---
Language: Bash
File path: N/A
```bash
strapi.add(....)
strapi.get(...)
```
# Strapi factories import have been updated
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/strapi-imports
## Breaking change description
Description: using the application init function:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/strapi-imports#breaking-change-description)
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/strapi-public-interface.code.ts
```ts
import strapi from '@strapi/strapi';
// or
const strapi = require('@strapi/strapi');
strapi();
```
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/strapi-public-interface.code.ts
```ts
import strapiDefault from '@strapi/strapi';
// or
import { factories } from '@strapi/strapi';
// or
const { factories } = require('@strapi/strapi');
// or
const { createCoreService } = require('@strapi/strapi').factories;
// or
const strapi = require('@strapi/strapi');
strapiDefault.factories.createCoreService(); // this is not possible anymore in v5
strapi.factories.createCoreService();
factories.createCoreService();
createCoreService();
```
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/strapi-public-interface.code.ts
```ts
import { createStrapi } from '@strapi/strapi';
const { createStrapi } = require('@strapi/strapi');
const strapi = require('@strapi/strapi');
strapi.createStrapi();
```
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/strapi-public-interface.code.ts
```ts
// Using the factories
import { factories } from '@strapi/strapi';
// or
const { factories } = require('@strapi/strapi');
// or
const { createCoreService } = require('@strapi/strapi').factories;
// or
const strapi = require('@strapi/strapi');
strapi.factories.createCoreService();
factories.createCoreService();
createCoreService();
// The recommended way is
const { factories } = require('@strapi/strapi');
import { factories } from '@strapi/strapi';
factories.createCoreService();
```
# strapi-utils is refactored
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/strapi-utils-refactored
## Additional Notes
Description: convertQueryParams is replaced:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/strapi-utils-refactored#additional-notes)
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/utils-public-interface.code.ts
```ts
// Strapi v4
import { convertQueryParams } from '@strapi/utils';
convertQueryParams.convertSortQueryParams(...); // now private function to simplify the api
convertQueryParams.convertStartQueryParams(...); // now private function to simplify the api
convertQueryParams.convertLimitQueryParams(...); // now private function to simplify the api
convertQueryParams.convertPopulateQueryParams(...); // now private function to simplify the api
convertQueryParams.convertFiltersQueryParams(...); // now private function to simplify the api
convertQueryParams.convertFieldsQueryParams(...); // now private function to simplify the api
convertQueryParams.convertPublicationStateParams(...); // now private function to simplify the api
convertQueryParams.transformParamsToQuery(...); // becomes the example below
// Strapi 5
// Those utils required the strapi app context, so we decided to expose a strapi service for it
strapi.get('query-params').transform();
```
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/utils-public-interface.code.ts
```ts
// Strapi v4
import { validate, sanitize } from '@strapi/utils';
validate.contentAPI.xxx();
sanitize.contentAPI.xxx();
// Strapi 5
// Those methods require the strapi app context
strapi.contentAPI.sanitize.xxx();
strapi.contentAPI.validate.xxx();
```
# documentId should be used instead of id in API calls
Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/use-document-id
## Breaking change description
Description: Entries were identified by their id:
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/breaking-changes/use-document-id#breaking-change-description)
Language: JSON
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/entity-service-document-service.code.ts
```json
{
"data": {
// system fields
"id": 14,
"attributes": {
// user fields
"title": "Article A"
"relation": {
"data": {
"id": "clkgylw7d000108lc4rw1bb6s"
"name": "Category A"
}
}
}
}
"meta": {
// …
}
}
```
Language: JSON
File path: /github.com/strapi/strapi/blob/develop/packages/utils/upgrade/resources/codemods/5.0.0/entity-service-document-service.code.ts
```json
{
"data": {
// system fields
"documentId": "clkgylmcc000008lcdd868feh",
"locale": "en",
// user fields
"title": "Article A"
"relation": {
// system fields
"documentId": "clkgylw7d000108lc4rw1bb6s"
// user fields
"name": "Category A"
}
}
"meta": {
// …
}
}
```
# Step-by-step guide to upgrade to Strapi 5
Source: https://docs.strapi.io/cms/migration/v4-to-v5/step-by-step
## Step 2: Run automated migrations
Description: Run the upgrade tool.
(Source: https://docs.strapi.io/cms/migration/v4-to-v5/step-by-step#step-2-run-automated-migrations)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade major
```
# Admin fetch client
Source: https://docs.strapi.io/cms/plugins-development/admin-fetch-client
## Inside a React component
Description: useFetchClient is a React hook that automatically provides an AbortSignal tied to the component lifecycle, so requests are cancelled when the component unmounts:
(Source: https://docs.strapi.io/cms/plugins-development/admin-fetch-client#inside-a-react-component)
Language: JavaScript
File path: my-plugin/admin/src/components/MyComponent.js
```js
import { useFetchClient } from '@strapi/strapi/admin';
const MyComponent = () => {
const { get } = useFetchClient();
const fetchData = async () => {
const { data } = await get('/my-plugin/my-endpoint');
// data contains the parsed JSON response
};
};
```
---
Language: TypeScript
File path: my-plugin/admin/src/components/MyComponent.tsx
```ts
import { useFetchClient } from '@strapi/strapi/admin';
const MyComponent = () => {
const { get } = useFetchClient();
const fetchData = async () => {
const { data } = await get('/my-plugin/my-endpoint');
// data contains the parsed JSON response
};
};
```
## Outside a React component
Description: getFetchClient works in any JavaScript context.
(Source: https://docs.strapi.io/cms/plugins-development/admin-fetch-client#outside-a-react-component)
Language: JavaScript
File path: my-plugin/admin/src/utils/api.js
```js
import { getFetchClient } from '@strapi/strapi/admin';
const { get, del } = getFetchClient();
export const fetchItems = async () => {
const { data } = await get('/my-plugin/items');
return data;
};
export const deleteItem = async (id) => {
await del(`/my-plugin/items/${id}`);
};
```
---
Language: TypeScript
File path: my-plugin/admin/src/utils/api.ts
```ts
import { getFetchClient } from '@strapi/strapi/admin';
const { get, del } = getFetchClient();
export const fetchItems = async () => {
const { data } = await get('/my-plugin/items');
return data;
};
export const deleteItem = async (id: string) => {
await del(`/my-plugin/items/${id}`);
};
```
## Sending data with post and put
Description: The post and put methods accept a payload as their second argument:
(Source: https://docs.strapi.io/cms/plugins-development/admin-fetch-client#sending-data)
Language: JavaScript
File path: my-plugin/admin/src/utils/api.js
```js
import { getFetchClient } from '@strapi/strapi/admin';
const { post, put } = getFetchClient();
// Create a new item
export const createItem = async (payload) => {
const { data } = await post('/my-plugin/items', payload);
return data;
};
// Update an existing item
export const updateItem = async (id, payload) => {
const { data } = await put(`/my-plugin/items/${id}`, payload);
return data;
};
```
---
Language: TypeScript
File path: my-plugin/admin/src/utils/api.ts
```ts
import { getFetchClient } from '@strapi/strapi/admin';
const { post, put } = getFetchClient();
// Create a new item
export const createItem = async (payload: Record) => {
const { data } = await post('/my-plugin/items', payload);
return data;
};
// Update an existing item
export const updateItem = async (id: string, payload: Record) => {
const { data } = await put(`/my-plugin/items/${id}`, payload);
return data;
};
```
## Query parameters
Description: Pass params to serialize query strings automatically:
(Source: https://docs.strapi.io/cms/plugins-development/admin-fetch-client#params)
Language: JavaScript
File path: my-plugin/admin/src/components/MyComponent.js
```js
const { data } = await get('/content-manager/collection-types/api::article.article', {
params: {
page: 1,
pageSize: 10,
sort: 'title:asc',
},
});
```
## Response types
Description: Non-JSON responses include status and headers in the return object:
(Source: https://docs.strapi.io/cms/plugins-development/admin-fetch-client#response-types)
Language: JavaScript
File path: my-plugin/admin/src/components/DownloadButton.js
```js
import { useFetchClient } from '@strapi/strapi/admin';
const DownloadButton = () => {
const { get } = useFetchClient();
const downloadFile = async (url) => {
const { data: blob, status, headers } = await get(url, { responseType: 'blob' });
// Process the blob, for example to trigger a file download
};
};
```
---
Language: TypeScript
File path: my-plugin/admin/src/components/DownloadButton.tsx
```ts
import { useFetchClient } from '@strapi/strapi/admin';
const DownloadButton = () => {
const { get } = useFetchClient();
const downloadFile = async (url: string) => {
const { data: blob, status, headers } = await get(url, { responseType: 'blob' });
// Process the blob, for example to trigger a file download
};
};
```
## Handling errors
Description: The fetch client throws a FetchError when a request fails.
(Source: https://docs.strapi.io/cms/plugins-development/admin-fetch-client#error-handling)
Language: JavaScript
File path: my-plugin/admin/src/components/MyComponent.js
```js
import { useFetchClient, isFetchError } from '@strapi/strapi/admin';
const MyComponent = () => {
const { get } = useFetchClient();
const fetchData = async () => {
try {
const { data } = await get('/my-plugin/my-endpoint');
// handle success
} catch (error) {
if (isFetchError(error)) {
// error.status contains the HTTP status code
console.error('Request failed:', error.status, error.message);
} else {
throw error; // re-throw non-fetch errors
}
}
};
};
```
---
Language: TypeScript
File path: my-plugin/admin/src/components/MyComponent.tsx
```ts
import { useFetchClient, isFetchError } from '@strapi/strapi/admin';
const MyComponent = () => {
const { get } = useFetchClient();
const fetchData = async () => {
try {
const { data } = await get('/my-plugin/my-endpoint');
// handle success
} catch (error) {
if (isFetchError(error)) {
// error.status contains the HTTP status code
console.error('Request failed:', error.status, error.message);
} else {
throw error; // re-throw non-fetch errors
}
}
};
};
```
# Admin hooks
Source: https://docs.strapi.io/cms/plugins-development/admin-hooks
## Creating hooks
Description: Create hook extension points with createHook() during the register lifecycle.
(Source: https://docs.strapi.io/cms/plugins-development/admin-hooks#creating-hooks)
Language: JavaScript
File path: my-plugin/admin/src/index.js
```js
export default {
register(app) {
app.createHook('My-PLUGIN/MY_HOOK');
},
};
```
---
Language: TypeScript
File path: my-plugin/admin/src/index.ts
```ts
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
register(app: StrapiApp) {
app.createHook('My-PLUGIN/MY_HOOK');
},
};
```
## Subscribing to hooks
Description: Subscribe to hooks with registerHook() during the bootstrap lifecycle, once all plugins are loaded.
(Source: https://docs.strapi.io/cms/plugins-development/admin-hooks#subscribing-to-hooks)
Language: JavaScript
File path: my-other-plugin/admin/src/index.js
```js
export default {
bootstrap(app) {
app.registerHook('My-PLUGIN/MY_HOOK', (...args) => {
console.log(args);
// important: return the mutated data
return args;
});
},
};
```
---
Language: TypeScript
File path: my-other-plugin/admin/src/index.ts
```ts
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
app.registerHook('My-PLUGIN/MY_HOOK', (...args: unknown[]) => {
console.log(args);
// important: return the mutated data
return args;
});
},
};
```
Language: JavaScript
File path: my-other-plugin/admin/src/index.js
```js
export default {
bootstrap(app) {
app.registerHook('My-PLUGIN/MY_HOOK', async (data) => {
const enrichedData = await fetchExternalData(data);
// always return data for waterfall hooks
return enrichedData;
});
},
};
```
---
Language: TypeScript
File path: my-other-plugin/admin/src/index.ts
```ts
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
app.registerHook('My-PLUGIN/MY_HOOK', async (data: unknown) => {
const enrichedData = await fetchExternalData(data);
// always return data for waterfall hooks
return enrichedData;
});
},
};
```
## INJECT-COLUMN-IN-TABLE
Description: The Admin/CM/pages/ListView/inject-column-in-table hook can add or mutate columns in the List View of the Content Manager:
(Source: https://docs.strapi.io/cms/plugins-development/admin-hooks#inject-column-in-table)
Language: JavaScript
File path: my-plugin/admin/src/index.js
```js
export default {
bootstrap(app) {
app.registerHook(
'Admin/CM/pages/ListView/inject-column-in-table',
({ displayedHeaders, layout }) => {
return {
displayedHeaders: [
...displayedHeaders,
{
attribute: { type: 'custom' },
label: 'External id',
name: 'externalId',
searchable: false,
sortable: false,
cellFormatter: (document) => document.externalId,
},
],
layout,
};
}
);
},
};
```
---
Language: TypeScript
File path: my-plugin/admin/src/index.ts
```ts
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
app.registerHook(
'Admin/CM/pages/ListView/inject-column-in-table',
({ displayedHeaders, layout }) => {
return {
displayedHeaders: [
...displayedHeaders,
{
attribute: { type: 'custom' },
label: 'External id',
name: 'externalId',
searchable: false,
sortable: false,
cellFormatter: (document) => document.externalId,
},
],
layout,
};
}
);
},
};
```
Language: TSX
File path: N/A
```tsx
runHookWaterfall(INJECT_COLUMN_IN_TABLE, {
displayedHeaders: ListFieldLayout[],
layout: ListFieldLayout,
});
```
Language: TypeScript
File path: N/A
```ts
interface ListFieldLayout {
/**
* The attribute data from the content-type's schema for the field
*/
attribute: Attribute.Any | { type: 'custom' };
/**
* Typically used by plugins to render a custom cell
*/
cellFormatter?: (
data: Document,
header: Omit,
{ collectionType, model }: { collectionType: string; model: string }
) => React.ReactNode;
label: string | MessageDescriptor;
/**
* the name of the attribute we use to display the actual name e.g. relations
* are just ids, so we use the mainField to display something meaninginful by
* looking at the target's schema
*/
mainField?: string;
name: string;
searchable?: boolean;
sortable?: boolean;
}
interface ListLayout {
layout: ListFieldLayout[];
components?: never;
metadatas: {
[K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['list'];
};
options: LayoutOptions;
settings: LayoutSettings;
}
type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object;
interface LayoutSettings extends Contracts.ContentTypes.Settings {
displayName?: string;
icon?: never;
}
```
## MUTATE-EDIT-VIEW-LAYOUT
Description: The following example subscribes to this hook to force all fields to full width:
(Source: https://docs.strapi.io/cms/plugins-development/admin-hooks#mutate-edit-view-layout)
Language: JavaScript
File path: my-plugin/admin/src/index.js
```js
export default {
bootstrap(app) {
app.registerHook(
'Admin/CM/pages/EditView/mutate-edit-view-layout',
({ layout, ...rest }) => {
// Force all fields to full width in the default edit layout
const updatedLayout = layout.map((rowGroup) =>
rowGroup.map((row) => row.map((field) => ({ ...field, size: 12 })))
);
return {
...rest,
layout: updatedLayout,
};
}
);
},
};
```
---
Language: TypeScript
File path: my-plugin/admin/src/index.ts
```ts
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
app.registerHook(
'Admin/CM/pages/EditView/mutate-edit-view-layout',
({ layout, ...rest }) => {
// Force all fields to full width in the default edit layout
const updatedLayout = layout.map((rowGroup) =>
rowGroup.map((row) => row.map((field) => ({ ...field, size: 12 })))
);
return {
...rest,
layout: updatedLayout,
};
}
);
},
};
```
Language: TypeScript
File path: N/A
```ts
interface EditLayout {
layout: Array>;
components: {
[uid: string]: {
layout: Array;
settings: Contracts.Components.ComponentConfiguration['settings'] & {
displayName?: string;
icon?: string;
};
};
};
metadatas: {
[K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['edit'];
};
options: LayoutOptions;
settings: LayoutSettings;
}
interface EditFieldSharedProps extends Omit {
hint?: string;
mainField?: string;
size: number;
unique?: boolean;
visible?: boolean;
}
/**
* Map over all the types in Attribute Types and use that to create a union of new types where the attribute type
* is under the property attribute and the type is under the property type.
*/
type EditFieldLayout = {
[K in Attribute.Kind]: EditFieldSharedProps & {
attribute: Extract;
type: K;
};
}[Attribute.Kind];
type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object;
interface LayoutSettings extends Contracts.ContentTypes.Settings {
displayName?: string;
icon?: never;
}
```
# Admin injection zones
Source: https://docs.strapi.io/cms/plugins-development/admin-injection-zones
## Injecting into Content Manager zones
Description: To inject a component into a Content Manager injection zone, use getPlugin('content-manager').injectComponent() in the bootstrap lifecycle:
(Source: https://docs.strapi.io/cms/plugins-development/admin-injection-zones#injecting-into-content-manager-zones)
Language: JavaScript
File path: admin/src/index.js
```js
import { MyCustomButton } from './components/MyCustomButton';
import { PreviewAction } from './components/PreviewAction';
import { PublishModalInfo } from './components/PublishModalInfo';
export default {
register(app) {
app.registerPlugin({
id: 'my-plugin',
name: 'My Plugin',
});
},
bootstrap(app) {
// Inject a button into the Edit view's right-links zone
// highlight-start
app
.getPlugin('content-manager')
.injectComponent('editView', 'right-links', {
name: 'my-plugin-custom-button',
Component: MyCustomButton,
});
// highlight-end
// Inject a component into the List view's actions zone
// highlight-start
app
.getPlugin('content-manager')
.injectComponent('listView', 'actions', {
name: 'my-plugin-list-action',
Component: () => ,
});
// highlight-end
// Inject additional information into the publish modal
// highlight-start
app
.getPlugin('content-manager')
.injectComponent('listView', 'publishModalAdditionalInfos', {
name: 'my-plugin-publish-modal-info',
Component: PublishModalInfo,
});
// highlight-end
// Inject a component into the Preview view's actions zone
// highlight-start
app
.getPlugin('content-manager')
.injectComponent('preview', 'actions', {
name: 'my-plugin-preview-action',
Component: PreviewAction,
});
// highlight-end
},
};
```
---
Language: TypeScript
File path: admin/src/index.ts
```ts
import type { StrapiApp } from '@strapi/admin/strapi-admin';
import { MyCustomButton } from './components/MyCustomButton';
import { PreviewAction } from './components/PreviewAction';
import { PublishModalInfo } from './components/PublishModalInfo';
export default {
register(app: StrapiApp) {
app.registerPlugin({
id: 'my-plugin',
name: 'My Plugin',
});
},
bootstrap(app: StrapiApp) {
// Inject a button into the Edit view's right-links zone
// highlight-start
app
.getPlugin('content-manager')
.injectComponent('editView', 'right-links', {
name: 'my-plugin-custom-button',
Component: MyCustomButton,
});
// highlight-end
// Inject a component into the List view's actions zone
// highlight-start
app
.getPlugin('content-manager')
.injectComponent('listView', 'actions', {
name: 'my-plugin-list-action',
Component: () => ,
});
// highlight-end
// Inject additional information into the publish modal
// highlight-start
app
.getPlugin('content-manager')
.injectComponent('listView', 'publishModalAdditionalInfos', {
name: 'my-plugin-publish-modal-info',
Component: PublishModalInfo,
});
// highlight-end
// Inject a component into the Preview view's actions zone
// highlight-start
app
.getPlugin('content-manager')
.injectComponent('preview', 'actions', {
name: 'my-plugin-preview-action',
Component: PreviewAction,
});
// highlight-end
},
};
```
## Custom injection zones
Description: Plugins can define their own injection zones to allow other plugins to extend their UI.
(Source: https://docs.strapi.io/cms/plugins-development/admin-injection-zones#custom-injection-zones)
Language: JavaScript
File path: admin/src/index.js
```js
export default {
register(app) {
app.registerPlugin({
id: 'dashboard',
name: 'Dashboard',
// highlight-start
injectionZones: {
homePage: {
top: [],
middle: [],
bottom: [],
},
sidebar: {
before: [],
after: [],
},
},
// highlight-end
});
},
};
```
---
Language: TypeScript
File path: admin/src/index.ts
```ts
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
register(app: StrapiApp) {
app.registerPlugin({
id: 'dashboard',
name: 'Dashboard',
// highlight-start
injectionZones: {
homePage: {
top: [],
middle: [],
bottom: [],
},
sidebar: {
before: [],
after: [],
},
},
// highlight-end
});
},
};
```
## Rendering injection zones in components
Description: In Strapi 5, the InjectionZone component from @strapi/helper-plugin is removed and has no direct replacement export.
(Source: https://docs.strapi.io/cms/plugins-development/admin-injection-zones#rendering-injection-zones-in-components)
Language: JavaScript
File path: admin/src/components/CustomInjectionZone.jsx
```js
import { useStrapiApp } from '@strapi/strapi/admin';
export const CustomInjectionZone = ({ area, ...props }) => {
const getPlugin = useStrapiApp('CustomInjectionZone', (state) => state.getPlugin);
const [pluginName, view, zone] = area.split('.');
const plugin = getPlugin(pluginName);
const components = plugin?.getInjectedComponents(view, zone);
if (!components?.length) {
return null;
}
return components.map(({ name, Component }) => );
};
```
---
Language: JavaScript
File path: admin/src/pages/Dashboard.jsx
```js
import { CustomInjectionZone } from '../components/CustomInjectionZone';
const Dashboard = () => {
return (
Dashboard
{/* Render components injected into the top zone */}
{/* Main dashboard content */}
{/* Render components injected into the bottom zone */}
}
}
```
Language: JSX
File path: N/A
```jsx
interface EditViewContext {
/**
* This will only be null if the content-type
* does not have draft & publish enabled.
*/
activeTab: 'draft' | 'published' | null;
/**
* Will be either 'single-types' | 'collection-types'
*/
collectionType: string;
/**
* Will be undefined if someone is creating an entry.
*/
document?: Document;
/**
* Will be undefined if someone is creating an entry.
*/
documentId?: string;
/**
* Will be undefined if someone is creating an entry.
*/
meta?: DocumentMetadata;
/**
* The current content-type's model.
*/
model: string;
}
```
## addEditViewSidePanel
Description: !addEditViewSidePanel
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#addeditviewsidepanel)
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/core/content-manager/admin/src/content-manager.ts
```ts
addEditViewSidePanel(panels: DescriptionReducer | PanelComponent[])
```
## PanelComponent
Description: A PanelComponent receives the properties listed in EditViewContext and returns an object with the following shape:
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#panelcomponent)
Language: TSX
File path: /github.com/strapi/strapi/blob/develop/packages/core/content-manager/admin/src/content-manager.ts
```tsx
type PanelComponent = (props: PanelComponentProps) => {
title: string;
content: React.ReactNode;
};
```
## addDocumentAction
Description: !Table-row in the List View
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#adddocumentaction)
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/core/content-manager/admin/src/content-manager.ts
```ts
addDocumentAction(actions: DescriptionReducer | DocumentActionComponent[])
```
## DocumentActionDescription
Description: The interface and properties of the API look like the following:
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#documentactiondescription)
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/core/content-manager/admin/src/content-manager.ts
```ts
interface DocumentActionDescription {
label: string;
onClick?: (event: React.SyntheticEvent) => Promise | boolean | void;
icon?: React.ReactNode;
/**
* @default false
*/
disabled?: boolean;
/**
* @default 'panel'
* @description Where the action should be rendered.
*/
position?: DocumentActionPosition | DocumentActionPosition[];
dialog?: DialogOptions | NotificationOptions | ModalOptions;
/**
* @default 'secondary'
*/
variant?: ButtonProps['variant'];
loading?: ButtonProps['loading'];
}
type DocumentActionPosition = 'panel' | 'header' | 'table-row' | 'preview' | 'relation-modal';
interface DialogOptions {
type: 'dialog';
title: string;
content?: React.ReactNode;
variant?: ButtonProps['variant'];
onConfirm?: () => void | Promise;
onCancel?: () => void | Promise;
}
interface NotificationOptions {
type: 'notification';
title: string;
link?: {
label: string;
url: string;
target?: string;
};
content?: string;
onClose?: () => void;
status?: NotificationConfig['type'];
timeout?: number;
}
interface ModalOptions {
type: 'modal';
title: string;
content: React.ComponentType<{
onClose: () => void;
}> | React.ReactNode;
footer?: React.ComponentType<{
onClose: () => void;
}> | React.ReactNode;
onClose?: () => void;
}
```
## addDocumentHeaderAction
Description: !addEditViewSidePanel
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#adddocumentheaderaction)
Language: TypeScript
File path: N/A
```ts
addDocumentHeaderAction(actions: DescriptionReducer | HeaderActionComponent[])
```
## HeaderActionDescription
Description: The interface and properties of the API look like the following:
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#headeractiondescription)
Language: TypeScript
File path: N/A
```ts
interface HeaderActionDescription {
disabled?: boolean;
label: string;
icon?: React.ReactNode;
type?: 'icon' | 'default';
onClick?: (event: React.SyntheticEvent) => Promise | boolean | void;
dialog?: DialogOptions;
options?: Array<{
disabled?: boolean;
label: string;
startIcon?: React.ReactNode;
textValue?: string;
value: string;
}>;
onSelect?: (value: string) => void;
value?: string;
}
interface DialogOptions {
type: 'dialog';
title: string;
content?: React.ReactNode;
footer?: React.ReactNode;
}
```
## addBulkAction
Description: !addEditViewSidePanel
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#addbulkaction)
Language: TypeScript
File path: N/A
```ts
addBulkAction(actions: DescriptionReducer | BulkActionComponent[])
```
## BulkActionDescription
Description: The interface and properties of the API look like the following:
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#bulkactiondescription)
Language: JSX
File path: N/A
```jsx
interface BulkActionDescription {
dialog?: DialogOptions | NotificationOptions | ModalOptions;
disabled?: boolean;
icon?: React.ReactNode;
label: string;
onClick?: (event: React.SyntheticEvent) => void;
/**
* @default 'default'
*/
type?: 'icon' | 'default';
/**
* @default 'secondary'
*/
variant?: ButtonProps['variant'];
}
```
# Plugin creation & setup
Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin
## Creating the plugin
Description: To create your plugin, ensure you are in the parent directory of where you want it to be created and run the following command:
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#creating-the-plugin)
Language: Bash
File path: N/A
```bash
yarn dlx @strapi/sdk-plugin init my-strapi-plugin
```
---
Language: Bash
File path: N/A
```bash
npx @strapi/sdk-plugin init my-strapi-plugin
```
## Linking the plugin to your project
Description: In a new terminal window, run the following commands:
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#linking-the-plugin-to-your-project)
Language: Bash
File path: N/A
```bash
cd /path/to/strapi/project
yarn dlx yalc add --link my-strapi-plugin && yarn install
```
---
Language: Bash
File path: N/A
```bash
cd /path/to/strapi/project
npx yalc add --link my-strapi-plugin && npm install
```
## Building the plugin for publishing
Description: When you are ready to publish your plugin, you will need to build it.
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#building-the-plugin-for-publishing)
Language: Bash
File path: N/A
```bash
yarn build && yarn verify
```
---
Language: Bash
File path: N/A
```bash
npm run build && npm run verify
```
## Working with the Plugin SDK in a monorepo environment
Description: However, if you are writing admin code, you might add an alias that targets the source code of your plugin to make it easier to work with within the context of the admin panel:
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#monorepo)
Language: TypeScript
File path: .config.ts
```ts
import path from 'node:path';
export default (config, webpack) => {
config.resolve.alias = {
...config.resolve.alias,
'my-strapi-plugin': path.resolve(
__dirname,
// We've assumed the plugin is local.
'../plugins/my-strapi-plugin/admin/src'
),
};
return config;
};
```
## Configuration with a local plugin
Description: When developing your plugin locally (using @strapi/sdk-plugin), your plugins configuration file looks like in the following example:
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#configuration-with-a-local-plugin)
Language: JavaScript
File path: /config/plugins.js|ts
```js
myplugin: {
enabled: true,
resolve: `./src/plugins/local-plugin`,
},
```
Language: TypeScript
File path: /src/index.ts
```ts
Error: 'X must be used within StrapiApp';
```
Language: TypeScript
File path: /src/index.ts
```ts
import { unstable_useContentManagerContext as useContentManagerContext } from '@strapi/strapi/admin';
```
## Server entry point
Description: The server entry point file initializes your plugin's server-side functionalities.
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#server-entry-point)
Language: TypeScript
File path: /src/index.ts
```ts
module.exports = () => {
return {
register,
config,
controllers,
contentTypes,
routes,
};
};
```
## Admin entry point
Description: The admin entry point file sets up your plugin within the Strapi admin panel.
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#admin-entry-point)
Language: TypeScript
File path: /src/index.ts
```ts
export default {
register(app) {},
bootstrap() {},
registerTrads({ locales }) {},
};
```
# How to create admin permissions from plugins
Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins
## Register the permissions server side
Description: Each individual permission has to registered in the bootstrap function of your plugin, as follows:
(Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins#register-the-permissions-server-side)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/bootstrap.js
```js
'use strict';
const bootstrap = ({ strapi }) => {
// Register permission actions.
const actions = [
{
section: 'plugins',
displayName: 'Access the overview page',
uid: 'overview.access',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Access the content manager sidebar',
uid: 'sidebar.access',
pluginName: 'my-plugin',
},
];
strapi.admin.services.permission.actionProvider.registerMany(actions);
};
module.exports = bootstrap;
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/bootstrap.ts
```ts
import type { Core } from '@strapi/strapi';
const bootstrap = ({ strapi }: { strapi: Core.Strapi }) => {
// Register permission actions.
const actions = [
{
section: 'plugins',
displayName: 'Access the overview page',
uid: 'overview.access',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Access the content manager sidebar',
uid: 'sidebar.access',
pluginName: 'my-plugin',
},
];
strapi.admin.services.permission.actionProvider.registerMany(actions);
};
export default bootstrap;
```
## Implement permissions on the admin panel side
Description: Before we can implement our permissions on the admin panel side we have to define them in a reusable configuration file.
(Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins#implement-permissions-on-the-admin-panel-side)
Language: JavaScript
File path: /src/plugins/my-plugin/admin/src/permissions.js|ts
```js
const pluginPermissions = {
'accessOverview': [{ action: 'plugin::my-plugin.overview.access', subject: null }],
'accessSidebar': [{ action: 'plugin::my-plugin.sidebar.access', subject: null }],
};
export default pluginPermissions;
```
## Page permissions
Description: Once you've created the configuration file you are ready to implement your permissions.
(Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins#page-permissions)
Language: JavaScript
File path: /src/plugins/my-plugin/admin/src/pages/HomePage.jsx|tsx
```js
import { Main } from '@strapi/design-system';
import { Page } from '@strapi/strapi/admin';
import { useIntl } from 'react-intl';
import pluginPermissions from '../permissions';
import { getTranslation } from '../utils/getTranslation';
const HomePage = () => {
const { formatMessage } = useIntl();
return (
Welcome to {formatMessage({ id: getTranslation('plugin.name') })}
);
};
export { HomePage };
```
## Menu link permissions
Description: The previous example makes sure that the permissions of a user that visits your page directly will be validated.
(Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins#menu-link-permissions)
Language: JavaScript
File path: /src/plugins/my-plugin/admin/src/index.js|ts
```js
import { getTranslation } from './utils/getTranslation';
import { PLUGIN_ID } from './pluginId';
import { Initializer } from './components/Initializer';
import { PluginIcon } from './components/PluginIcon';
import pluginPermissions from './permissions';
export default {
register(app) {
app.addMenuLink({
to: `plugins/${PLUGIN_ID}`,
icon: PluginIcon,
intlLabel: {
id: `${PLUGIN_ID}.plugin.name`,
defaultMessage: PLUGIN_ID,
},
Component: async () => {
const { App } = await import('./pages/App');
return App;
},
permissions: [
pluginPermissions.accessOverview[0],
],
});
app.registerPlugin({
id: PLUGIN_ID,
initializer: Initializer,
isReady: false,
name: PLUGIN_ID,
});
},
};
```
## Custom permissions with the useRBAC hook
Description: To get even more control over the permission of the admin user you can use the useRBAC hook.
(Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins#custom-permissions-with-the-userbac-hook)
Language: JavaScript
File path: /src/plugins/my-plugin/admin/src/components/Sidebar.jsx|tsx
```js
import React from 'react';
import { useRBAC } from '@strapi/strapi/admin';
import pluginPermissions from '../../permissions';
const Sidebar = () => {
const {
allowedActions: { canAccessSidebar },
} = useRBAC(pluginPermissions);
if (!canAccessSidebar) {
return null;
}
return (
Sidebar component
);
};
export default Sidebar;
```
# How to create components for Strapi plugins
Source: https://docs.strapi.io/cms/plugins-development/guides/create-components-for-plugins
## Reviewing the component structure
Description: Components in Strapi follow the following format in their definition:
(Source: https://docs.strapi.io/cms/plugins-development/guides/create-components-for-plugins#reviewing-the-component-structure)
Language: JSON
File path: /my-plugin/server/components/category/component-name.json
```json
{
"attributes": {
"myComponent": {
"type": "component",
"repeatable": true,
"component": "category.componentName"
}
}
}
```
## Component schema example
Description: A component schema defines the structure of a reusable data fragment.
(Source: https://docs.strapi.io/cms/plugins-development/guides/create-components-for-plugins#component-schema-example)
Language: JSON
File path: my-plugin/server/components/my-category/my-component.json
```json
{
"collectionName": "components_my_category_my_components",
"info": {
"displayName": "My Component",
"icon": "align-justify"
},
"attributes": {
"name": {
"type": "string",
"required": true
},
"description": {
"type": "text"
}
}
}
```
# How to pass data from server to admin panel with a Strapi plugin
Source: https://docs.strapi.io/cms/plugins-development/guides/pass-data-from-server-to-admin
## Create a custom admin route
Description: The following code will declare a custom admin route for the my-plugin plugin:
(Source: https://docs.strapi.io/cms/plugins-development/guides/pass-data-from-server-to-admin#create-a-custom-admin-route)
Language: JavaScript
File path: /my-plugin/server/routes/index.js
```js
module.exports = {
'pass-data': {
type: 'admin',
routes: [
{
method: 'GET',
path: '/pass-data',
handler: 'myPluginContentType.index',
config: {
policies: [],
auth: false,
},
},
]
}
// ...
};
```
Language: JavaScript
File path: /my-plugin/server/controllers/my-plugin-content-type.js
```js
'use strict';
module.exports = {
async index(ctx) {
ctx.body = 'You are in the my-plugin-content-type controller!';
}
}
```
## Get the data in the admin panel
Description: So for instance, if you create an /admin/src/api/foobar.js file and copy and paste the following code example:
(Source: https://docs.strapi.io/cms/plugins-development/guides/pass-data-from-server-to-admin#get-the-data-in-the-admin-panel)
Language: JavaScript
File path: /my-plugin/admin/src/api/foobar.js
```js
import { getFetchClient } from '@strapi/strapi/admin';
const { get } = getFetchClient();
const foobarRequests = {
getFoobar: async () => {
const { data } = await get('/my-plugin/pass-data');
return data;
},
};
export default foobarRequests;
```
Language: JavaScript
File path: /my-plugin/admin/src/components/MyComponent/index.js
```js
import foobarRequests from "../../api/foobar";
const [foobar, setFoobar] = useState([]);
// …
useEffect(() => {
foobarRequests.getFoobar().then(data => {
setFoobar(data);
});
}, [setFoobar]);
// …
```
# How to store and access data from a Strapi plugin
Source: https://docs.strapi.io/cms/plugins-development/guides/store-and-access-data
## Create a content-type for your plugin
Description: To create a content-type with the CLI generator, run the following command in a terminal within the server/src/ directory of your plugin:
(Source: https://docs.strapi.io/cms/plugins-development/guides/store-and-access-data#create-a-content-type-for-your-plugin)
Language: Bash
File path: N/A
```bash
yarn strapi generate content-type
```
---
Language: Bash
File path: N/A
```bash
npm run strapi generate content-type
```
Language: JSON
File path: /server/content-types/my-plugin-content-type/schema.json
```json
{
"kind": "collectionType",
"collectionName": "my_plugin_content_types",
"info": {
"singularName": "my-plugin-content-type",
"pluralName": "my-plugin-content-types",
"displayName": "My Plugin Content-Type"
},
"options": {
"draftAndPublish": false,
"comment": ""
},
"pluginOptions": {
"content-manager": {
"visible": true
},
"content-type-builder": {
"visible": true
}
},
"attributes": {
"name": {
"type": "string"
}
}
}
```
## Ensure plugin content-types are imported
Description: In the /server/index.js file, import the content-types:
(Source: https://docs.strapi.io/cms/plugins-development/guides/store-and-access-data#ensure-plugin-content-types-are-imported)
Language: JavaScript
File path: /server/index.js
```js
'use strict';
const register = require('./register');
const bootstrap = require('./bootstrap');
const destroy = require('./destroy');
const config = require('./config');
const contentTypes = require('./content-types');
const controllers = require('./controllers');
const routes = require('./routes');
const middlewares = require('./middlewares');
const policies = require('./policies');
const services = require('./services');
module.exports = {
register,
bootstrap,
destroy,
config,
controllers,
routes,
services,
contentTypes,
policies,
middlewares,
};
```
Language: JavaScript
File path: /server/content-types/index.js
```js
'use strict';
module.exports = {
// In the line below, replace my-plugin-content-type
// with the actual name and folder path of your content type
"my-plugin-content-type": require('./my-plugin-content-type'),
};
```
Language: JavaScript
File path: /server/content-types/my-plugin-content-type/index.js
```js
'use strict';
const schema = require('./schema');
module.exports = {
schema,
};
```
## Interact with data from the plugin
Description: Here is how to find all the entries for the my-plugin-content-type collection type created for a plugin called my-plugin:
(Source: https://docs.strapi.io/cms/plugins-development/guides/store-and-access-data#interact-with-data-from-the-plugin)
Language: JavaScript
File path: /server/content-types/index.js
```js
// Using the Document Service API
let data = await strapi.documents('plugin::my-plugin.my-plugin-content-type').findMany();
// Using the Query Engine API
let data = await strapi.db.query('plugin::my-plugin.my-plugin-content-type').findMany();
```
# Plugin SDK reference
Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk
## npx @strapi/sdk-plugin init
Description: Create a new plugin at a given path.
(Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk#npx-strapi-sdk-plugin-init)
Language: Bash
File path: N/A
```bash
npx @strapi/sdk-plugin init
```
## strapi-plugin build
Description: Bundle the Strapi plugin for publishing.
(Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk#strapi-plugin-build)
Language: Bash
File path: N/A
```bash
strapi-plugin build
```
## strapi-plugin watch:link
Description: For testing purposes, it is very convenient to link your plugin to an existing application to experiment with it in real condition.
(Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk#strapi-plugin-watch-link)
Language: Bash
File path: .config.ts
```bash
strapi-plugin watch:link
```
## strapi-plugin watch
Description: Watch the plugin source code for any change and rebuild it everytime.
(Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk#strapi-plugin-watch)
Language: Bash
File path: .config.ts
```bash
strapi-plugin watch
```
## strapi-plugin verify
Description: Verify the output of the plugin before publishing it.
(Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk#strapi-plugin-verify)
Language: Bash
File path: .config.ts
```bash
strapi-plugin verify
```
# Plugins extension
Source: https://docs.strapi.io/cms/plugins-development/plugins-extension
## Plugins extension
Description: Example of extensions folder structure
(Source: https://docs.strapi.io/cms/plugins-development/plugins-extension#plugins-extension)
Language: Bash
File path: N/A
```bash
/extensions
/some-plugin-to-extend
strapi-server.js|ts
/content-types
/some-content-type-to-extend
schema.json
/another-content-type-to-extend
schema.json
/another-plugin-to-extend
strapi-server.js|ts
```
## Within the extensions folder
Description: Example of backend extension
(Source: https://docs.strapi.io/cms/plugins-development/plugins-extension#within-the-extensions-folder)
Language: JavaScript
File path: ./src/extensions/some-plugin-to-extend/strapi-server.js|ts
```js
module.exports = (plugin) => {
plugin.controllers.controllerA.find = (ctx) => {};
plugin.policies[newPolicy] = (ctx) => {};
plugin.routes['content-api'].routes.push({
method: 'GET',
path: '/route-path',
handler: 'controller.action',
});
return plugin;
};
```
Language: JavaScript
File path: ./src/extensions/upload/strapi-server.js|ts
```js
module.exports = (plugin) => {
plugin.services['image-manipulation'].generateFileName = (file) => {
// Example: prefix a timestamp before the generated base name
return `${Date.now()}_${name}`;
};
return plugin;
};
```
## Within the register and bootstrap functions
Description: Example of extending a plugin's content-type within ./src/index.js|ts
(Source: https://docs.strapi.io/cms/plugins-development/plugins-extension#within-the-register-and-bootstrap-functions)
Language: JavaScript
File path: ./src/index.js|ts
```js
module.exports = {
register({ strapi }) {
const contentTypeName = strapi.contentType('plugin::my-plugin.content-type-name')
contentTypeName.attributes = {
// Spread previous defined attributes
...contentTypeName.attributes,
// Add new, or override attributes
'toto': {
type: 'string',
}
}
},
bootstrap({ strapi }) {},
};
```
# Server API for plugins
Source: https://docs.strapi.io/cms/plugins-development/server-api
## Entry file
Description: A minimal entry file looks like this:
(Source: https://docs.strapi.io/cms/plugins-development/server-api#entry-file)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/index.js
```js
'use strict';
const register = require('./register');
const bootstrap = require('./bootstrap');
const destroy = require('./destroy');
const config = require('./config');
const contentTypes = require('./content-types');
const routes = require('./routes');
const controllers = require('./controllers');
const services = require('./services');
const policies = require('./policies');
const middlewares = require('./middlewares');
module.exports = () => ({
register,
bootstrap,
destroy,
config,
contentTypes,
routes,
controllers,
services,
policies,
middlewares,
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/index.ts
```ts
import register from './register';
import bootstrap from './bootstrap';
import destroy from './destroy';
import config from './config';
import contentTypes from './content-types';
import routes from './routes';
import controllers from './controllers';
import services from './services';
import policies from './policies';
import middlewares from './middlewares';
export default () => ({
register,
bootstrap,
destroy,
config,
contentTypes,
routes,
controllers,
services,
policies,
middlewares,
});
```
# Server configuration
Source: https://docs.strapi.io/cms/plugins-development/server-configuration
## Configuration example
Description: :::note The { env } argument passed to the default function is the same env utility used across Strapi configuration files.
(Source: https://docs.strapi.io/cms/plugins-development/server-configuration#configuration-example)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/config/index.js
```js
'use strict';
module.exports = {
default: ({ env }) => ({
enabled: true,
maxItems: env.int('MY_PLUGIN_MAX_ITEMS', 10),
endpoint: env('MY_PLUGIN_ENDPOINT', 'https://api.example.com'),
}),
validator: (config) => {
if (typeof config.enabled !== 'boolean') {
throw new Error('"enabled" must be a boolean');
}
if (typeof config.maxItems !== 'number' || config.maxItems < 1) {
throw new Error('"maxItems" must be a positive number');
}
},
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/config/index.ts
```ts
export default {
default: ({ env }: { env: any }) => ({
enabled: true,
maxItems: env.int('MY_PLUGIN_MAX_ITEMS', 10),
endpoint: env('MY_PLUGIN_ENDPOINT', 'https://api.example.com'),
}),
validator: (config: { enabled: unknown; maxItems: unknown }) => {
if (typeof config.enabled !== 'boolean') {
throw new Error('"enabled" must be a boolean');
}
if (typeof config.maxItems !== 'number' || config.maxItems < 1) {
throw new Error('"maxItems" must be a positive number');
}
},
};
```
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = {
'my-plugin': {
enabled: true,
config: {
maxItems: 25,
endpoint: 'https://api.production.example.com',
},
},
};
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default {
'my-plugin': {
enabled: true,
config: {
maxItems: 25,
endpoint: 'https://api.production.example.com',
},
},
};
```
## Runtime access
Description: Once the plugin is loaded, its configuration is available anywhere the strapi object is accessible:
(Source: https://docs.strapi.io/cms/plugins-development/server-configuration#runtime-access)
Language: JavaScript
File path: /plugins.js
```js
// Read one key
const maxItems = strapi.plugin('my-plugin').config('maxItems');
```
---
Language: JavaScript
File path: /plugins.js
```js
// Read the entire plugin config object
const pluginConfig = strapi.config.get('plugin::my-plugin');
```
# Server content-types
Source: https://docs.strapi.io/cms/plugins-development/server-content-types
## Declaration
Description: The contentTypes export is an object where each key registers a content-type under the plugin namespace.
(Source: https://docs.strapi.io/cms/plugins-development/server-content-types#declaration)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/content-types/index.js
```js
'use strict';
const article = require('./article');
module.exports = {
// highlight-next-line
article: { schema: article }, // recommended: keep key aligned with info.singularName
};
```
---
Language: JSON
File path: /src/plugins/my-plugin/server/src/content-types/article/schema.json
```json
{
"kind": "collectionType",
"collectionName": "my_plugin_articles",
"info": {
// highlight-next-line
"singularName": "article",
"pluralName": "articles",
"displayName": "Article"
},
"options": {
"draftAndPublish": false
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"body": {
"type": "richtext"
}
}
}
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/content-types/index.ts
```ts
import article from './article';
export default {
// highlight-next-line
article: { schema: article }, // recommended: keep key aligned with info.singularName
};
```
---
Language: JSON
File path: /src/plugins/my-plugin/server/src/content-types/article/schema.json
```json
{
"kind": "collectionType",
"collectionName": "my_plugin_articles",
"info": {
// highlight-next-line
"singularName": "article",
"pluralName": "articles",
"displayName": "Article"
},
"options": {
"draftAndPublish": false
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"body": {
"type": "richtext"
}
}
}
```
## UIDs and naming conventions
Description: When a plugin content-type is registered, Strapi builds its runtime UID from the plugin namespace and the key used in the contentTypes export:
(Source: https://docs.strapi.io/cms/plugins-development/server-content-types#uids-and-naming-conventions)
Language: JavaScript
File path: N/A
```
plugin::.
```
Language: JavaScript
File path: N/A
```
plugin::.
```
## Querying with the Document Service API
Description: Use the Document Service API to query plugin content-types from controllers, services, or lifecycle hooks:
(Source: https://docs.strapi.io/cms/plugins-development/server-content-types#querying-with-the-document-service-api)
Language: JavaScript
File path: N/A
```js
module.exports = ({ strapi }) => ({
async findAll(params = {}) {
// highlight-next-line
return strapi.documents('plugin::my-plugin.article').findMany(params);
},
async create(data) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
});
```
---
Language: JavaScript
File path: N/A
```js
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async findAll(params: Record = {}) {
// highlight-next-line
return strapi.documents('plugin::my-plugin.article').findMany(params);
},
async create(data: Record) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
});
```
## Accessing the schema
Description: Use the content-type getter to retrieve the schema object, for example to pass it to the sanitization API:
(Source: https://docs.strapi.io/cms/plugins-development/server-content-types#accessing-the-schema)
Language: JavaScript
File path: N/A
```js
const schema = strapi.contentType('plugin::my-plugin.article');
const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);
```
---
Language: TypeScript
File path: N/A
```ts
const schema = strapi.contentType('plugin::my-plugin.article');
const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);
```
# Server controllers & services
Source: https://docs.strapi.io/cms/plugins-development/server-controllers-services
## Declaration
Description: The export key used in controllers/index.js|ts must match the handler name used in route definitions.
(Source: https://docs.strapi.io/cms/plugins-development/server-controllers-services#declaration)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/controllers/index.js
```js
'use strict';
const article = require('./article');
module.exports = {
article,
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/controllers/article.js
```js
'use strict';
module.exports = ({ strapi }) => ({
async find(ctx) {
const articles = await strapi
.plugin('my-plugin')
.service('article')
.findAll();
ctx.body = articles;
},
async findOne(ctx) {
const { documentId } = ctx.params;
const article = await strapi
.plugin('my-plugin')
.service('article')
.findOne(documentId);
if (!article) {
return ctx.notFound('Article not found');
}
ctx.body = article;
},
async create(ctx) {
const article = await strapi
.plugin('my-plugin')
.service('article')
.create(ctx.request.body);
ctx.status = 201;
ctx.body = article;
},
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/controllers/index.ts
```ts
import article from './article';
export default {
article,
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/controllers/article.ts
```ts
import type { Core } from '@strapi/strapi';
interface ArticleService {
findAll(): Promise;
findOne(id: string): Promise;
create(data: unknown): Promise;
}
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async find(ctx: any) {
// Limitation: in @strapi/types, plugin services are currently typed as unknown.
const articleService = strapi.plugin('my-plugin').service('article') as ArticleService;
ctx.body = await articleService.findAll();
},
async findOne(ctx: any) {
const { documentId } = ctx.params;
const article = await (strapi.plugin('my-plugin').service('article') as ArticleService).findOne(documentId);
if (!article) {
return ctx.notFound('Article not found');
}
ctx.body = article;
},
async create(ctx: any) {
const articleService = strapi.plugin('my-plugin').service('article') as ArticleService;
ctx.status = 201;
ctx.body = await articleService.create(ctx.request.body);
},
});
```
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/services/index.js
```js
'use strict';
const article = require('./article');
module.exports = {
article,
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/services/article.js
```js
'use strict';
module.exports = ({ strapi }) => ({
async findAll(params = {}) {
// highlight-next-line
return strapi.documents('plugin::my-plugin.article').findMany(params);
},
async findOne(documentId) {
return strapi.documents('plugin::my-plugin.article').findOne({
documentId,
});
},
async create(data) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
async update(documentId, data) {
return strapi.documents('plugin::my-plugin.article').update({
documentId,
data,
});
},
async delete(documentId) {
return strapi.documents('plugin::my-plugin.article').delete({
documentId,
});
},
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/services/index.ts
```ts
import article from './article';
export default {
article,
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/services/article.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async findAll(params: Record = {}) {
// highlight-next-line
return strapi.documents('plugin::my-plugin.article').findMany(params);
},
async findOne(documentId: string) {
return strapi.documents('plugin::my-plugin.article').findOne({
documentId,
});
},
async create(data: Record) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
async update(documentId: string, data: Record) {
return strapi.documents('plugin::my-plugin.article').update({
documentId,
data,
});
},
async delete(documentId: string) {
return strapi.documents('plugin::my-plugin.article').delete({
documentId,
});
},
});
```
## Sanitization
Description: Plugin controllers are plain factory functions and do not extend createCoreController like in the Strapi core (see backend customization for details).
(Source: https://docs.strapi.io/cms/plugins-development/server-controllers-services#sanitization)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/controllers/article.js
```js
module.exports = ({ strapi }) => ({
async find(ctx) {
// highlight-start
const schema = strapi.contentType('plugin::my-plugin.article');
const sanitizedQuery = await strapi.contentAPI.sanitize.query(
ctx.query, schema, { auth: ctx.state.auth }
);
// highlight-end
const articles = await strapi.plugin('my-plugin').service('article').findAll(sanitizedQuery);
// highlight-next-line
ctx.body = await strapi.contentAPI.sanitize.output(articles, schema, { auth: ctx.state.auth });
},
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/controllers/article.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async find(ctx: any) {
// highlight-start
const schema = strapi.contentType('plugin::my-plugin.article');
const sanitizedQuery = await strapi.contentAPI.sanitize.query(
ctx.query, schema, { auth: ctx.state.auth }
);
// highlight-end
const articles = await (strapi.plugin('my-plugin').service('article') as any).findAll(sanitizedQuery);
// highlight-next-line
ctx.body = await strapi.contentAPI.sanitize.output(articles, schema, { auth: ctx.state.auth });
},
});
```
## End-to-end example
Description: Code example from "End-to-end example"
(Source: https://docs.strapi.io/cms/plugins-development/server-controllers-services#end-to-end-example)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
module.exports = {
'content-api': {
type: 'content-api',
routes: [
{
method: 'GET',
path: '/articles',
// highlight-next-line
handler: 'article.find', // maps to controllers/article.js → find()
config: { auth: false },
},
{
method: 'POST',
path: '/articles',
// highlight-next-line
handler: 'article.create', // maps to controllers/article.js → create()
config: { auth: false },
},
],
},
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/controllers/article.js
```js
'use strict';
module.exports = ({ strapi }) => ({
// highlight-next-line
async find(ctx) {
ctx.body = await strapi.plugin('my-plugin').service('article').findAll();
// Note: sanitize query and output in production — see the Sanitization section above
},
// highlight-next-line
async create(ctx) {
const article = await strapi
.plugin('my-plugin')
.service('article')
.create(ctx.request.body);
ctx.status = 201;
ctx.body = article;
},
});
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/services/article.js
```js
'use strict';
module.exports = ({ strapi }) => ({
findAll() {
return strapi.documents('plugin::my-plugin.article').findMany();
},
create(data) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
export default {
'content-api': {
type: 'content-api' as const,
routes: [
{
method: 'GET' as const,
path: '/articles',
// highlight-next-line
handler: 'article.find', // maps to controllers/article.ts → find()
config: { auth: false },
},
{
method: 'POST' as const,
path: '/articles',
// highlight-next-line
handler: 'article.create', // maps to controllers/article.ts → create()
config: { auth: false },
},
],
},
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/controllers/article.ts
```ts
import type { Core } from '@strapi/strapi';
interface ArticleService {
findAll(): Promise;
create(data: unknown): Promise;
}
export default ({ strapi }: { strapi: Core.Strapi }) => ({
// highlight-next-line
async find(ctx: any) {
ctx.body = await (strapi.plugin('my-plugin').service('article') as ArticleService).findAll();
// Note: sanitize query and output in production — see the Sanitization section above
},
// highlight-next-line
async create(ctx: any) {
const article = await (strapi.plugin('my-plugin').service('article') as ArticleService)
.create(ctx.request.body);
ctx.status = 201;
ctx.body = article;
},
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/services/article.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => ({
findAll() {
// highlight-next-line
return strapi.documents('plugin::my-plugin.article').findMany();
},
create(data: Record) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
});
```
# Server getters & usage
Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage
## Getter styles
Description: Top-level getters chain through the plugin name:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#getter-styles)
Language: Bash
File path: N/A
```bash
strapi.plugin('plugin-name').service('service-name')
strapi.plugin('plugin-name').controller('controller-name')
```
Language: Bash
File path: N/A
```bash
strapi.service('plugin::plugin-name.service-name')
strapi.controller('plugin::plugin-name.controller-name')
```
## Calling a plugin service from a controller
Description: The most common pattern: a controller delegates to its own plugin's service:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#calling-a-plugin-service-from-a-controller)
Language: JavaScript
File path: /src/plugins/todo/server/src/controllers/task.js
```js
'use strict';
module.exports = ({ strapi }) => ({
async find(ctx) {
// highlight-next-line
const tasks = await strapi.plugin('todo').service('task').findAll(); // top-level getter: preferred inside your own plugin
ctx.body = tasks;
},
async create(ctx) {
const task = await strapi
.plugin('todo')
.service('task')
.create(ctx.request.body);
ctx.status = 201;
ctx.body = task;
},
});
```
---
Language: TypeScript
File path: /src/plugins/todo/server/src/controllers/task.ts
```ts
import type { Context } from 'koa';
import type { Core } from '@strapi/strapi';
type TaskService = {
findAll(): Promise;
create(data: unknown): Promise;
};
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async find(ctx: Context) {
// Narrow cast: plugin services require app-level type augmentation for full typing.
const tasks = await (strapi.plugin('todo').service('task') as TaskService).findAll();
ctx.body = tasks;
},
async create(ctx: Context) {
const task = await (strapi.plugin('todo').service('task') as TaskService).create(
(ctx.request as any).body
);
(ctx as any).status = 201;
ctx.body = task;
},
});
```
## Calling a plugin service from bootstrap
Description: Services called in bootstrap() have access to the full strapi instance, including other plugins' services:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#calling-a-plugin-service-from-bootstrap)
Language: JavaScript
File path: /src/plugins/todo/server/src/bootstrap.js
```js
'use strict';
module.exports = async ({ strapi }) => {
// Call own plugin service to seed initial data
const count = await strapi.plugin('todo').service('task').count();
if (count === 0) {
await strapi.plugin('todo').service('task').create({
title: 'Welcome task',
done: false,
});
}
};
```
---
Language: TypeScript
File path: /src/plugins/todo/server/src/bootstrap.ts
```ts
import type { Core } from '@strapi/strapi';
type TaskService = {
count(): Promise;
create(data: unknown): Promise;
};
export default async ({ strapi }: { strapi: Core.Strapi }) => {
// Narrow cast: plugin services are resolved dynamically unless your project augments Strapi service typings.
const taskService = strapi.plugin('todo').service('task') as TaskService;
// highlight-next-line
const count = await taskService.count();
if (count === 0) {
await taskService.create({ title: 'Welcome task', done: false });
}
};
```
## Calling across plugins or from application code
Description: From application-level controllers or services (outside the plugin), or when calling from another plugin, global getters using the full UID are often clearer:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#calling-across-plugins-or-from-application-code)
Language: JavaScript
File path: /src/api/project/controllers/project.js
```js
'use strict';
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::project.project', ({ strapi }) => ({
async create(ctx) {
const { data, meta } = await super.create(ctx);
// highlight-next-line
await strapi.service('plugin::todo.task').create({ // global getter: preferred in application code
title: `Review project: ${data.attributes.name}`,
done: false,
});
return { data, meta };
},
}));
```
---
Language: TypeScript
File path: /src/api/project/controllers/project.ts
```ts
import { factories } from '@strapi/strapi';
type TaskService = {
create(data: unknown): Promise;
};
export default factories.createCoreController(
'api::project.project',
({ strapi }) => ({
async create(ctx: any) {
const { data, meta } = await super.create(ctx);
// highlight-next-line
// Narrow cast: this generic documentation cannot infer your app-specific service signatures.
await (strapi.service('plugin::todo.task') as TaskService).create({
title: `Review project: ${data.attributes.name}`,
done: false,
});
return { data, meta };
},
})
);
```
## Reading plugin configuration at runtime
Description: From application-level controllers or services (outside the plugin), or when calling from another plugin, global getters using the full UID are often clearer:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#reading-plugin-configuration-at-runtime)
Language: JavaScript
File path: N/A
```js
// Read a single key
const maxItems = strapi.plugin('todo').config('maxItems');
```
---
Language: JavaScript
File path: N/A
```js
// Read the full config object
const todoConfig = strapi.config.get('plugin::todo');
```
---
Language: JavaScript
File path: N/A
```js
// Read a nested key
const endpoint = strapi.config.get('plugin::todo.endpoint');
```
## Accessing a content-type schema
Description: Use the content-type getter when you need the schema object, for example to pass it to the sanitization API:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#accessing-a-content-type-schema)
Language: JavaScript
File path: N/A
```js
// Access the content-type schema
const schema = strapi.contentType('plugin::todo.task');
const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);
```
---
Language: TypeScript
File path: N/A
```ts
// highlight-next-line
const schema = strapi.contentType('plugin::todo.task'); // access the content-type schema
const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);
```
# Server lifecycle
Source: https://docs.strapi.io/cms/plugins-development/server-lifecycle
## register()
Description: Use register() to: - Register the server side of custom fields - Register database migrations - Register server middlewares on the Strapi HTTP server (e.g.
(Source: https://docs.strapi.io/cms/plugins-development/server-lifecycle#register)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/register.js
```js
'use strict';
module.exports = ({ strapi }) => {
// Register a server-level middleware early in startup
strapi.server.use(async (ctx, next) => {
ctx.set('X-Plugin-Version', '1.0.0');
await next();
});
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/register.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => {
// Register a server-level middleware early in startup
strapi.server.use(async (ctx: any, next: () => Promise) => {
ctx.set('X-Plugin-Version', '1.0.0');
await next();
});
};
```
## bootstrap()
Description: Register admin RBAC actions using strapi.service('admin::permission').actionProvider.registerMany(...) - Register cron jobs - Subscribe to database lifecycle events - Call services from your plugin or other plugins - Set up cross-plugin integrations that require other plugins to be registered first
(Source: https://docs.strapi.io/cms/plugins-development/server-lifecycle#bootstrap)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/bootstrap.js
```js
'use strict';
module.exports = async ({ strapi }) => {
// Register admin RBAC actions for this plugin
await strapi.service('admin::permission').actionProvider.registerMany([
{
section: 'plugins',
displayName: 'Read',
uid: 'read',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Settings',
uid: 'settings',
pluginName: 'my-plugin',
},
]);
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/bootstrap.ts
```ts
import type { Core } from '@strapi/strapi';
export default async ({ strapi }: { strapi: Core.Strapi }) => {
// Register admin RBAC actions for this plugin
await strapi.service('admin::permission').actionProvider.registerMany([
{
section: 'plugins',
displayName: 'Read',
uid: 'read',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Settings',
uid: 'settings',
pluginName: 'my-plugin',
},
]);
};
```
## destroy()
Description: Use destroy() to: - Close external connections (databases, message queues, WebSocket servers) - Clear intervals or timeouts set in bootstrap() - Remove event listeners registered during the plugin's lifetime
(Source: https://docs.strapi.io/cms/plugins-development/server-lifecycle#destroy)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/destroy.js
```js
'use strict';
module.exports = ({ strapi }) => {
// Close an external connection opened in bootstrap()
strapi.plugin('my-plugin').service('queue').disconnect();
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/destroy.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => {
// Close an external connection opened in bootstrap()
strapi.plugin('my-plugin').service('queue').disconnect();
};
```
# Server policies & middlewares
Source: https://docs.strapi.io/cms/plugins-development/server-policies-middlewares
## Declaration
Description: :::note The exact shape of policyContext.state.user depends on the authentication context (for example, admin panel authentication vs.
(Source: https://docs.strapi.io/cms/plugins-development/server-policies-middlewares#declaration)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/policies/index.js
```js
'use strict';
const hasRole = require('./has-role');
module.exports = {
hasRole,
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/policies/has-role.js
```js
'use strict';
// Allow the request only if the user has the role specified in the route config
// Usage in route: { name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } }
module.exports = (policyContext, config, { strapi }) => {
const { user } = policyContext.state;
const targetRole = config.role;
if (!user || !targetRole) {
return false;
}
// Supports both `user.role` and `user.roles` shapes depending on auth strategy.
const roles = Array.isArray(user.roles)
? user.roles
: user.role
? [user.role]
: [];
return roles.some((role) => {
if (typeof role === 'string') return role === targetRole;
return role?.code === targetRole || role?.name === targetRole;
});
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/policies/index.ts
```ts
import hasRole from './has-role';
export default {
hasRole,
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/policies/has-role.ts
```ts
import type { Core } from '@strapi/strapi';
type UserRole = { code?: string; name?: string };
// Allow the request only if the user has the role specified in the route config
// Usage in route: { name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } }
export default (
policyContext: Core.PolicyContext,
config: { role?: string },
{ strapi }: { strapi: Core.Strapi }
) => {
const { user } = policyContext.state;
const targetRole = config?.role;
if (!user || !targetRole) {
return false;
}
// Supports both `user.role` and `user.roles` shapes depending on auth strategy.
const userWithRoles = user as { roles?: UserRole[]; role?: UserRole };
const roles: UserRole[] = Array.isArray(userWithRoles.roles)
? userWithRoles.roles
: userWithRoles.role
? [userWithRoles.role]
: [];
return roles.some((role) => role?.code === targetRole || role?.name === targetRole);
};
```
## Usage in routes
Description: Once declared, reference a plugin policy from a route using the plugin::my-plugin.policy-name namespace:
(Source: https://docs.strapi.io/cms/plugins-development/server-policies-middlewares#usage-in-routes)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
module.exports = [
{
method: 'GET',
path: '/dashboard',
handler: 'dashboard.find',
config: {
// highlight-next-line
policies: ['plugin::my-plugin.isActive'], // simple reference by namespaced name
},
},
{
method: 'DELETE',
path: '/articles/:id',
handler: 'article.delete',
config: {
// highlight-next-line
policies: [{ name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } }], // with per-route config
},
},
{
method: 'GET',
path: '/public',
handler: 'article.findAll',
config: {
// highlight-next-line
policies: [(policyContext, config, { strapi }) => true], // inline policy, no registration needed
},
},
];
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
import type { Core } from '@strapi/strapi';
export default [
{
method: 'GET' as const,
path: '/dashboard',
handler: 'dashboard.find',
config: {
// highlight-next-line
policies: ['plugin::my-plugin.isActive'], // simple reference by namespaced name
},
},
{
method: 'DELETE' as const,
path: '/articles/:id',
handler: 'article.delete',
config: {
// highlight-next-line
policies: [{ name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } }], // with per-route config
},
},
{
method: 'GET' as const,
path: '/public',
handler: 'article.findAll',
config: {
// highlight-next-line
policies: [(policyContext: Core.PolicyContext, config: unknown, { strapi }: { strapi: Core.Strapi }) => true], // inline policy, no registration needed
},
},
];
```
## Route-level middlewares
Description: :::note - middlewares exports middleware functions from the plugin so they can be referenced and reused in route config.
(Source: https://docs.strapi.io/cms/plugins-development/server-policies-middlewares#route-level-middlewares)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/middlewares/index.js
```js
'use strict';
const logRequest = require('./log-request');
module.exports = {
logRequest,
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/middlewares/log-request.js
```js
'use strict';
module.exports = (config, { strapi }) => async (ctx, next) => {
strapi.log.info(`[my-plugin] ${ctx.method} ${ctx.url}`);
await next();
strapi.log.info(`[my-plugin] → ${ctx.status}`);
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/middlewares/index.ts
```ts
import logRequest from './log-request';
export default {
logRequest,
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/middlewares/log-request.ts
```ts
import type { Core } from '@strapi/strapi';
export default (config: unknown, { strapi }: { strapi: Core.Strapi }) =>
async (ctx: any, next: () => Promise) => {
strapi.log.info(`[my-plugin] ${ctx.method} ${ctx.url}`);
await next();
strapi.log.info(`[my-plugin] → ${ctx.status}`);
};
```
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
module.exports = [
{
method: 'POST',
path: '/articles',
handler: 'article.create',
config: {
// highlight-next-line
middlewares: ['plugin::my-plugin.logRequest'],
},
},
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
middlewares: [
// highlight-next-line
async (ctx, next) => {
// inline middleware, no registration needed
ctx.query.pageSize = ctx.query.pageSize || '10';
await next();
},
],
},
},
];
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
export default [
{
method: 'POST' as const,
path: '/articles',
handler: 'article.create',
config: {
// highlight-next-line
middlewares: ['plugin::my-plugin.logRequest'],
},
},
{
method: 'GET' as const,
path: '/articles',
handler: 'article.find',
config: {
middlewares: [
async (ctx: any, next: () => Promise) => {
// inline middleware, no registration needed
ctx.query.pageSize = ctx.query.pageSize || '10';
await next();
},
],
},
},
];
```
## Server-level middlewares
Description: A server-level middleware is registered on the Strapi HTTP server directly and runs for every request, not just plugin routes.
(Source: https://docs.strapi.io/cms/plugins-development/server-policies-middlewares#server-level-middlewares)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/register.js
```js
'use strict';
module.exports = ({ strapi }) => {
// Attached to the global server pipeline — runs per matching request
strapi.server.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/register.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => {
// Attached to the global server pipeline — runs per matching request
strapi.server.use(async (ctx: any, next: () => Promise) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
};
```
# Server routes
Source: https://docs.strapi.io/cms/plugins-development/server-routes
## Array format
Description: :::tip To expose Content API routes, use the named router format with type: 'content-api'.
(Source: https://docs.strapi.io/cms/plugins-development/server-routes#array-format)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
module.exports = [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/articles',
handler: 'article.create',
config: {
policies: [],
},
},
];
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
export default [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/articles',
handler: 'article.create',
config: {
policies: [],
},
},
];
```
## Named router format
Description: With the named router format, use an object with named keys (admin, content-api, or any custom name) to declare separate router groups.
(Source: https://docs.strapi.io/cms/plugins-development/server-routes#named-router-format)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
const adminRoutes = require('./admin');
const contentApiRoutes = require('./content-api');
module.exports = {
// highlight-start
admin: adminRoutes,
'content-api': contentApiRoutes,
// highlight-end
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/admin/index.js
```js
'use strict';
module.exports = {
type: 'admin',
routes: [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
policies: ['admin::isAuthenticatedAdmin'],
},
},
],
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/content-api/index.js
```js
'use strict';
module.exports = {
type: 'content-api',
routes: [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
policies: [],
},
},
],
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
import adminRoutes from './admin';
import contentApiRoutes from './content-api';
export default {
// highlight-start
admin: adminRoutes,
'content-api': contentApiRoutes,
// highlight-end
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/admin/index.ts
```ts
export default {
type: 'admin' as const,
routes: [
{
method: 'GET' as const,
path: '/articles',
handler: 'article.find',
config: {
policies: ['admin::isAuthenticatedAdmin'],
},
},
],
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/content-api/index.ts
```ts
export default {
type: 'content-api' as const,
routes: [
{
method: 'GET' as const,
path: '/articles',
handler: 'article.find',
config: {
policies: [],
},
},
],
};
```
## Factory callback format
Description: module.exports = ({ strapi }) => ({ ...
(Source: https://docs.strapi.io/cms/plugins-development/server-routes#factory-callback-format)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
module.exports = {
'content-api': ({ strapi }) => ({
type: 'content-api',
routes: [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
// highlight-next-line
auth: strapi.plugin('my-plugin').config('publicRead') ? false : {},
},
},
],
}),
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
import type { Core } from '@strapi/strapi';
const routes: Record<
string,
Core.RouterConfig | ((args: { strapi: Core.Strapi }) => Core.RouterConfig)
> = {
'content-api': ({ strapi }) => ({
type: 'content-api',
routes: [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
// highlight-next-line
auth: strapi.plugin('my-plugin').config('publicRead') ? false : {},
},
},
],
}),
};
export default routes;
```
## Defaults applied by Strapi
Description: The following 2 declarations are equivalent.
(Source: https://docs.strapi.io/cms/plugins-development/server-routes#defaults-applied-by-strapi)
Language: JavaScript
File path: Implicit
```js
module.exports = [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
},
];
```
---
Language: JavaScript
File path: Equivalent
```js
module.exports = {
admin: {
type: 'admin',
prefix: '/my-plugin',
routes: [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
auth: {
// highlight-next-line
scope: ['plugin::my-plugin.article.find'], // auto-generated from handler string
},
},
},
],
},
};
```
---
Language: JavaScript
File path: Implicit
```js
export default [
{
method: 'GET' as const,
path: '/articles',
handler: 'article.find',
},
];
```
---
Language: JavaScript
File path: Equivalent
```js
export default {
admin: {
type: 'admin' as const,
prefix: '/my-plugin',
routes: [
{
method: 'GET' as const,
path: '/articles',
handler: 'article.find',
config: {
auth: {
// highlight-next-line
scope: ['plugin::my-plugin.article.find'], // auto-generated from handler string
},
},
},
],
},
};
```
# Documentation plugin
Source: https://docs.strapi.io/cms/plugins/documentation
## Installation
Description: To install the documentation plugin, run following command in your terminal:
(Source: https://docs.strapi.io/cms/plugins/documentation#installation)
Language: Bash
File path: />`:/documentation//openapi.js
```bash
yarn add @strapi/plugin-documentation
```
---
Language: Bash
File path: />`:/documentation//openapi.js
```bash
npm install @strapi/plugin-documentation
```
## Code-based configuration
Description: The following is an example configuration:
(Source: https://docs.strapi.io/cms/plugins/documentation#code-based-configuration)
Language: JSON
File path: src/extensions/documentation/config/settings.json
```json
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "DOCUMENTATION",
"description": "",
"termsOfService": "YOUR_TERMS_OF_SERVICE_URL",
"contact": {
"name": "TEAM",
"email": "contact-email@something.io",
"url": "mywebsite.io"
},
"license": {
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"x-strapi-config": {
"plugins": ["upload", "users-permissions"],
"path": "/documentation"
},
"servers": [
{
"url": "http://localhost:1337/api",
"description": "Development server"
}
],
"externalDocs": {
"description": "Find out more",
"url": "https://docs.strapi.io/developer-docs/latest/getting-started/introduction.html"
},
"security": [
{
"bearerAuth": []
}
]
}
```
## Creating a new version of the documentation
Description: To create a new version, change the info.version key in the settings.json file:
(Source: https://docs.strapi.io/cms/plugins/documentation#create-a-new-version-of-the-documentation)
Language: JSON
File path: src/extensions/documentation/config/settings.json
```json
{
"info": {
"version": "2.0.0"
}
}
```
## Defining which plugins need documentation generated
Description: If you want plugins to be included in documentation generation, they should be included in the plugins array in the x-strapi-config object.
(Source: https://docs.strapi.io/cms/plugins/documentation#define-which-plugins)
Language: JSON
File path: src/extensions/documentation/config/settings.json
```json
{
"x-strapi-config": {
"plugins": ["upload", "users-permissions"]
}
}
```
## excludeFromGeneration()
Description: | Parameter | Type | Description | | --------- | -------------------------- | -------------------------------------------------------- | | api | String or Array of Strings | The name of the API/plugin, or list of names, to exclude |
(Source: https://docs.strapi.io/cms/plugins/documentation#excluding-from-generation)
Language: JavaScript
File path: Application
```js
module.exports = {
register({ strapi }) {
strapi
.plugin("documentation")
.service("override")
.excludeFromGeneration("restaurant");
// or several
strapi
.plugin("documentation")
.service("override")
.excludeFromGeneration(["address", "upload"]);
}
}
```
## registerOverride()
Description: If the override should only be applied to a specific version, the override must include a value for info.version.
(Source: https://docs.strapi.io/cms/plugins/documentation#register-override)
Language: JavaScript
File path: Application
```js
module.exports = {
register({ strapi }) {
if (strapi.plugin('documentation')) {
const override = {
// Only run this override for version 1.0.0
info: { version: '1.0.0' },
paths: {
'/answer-to-everything': {
get: {
responses: { 200: { description: "*" }}
}
}
}
}
strapi
.plugin('documentation')
.service('override')
.registerOverride(override, {
// Specify the origin in case the user does not want this plugin documented
pluginOrigin: 'upload',
// The override provides everything don't generate anything
excludeFromGeneration: ['upload'],
});
}
},
}
```
## mutateDocumentation()
Description: | Parameter | Type | Description | | --------------------------- | ------ | ---------------------------------------------------------------------- | | generatedDocumentationDraft | Object | The generated documentation with applied overrides as a mutable object |
(Source: https://docs.strapi.io/cms/plugins/documentation#mutate-documentation)
Language: JavaScript
File path: config/plugins.js
```js
module.exports = {
documentation: {
config: {
"x-strapi-config": {
mutateDocumentation: (generatedDocumentationDraft) => {
generatedDocumentationDraft.paths[
"/answer-to-everything" // must be an existing path
].get.responses["200"].description = "*";
},
},
},
},
};
```
# GraphQL plugin
Source: https://docs.strapi.io/cms/plugins/graphql
## Installation
Description: To install the GraphQL plugin, run the following command in your terminal:
(Source: https://docs.strapi.io/cms/plugins/graphql#installation)
Language: Bash
File path: N/A
```bash
yarn add @strapi/plugin-graphql
```
---
Language: Bash
File path: N/A
```bash
npm install @strapi/plugin-graphql
```
## Available options
Description: The following is an example custom configuration:
(Source: https://docs.strapi.io/cms/plugins/graphql#available-options)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = {
graphql: {
config: {
endpoint: '/graphql',
shadowCRUD: true,
landingPage: false, // disable Sandbox everywhere
depthLimit: 7,
amountLimit: 100,
apolloServer: {
tracing: false,
},
},
},
};
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default () => ({
graphql: {
config: {
endpoint: '/graphql',
shadowCRUD: true,
landingPage: false, // disable Sandbox everywhere
depthLimit: 7,
amountLimit: 100,
apolloServer: {
tracing: false,
},
},
},
})
```
## Dynamically enable Apollo Sandbox
Description: You can use a function to dynamically enable Apollo Sandbox depending on the environment:
(Source: https://docs.strapi.io/cms/plugins/graphql#dynamically-enable-apollo-sandbox)
Language: JavaScript
File path: ./config/plugins.js
```js
module.exports = ({ env }) => {
graphql: {
config: {
endpoint: '/graphql',
shadowCRUD: true,
landingPage: (strapi) => {
if (env("NODE_ENV") !== "production") {
return true;
} else {
return false;
}
},
},
},
};
```
---
Language: TypeScript
File path: ./config/plugins.ts
```ts
export default ({ env }) => {
graphql: {
config: {
endpoint: '/graphql',
shadowCRUD: true,
landingPage: (strapi) => {
if (env("NODE_ENV") !== "production") {
return true;
} else {
return false;
}
},
},
},
};
```
## CORS exceptions for Landing Page
Description: To add them globally, you can merge the following into your middleware configuration:
(Source: https://docs.strapi.io/cms/plugins/graphql#cors-exceptions-for-landing-page)
Language: JavaScript
File path: ./middlewares/graphql-security.js
```js
module.exports = (config, { strapi }) => {
return async (ctx, next) => {
if (ctx.request.path === '/graphql') {
ctx.set('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.jsdelivr.net apollo-server-landing-page.cdn.apollographql.com; connect-src 'self' https:; img-src 'self' data: blob: apollo-server-landing-page.cdn.apollographql.com; media-src 'self' data: blob: apollo-server-landing-page.cdn.apollographql.com; frame-src sandbox.embed.apollographql.com; manifest-src apollo-server-landing-page.cdn.apollographql.com;");
}
await next();
};
};
```
---
Language: TypeScript
File path: ./middlewares/graphql-security.ts
```ts
export default (config, { strapi }) => {
return async (ctx, next) => {
if (ctx.request.path === '/graphql') {
ctx.set('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.jsdelivr.net apollo-server-landing-page.cdn.apollographql.com; connect-src 'self' https:; img-src 'self' data: blob: apollo-server-landing-page.cdn.apollographql.com; media-src 'self' data: blob: apollo-server-landing-page.cdn.apollographql.com; frame-src sandbox.embed.apollographql.com; manifest-src apollo-server-landing-page.cdn.apollographql.com;");
}
await next();
};
};
```
Language: JSON
File path: /config/middlewares
```json
{
name: "strapi::security",
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
"connect-src": ["'self'", "https:", "apollo-server-landing-page.cdn.apollographql.com"],
"img-src": ["'self'", "data:", "blob:", "apollo-server-landing-page.cdn.apollographql.com"],
"script-src": ["'self'", "'unsafe-inline'", "apollo-server-landing-page.cdn.apollographql.com"],
"style-src": ["'self'", "'unsafe-inline'", "apollo-server-landing-page.cdn.apollographql.com"],
"frame-src": ["sandbox.embed.apollographql.com"]
}
}
}
}
```
## Shadow CRUD
Description: If you've generated an API called Document using the interactive strapi generate CLI or the administration panel, your model looks like this:
(Source: https://docs.strapi.io/cms/plugins/graphql#shadow-crud)
Language: JSON
File path: /src/api/[api-name]/content-types/document/schema.json
```json
{
"kind": "collectionType",
"collectionName": "documents",
"info": {
"singularName": "document",
"pluralName": "documents",
"displayName": "document",
"name": "document"
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {},
"attributes": {
"name": {
"type": "string"
},
"description": {
"type": "richtext"
},
"locked": {
"type": "boolean"
}
}
}
```
Language: GRAPHQL
File path: N/A
```graphql
# Document's Type definition
input DocumentFiltersInput {
name: StringFilterInput
description: StringFilterInput
locked: BooleanFilterInput
createdAt: DateTimeFilterInput
updatedAt: DateTimeFilterInput
publishedAt: DateTimeFilterInput
and: [DocumentFiltersInput]
or: [DocumentFiltersInput]
not: DocumentFiltersInput
}
input DocumentInput {
name: String
description: String
locked: Boolean
createdAt: DateTime
updatedAt: DateTime
publishedAt: DateTime
}
type Document {
name: String
description: String
locked: Boolean
createdAt: DateTime
updatedAt: DateTime
publishedAt: DateTime
}
type DocumentEntity {
id: ID
attributes: Document
}
type DocumentEntityResponse {
data: DocumentEntity
}
type DocumentEntityResponseCollection {
data: [DocumentEntity!]!
meta: ResponseCollectionMeta!
}
type DocumentRelationResponseCollection {
data: [DocumentEntity!]!
}
# Queries to retrieve one or multiple restaurants.
type Query {
document(id: ID): DocumentEntityResponse
documents(
filters: DocumentFiltersInput
pagination: PaginationArg = {}
sort: [String] = []
publicationState: PublicationState = LIVE
):DocumentEntityResponseCollection
}
# Mutations to create, update or delete a restaurant.
type Mutation {
createDocument(data: DocumentInput!): DocumentEntityResponse
updateDocument(id: ID!, data: DocumentInput!): DocumentEntityResponse
deleteDocument(id: ID!): DocumentEntityResponse
}
```
## Customization
Description: Example of GraphQL customizations
(Source: https://docs.strapi.io/cms/plugins/graphql#customization)
Language: JavaScript
File path: /src/index.js
```js
module.exports = {
/**
* An asynchronous register function that runs before
* your application is initialized.
*
* This gives you an opportunity to extend code.
*/
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.shadowCRUD('api::restaurant.restaurant').disable();
extensionService.shadowCRUD('api::category.category').disableQueries();
extensionService.shadowCRUD('api::address.address').disableMutations();
extensionService.shadowCRUD('api::document.document').field('locked').disable();
extensionService.shadowCRUD('api::like.like').disableActions(['create', 'update', 'delete']);
const extension = ({ nexus }) => ({
// Nexus
types: [
nexus.objectType({
name: 'Book',
definition(t) {
t.string('title');
},
}),
],
plugins: [
nexus.plugin({
name: 'MyPlugin',
onAfterBuild(schema) {
console.log(schema);
},
}),
],
// GraphQL SDL
typeDefs: `
type Article {
name: String
}
`,
resolvers: {
Query: {
address: {
resolve() {
return { value: { city: 'Montpellier' } };
},
},
},
},
resolversConfig: {
'Query.address': {
auth: false,
},
},
});
extensionService.use(extension);
},
};
```
---
Language: TypeScript
File path: /src/index.ts
```ts
export default {
/**
* An asynchronous register function that runs before
* your application is initialized.
*
* This gives you an opportunity to extend code.
*/
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.shadowCRUD('api::restaurant.restaurant').disable();
extensionService.shadowCRUD('api::category.category').disableQueries();
extensionService.shadowCRUD('api::address.address').disableMutations();
extensionService.shadowCRUD('api::document.document').field('locked').disable();
extensionService.shadowCRUD('api::like.like').disableActions(['create', 'update', 'delete']);
const extension = ({ nexus }) => ({
// Nexus
types: [
nexus.objectType({
name: 'Book',
definition(t) {
t.string('title');
},
}),
],
plugins: [
nexus.plugin({
name: 'MyPlugin',
onAfterBuild(schema) {
console.log(schema);
},
}),
],
// GraphQL SDL
typeDefs: `
type Article {
name: String
}
`,
resolvers: {
Query: {
address: {
resolve() {
return { value: { city: 'Montpellier' } };
},
},
},
},
resolversConfig: {
'Query.address': {
auth: false,
},
},
});
extensionService.use(extension);
},
};
```
## Disabling operations in the Shadow CRUD
Description: Examples:
(Source: https://docs.strapi.io/cms/plugins/graphql#disabling-operations-in-the-shadow-crud)
Language: JavaScript
File path: N/A
```js
// Disable the 'find' operation on the 'restaurant' content-type in the 'restaurant' API
strapi
.plugin('graphql')
.service('extension')
.shadowCRUD('api::restaurant.restaurant')
.disableAction('find')
// Disable the 'name' field on the 'document' content-type in the 'document' API
strapi
.plugin('graphql')
.service('extension')
.shadowCRUD('api::document.document')
.field('name')
.disable()
```
## Extending the schema
Description: Example:
(Source: https://docs.strapi.io/cms/plugins/graphql#extending-the-schema)
Language: JavaScript
File path: /src/index.js
```js
module.exports = {
register({ strapi }) {
const extension = ({ nexus }) => ({
types: [
nexus.objectType({
…
}),
],
plugins: [
nexus.plugin({
…
})
]
})
strapi.plugin('graphql').service('extension').use(extension)
}
}
```
---
Language: TypeScript
File path: ./src/index.ts
```ts
export default {
register({ strapi }) {
const extension = ({ nexus }) => ({
types: [
nexus.objectType({
…
}),
],
plugins: [
nexus.plugin({
…
})
]
})
strapi.plugin('graphql').service('extension').use(extension)
}
}
```
## Authorization configuration
Description: Examples of authorization configuration
(Source: https://docs.strapi.io/cms/plugins/graphql#authorization-configuration)
Language: JavaScript
File path: /src/index.js
```js
module.exports = {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
/**
* Querying the Categories content-type
* bypasses the authorization system.
*/
auth: false
},
'Query.restaurants': {
/**
* Querying the Restaurants content-type
* requires the find permission
* on the 'Address' content-type
* of the 'Address' API
*/
auth: {
scope: ['api::address.address.find']
}
},
}
})
}
}
```
---
Language: TypeScript
File path: /src/index.ts
```ts
export default {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
/**
* Querying the Categories content-type
* bypasses the authorization system.
*/
auth: false
},
'Query.restaurants': {
/**
* Querying the Restaurants content-type
* requires the find permission
* on the 'Address' content-type
* of the 'Address' API
*/
auth: {
scope: ['api::address.address.find']
}
},
}
})
}
}
```
## Policies
Description: Example of GraphQL policies applied to resolvers
(Source: https://docs.strapi.io/cms/plugins/graphql#policies)
Language: JavaScript
File path: /src/index.js
```js
module.exports = {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
policies: [
(context, { strapi }) => {
console.log('hello', context.parent)
/**
* If 'categories' have a parent, the function returns true,
* so the request won't be blocked by the policy.
*/
return context.parent !== undefined;
}
/**
* Uses a policy already created in Strapi.
*/
"api::model.policy-name",
/**
* Uses a policy already created in Strapi with a custom configuration
*/
{name:"api::model.policy-name", config: {/* all config values I want to pass to the strapi policy */} },
],
auth: false,
},
}
})
}
}
```
---
Language: TypeScript
File path: /src/index.ts
```ts
export default {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
policies: [
(context, { strapi }) => {
console.log('hello', context.parent)
/**
* If 'categories' have a parent, the function returns true,
* so the request won't be blocked by the policy.
*/
return context.parent !== undefined;
}
/**
* Uses a policy already created in Strapi.
*/
"api::model.policy-name",
/**
* Uses a policy already created in Strapi with a custom configuration
*/
{name:"api::model.policy-name", config: {/* all the configuration values to pass to the strapi policy */} },
],
auth: false,
},
}
})
}
}
```
## Middlewares
Description: Examples of GraphQL middlewares applied to a resolver
(Source: https://docs.strapi.io/cms/plugins/graphql#middlewares)
Language: JavaScript
File path: N/A
```js
module.exports = {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
middlewares: [
/**
* Basic middleware example #1
* Log resolving time in console
*/
async (next, parent, args, context, info) => {
console.time('Resolving categories');
// call the next resolver
const res = await next(parent, args, context, info);
console.timeEnd('Resolving categories');
return res;
},
/**
* Basic middleware example #2
* Enable server-side shared caching
*/
async (next, parent, args, context, info) => {
info.cacheControl.setCacheHint({ maxAge: 60, scope: "PUBLIC" });
return next(parent, args, context, info);
},
/**
* Basic middleware example #3
* change the 'name' attribute of parent with id 1 to 'foobar'
*/
(resolve, parent, ...rest) => {
if (parent.id === 1) {
return resolve({...parent, name: 'foobar' }, ...rest);
}
return resolve(parent, ...rest);
}
/**
* Basic middleware example #4
* Uses a middleware already created in Strapi.
*/
"api::model.middleware-name",
/**
* Basic middleware example #5
* Uses a middleware already created in Strapi with a custom configuration
*/
{ name: "api::model.middleware-name", options: { /* all config values I want to pass to the strapi middleware */ } },
],
auth: false,
},
}
})
}
}
```
---
Language: JavaScript
File path: N/A
```js
export default {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
middlewares: [
/**
* Basic middleware example #1
* Log resolving time in console
*/
async (next, parent, args, context, info) => {
console.time('Resolving categories');
// call the next resolver
const res = await next(parent, args, context, info);
console.timeEnd('Resolving categories');
return res;
},
/**
* Basic middleware example #2
* Enable server-side shared caching
*/
async (next, parent, args, context, info) => {
info.cacheControl.setCacheHint({ maxAge: 60, scope: "PUBLIC" });
return next(parent, args, context, info);
},
/**
* Basic middleware example #3
* change the 'name' attribute of parent with id 1 to 'foobar'
*/
(resolve, parent, ...rest) => {
if (parent.id === 1) {
return resolve({...parent, name: 'foobar' }, ...rest);
}
return resolve(parent, ...rest);
}
/**
* Basic middleware example #4
* Uses a middleware already created in Strapi.
*/
"api::model.middleware-name",
/**
* Basic middleware example #5
* Uses a middleware already created in Strapi with a custom configuration
*/
{name:"api::model.middleware-name", options: {/* all the configuration values to pass to the middleware */} },
],
auth: false,
},
}
})
}
}
```
## Usage
Description: :::note Strapi uses documentId as the unique identifier for entities instead of id.
(Source: https://docs.strapi.io/cms/plugins/graphql#usage)
Language: JavaScript
File path: N/A
```js
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
const client = new ApolloClient({
link: new HttpLink({ uri: "http://localhost:1337/graphql" }),
cache: new InMemoryCache({
dataIdFromObject: (o) => `${o.__typename}:${o["documentId"]}`,
}),
});
```
## Registration
Description: Code example from "Registration"
(Source: https://docs.strapi.io/cms/plugins/graphql#registration)
Language: DOCKERFILE
File path: N/A
```dockerfile
mutation {
register(input: { username: "username", email: "email", password: "password" }) {
jwt
user {
username
email
}
}
}
```
## Authentication
Description: Code example from "Authentication"
(Source: https://docs.strapi.io/cms/plugins/graphql#authentication)
Language: GRAPHQL
File path: N/A
```graphql
mutation {
login(input: { identifier: "email", password: "password" }) {
jwt
}
}
```
## Usage with API tokens
Description: :::note Using API tokens in the the GraphQL Sandbox requires adding the authorization header with your token in the HTTP HEADERS tab:
(Source: https://docs.strapi.io/cms/plugins/graphql#api-tokens)
Language: JSON
File path: N/A
```json
{
"Authorization" : "Bearer "
}
```
# Sentry plugin
Source: https://docs.strapi.io/cms/plugins/sentry
## Installation
Description: Install the Sentry plugin by adding the dependency to your Strapi application as follows:
(Source: https://docs.strapi.io/cms/plugins/sentry#installation)
Language: Bash
File path: N/A
```bash
yarn add @strapi/plugin-sentry
```
---
Language: Bash
File path: N/A
```bash
npm install @strapi/plugin-sentry
```
## Configuration
Description: The following is an example basic configuration:
(Source: https://docs.strapi.io/cms/plugins/sentry#configuration)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ...
sentry: {
enabled: true,
config: {
dsn: env('SENTRY_DSN'),
sendMetadata: true,
},
},
// ...
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// ...
sentry: {
enabled: true,
config: {
dsn: env('SENTRY_DSN'),
sendMetadata: true,
},
},
// ...
});
```
## Disabling for non-production environments
Description: You can make use of that by using the env utility to set the dsn configuration property depending on the environment.
(Source: https://docs.strapi.io/cms/plugins/sentry#disabling-for-non-production-environments)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// …
sentry: {
enabled: true,
config: {
// Only set `dsn` property in production
dsn: env('NODE_ENV') === 'production' ? env('SENTRY_DSN') : null,
},
},
// …
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// …
sentry: {
enabled: true,
config: {
// Only set `dsn` property in production
dsn: env('NODE_ENV') === 'production' ? env('SENTRY_DSN') : null,
},
},
// …
});
```
## Disabling the plugin completely
Description: Like every other Strapi plugin, you can also disable this plugin in the plugins configuration file.
(Source: https://docs.strapi.io/cms/plugins/sentry#disabling-the-plugin-completely)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// …
sentry: {
enabled: false,
},
// …
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// …
sentry: {
enabled: false,
},
// …
});
```
## Usage
Description: After installing and configuring the plugin, you can access a Sentry service in your Strapi application as follows:
(Source: https://docs.strapi.io/cms/plugins/sentry#usage)
Language: JavaScript
File path: N/A
```js
const sentryService = strapi.plugin('sentry').service('sentry');
```
Language: JavaScript
File path: N/A
```js
try {
// Your code here
} catch (error) {
// Either send a simple error
strapi
.plugin('sentry')
.service('sentry')
.sendError(error);
// Or send an error with a customized Sentry scope
strapi
.plugin('sentry')
.service('sentry')
.sendError(error, (scope, sentryInstance) => {
// Customize the scope here
scope.setTag('my_custom_tag', 'Tag value');
});
throw error;
}
```
Language: JavaScript
File path: N/A
```js
const sentryInstance = strapi
.plugin('sentry')
.service('sentry')
.getInstance();
```
# Quick Start Guide - Strapi Developer Docs
Source: https://docs.strapi.io/cms/quick-start
## Step 1: Run the installation script and create a Strapi Cloud account
Description: Run the following command in a terminal:
(Source: https://docs.strapi.io/cms/quick-start#step-1-run-the-installation-script-and-create-a-strapi-cloud-account)
Language: Bash
File path: N/A
```bash
npx create-strapi@latest my-strapi-project
```
## Step 2: Register the first local administrator user
Description: Once the installation is complete, you need to start the server.
(Source: https://docs.strapi.io/cms/quick-start#step-2-register-the-first-local-administrator-user)
Language: Bash
File path: .strapi-cloud.js
```bash
cd my-strapi-project && npm run develop
```
## ️ Part C: Deploy to Strapi Cloud
Description: If the server for your local Strapi project is running, which should be the case if you followed this tutorial so far, press Ctrl-C to stop the server.
(Source: https://docs.strapi.io/cms/quick-start#part-c-deploy-to-strapi-cloud)
Language: Bash
File path: N/A
```bash
npm run strapi deploy
```
## Step 6: Use the API
Description: Click me to view an example of API response:
(Source: https://docs.strapi.io/cms/quick-start#step-6-use-the-api)
Language: JSON
File path: N/A
```json
{
"data": [
{
"id": 3,
"documentId": "wf7m1n3g8g22yr5k50hsryhk",
"Name": "Biscotte Restaurant",
"Description": [
{
"type": "paragraph",
"children": [
{
"type": "text",
"text": "Welcome to Biscotte restaurant! Restaurant Biscotte offers a cuisine based on fresh, quality products, often local, organic when possible, and always produced by passionate producers."
}
]
}
],
"createdAt": "2024-09-10T12:49:32.350Z",
"updatedAt": "2024-09-10T13:14:18.275Z",
"publishedAt": "2024-09-10T13:14:18.280Z",
"locale": null
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 1,
"total": 1
}
}
}
```
# strapi-utils
Source: https://docs.strapi.io/cms/strapi-utils
## async
Description: The async namespace provides asynchronous utility functions.
(Source: https://docs.strapi.io/cms/strapi-utils#async)
Language: JavaScript
File path: N/A
```js
const { async } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const { async: asyncUtils } = require('@strapi/utils');
// Compose async functions into a pipeline
const result = await asyncUtils.pipe(
fetchUser,
enrichWithProfile,
formatResponse
)(userId);
// Reduce an array asynchronously (note the curried call)
const total = await asyncUtils.reduce([1, 2, 3])(
async (sum, n) => sum + n,
0
); // 6
```
## contentTypes
Description: The contentTypes namespace exposes constants and helper functions for working with Strapi content-type schemas.
(Source: https://docs.strapi.io/cms/strapi-utils#contenttypes)
Language: JavaScript
File path: N/A
```js
const { contentTypes } = require('@strapi/utils');
```
## Schema inspection functions
Description: The following example iterates over a content type's attributes to find relations and writable fields:
(Source: https://docs.strapi.io/cms/strapi-utils#schema-inspection-functions)
Language: JavaScript
File path: N/A
```js
const { contentTypes } = require('@strapi/utils');
const articleSchema = strapi.contentType('api::article.article');
// List all relation fields
for (const [name, attribute] of Object.entries(articleSchema.attributes)) {
if (contentTypes.isRelationalAttribute(attribute)) {
console.log(`${name} is a relation`);
}
}
// Get only the fields that can be written to
const writableFields = contentTypes.getWritableAttributes(articleSchema);
// Check if draft and publish is enabled
if (contentTypes.hasDraftAndPublish(articleSchema)) {
console.log('This content type supports drafts');
}
```
## env
Description: A helper function to read environment variables with type-safe parsing.
(Source: https://docs.strapi.io/cms/strapi-utils#env)
Language: JavaScript
File path: N/A
```js
const { env } = require('@strapi/utils');
// or in TypeScript: import { env } from '@strapi/utils';
```
Language: JavaScript
File path: /config/server.js
```js
const { env } = require('@strapi/utils');
module.exports = {
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
};
```
## errors
Description: The error classes are imported as follows:
(Source: https://docs.strapi.io/cms/strapi-utils#errors)
Language: JavaScript
File path: N/A
```js
const { errors } = require('@strapi/utils');
// or in TypeScript: import { errors } from '@strapi/utils';
```
Language: JavaScript
File path: N/A
```js
const { errors } = require('@strapi/utils');
// In a service or lifecycle hook
throw new errors.ApplicationError('Something went wrong', { foo: 'bar' });
// In a policy
throw new errors.PolicyError('Access denied', { policy: 'is-owner' });
```
## file
Description: The file namespace provides helpers for working with streams and file sizes.
(Source: https://docs.strapi.io/cms/strapi-utils#file)
Language: JavaScript
File path: N/A
```js
const { file } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const { file } = require('@strapi/utils');
const buffer = await file.streamToBuffer(uploadStream);
const sizeInKb = file.bytesToKbytes(buffer.length);
console.log(`Uploaded ${file.bytesToHumanReadable(buffer.length)} (${sizeInKb} KB)`);
```
## hooks
Description: Factory functions to create hook registries.
(Source: https://docs.strapi.io/cms/strapi-utils#hooks)
Language: JavaScript
File path: N/A
```js
const { hooks } = require('@strapi/utils');
```
## Available hook factories
Description: The following example registers and calls handlers with a series hook A series hook executes handlers sequentially, one after another, in the order they were registered.
(Source: https://docs.strapi.io/cms/strapi-utils#available-hook-factories)
Language: JavaScript
File path: N/A
```js
const { hooks } = require('@strapi/utils');
const myHook = hooks.createAsyncSeriesHook();
myHook.register(async (context) => {
console.log('First handler', context);
});
myHook.register(async (context) => {
console.log('Second handler', context);
});
// Execute all handlers in order
await myHook.call({ data: 'example' });
```
## pagination
Description: The pagination namespace provides helpers for handling pagination parameters.
(Source: https://docs.strapi.io/cms/strapi-utils#pagination)
Language: JavaScript
File path: N/A
```js
const { pagination } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const { pagination } = require('@strapi/utils');
const params = pagination.withDefaultPagination({ page: 2 }, { maxLimit: 100 });
const info = pagination.transformPagedPaginationInfo(params, 250);
// { page: 2, pageSize: 25, pageCount: 10, total: 250 }
```
## parseType
Description: Cast a value to a specific Strapi field type.
(Source: https://docs.strapi.io/cms/strapi-utils#parsetype)
Language: JavaScript
File path: N/A
```js
const { parseType } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
parseType({ type: 'boolean', value: 'true' }); // true
parseType({ type: 'integer', value: '42' }); // 42
parseType({ type: 'date', value: '2024-01-15T10:30:00Z' }); // '2024-01-15'
```
## policy
Description: Helpers to create and manage policies.
(Source: https://docs.strapi.io/cms/strapi-utils#policy)
Language: JavaScript
File path: N/A
```js
const { policy } = require('@strapi/utils');
```
## createPolicy
Description: The following example creates a policy with a configuration validator:
(Source: https://docs.strapi.io/cms/strapi-utils#createpolicy)
Language: JavaScript
File path: N/A
```js
const myPolicy = policy.createPolicy({
name: 'is-owner',
validator: (config) => {
if (!config.field) throw new Error('Missing field');
},
handler: (ctx, config, { strapi }) => {
// policy logic
return true;
},
});
```
## createPolicyContext
Description: The createPolicyContext function creates a typed context object for use within a policy handler.
(Source: https://docs.strapi.io/cms/strapi-utils#createpolicycontext)
Language: JavaScript
File path: N/A
```js
const policyCtx = policy.createPolicyContext('admin', ctx);
policyCtx.is('admin'); // true
policyCtx.type; // 'admin'
```
## primitives
Description: Low-level data transformation helpers.
(Source: https://docs.strapi.io/cms/strapi-utils#primitives)
Language: JavaScript
File path: N/A
```js
const { strings, objects, arrays, dates } = require('@strapi/utils');
```
## providerFactory
Description: Create a pluggable registry that stores and retrieves items by key, with lifecycle hooks.
(Source: https://docs.strapi.io/cms/strapi-utils#providerfactory)
Language: JavaScript
File path: N/A
```js
const { providerFactory } = require('@strapi/utils');
```
## Provider hooks
Description: The following example creates a provider and registers an item with a lifecycle hook:
(Source: https://docs.strapi.io/cms/strapi-utils#provider-hooks)
Language: JavaScript
File path: N/A
```js
const { providerFactory } = require('@strapi/utils');
const registry = providerFactory();
registry.hooks.willRegister.register(async ({ key, value }) => {
console.log(`About to register: ${key}`);
});
await registry.register('my-provider', { execute: () => {} });
registry.get('my-provider'); // { execute: [Function] }
registry.has('my-provider'); // true
registry.size(); // 1
```
## relations
Description: The relations namespace provides helpers to inspect the cardinality of relation attributes (e.g., one-to-many vs.
(Source: https://docs.strapi.io/cms/strapi-utils#relations)
Language: JavaScript
File path: N/A
```js
const { relations } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const { relations, contentTypes } = require('@strapi/utils');
const schema = strapi.contentType('api::article.article');
for (const [name, attribute] of Object.entries(schema.attributes)) {
if (contentTypes.isRelationalAttribute(attribute) && relations.isAnyToMany(attribute)) {
console.log(`${name} is a *-to-many relation`);
}
}
```
## sanitize
Description: The namespace is imported as follows:
(Source: https://docs.strapi.io/cms/strapi-utils#sanitize)
Language: JavaScript
File path: N/A
```js
const { sanitize } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const sanitizers = sanitize.createAPISanitizers({
getModel: strapi.getModel.bind(strapi),
});
```
## setCreatorFields
Description: Set createdBy and updatedBy fields on an entity.
(Source: https://docs.strapi.io/cms/strapi-utils#setcreatorfields)
Language: JavaScript
File path: N/A
```js
const { setCreatorFields } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const { setCreatorFields } = require('@strapi/utils');
const addCreator = setCreatorFields({ user: { id: 1 } });
const data = addCreator({ title: 'My Article' });
// { title: 'My Article', createdBy: 1, updatedBy: 1 }
const updateCreator = setCreatorFields({ user: { id: 2 }, isEdition: true });
const updated = updateCreator(data);
// { title: 'My Article', createdBy: 1, updatedBy: 2 }
```
## validate
Description: The namespace is imported as follows:
(Source: https://docs.strapi.io/cms/strapi-utils#validate)
Language: JavaScript
File path: N/A
```js
const { validate } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const validators = validate.createAPIValidators({
getModel: strapi.getModel.bind(strapi),
});
```
Language: JavaScript
File path: N/A
```js
const { validate, errors } = require('@strapi/utils');
const validators = validate.createAPIValidators({
getModel: strapi.getModel.bind(strapi),
});
try {
await validators.query(ctx.query, 'api::article.article', {
auth: ctx.state.auth,
});
} catch (error) {
// error is a ValidationError with details about which fields failed
console.error(error.message, error.details);
}
```
## yup
Description: The yup namespace re-exports the with Strapi-specific extensions.
(Source: https://docs.strapi.io/cms/strapi-utils#yup)
Language: JavaScript
File path: N/A
```js
const { yup } = require('@strapi/utils');
```
## Schema validation helpers
Description: validateYupSchema and validateYupSchemaSync are top-level exports from @strapi/utils, not part of the yup namespace:
(Source: https://docs.strapi.io/cms/strapi-utils#schema-validation-helpers)
Language: JavaScript
File path: N/A
```js
const { validateYupSchema, validateYupSchemaSync } = require('@strapi/utils');
```
## zod
Description: Strapi re-exports the z instance from and provides a validateZod helper that wraps a Zod schema into a Strapi-style validator.
(Source: https://docs.strapi.io/cms/strapi-utils#zod)
Language: JavaScript
File path: N/A
```js
const { validateZod, z } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const schema = z.object({
name: z.string().min(1),
age: z.number().positive(),
});
const validate = validateZod(schema);
const parsed = validate({ name: 'Alice', age: 30 }); // returns parsed data
validate({ name: '' }); // throws ValidationError
```
# Templates
Source: https://docs.strapi.io/cms/templates
## Using a template
Description: To create a new Strapi project based on a template, run the following command:
(Source: https://docs.strapi.io/cms/templates#using-a-template)
Language: Bash
File path: N/A
```bash
yarn create strapi-app my-project --template
```
---
Language: Bash
File path: N/A
```bash
npx create-strapi-app@latest my-project --template
```
# Testing
Source: https://docs.strapi.io/cms/testing
## Install tools
Description: Install Jest and Supertest by running the following command in a terminal:
(Source: https://docs.strapi.io/cms/testing#install-tools)
Language: Bash
File path: N/A
```bash
yarn add jest supertest --dev
```
---
Language: Bash
File path: N/A
```bash
npm install jest supertest --save-dev
```
Language: JSON
File path: N/A
```json
"scripts": {
"build": "strapi build",
"console": "strapi console",
"deploy": "strapi deploy",
"dev": "strapi develop",
"develop": "strapi develop",
"seed:example": "node ./scripts/seed.js",
"start": "strapi start",
"strapi": "strapi",
"upgrade": "npx @strapi/upgrade latest",
"upgrade:dry": "npx @strapi/upgrade latest --dry",
"test": "jest --forceExit --detectOpenHandles"
},
```
Language: JSON
File path: N/A
```json
"jest": {
"testPathIgnorePatterns": [
"/node_modules/",
".tmp",
".cache"
],
"testEnvironment": "node",
"moduleNameMapper": {
"^/create-service$": "/create-service"
}
}
```
## Controller example
Description: Create a test file such as ./tests/todo-controller.test.js that instantiates your controller with a mocked Strapi object and verifies every call the controller performs:
(Source: https://docs.strapi.io/cms/testing#controller-example)
Language: JavaScript
File path: ./tests/todo-controller.test.js
```js
const todoController = require('./todo-controller');
describe('Todo controller', () => {
let strapi;
beforeEach(() => {
strapi = {
plugin: jest.fn().mockReturnValue({
service: jest.fn().mockReturnValue({
create: jest.fn().mockReturnValue({
data: {
name: 'test',
status: false,
},
}),
complete: jest.fn().mockReturnValue({
data: {
id: 1,
status: true,
},
}),
}),
}),
};
});
it('creates a todo item', async () => {
const ctx = {
request: {
body: {
name: 'test',
},
},
body: null,
};
await todoController({ strapi }).index(ctx);
expect(ctx.body).toBe('created');
expect(strapi.plugin('todo').service('create').create).toHaveBeenCalledTimes(1);
});
it('completes a todo item', async () => {
const ctx = {
request: {
body: {
id: 1,
},
},
body: null,
};
await todoController({ strapi }).complete(ctx);
expect(ctx.body).toBe('todo completed');
expect(strapi.plugin('todo').service('complete').complete).toHaveBeenCalledTimes(1);
});
});
```
## Service example
Description: Services can be tested in the same test suite or in a dedicated file by mocking only the Strapi query layer they call into.
(Source: https://docs.strapi.io/cms/testing#service-example)
Language: JavaScript
File path: ./tests/create-service.test.js
```js
const createService = require('./create-service');
describe('Create service', () => {
let strapi;
beforeEach(() => {
strapi = {
query: jest.fn().mockReturnValue({
create: jest.fn().mockReturnValue({
data: {
name: 'test',
status: false,
},
}),
}),
};
});
it('persists a todo item', async () => {
const todo = await createService({ strapi }).create({ name: 'test' });
expect(strapi.query('plugin::todo.todo').create).toHaveBeenCalledTimes(1);
expect(todo.data.name).toBe('test');
});
});
```
## Set up a testing environment
Description: Once jest is running it uses the test environment, so create ./config/env/test/database.js with the following:
(Source: https://docs.strapi.io/cms/testing#set-up-a-testing-environment)
Language: JavaScript
File path: ./config/env/test/database.js
```js
module.exports = ({ env }) => {
const filename = env('DATABASE_FILENAME', '.tmp/test.db');
const rawClient = env('DATABASE_CLIENT', 'sqlite');
const client = ['sqlite3', 'better-sqlite3'].includes(rawClient) ? 'sqlite' : rawClient;
return {
connection: {
client,
connection: {
filename,
},
useNullAsDefault: true,
},
};
};
```
## TypeScript compiler configuration
Description: Create tests/ts-compiler-options.js with the following content:
(Source: https://docs.strapi.io/cms/testing#typescript-compiler-configuration)
Language: JavaScript
File path: ./tests/ts-compiler-options.js
```js
const fs = require('fs');
const path = require('path');
const ts = require('typescript');
const projectRoot = path.resolve(__dirname, '..');
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
const baseCompilerOptions = {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2019,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
esModuleInterop: true,
jsx: ts.JsxEmit.React,
};
const loadCompilerOptions = () => {
let options = { ...baseCompilerOptions };
if (!fs.existsSync(tsconfigPath)) {
return options;
}
try {
const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
const parsed = ts.parseConfigFileTextToJson(tsconfigPath, tsconfigContent);
if (!parsed.error && parsed.config && parsed.config.compilerOptions) {
options = {
...options,
...parsed.config.compilerOptions,
};
}
} catch (error) {
// Ignore tsconfig parsing errors and fallback to defaults
}
return options;
};
module.exports = {
compilerOptions: loadCompilerOptions(),
loadCompilerOptions,
};
```
## TypeScript runtime loader
Description: Create tests/ts-runtime.js with the following content:
(Source: https://docs.strapi.io/cms/testing#typescript-runtime-loader)
Language: JavaScript
File path: ./tests/ts-runtime.js
```js
const Module = require('module');
const { compilerOptions } = require('./ts-compiler-options');
const fs = require('fs');
const ts = require('typescript');
const extensions = Module._extensions;
if (!extensions['.ts']) {
extensions['.ts'] = function compileTS(module, filename) {
const source = fs.readFileSync(filename, 'utf8');
const output = ts.transpileModule(source, {
compilerOptions,
fileName: filename,
reportDiagnostics: false,
});
return module._compile(output.outputText, filename);
};
}
if (!extensions['.tsx']) {
extensions['.tsx'] = extensions['.ts'];
}
module.exports = {
compilerOptions,
};
```
## Main test harness
Description: Code example from "Main test harness"
(Source: https://docs.strapi.io/cms/testing#main-test-harness)
Language: JavaScript
File path: ./tests/strapi.js
```js
try {
require('ts-node/register/transpile-only');
} catch (err) {
try {
require('@strapi/typescript-utils/register');
} catch (strapiRegisterError) {
require('./ts-runtime');
}
}
const fs = require('fs');
const path = require('path');
const Module = require('module');
const ts = require('typescript');
const databaseConnection = require('@strapi/database/dist/connection.js');
const knexFactory = require('knex');
const strapiCoreRoot = path.dirname(require.resolve('@strapi/core/package.json'));
const loadConfigFilePath = path.join(strapiCoreRoot, 'dist', 'utils', 'load-config-file.js');
const loadConfigFileModule = require(loadConfigFilePath);
const { compilerOptions: baseCompilerOptions } = require('./ts-compiler-options');
// ============================================
// 1. PATCH: TypeScript Configuration Loader
// ============================================
// This section patches Strapi's configuration loader to support TypeScript config files
// (.ts, .cts, .mts). Without this, Strapi would only load .js and .json config files.
if (!loadConfigFileModule.loadConfigFile.__tsRuntimePatched) {
const strapiUtils = require('@strapi/utils');
const originalLoadConfigFile = loadConfigFileModule.loadConfigFile;
const loadTypeScriptConfig = (file) => {
const source = fs.readFileSync(file, 'utf8');
const options = {
...baseCompilerOptions,
module: ts.ModuleKind.CommonJS,
};
const output = ts.transpileModule(source, {
compilerOptions: options,
fileName: file,
reportDiagnostics: false,
});
const moduleInstance = new Module(file);
moduleInstance.filename = file;
moduleInstance.paths = Module._nodeModulePaths(path.dirname(file));
moduleInstance._compile(output.outputText, file);
const exported = moduleInstance.exports;
const resolved = exported && exported.__esModule ? exported.default : exported;
if (typeof resolved === 'function') {
return resolved({ env: strapiUtils.env });
}
return resolved;
};
const patchedLoadConfigFile = (file) => {
const extension = path.extname(file).toLowerCase();
if (extension === '.ts' || extension === '.cts' || extension === '.mts') {
return loadTypeScriptConfig(file);
}
return originalLoadConfigFile(file);
};
patchedLoadConfigFile.__tsRuntimePatched = true;
loadConfigFileModule.loadConfigFile = patchedLoadConfigFile;
require.cache[loadConfigFilePath].exports = loadConfigFileModule;
}
// ============================================
// 2. PATCH: Configuration Directory Scanner
// ============================================
// This section patches how Strapi scans the config directory to:
// - Support TypeScript extensions (.ts, .cts, .mts)
// - Validate config file names
// - Prevent loading of restricted filenames
const configLoaderPath = path.join(strapiCoreRoot, 'dist', 'configuration', 'config-loader.js');
const originalLoadConfigDir = require(configLoaderPath);
const validExtensions = ['.js', '.json', '.ts', '.cts', '.mts'];
const mistakenFilenames = {
middleware: 'middlewares',
plugin: 'plugins',
};
const restrictedFilenames = [
'uuid',
'hosting',
'license',
'enforce',
'disable',
'enable',
'telemetry',
'strapi',
'internal',
'launchedAt',
'serveAdminPanel',
'autoReload',
'environment',
'packageJsonStrapi',
'info',
'dirs',
...Object.keys(mistakenFilenames),
];
const strapiConfigFilenames = ['admin', 'server', 'api', 'database', 'middlewares', 'plugins', 'features'];
if (!originalLoadConfigDir.__tsRuntimePatched) {
const patchedLoadConfigDir = (dir) => {
if (!fs.existsSync(dir)) {
return {};
}
const entries = fs.readdirSync(dir, { withFileTypes: true });
const seenFilenames = new Set();
const configFiles = entries.reduce((acc, entry) => {
if (!entry.isFile()) {
return acc;
}
const extension = path.extname(entry.name);
const extensionLower = extension.toLowerCase();
const baseName = path.basename(entry.name, extension);
const baseNameLower = baseName.toLowerCase();
if (!validExtensions.includes(extensionLower)) {
console.warn(`Config file not loaded, extension must be one of ${validExtensions.join(',')}): ${entry.name}`);
return acc;
}
if (restrictedFilenames.includes(baseNameLower)) {
console.warn(`Config file not loaded, restricted filename: ${entry.name}`);
if (baseNameLower in mistakenFilenames) {
console.log(`Did you mean ${mistakenFilenames[baseNameLower]}?`);
}
return acc;
}
const restrictedPrefix = [...restrictedFilenames, ...strapiConfigFilenames].find(
(restrictedName) => restrictedName.startsWith(baseNameLower) && restrictedName !== baseNameLower
);
if (restrictedPrefix) {
console.warn(`Config file not loaded, filename cannot start with ${restrictedPrefix}: ${entry.name}`);
return acc;
}
if (seenFilenames.has(baseNameLower)) {
console.warn(`Config file not loaded, case-insensitive name matches other config file: ${entry.name}`);
return acc;
}
seenFilenames.add(baseNameLower);
acc.push(entry);
return acc;
}, []);
return configFiles.reduce((acc, entry) => {
const extension = path.extname(entry.name);
const key = path.basename(entry.name, extension);
const filePath = path.resolve(dir, entry.name);
acc[key] = loadConfigFileModule.loadConfigFile(filePath);
return acc;
}, {});
};
patchedLoadConfigDir.__tsRuntimePatched = true;
require.cache[configLoaderPath].exports = patchedLoadConfigDir;
}
// ============================================
// 3. PATCH: Database Connection Handler
// ============================================
// This section normalizes database client names for testing.
// Maps Strapi's client names (sqlite, mysql, postgres) to actual driver names
// (sqlite3, mysql2, pg) and handles connection pooling.
databaseConnection.createConnection = (() => {
const clientMap = {
sqlite: 'sqlite3',
mysql: 'mysql2',
postgres: 'pg',
};
return (userConfig, strapiConfig) => {
if (!clientMap[userConfig.client]) {
throw new Error(`Unsupported database client ${userConfig.client}`);
}
const knexConfig = {
...userConfig,
client: clientMap[userConfig.client],
};
if (strapiConfig?.pool?.afterCreate) {
knexConfig.pool = knexConfig.pool || {};
const userAfterCreate = knexConfig.pool?.afterCreate;
const strapiAfterCreate = strapiConfig.pool.afterCreate;
knexConfig.pool.afterCreate = (conn, done) => {
strapiAfterCreate(conn, (err, nativeConn) => {
if (err) {
return done(err, nativeConn);
}
if (userAfterCreate) {
return userAfterCreate(nativeConn, done);
}
return done(null, nativeConn);
});
};
}
return knexFactory(knexConfig);
};
})();
// ============================================
// 4. TEST ENVIRONMENT SETUP
// ============================================
// Configure Jest timeout and set required environment variables for testing
if (typeof jest !== 'undefined' && typeof jest.setTimeout === 'function') {
jest.setTimeout(30000);
}
const { createStrapi } = require('@strapi/strapi');
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
process.env.APP_KEYS = process.env.APP_KEYS || 'testKeyOne,testKeyTwo';
process.env.API_TOKEN_SALT = process.env.API_TOKEN_SALT || 'test-api-token-salt';
process.env.ADMIN_JWT_SECRET = process.env.ADMIN_JWT_SECRET || 'test-admin-jwt-secret';
process.env.TRANSFER_TOKEN_SALT = process.env.TRANSFER_TOKEN_SALT || 'test-transfer-token-salt';
process.env.ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || '0123456789abcdef0123456789abcdef';
process.env.JWT_SECRET = process.env.JWT_SECRET || 'test-jwt-secret';
process.env.DATABASE_CLIENT = process.env.DATABASE_CLIENT || 'sqlite';
process.env.DATABASE_FILENAME = process.env.DATABASE_FILENAME || ':memory:';
process.env.STRAPI_DISABLE_CRON = 'true';
process.env.PORT = process.env.PORT || '0';
const databaseClient = process.env.DATABASE_CLIENT || 'sqlite';
const clientMap = {
sqlite: 'sqlite3',
'better-sqlite3': 'sqlite3',
mysql: 'mysql2',
postgres: 'pg',
};
const driver = clientMap[databaseClient];
if (!driver) {
throw new Error(`Unsupported database client "${databaseClient}".`);
}
if (databaseClient === 'better-sqlite3') {
process.env.DATABASE_CLIENT = 'sqlite';
}
require(driver);
let instance;
// ============================================
// 5. STRAPI INSTANCE MANAGEMENT
// ============================================
// Functions to set up and tear down a Strapi instance for testing
async function setupStrapi() {
if (!instance) {
instance = await createStrapi().load();
// Register the /api/hello test route automatically
const contentApi = instance.server?.api?.('content-api');
if (contentApi && !instance.__helloRouteRegistered) {
const createHelloService = require(path.join(
__dirname,
'..',
'src',
'api',
'hello',
'services',
'hello'
));
const helloService = createHelloService({ strapi: instance });
contentApi.routes([
{
method: 'GET',
path: '/hello',
handler: async (ctx) => {
ctx.body = await helloService.getMessage();
},
config: {
auth: false,
},
},
]);
contentApi.mount(instance.server.router);
instance.__helloRouteRegistered = true;
}
await instance.start();
global.strapi = instance;
// Optionally seed example data for tests if requested
if (process.env.TEST_SEED === 'true') {
try {
const { seedExampleApp } = require(path.join(__dirname, '..', 'scripts', 'seed'));
await seedExampleApp();
} catch (e) {
console.warn('Seeding failed:', e);
}
}
// Patch the user service to automatically assign the authenticated role
const userService = strapi.plugins['users-permissions']?.services?.user;
if (userService) {
const originalAdd = userService.add.bind(userService);
userService.add = async (values) => {
const data = { ...values };
if (!data.role) {
const defaultRole = await strapi.db
.query('plugin::users-permissions.role')
.findOne({ where: { type: 'authenticated' } });
if (defaultRole) {
data.role = defaultRole.id;
}
}
return originalAdd(data);
};
}
}
return instance;
}
async function cleanupStrapi() {
if (!global.strapi) {
return;
}
const dbSettings = strapi.config.get('database.connection');
await strapi.server.httpServer.close();
await strapi.db.connection.destroy();
if (typeof strapi.destroy === 'function') {
await strapi.destroy();
}
if (dbSettings && dbSettings.connection && dbSettings.connection.filename) {
const tmpDbFile = dbSettings.connection.filename;
if (fs.existsSync(tmpDbFile)) {
fs.unlinkSync(tmpDbFile);
}
}
}
module.exports = { setupStrapi, cleanupStrapi };
```
## (optional) Seed predictable test data
Description: Export a seeding function from your project script (e.g.
(Source: https://docs.strapi.io/cms/testing#optional-seed-predictable-test-data)
Language: JavaScript
File path: ./scripts/seed.js
```js
TEST_SEED=true yarn test
```
---
Language: JavaScript
File path: ./scripts/seed.js
```js
TEST_SEED=true npm run test
```
Language: JavaScript
File path: ./scripts/seed.js
```js
async function seedExampleApp() {
// In test environment, skip complex seeding and just log
if (process.env.NODE_ENV === 'test') {
console.log('Test seeding: Skipping complex data import (not needed for basic tests)');
return;
}
const shouldImportSeedData = await isFirstRun();
if (shouldImportSeedData) {
try {
console.log('Setting up the template...');
await importSeedData();
console.log('Ready to go');
} catch (error) {
console.log('Could not import seed data');
console.error(error);
}
}
}
// Allow usage both as a CLI and as a library from tests
if (require.main === module) {
main().catch((error) => {
console.error(error);
process.exit(1);
});
}
module.exports = { seedExampleApp };
```
## Create smoke tests
Description: With the harness in place you can confirm Strapi boots correctly by adding a minimal Jest suite with the following smoke tests Smoke tests are basic tests that verify the most critical functionality works.
(Source: https://docs.strapi.io/cms/testing#create-smoke-tests)
Language: JavaScript
File path: ./tests/app.test.js
```js
const { setupStrapi, cleanupStrapi } = require('./strapi');
/** this code is called once before any test is called */
beforeAll(async () => {
await setupStrapi(); // Singleton so it can be called many times
});
/** this code is called once before all the tests are finished */
afterAll(async () => {
await cleanupStrapi();
});
it('strapi is defined', () => {
expect(strapi).toBeDefined();
});
require('./hello');
require('./user');
```
Language: YAML
File path: /app.test.js
```yaml
PASS tests/create-service.test.js
PASS tests/todo-controller.test.js
Test Suites: 6 passed, 6 total
Tests: 7 passed, 7 total
Snapshots: 0 total
Time: 7.952 s
Ran all test suites.
✨ Done in 8.63s.
```
## Test a basic API endpoint
Description: Create tests/hello.test.js with the following:
(Source: https://docs.strapi.io/cms/testing#test-a-basic-api-endpoint)
Language: JavaScript
File path: ./tests/hello.test.js
```js
const { setupStrapi, cleanupStrapi } = require('./strapi');
const request = require('supertest');
beforeAll(async () => {
await setupStrapi();
});
afterAll(async () => {
await cleanupStrapi();
});
it('should return hello world', async () => {
await request(strapi.server.httpServer)
.get('/api/hello')
.expect(200)
.then((data) => {
expect(data.text).toBe('Hello World!');
});
});
```
## Test API authentication
Description: Create tests/auth.test.js:
(Source: https://docs.strapi.io/cms/testing#test-api-authentication)
Language: JavaScript
File path: ./tests/auth.test.js
```js
const { setupStrapi, cleanupStrapi } = require('./strapi');
const request = require('supertest');
beforeAll(async () => {
await setupStrapi();
});
afterAll(async () => {
await cleanupStrapi();
});
// User mock data
const mockUserData = {
username: 'tester',
email: 'tester@strapi.com',
provider: 'local',
password: '1234abc',
confirmed: true,
blocked: null,
};
it('should login user and return JWT token', async () => {
await strapi.plugins['users-permissions'].services.user.add({
...mockUserData,
});
await request(strapi.server.httpServer)
.post('/api/auth/local')
.set('accept', 'application/json')
.set('Content-Type', 'application/json')
.send({
identifier: mockUserData.email,
password: mockUserData.password,
})
.expect('Content-Type', /json/)
.expect(200)
.then((data) => {
expect(data.body.jwt).toBeDefined();
});
});
```
## Advanced API testing with user permissions
Description: Create tests/user.test.js:
(Source: https://docs.strapi.io/cms/testing#advanced-api-testing-with-user-permissions)
Language: JavaScript
File path: ./tests/user.test.js
```js
const { setupStrapi, cleanupStrapi } = require('./strapi');
const request = require('supertest');
beforeAll(async () => {
await setupStrapi();
});
afterAll(async () => {
await cleanupStrapi();
});
let authenticatedUser = {};
// User mock data
const mockUserData = {
username: 'tester',
email: 'tester@strapi.com',
provider: 'local',
password: '1234abc',
confirmed: true,
blocked: null,
};
describe('User API', () => {
beforeAll(async () => {
await strapi.plugins['users-permissions'].services.user.add({
...mockUserData,
});
const response = await request(strapi.server.httpServer)
.post('/api/auth/local')
.set('accept', 'application/json')
.set('Content-Type', 'application/json')
.send({
identifier: mockUserData.email,
password: mockUserData.password,
});
authenticatedUser.jwt = response.body.jwt;
authenticatedUser.user = response.body.user;
});
it('should return users data for authenticated user', async () => {
await request(strapi.server.httpServer)
.get('/api/users/me')
.set('accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Authorization', 'Bearer ' + authenticatedUser.jwt)
.expect('Content-Type', /json/)
.expect(200)
.then((data) => {
expect(data.body).toBeDefined();
expect(data.body.id).toBe(authenticatedUser.user.id);
expect(data.body.username).toBe(authenticatedUser.user.username);
expect(data.body.email).toBe(authenticatedUser.user.email);
});
});
});
```
## Automate tests with GitHub Actions
Description: To go further, you can run your Jest test suite automatically on every push and pull request with .
(Source: https://docs.strapi.io/cms/testing#automate-tests-with-github-actions)
Language: DOCKERFILE
File path: ./.github/workflows/test.yaml
```dockerfile
name: 'Tests'
on:
pull_request:
push:
jobs:
run-tests:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install modules
run: npm ci
- name: Run Tests
run: npm run test
```
# TypeScript
Source: https://docs.strapi.io/cms/typescript
## Getting Started with TypeScript in Strapi
Description: Create a new TypeScript project in Strapi by running the following command in a terminal (additional details can be found in the CLI installation documentation):
(Source: https://docs.strapi.io/cms/typescript#getting-started-with-typescript-in-strapi)
Language: Bash
File path: N/A
```bash
yarn create strapi-app my-project --typescript
```
---
Language: Bash
File path: N/A
```bash
npx create-strapi-app@latest my-project --typescript
```
# Adding TypeScript support
Source: https://docs.strapi.io/cms/typescript/adding-support-to-existing-project
## Adding TypeScript support to existing Strapi projects
Description: Add a tsconfig.json file at the project root and copy the following code, with the allowJs flag, to the file:
(Source: https://docs.strapi.io/cms/typescript/adding-support-to-existing-project#adding-typescript-support-to-existing-strapi-projects)
Language: Bash
File path: N/A
```bash
yarn build
yarn develop
```
---
Language: Bash
File path: N/A
```bash
npm run build
npm run develop
```
Language: JSON
File path: ./tsconfig.json
```json
{
"extends": "@strapi/typescript-utils/tsconfigs/server",
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"allowJs": true //enables the build without .ts files
},
"include": [
"./",
"src/**/*.json"
],
"exclude": [
"node_modules/",
"build/",
"dist/",
".cache/",
".tmp/",
"src/admin/",
"**/*.test.ts",
"src/plugins/**"
]
}
```
Language: JSON
File path: ./src/admin/tsconfig.json
```json
{
"extends": "@strapi/typescript-utils/tsconfigs/admin",
"include": [
"../plugins/**/admin/src/**/*",
"./"
],
"exclude": [
"node_modules/",
"build/",
"dist/",
"**/*.test.ts"
]
}
```
Language: DOCKERFILE
File path: ./config/database.ts
```dockerfile
const path = require('path');
module.exports = ({ env }) => ({
connection: {
client: 'sqlite',
connection: {
filename: path.join(
__dirname,
"..",
"..",
env("DATABASE_FILENAME", ".tmp/data.db")
),
},
useNullAsDefault: true,
},
});
```
# TypeScript development
Source: https://docs.strapi.io/cms/typescript/development
## Use Strapi TypeScript typings
Description: Open the ./src/index.ts file from your code editor.
(Source: https://docs.strapi.io/cms/typescript/development#use-strapi-typescript-typings)
Language: TypeScript
File path: ./src/index.ts
```ts
import type { Core } from '@strapi/strapi';
export default {
register({ strapi }: { strapi: Core.Strapi }) {
// ...
},
};
```
## Generate typings for content-types schemas
Description: To use ts:generate-typesrun the following code in a terminal at the project root:
(Source: https://docs.strapi.io/cms/typescript/development#generate-typings-for-content-types-schemas)
Language: Bash
File path: ./src/index.ts
```bash
npm run strapi ts:generate-types --debug #optional flag to display additional logging
```
---
Language: Bash
File path: ./src/index.ts
```bash
yarn strapi ts:generate-types --debug #optional flag to display additional logging
```
## Fix build issues with the generated types
Description: To do that, edit the tsconfig.json of the Strapi project and add types/generated/** to the exclude array:
(Source: https://docs.strapi.io/cms/typescript/development#fix-build-issues-with-the-generated-types)
Language: JSON
File path: ./tsconfig.json
```json
// ...
"exclude": [
"node_modules/",
"build/",
"dist/",
".cache/",
".tmp/",
".strapi/",
"src/admin/",
"**/*.test.ts",
"src/plugins/**",
"types/generated/**"
]
// ...
```
## Use the strapi() factory
Description: Strapi can be run programmatically by using the strapi() factory.
(Source: https://docs.strapi.io/cms/typescript/development#use-the-createstrapi-factory)
Language: JavaScript
File path: ./server.js
```js
const strapi = require('@strapi/strapi');
const app = strapi.createStrapi({ distDir: './dist' });
app.start();
```
## Use the strapi.compile() function
Description: The strapi.compile() function should be mostly used for developing tools that need to start a Strapi instance and detect whether the project includes TypeScript code.
(Source: https://docs.strapi.io/cms/typescript/development#use-the-strapi-compile-function)
Language: JavaScript
File path: /typescript.js
```js
const strapi = require('@strapi/strapi');
strapi.compile().then(appContext => strapi(appContext).start());
```
# TypeScript - Manipulating Documents and Entries
Source: https://docs.strapi.io/cms/typescript/documents-and-entries
## Type Imports
Description: The UID namespace contains literal unions representing the available resources in the application.
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#type-imports)
Language: JavaScript
File path: N/A
```js
import type { UID } from '@strapi/strapi';
```
Language: JavaScript
File path: N/A
```js
import type { Data } from '@strapi/strapi';
```
## Generic documents
Description: When dealing with generic data, it is recommended to use non-parametrized forms of the Data types.
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#generic-documents)
Language: TypeScript
File path: N/A
```typescript
async function save(name: string, document: Data.ContentType) {
await writeCSV(name, document);
// ^ {
// id: Data.ID;
// documentId: string;
// createdAt?: DateTimeValue;
// updatedAt?: DateTimeValue;
// publishedAt?: DateTimeValue;
// ...
// }
}
```
Language: TypeScript
File path: N/A
```typescript
if ('my_prop' in document) {
return document.my_prop;
}
```
## Generic components
Description: Code example from "Generic components"
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#generic-components)
Language: Fish
File path: N/A
```fish
function renderComponent(parent: Node, component: Data.Component) {
const elements: Element[] = [];
const properties = Object.entries(component);
for (const [name, value] of properties) {
// ^ ^
// string any
const paragraph = document.createElement('p');
paragraph.textContent = `Key: ${name}, Value: ${value}`;
elements.push(paragraph);
}
parent.append(...elements);
}
```
## Known documents
Description: When manipulating known entities, it is possible to parametrize Data types for better type safety and code completion.
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#known-documents)
Language: Fish
File path: N/A
```fish
const ALL_CATEGORIES = ['food', 'tech', 'travel'];
function validateArticle(article: Data.ContentType<'api::article.article'>) {
const { title, category } = article;
// ^? ^?
// string Data.ContentType<'api::category.category'>
if (title.length < 5) {
throw new Error('Title too short');
}
if (!ALL_CATEGORIES.includes(category.name)) {
throw new Error(`Unknown category ${category.name}`);
}
}
```
## Known components
Description: When manipulating known entities, it is possible to parametrize Data types for better type safety and code completion.
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#known-components)
Language: Fish
File path: N/A
```fish
function processUsageMetrics(
id: string,
metrics: Data.Component<'app.metrics'>
) {
telemetry.send(id, { clicks: metrics.clicks, views: metrics.views });
}
```
## Entities subsets
Description: Using the types' second parameter (TKeys), it is possible to obtain a subset of an entity.
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#entities-subsets)
Language: TypeScript
File path: N/A
```typescript
type Credentials = Data.ContentType<'api::account.account', 'email' | 'password'>;
// ^? { email: string; password: string }
```
---
Language: TypeScript
File path: N/A
```typescript
type UsageMetrics = Data.Component<'app.metrics', 'clicks' | 'views'>;
// ^? { clicks: number; views: number }
```
## Type argument inference
Description: In the following example, the uid type is inferred upon usage as T and used as a type parameter for the document.
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#type-argument-inference)
Language: JavaScript
File path: N/A
```js
import type { UID } from '@strapi/strapi';
function display(
uid: T,
document: Data.ContentType
) {
switch (uid) {
case 'api::article.article': {
return document.title;
// ^? string
// ^? Data.ContentType<'api::article.article'>
}
case 'api::category.category': {
return document.name;
// ^? string
// ^? Data.ContentType<'api::category.category'>
}
case 'api::account.account': {
return document.email;
// ^? string
// ^? Data.ContentType<'api::account.account'>
}
default: {
throw new Error(`unknown content-type uid: "${uid}"`);
}
}
}
```
Language: TypeScript
File path: N/A
```typescript
declare const article: Data.Document<'api::article.article'>;
declare const category: Data.Document<'api::category.category'>;
declare const account: Data.Document<'api::account.account'>;
display('api::article.article', article);
display('api::category.category', category);
display('api::account.account', account);
// ^ ✅
display('api::article.article', category);
// ^ Error: "category" is not assignable to parameter of type ContentType<'api::article.article'>
```
# Upgrade tool
Source: https://docs.strapi.io/cms/upgrade-tool
## Upgrade to a major version
Description: Run the upgrade tool with the major parameter to upgrade the project to the next major version of Strapi:
(Source: https://docs.strapi.io/cms/upgrade-tool#upgrade-to-a-major-version)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade major
```
## Upgrade to a minor version
Description: Run the upgrade tool with the minor parameter to upgrade the project to the latest minor and patch version of Strapi:
(Source: https://docs.strapi.io/cms/upgrade-tool#upgrade-to-a-minor-version)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade minor
```
## Upgrade to a patch version
Description: Run the upgrade tool with the patch parameter to upgrade the project to the latest patch version in the current minor and major version of Strapi:
(Source: https://docs.strapi.io/cms/upgrade-tool#upgrade-to-a-patch-version)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade patch
```
## Upgrade to the latest version
Description: Run the upgrade tool with the latest parameter to upgrade the project to the latest available version regardless of the current Strapi version:
(Source: https://docs.strapi.io/cms/upgrade-tool#upgrade-to-the-latest-version)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade latest
```
## Run codemods only
Description: To view a list of the available codemods, use the ls command:
(Source: https://docs.strapi.io/cms/upgrade-tool#run-codemods-only)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade codemods ls
```
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade codemods run
```
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade codemods run 5.0.0-strapi-codemod-uid
```
## Simulate the upgrade without updating any files (dry run)
Description: Examples:
(Source: https://docs.strapi.io/cms/upgrade-tool#simulate-the-upgrade-without-updating-any-files-dry-run)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade major --dry
npx @strapi/upgrade minor --dry
npx @strapi/upgrade patch --dry
```
## Select a path for the Strapi application folder
Description: Example:
(Source: https://docs.strapi.io/cms/upgrade-tool#select-a-path-for-the-strapi-application-folder)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade major -p /path/to/the/Strapi/application/folder
```
## Get the current version
Description: Example:
(Source: https://docs.strapi.io/cms/upgrade-tool#get-the-current-version)
Language: Bash
File path: N/A
```sh
$ npx @strapi/upgrade -V
4.15.1
```
## Get detailed debugging information
Description: When passing the --debug option (or its -d shorthand), the upgrade tool provides more detailed logs while running:
(Source: https://docs.strapi.io/cms/upgrade-tool#get-detailed-debugging-information)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade --debug
```
## Execute the upgrade silently
Description: When passing the --silent option (or its -s shorthand), the tool executes the upgrade without providing any log:
(Source: https://docs.strapi.io/cms/upgrade-tool#execute-the-upgrade-silently)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade --silent
```
## Answer yes to every prompt
Description: When passing the --yes option (or its -y shorthand), the tool automatically answers "yes" to every prompt:
(Source: https://docs.strapi.io/cms/upgrade-tool#answer-yes-to-every-prompt)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade --yes`
```
## Get help
Description: Examples:
(Source: https://docs.strapi.io/cms/upgrade-tool#get-help)
Language: Bash
File path: N/A
```sh
$ npx @strapi/upgrade -h
Usage: upgrade [options]
Options:
-V, --version output the version number
-h, --help Print command line options
Commands:
major [options] Upgrade to the next available major version of Strapi
minor [options] Upgrade to ...
patch [options] Upgrade to ...
help [command] Print options for a specific command
```
---
Language: Bash
File path: N/A
```sh
$ npx @strapi/upgrade major -h
Usage: upgrade major [options]
Upgrade to the next available major version of Strapi
Options:
-p, --project-path Path to the Strapi project
-n, --dry Simulate the upgrade without updating any files (default: false)
-d, --debug Get more logs in debug mode (default: false)
-s, --silent Don't log anything (default: false)
-h, --help Display help for command
-y, --yes Automatically answer yes to every prompt
```
# Usage information
Source: https://docs.strapi.io/cms/usage-information
## Opt-out
Description: The default data collection feature can be disabled using the following CLI command:
(Source: https://docs.strapi.io/cms/usage-information#opt-out)
Language: Bash
File path: N/A
```bash
yarn strapi telemetry:disable
```
---
Language: Bash
File path: N/A
```bash
npm run strapi telemetry:disable
```