`preview` to return both draft entries & published entries (default)
| `PublicationStateParameter` |
+:::note
+For single types, "findMany" returns the entry data as an object instead of an array of entries.
+:::
+
### Example
```js
diff --git a/docusaurus/docs/dev-docs/api/entity-service/order-pagination.md b/docusaurus/docs/dev-docs/api/entity-service/order-pagination.md
index 2349ac9597..a3eca9de82 100644
--- a/docusaurus/docs/dev-docs/api/entity-service/order-pagination.md
+++ b/docusaurus/docs/dev-docs/api/entity-service/order-pagination.md
@@ -64,7 +64,22 @@ strapi.entityService.findMany('api::article.article', {
## Pagination
-To paginate results returned by the Entity Service API, you can use the `start` and `limit` parameters:
+Results can be paginated using 2 different strategies (see [REST API documentation](/dev-docs/api/rest/sort-pagination#pagination) for more details):
+
+- pagination by page, when defining the `page` and `pageSize` parameters,
+- and pagination by offset, when defining the `start` and `limit` parameters.
+
+2 different functions can be used to paginate results with the Entity Service API and accept different pagination strategies:
+
+| Function name | Possible pagination method(s) |
+| ------------- | ----------------------------------------------------------- |
+| `findMany()` | Offset pagination only |
+| `findPage()` |
Offset pagination
Page pagination
|
+
+
+
+
+`findMany()` should only be used with offset pagination:
```js
strapi.entityService.findMany('api::article.article', {
@@ -73,11 +88,35 @@ strapi.entityService.findMany('api::article.article', {
});
```
-You may instead use the `page` and `pageSize` parameters:
+
+
+
+`findPage()` accepts both offset and page pagination, provided you use only one pagination strategy per query:
+
+
+
```js
-strapi.entityService.findMany('api::article.article', {
+strapi.entityService.findPage('api::article.article', {
+ start: 10,
+ limit: 15,
+});
+```
+
+
+
+
+
+```js
+strapi.entityService.findPage('api::article.article', {
page: 1,
pageSize: 15,
});
```
+
+
+
+
+
+
+
diff --git a/docusaurus/docs/dev-docs/api/plugins/admin-panel-api.md b/docusaurus/docs/dev-docs/api/plugins/admin-panel-api.md
index 796cb4aec3..d594e0e529 100644
--- a/docusaurus/docs/dev-docs/api/plugins/admin-panel-api.md
+++ b/docusaurus/docs/dev-docs/api/plugins/admin-panel-api.md
@@ -1,21 +1,28 @@
+---
+sidebar_label: Admin Panel API
+pagination_prev: dev-docs/plugins/development/plugin-structure
+toc_max_heading_level: 4
+---
+
# Admin Panel API for plugins
-A Strapi [plugin](/dev-docs/plugins) can interact with both the [back end](/dev-docs/api/plugins/server-api) or the front end of the Strapi app. The Admin Panel API is about the front end part, i.e. it allows a plugin to customize Strapi's [admin panel](/user-docs/intro).
+A Strapi [plugin](/dev-docs/plugins) can interact with both the [back end](/dev-docs/api/plugins/server-api) and the front end of a Strapi application. The Admin Panel API is about the front end part, i.e. it allows a plugin to customize Strapi's [admin panel](/user-docs/intro).
The admin panel is a [React](https://reactjs.org/) application that can embed other React applications. These other React applications are the admin parts of each Strapi plugin.
-To create a plugin that interacts with the Admin Panel API:
-
-1. Create an [entry file](#entry-file).
-2. Within this file, declare and export a plugin interface that uses the [available actions](#available-actions).
-3. Require this plugin interface in a `strapi-admin.js` file at the root of the plugin package folder:
+:::prerequisites
+You have [created a Strapi plugin](/dev-docs/plugins/development/create-a-plugin).
+:::
- ```js title="[plugin-name]/strapi-admin.js"
+The Admin Panel API includes:
- 'use strict';
+- an [entry file](#entry-file) which exports the required interface,
+- [lifecycle functions](#lifecycle-functions) and the `registerTrad()` [async function](#async-function),
+- and several [specific APIs](#available-actions) for your plugin to interact with the admin panel.
- module.exports = require('./admin/src').default;
- ```
+:::note
+The whole code for the admin panel part of your plugin could live in the `/strapi-admin.js|ts` or `/admin/src/index.js|ts` file. However, it's recommended to split the code into different folders, just like the [structure](/dev-docs/plugins/development/plugin-structure) created by the `strapi generate plugin` CLI generator command.
+:::
## Entry file
diff --git a/docusaurus/docs/dev-docs/api/plugins/server-api.md b/docusaurus/docs/dev-docs/api/plugins/server-api.md
index 8977774658..0b02aaf45e 100644
--- a/docusaurus/docs/dev-docs/api/plugins/server-api.md
+++ b/docusaurus/docs/dev-docs/api/plugins/server-api.md
@@ -1,19 +1,31 @@
---
title: Server API for plugins
+sidebar_label: Server API
displayed_sidebar: devDocsSidebar
description: Strapi's Server API for plugins allows a Strapi plugin to customize the back end part (i.e. the server) of your application.
-sidebarDepth: 3
-
---
# Server API for plugins
-A Strapi [plugin](/dev-docs/plugins) can interact with the backend or the [frontend](/dev-docs/api/plugins/admin-panel-api) of the Strapi application. The Server API is about the backend part.
+A Strapi [plugin](/dev-docs/plugins) can interact with both the back end and the [front end](/dev-docs/api/plugins/admin-panel-api) of a Strapi application. The Server API is about the back-end part, i.e. how the plugin interacts with the server part of a Strapi application.
+
+:::prerequisites
+You have [created a Strapi plugin](/dev-docs/plugins/development/create-a-plugin).
+:::
-Creating and using a plugin interacting with the Server API consists of 2 steps:
+The Server API includes:
-1. Declare and export the plugin interface within the [`strapi-server.js` entry file](#entry-file)
-2. [Use the exported interface](#usage)
+- an [entry file](#entry-file) which export the required interface,
+- [lifecycle functions](#lifecycle-functions),
+- a [configuration](#configuration) API,
+- the ability to add [cron](#cron) jobs,
+- and the ability to [customize all elements of the back-end server](#backend-customization).
+
+Once you have declared and exported the plugin interface, you will be able to [use the plugin interface](#usage).
+
+:::note
+The whole code for the server part of your plugin could live in the `/strapi-server.js|ts` or `/server/index.js|ts` file. However, it's recommended to split the code into different folders, just like the [structure](/dev-docs/plugins/development/plugin-structure) created by the `strapi generate plugin` CLI generator command.
+:::
## Entry file
@@ -35,7 +47,7 @@ This function is called to load the plugin, before the application is [bootstrap
**Example:**
-```js title="path ./src/plugins/my-plugin/strapi-server.js"
+```js title="./src/plugins/my-plugin/strapi-server.js"
module.exports = () => ({
register({ strapi }) {
@@ -52,7 +64,7 @@ The [bootstrap](/dev-docs/configurations/functions#bootstrap) function is called
**Example:**
-```js title="path: ./src/plugins/my-plugin/strapi-server.js"
+```js title="./src/plugins/my-plugin/strapi-server.js"
module.exports = () => ({
bootstrap({ strapi }) {
@@ -69,7 +81,7 @@ The [destroy](/dev-docs/configurations/functions#destroy) lifecycle function is
**Example:**
-```js title="path: ./src/plugins/my-plugin/strapi-server.js"
+```js title="./src/plugins/my-plugin/strapi-server.js"
module.exports = () => ({
destroy({ strapi }) {
@@ -80,7 +92,7 @@ module.exports = () => ({
## Configuration
-`config` stores the default plugin configuration.
+`config` stores the default plugin configuration. It loads and validates the configuration inputted from the user within the [`./config/plugins.js` configuration file](/dev-docs/configurations/plugins).
**Type**: `Object`
@@ -91,7 +103,7 @@ module.exports = () => ({
**Example:**
-```js title="path: ./src/plugins/my-plugin/strapi-server.js or ./src/plugins/my-plugin/server/index.js"
+```js title="./src/plugins/my-plugin/strapi-server.js or ./src/plugins/my-plugin/server/index.js"
const config = require('./config');
@@ -112,11 +124,15 @@ Once defined, the configuration can be accessed:
- with `strapi.plugin('plugin-name').config('some-key')` for a specific configuration property,
- or with `strapi.config.get('plugin.plugin-name')` for the whole configuration object.
+:::tip
+Run `yarn strapi console` or `npm run strapi console` to access the strapi object in a live console.
+:::
+
## Cron
The `cron` object allows you to add cron jobs to the Strapi instance.
-```js title="path: ./src/plugins/my-plugin/strapi-server.js"
+```js title="./src/plugins/my-plugin/strapi-server.js"
module.exports = () => ({
bootstrap({ strapi }) {
strapi.cron.add({
@@ -154,6 +170,12 @@ strapi.cron.jobs
## Backend customization
+All elements of the back-end server of Strapi can be customized through a plugin using the Server API.
+
+:::prerequisites
+To better understand this section, ensure you have read through the [back-end customization](/dev-docs/backend-customization) documentation of a Strapi application.
+:::
+
### Content-types
An object with the [content-types](/dev-docs/backend-customization/models) the plugin provides.
@@ -232,7 +254,10 @@ An array of [routes](/dev-docs/backend-customization/routes) configuration.
**Type**: `Object[]`
-**Example:**
+**Examples:**
+
+
+
```js title="path: ./src/plugins/my-plugin/strapi-server.js"
@@ -265,6 +290,53 @@ module.exports = [
];
```
+
+
+
+
+It is also possible to combine both admin and Content API routes if you need different policies on these:
+
+```js title="./src/plugins/my-plugin/server/routes/index.js"
+
+module.exports = {
+ admin: require('./admin'),
+ 'content-api': require('./content-api'),
+};
+```
+
+```js title="./src/plugins/my-plugin/server/routes/admin/index.js"
+
+module.exports = {
+ type: 'admin',
+ routes: [{
+ method: 'GET',
+ path: '/model',
+ handler: 'controllerName.action',
+ config: {
+ policies: ['policyName'],
+ },
+ }],
+};
+```
+
+```js title="./src/plugins/my-plugin/server/routes/content-api/index.js"
+
+module.exports = {
+ type: 'content-api',
+ routes: [{
+ method: 'GET',
+ path: '/model',
+ handler: 'controllerName.action',
+ config: {
+ policies: ['differentPolicyName'],
+ },
+ }],
+};
+```
+
+
+
+
### Controllers
An object with the [controllers](/dev-docs/backend-customization/controllers) the plugin provides.
@@ -274,14 +346,14 @@ An object with the [controllers](/dev-docs/backend-customization/controllers) th
**Example:**
-```js title="path: ./src/plugins/my-plugin/strapi-server.js"
+```js title="./src/plugins/my-plugin/strapi-server.js"
"use strict";
module.exports = require('./server');
```
-```js title="path: ./src/plugins/my-plugin/server/index.js"
+```js title="./src/plugins/my-plugin/server/index.js"
const controllers = require('./controllers');
@@ -290,7 +362,7 @@ module.exports = () => ({
});
```
-```js title="path: ./src/plugins/my-plugin/server/controllers/index.js"
+```js title="./src/plugins/my-plugin/server/controllers/index.js"
const controllerA = require('./controller-a');
const controllerB = require('./controller-b');
@@ -301,7 +373,7 @@ module.exports = {
};
```
-```js title="path: ./src/plugins/my-plugin/server/controllers/controller-a.js"
+```js title="./src/plugins/my-plugin/server/controllers/controller-a.js"
module.exports = ({ strapi }) => ({
doSomething(ctx) {
@@ -320,14 +392,14 @@ Services should be functions taking `strapi` as a parameter.
**Example:**
-```js title="path: ./src/plugins/my-plugin/strapi-server.js"
+```js title="./src/plugins/my-plugin/strapi-server.js"
"use strict";
module.exports = require('./server');
```
-```js title="path: ./src/plugins/my-plugin/server/index.js"
+```js title="./src/plugins/my-plugin/server/index.js"
const services = require('./services');
@@ -336,7 +408,7 @@ module.exports = () => ({
});
```
-```js title="path: ./src/plugins/my-plugin/server/services/index.js"
+```js title="./src/plugins/my-plugin/server/services/index.js"
const serviceA = require('./service-a');
const serviceB = require('./service-b');
@@ -347,7 +419,7 @@ module.exports = {
};
```
-```js title="path: ./src/plugins/my-plugin/server/services/service-a.js"
+```js title="./src/plugins/my-plugin/server/services/service-a.js"
module.exports = ({ strapi }) => ({
someFunction() {
@@ -364,14 +436,14 @@ An object with the [policies](/dev-docs/backend-customization/policies) the plug
**Example:**
-```js title="path: ./src/plugins/my-plugin/strapi-server.js"
+```js title="./src/plugins/my-plugin/strapi-server.js"
"use strict";
module.exports = require('./server');
```
-```js title="path: ./src/plugins/my-plugin/server/index.js"
+```js title="./src/plugins/my-plugin/server/index.js"
const policies = require('./policies');
@@ -380,7 +452,7 @@ module.exports = () => ({
});
```
-```js title="path: ./src/plugins/my-plugin/server/policies/index.js"
+```js title="./src/plugins/my-plugin/server/policies/index.js"
const policyA = require('./policy-a');
const policyB = require('./policy-b');
@@ -391,7 +463,7 @@ module.exports = {
};
```
-```js title="path: ./src/plugins/my-plugin/server/policies/policy-a.js"
+```js title="./src/plugins/my-plugin/server/policies/policy-a.js"
module.exports = (policyContext, config, { strapi }) => {
if (ctx.state.user && ctx.state.user.isActive) {
@@ -410,50 +482,54 @@ An object with the [middlewares](/dev-docs/configurations/middlewares) the plugi
**Example:**
-```js title="path: ./src/plugins/my-plugin/strapi-server.js"
-
-"use strict";
-
-module.exports = require('./server');
-```
-
-```js title="path: ./src/plugins/my-plugin/server/index.js"
+```js title="./src/plugins/my-plugin/server/middlewares/your-middleware.js"
-const middlewares = require('./middlewares');
-module.exports = () => ({
- middlewares,
-});
+/**
+ * The your-middleware.js file
+ * declares a basic middleware function and exports it.
+ */
+'use strict';
+module.exports = async (ctx, next) => {
+ console.log("your custom logic")
+ await next();
+}
```
-```js title="path: ./src/plugins/my-plugin/server/middlewares/index.js"
+```js title="./src/plugins/my-plugin/server/middlewares/index.js"
-const middlewareA = require('./middleware-a');
-const middlewareB = require('./middleware-b');
+/**
+ * The middleware function previously created
+ * is imported from its file and
+ * exported by the middlewares index.
+ */
+'use strict';
+const yourMiddleware = require('./your-middleware');
module.exports = {
- middlewareA,
- middlewareB,
+ yourMiddleware
};
```
-```js title="path: ./src/plugins/my-plugin/server/middlewares/middleware-a.js"
+```js title="./src/plugins/my-plugin/server/register.js"
-module.exports = (options, { strapi }) => {
- return async (ctx, next) => {
- const start = Date.now();
- await next();
- const delta = Math.ceil(Date.now() - start);
+/**
+ * The middleware is called from
+ * the plugin's register lifecycle function.
+ */
+'use strict';
+const middlewares = require('./middlewares');
- strapi.log.http(`${ctx.method} ${ctx.url} (${delta} ms) ${ctx.status}`);
- };
+module.exports = ({ strapi }) => {
+ strapi.server.use(middlewares.yourMiddleware);
};
```
## Usage
-Once a plugin is exported and loaded into Strapi, its features are accessible in the code through getters. The Strapi instance (`strapi`) exposes top-level getters and global getters.
+Once a plugin is exported and loaded into Strapi, its features are accessible in the code through getters. The Strapi instance (`strapi`) exposes both top-level getters and global getters:
-While top-level getters imply chaining functions, global getters are syntactic sugar that allows direct access using a feature's uid:
+- top-level getters imply chaining functions (e.g., `strapi.plugin('the-plugin-name').controller('the-controller-name'`),
+- global getters are syntactic sugar that allows direct access using a feature's uid (e.g., `strapi.controller('plugin::plugin-name.controller-name')`).
```js
// Access an API or a plugin controller using a top-level getter
diff --git a/docusaurus/docs/dev-docs/api/query-engine.md b/docusaurus/docs/dev-docs/api/query-engine.md
index a18c6dc83a..f2ad9997ba 100644
--- a/docusaurus/docs/dev-docs/api/query-engine.md
+++ b/docusaurus/docs/dev-docs/api/query-engine.md
@@ -9,10 +9,15 @@ import BackendIntroCrosslink from '/docs/snippets/backend-custom-intro-crosslink
# Query Engine API
-The Strapi backend provides a Query Engine API to interact with the database layer at a lower level. The Query Engine API should mostly be used by plugin developers and developers adding custom business logic to their applications. In most use cases, it's recommended to use the [Entity Service API](/dev-docs/api/entity-service/) instead.
+:::prerequisites
+Before diving deeper into the Query Engine API documentation, it is recommended that you read the following introductions:
+- the [backend customization introduction](/dev-docs/backend-customization),
+- and the [Content API introduction](/dev-docs/api/content-api).
+:::
-
+The Strapi backend provides a Query Engine API to interact with the database layer at a lower level. The Query Engine API should mostly be used by plugin developers and developers adding custom business logic to their applications.
+π In most use cases, it's recommended to use the [Entity Service API](/dev-docs/api/entity-service/) instead of the Query Engine API.
:::strapi Entity Service API vs. Query Engine API
@@ -38,7 +43,12 @@ strapi.db.query('api::blog.article').findMany({ // uid syntax: 'api::api-name.co
## Available operations
-The Query Engine allows operations on database entries, such as:
+The Query Engine allows the following operations on database entries:
-- CRUD operations on [single entries](/dev-docs/api/query-engine/single-operations) or [multiple entries](/dev-docs/api/query-engine/bulk-operations)
-- [filtering entries](/dev-docs/api/query-engine/filtering), [populating relations](/dev-docs/api/query-engine/populating) and [ordering and paginating queries results](/dev-docs/api/query-engine/order-pagination)
+
+
+
+
+
+
+
diff --git a/docusaurus/docs/dev-docs/api/query-engine/populating.md b/docusaurus/docs/dev-docs/api/query-engine/populating.md
index 207340eb20..0de7963b16 100644
--- a/docusaurus/docs/dev-docs/api/query-engine/populating.md
+++ b/docusaurus/docs/dev-docs/api/query-engine/populating.md
@@ -43,24 +43,21 @@ Complex populating can also be achieved by applying `where` filters and select o
```js
strapi.db.query('api::article.article').findMany({
- populate: {
+ where: {
relationA: {
- where: {
- name: {
- $contains: 'Strapi',
- },
+ name: {
+ $contains: 'Strapi',
},
},
+ },
- repeatableComponent: {
- select: ['someAttributeName'],
- orderBy: ['someAttributeName'],
- populate: {
- componentRelationA: true,
- },
+ repeatableComponent: {
+ select: ['someAttributeName'],
+ orderBy: ['someAttributeName'],
+ populate: {
+ componentRelationA: true,
+ dynamiczoneA: true,
},
-
- dynamiczoneA: true,
},
});
```
@@ -94,4 +91,4 @@ strapi.db.query('api::article.article').findMany('api::article.article', {
},
},
});
-```
\ No newline at end of file
+```
diff --git a/docusaurus/docs/dev-docs/api/rest.md b/docusaurus/docs/dev-docs/api/rest.md
index 0ec2454dd2..68a74c8154 100644
--- a/docusaurus/docs/dev-docs/api/rest.md
+++ b/docusaurus/docs/dev-docs/api/rest.md
@@ -1,5 +1,5 @@
---
-title: REST API
+title: REST API reference
description: Interact with your Content-Types using the REST API endpoints Strapi generates for you.
displayed_sidebar: restApiSidebar
@@ -9,12 +9,16 @@ displayed_sidebar: restApiSidebar
The REST API allows accessing the [content-types](/dev-docs/backend-customization/models) through API endpoints. Strapi automatically creates [API endpoints](#endpoints) when a content-type is created. [API parameters](/dev-docs/api/rest/parameters) can be used when querying API endpoints to refine the results.
-:::note
+:::caution
All content types are private by default and need to be either made public or queries need to be authenticated with the proper permissions. See the [Quick Start Guide](/dev-docs/quick-start#step-3-set-roles--permissions), the user guide for the [Users & Permissions plugin](/user-docs/users-roles-permissions/configuring-end-users-roles), and [API tokens configuration documentation](/dev-docs/configurations/api-tokens) for more details.
:::
-:::caution
-The REST API by default does not populate any relations, media fields, components, or dynamic zones. Use the [`populate` parameter](/dev-docs/api/rest/populate-select) to populate specific fields.
+:::note
+By default, the REST API responses only include top-level fields and does not populate any relations, media fields, components, or dynamic zones. Use the [`populate` parameter](/dev-docs/api/rest/populate-select) to populate specific fields. Ensure that the find permission is given to the field(s) for the relation(s) you populate.
+:::
+
+:::strapi Upload plugin API
+The Upload plugin (which handles media found in the [Media Library](/user-docs/media-library)) has a specific API described in the [Upload plugin documentation](/dev-docs/plugins/upload).
:::
## Endpoints
@@ -85,6 +89,10 @@ For each Content-Type, the following endpoints are automatically generated:
[Components](/dev-docs/backend-customization/models#components) don't have API endpoints.
:::
+:::tip
+API endpoints are prefixed with `/api` by default. This can be changed by setting a different value for the `rest.prefix` configuration parameter (see [API calls configuration](/dev-docs/configurations/api)).
+:::
+
## Requests
Requests return a response as an object which usually includes the following keys:
@@ -239,8 +247,8 @@ While creating an entry, you can define its relations and their order (see [Mana
{
"data": {
"title": "Hello",
- "relation": 2,
- "relations": [2, 4],
+ "relation_field_a": 2,
+ "relation_field_b": [2, 4],
"link": {
"id": 1,
"type": "abc"
@@ -301,8 +309,8 @@ Fields that aren't sent in the query are not changed in the database. Send a `nu
{
"data": {
"title": "Hello",
- "relation": 2,
- "relations": [2, 4],
+ "relation_field_a": 2,
+ "relation_field_b": [2, 4],
}
}
```
diff --git a/docusaurus/docs/dev-docs/api/rest/guides/intro.md b/docusaurus/docs/dev-docs/api/rest/guides/intro.md
new file mode 100644
index 0000000000..274519021d
--- /dev/null
+++ b/docusaurus/docs/dev-docs/api/rest/guides/intro.md
@@ -0,0 +1,24 @@
+---
+title: REST API Guides
+description: Deep dive into some specific REST API topics using guides that extensively explain some use cases or give step-by-step instructions.
+pagination_prev: dev-docs/api/rest
+pagination_next: dev-docs/api/rest/guides/understanding-populate
+---
+
+# REST API Guides
+
+The [REST API reference](/dev-docs/api/rest) documentation is meant to provide a quick reference for all the endpoints and parameters available.
+
+## Guides
+
+The following guides, officially maintained by the Strapi Documentation team, cover dedicated topics and provide detailed explanations (guides indicated with π§ ) or step-by-step instructions (guides indicated with π οΈ) for some use cases:
+
+
+
+
+## Additional resources
+
+Additional tutorials and guides can be found in the following blog posts:
+
+
+
diff --git a/docusaurus/docs/dev-docs/api/rest/guides/populate-creator-fields.md b/docusaurus/docs/dev-docs/api/rest/guides/populate-creator-fields.md
new file mode 100644
index 0000000000..764ac3cacf
--- /dev/null
+++ b/docusaurus/docs/dev-docs/api/rest/guides/populate-creator-fields.md
@@ -0,0 +1,66 @@
+---
+title: How to populate creator fields
+description: Learn how to populate creator fields such as createdBy and updatedBy by creating a custom controller that leverages the populate parameter.
+---
+
+# π οΈ How to populate creator fields such as `createdBy` and `updatedBy`
+
+The creator fields `createdBy` and `updatedBy` are removed from the [REST API](/dev-docs/api/rest) response by default. These 2 fields can be returned in the REST API by activating the `populateCreatorFields` parameter at the content-type level.
+
+:::note
+
+The `populateCreatorFields` property is not available to the GraphQL API.
+
+Only the following fields will be populated: `id`, `firstname`, `lastname`, `username`, `preferedLanguage`, `createdAt`, and `updatedAt`.
+:::
+
+To add `createdBy` and `updatedBy` to the API response:
+
+1. Open the content-type `schema.json` file.
+2. Add `"populateCreatorFields": true` to the `options` object:
+
+ ```json
+ "options": {
+ "draftAndPublish": true,
+ "populateCreatorFields": true
+ },
+ ```
+
+3. Save the `schema.json`.
+4. Create a new route middleware either using the [generate CLI](/dev-docs/cli.md) or by manually creating a new file in `./src/api/[content-type-name]/middlewares/[your-middleware-name].js`
+5. Add the following piece of code, you can modify this example to suit your needs:
+
+ ```js title="./src/api/test/middlewares/defaultTestPopulate.js"
+ "use strict";
+
+ module.exports = (config, { strapi }) => {
+ return async (ctx, next) => {
+ if (!ctx.query.populate) {
+ ctx.query.populate = ["createdBy", "updatedBy"];
+ }
+
+ await next();
+ };
+ };
+ ```
+
+6. Modify your default route factory to enable this middleware on the specific routes you want this population to apply to and replacing the content-type/middleware name with yours:
+
+ ```js title="./src/api/test/routes/test.js"
+ "use strict";
+
+ const { createCoreRouter } = require("@strapi/strapi").factories;
+
+ module.exports = createCoreRouter("api::test.test", {
+ config: {
+ find: {
+ middlewares: ["api::test.default-test-populate"],
+ },
+ findOne: {
+ middlewares: ["api::test.default-test-populate"],
+ },
+ },
+ });
+ ```
+
+REST API requests with no `populate` parameter will include the `createdBy` or `updatedBy` fields by default.
diff --git a/docusaurus/docs/dev-docs/api/rest/guides/understanding-populate.md b/docusaurus/docs/dev-docs/api/rest/guides/understanding-populate.md
new file mode 100644
index 0000000000..7aa47edc76
--- /dev/null
+++ b/docusaurus/docs/dev-docs/api/rest/guides/understanding-populate.md
@@ -0,0 +1,1596 @@
+---
+title: Understanding populate
+description: Learn what populating means and how you can use the populate parameter in your REST API queries to add additional fields to your responses.
+displayed_sidebar: restApiSidebar
+toc_max_heading_level: 6
+---
+
+import QsIntroFull from '/docs/snippets/qs-intro-full.md'
+import QsForQueryTitle from '/docs/snippets/qs-for-query-title.md'
+import QsForQueryBody from '/docs/snippets/qs-for-query-body.md'
+import ScreenshotNumberReference from '/src/components/ScreenshotNumberReference.jsx';
+
+# π§ Understanding the `populate` parameter for the REST API
+
+When querying content-types with Strapi's [REST API](/dev-docs/api/rest), by default, responses only include top-level fields and do not include any relations, media fields, components, or dynamic zones.
+
+Populating in the context of the Strapi REST API means including additional content with your response by returning more fields than the ones returned by default. You use the [`populate` parameter](#population) to achieve this.
+
+:::info
+Throughout this guide, examples are built with real data queried from the server included with the [FoodAdvisor](https://github.com/strapi/foodadvisor) example application. To test examples by yourself, setup FoodAdvisor, start the server in the `/api/` folder, and ensure that proper `find` permissions are given for the queried content-types before sending your queries.
+:::
+
+The present guide will cover detailed explanations for the following use cases:
+
+- populate [all fields and relations, 1 level deep](#populate-all-relations-and-fields-1-level-deep),
+- populate [some fields and relations, 1 level deep](#populate-1-level-deep-for-specific-relations),
+- populate [some fields and relations, several levels deep](#populate-several-levels-deep-for-specific-relations),
+- populate [components](#populate-components),
+- populate [dynamic zones](#populate-dynamic-zones).
+
+:::info
+Populating several levels deep is often called "deep populate".
+:::
+
+:::strapi Advanced use case: Populating creator fields
+In addition to the various ways of using the `populate` parameter in your queries, you can also build a custom controller as a workaround to populate creator fields (e.g., `createdBy` and `updatedBy`). This is explained in the dedicated [How to populate creator fields](/dev-docs/api/rest/guides/populate-creator-fields) guide.
+:::
+
+## Populate all relations and fields, 1 level deep
+
+You can return all relations, media fields, components and dynamic zones with a single query. For relations, this will only work 1 level deep, to prevent performance issues and long response times.
+
+To populate everything 1 level deep, add the `populate=*` parameter to your query.
+
+The following diagram compares data returned by the [FoodAdvisor](https://github.com/strapi/foodadvisor) example application with and without populating everything 1 level deep:
+
+![Diagram with populate use cases with FoodAdvisor data ](/img/assets/rest-api/populate-foodadvisor-diagram1.png)
+
+Let's compare and explain what happens with and without this query parameter:
+
+### Example: Without `populate`
+
+Without the populate parameter, a `GET` request to `/api/articles` only returns the default attributes and does not return any media fields, relations, components or dynamic zones.
+
+The following example is the full response for all 4 entries from the `articles` content-types.
+
+Notice how the response only includes the `title`, `slug`, `createdAt`, `updatedAt`, `publishedAt`, and `locale` fields, and the field content of the article as handled by the CKEditor plugin (`ckeditor_content`, truncated for brevity):
+
+
+
+
+`GET /api/articles`
+
+
+
+
+
+```json
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": // truncated content
+ }
+ },
+ {
+ "id": 2,
+ "attributes": {
+ "title": "What are chinese hamburgers and why aren't you eating them?",
+ "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
+ "createdAt": "2021-11-11T13:33:19.948Z",
+ "updatedAt": "2023-06-01T14:32:50.984Z",
+ "publishedAt": "2022-09-22T12:36:48.312Z",
+ "locale": "en",
+ "ckeditor_content": // truncated content
+ }
+ },
+ {
+ "id": 3,
+ "attributes": {
+ "title": "7 Places worth visiting for the food alone",
+ "slug": "7-places-worth-visiting-for-the-food-alone",
+ "createdAt": "2021-11-12T13:33:19.948Z",
+ "updatedAt": "2023-06-02T11:30:00.075Z",
+ "publishedAt": "2023-06-02T11:30:00.075Z",
+ "locale": "en",
+ "ckeditor_content": // truncated content
+ }
+ },
+ {
+ "id": 4,
+ "attributes": {
+ "title": "If you don't finish your plate in these countries, you might offend someone",
+ "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone",
+ "createdAt": "2021-11-15T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:59:35.148Z",
+ "publishedAt": "2022-09-22T12:35:53.899Z",
+ "locale": "en",
+ "ckeditor_content": // truncated content
+ }
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+}
+```
+
+
+
+
+### Example: With `populate=*`
+
+With the `populate=*` parameter, a `GET` request to `/api/articles` also returns all media fields, first-level relations, components and dynamic zones.
+
+The following example is the full response for the first of all 4 entries from the `articles` content-types (the data from articles with ids 2, 3, and 4 is truncated for brevity).
+
+Scroll down to see that the response size is much bigger than without populate. The response now includes additional fields (see highlighted lines) such as:
+* the `image` media field (which stores all information about the article cover, including all its different formats),
+* the first-level fields of the `blocks` dynamic zone and the `seo` component,
+* the `category` relation and its fields,
+* and even some information about the articles translated in other languages, as shown by the `localizations` object.
+
+:::tip
+To populate deeply nested components, see the [populate components](#populate-components) section.
+:::
+
+
+
+
+
+`GET /api/articles?populate=*`
+
+
+
+
+
+```json {13-122}
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": // truncated content
+ "image": {
+ "data": {
+ "id": 12,
+ "attributes": {
+ "name": "Basque dish",
+ "alternativeText": "Basque dish",
+ "caption": "Basque dish",
+ "width": 758,
+ "height": 506,
+ "formats": {
+ "thumbnail": {
+ "name": "thumbnail_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg",
+ "hash": "thumbnail_basque_cuisine_17fa4567e0_f033424240",
+ "ext": ".jpeg",
+ "mime": "image/jpeg",
+ "width": 234,
+ "height": 156,
+ "size": 11.31,
+ "path": null,
+ "url": "/uploads/thumbnail_basque_cuisine_17fa4567e0_f033424240.jpeg"
+ },
+ "medium": {
+ "name": "medium_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg",
+ "hash": "medium_basque_cuisine_17fa4567e0_f033424240",
+ "ext": ".jpeg",
+ "mime": "image/jpeg",
+ "width": 750,
+ "height": 501,
+ "size": 82.09,
+ "path": null,
+ "url": "/uploads/medium_basque_cuisine_17fa4567e0_f033424240.jpeg"
+ },
+ "small": {
+ "name": "small_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg",
+ "hash": "small_basque_cuisine_17fa4567e0_f033424240",
+ "ext": ".jpeg",
+ "mime": "image/jpeg",
+ "width": 500,
+ "height": 334,
+ "size": 41.03,
+ "path": null,
+ "url": "/uploads/small_basque_cuisine_17fa4567e0_f033424240.jpeg"
+ }
+ },
+ "hash": "basque_cuisine_17fa4567e0_f033424240",
+ "ext": ".jpeg",
+ "mime": "image/jpeg",
+ "size": 58.209999999999994,
+ "url": "/uploads/basque_cuisine_17fa4567e0_f033424240.jpeg",
+ "previewUrl": null,
+ "provider": "local",
+ "provider_metadata": null,
+ "createdAt": "2021-11-23T14:05:33.460Z",
+ "updatedAt": "2021-11-23T14:05:46.084Z"
+ }
+ }
+ },
+ "blocks": [
+ {
+ "id": 2,
+ "__component": "blocks.related-articles"
+ },
+ {
+ "id": 2,
+ "__component": "blocks.cta-command-line",
+ "theme": "primary",
+ "title": "Want to give a try to a Strapi starter?",
+ "text": "β€οΈ",
+ "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git"
+ }
+ ],
+ "seo": {
+ "id": 1,
+ "metaTitle": "Articles - FoodAdvisor",
+ "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor",
+ "keywords": "food",
+ "metaRobots": null,
+ "structuredData": null,
+ "metaViewport": null,
+ "canonicalURL": null
+ },
+ "category": {
+ "data": {
+ "id": 4,
+ "attributes": {
+ "name": "European",
+ "slug": "european",
+ "createdAt": "2021-11-09T13:33:20.123Z",
+ "updatedAt": "2021-11-09T13:33:20.123Z"
+ }
+ }
+ },
+ "localizations": {
+ "data": [
+ {
+ "id": 10,
+ "attributes": {
+ "title": "Voici pourquoi il faut essayer la cuisine basque, selon un chef basque",
+ "slug": "voici-pourquoi-il-faut-essayer-la-cuisine-basque-selon-un-chef-basque",
+ "createdAt": "2021-11-18T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.606Z",
+ "publishedAt": "2022-09-22T13:00:00.069Z",
+ "locale": "fr-FR",
+ "ckeditor_content": // truncated content
+ }
+ }
+ ]
+ }
+ }
+ },
+ {
+ "id": 2,
+ // truncated content
+ },
+ {
+ "id": 3,
+ // truncated content
+ },
+ {
+ "id": 4,
+ // truncated content
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+```
+
+
+
+
+## Populate specific relations and fields
+
+You can also populate specific relations and fields, by explicitly defining what to populate. This requires that you know the name of fields and relations to populate.
+
+Relations and fields populated this way can be 1 or several levels deep. The following diagram compares data returned by the [FoodAdvisor](https://github.com/strapi/foodadvisor) example application when you populate [1 level deep](#populate-1-level-deep-for-specific-relations) vs. [2 levels deep](#populate-several-levels-deep-for-specific-relations):
+
+![Diagram with populate use cases with FoodAdvisor data ](/img/assets/rest-api/populate-foodadvisor-diagram2.png)
+
+
+Depending on your data structure, you might get similar data presented in different ways with different queries. For instance, the FoodAdvisor example application includes the article, category, and restaurant content-types that are all in relation to each other in different ways. This means that if you want to get data about the 3 content-types in a single GET request, you have 2 options:
+
+- query articles and populate categories, plus populate the nested relation between categories and restaurants ([2 levels deep population](#populate-several-levels-deep-for-specific-relations))
+- query categories and populate both articles and restaurants because categories have a 1st level relation with the 2 other content-types ([1 level deep](#populate-1-level-deep-for-specific-relations))
+
+The 2 different strategies are illustrated in the following diagram:
+
+![Diagram with populate use cases with FoodAdvisor data ](/img/assets/rest-api/populate-foodadvisor-diagram3.png)
+
+
+
+
+Populate as an object vs. populate as an array: Using the interactive query builder
+
+The syntax for advanced query parameters can be quite complex to build manually. We recommend you use our [interactive query builder](/dev-docs/api/rest/interactive-query-builder) tool to generate the URL.
+
+Using this tool, you will write clean and readable requests in a familiar (JavaScript) format, which should help you understand the differences between different queries and different ways of populating. For instance, populating 2 levels deep implies using populate as an object, while populating several relations 1 level deep implies using populate as an array:
+
+
+
+
+Populate as an object (to populate 1 relation several levels deep):
+
+```json
+{
+ populate: {
+ category: {
+ populate: ['restaurants'],
+ },
+ },
+}
+```
+
+
+
+
+Populate as an array (to populate many relations 1 level deep)
+
+```json
+{
+ populate: [
+ 'articles',
+ 'restaurants'
+ ],
+}
+
+```
+
+
+
+
+
+
+### Populate 1 level deep for specific relations
+
+You can populate specific relations 1 level deep by using the populate parameter as an array.
+
+Since the REST API uses the [LHS bracket notation](https://christiangiacomi.com/posts/rest-design-principles/#lhs-brackets) (i.e., with square brackets `[]`), the parameter syntaxes to populate 1 level deep would look like the following:
+
+| How many relations to populate | Syntax example |
+|-------------------------------|--------------------|
+| Only 1 relation | `populate[0]=a-relation-name` |
+| Several relations | `populate[0]=relation-name&populate[1]=another-relation-name&populate[2]=yet-another-relation-name` |
+
+Let's compare and explain what happens with and without populating relations 1 level deep when sending queries to the [FoodAdvisor](https://github.com/strapi/foodadvisor) example application:
+
+#### Example: Without `populate`
+
+Without the populate parameter, a `GET` request to `/api/articles` only returns the default attributes.
+
+The following example is the full response for all 4 entries from the `articles` content-type.
+
+Notice that the response does not include any media fields, relations, components or dynamic zones:
+
+
+
+
+
+
+`GET /api/articles`
+
+
+
+
+
+```json
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ }
+ },
+ {
+ "id": 2,
+ "attributes": {
+ "title": "What are chinese hamburgers and why aren't you eating them?",
+ "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
+ "createdAt": "2021-11-11T13:33:19.948Z",
+ "updatedAt": "2023-06-01T14:32:50.984Z",
+ "publishedAt": "2022-09-22T12:36:48.312Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ }
+ },
+ {
+ "id": 3,
+ "attributes": {
+ "title": "7 Places worth visiting for the food alone",
+ "slug": "7-places-worth-visiting-for-the-food-alone",
+ "createdAt": "2021-11-12T13:33:19.948Z",
+ "updatedAt": "2023-06-02T11:30:00.075Z",
+ "publishedAt": "2023-06-02T11:30:00.075Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ }
+ },
+ {
+ "id": 4,
+ "attributes": {
+ "title": "If you don't finish your plate in these countries, you might offend someone",
+ "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone",
+ "createdAt": "2021-11-15T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:59:35.148Z",
+ "publishedAt": "2022-09-22T12:35:53.899Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ }
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+}
+```
+
+
+
+
+#### Example: With `populate[0]=category`
+
+With `populate[0]=category` added to the request, we explicitly ask to include some information about `category`, which is a relation field that links the `articles` and the `categories` content-types.
+
+The following example is the full response for all 4 entries from the `articles` content-type.
+
+Notice that the response now includes additional data with the `category` field for each article (see highlighted lines):
+
+
+
+
+`GET /api/articles?populate[0]=category`
+
+
+
+
+
+```json {13-23,36-46,59-69,82-92}
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "category": {
+ "data": {
+ "id": 4,
+ "attributes": {
+ "name": "European",
+ "slug": "european",
+ "createdAt": "2021-11-09T13:33:20.123Z",
+ "updatedAt": "2021-11-09T13:33:20.123Z"
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": 2,
+ "attributes": {
+ "title": "What are chinese hamburgers and why aren't you eating them?",
+ "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
+ "createdAt": "2021-11-11T13:33:19.948Z",
+ "updatedAt": "2023-06-01T14:32:50.984Z",
+ "publishedAt": "2022-09-22T12:36:48.312Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "category": {
+ "data": {
+ "id": 13,
+ "attributes": {
+ "name": "Chinese",
+ "slug": "chinese",
+ "createdAt": "2021-11-09T13:33:20.123Z",
+ "updatedAt": "2021-11-09T13:33:20.123Z"
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": 3,
+ "attributes": {
+ "title": "7 Places worth visiting for the food alone",
+ "slug": "7-places-worth-visiting-for-the-food-alone",
+ "createdAt": "2021-11-12T13:33:19.948Z",
+ "updatedAt": "2023-06-02T11:30:00.075Z",
+ "publishedAt": "2023-06-02T11:30:00.075Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "category": {
+ "data": {
+ "id": 3,
+ "attributes": {
+ "name": "International",
+ "slug": "international",
+ "createdAt": "2021-11-09T13:33:20.123Z",
+ "updatedAt": "2021-11-09T13:33:20.123Z"
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": 4,
+ "attributes": {
+ "title": "If you don't finish your plate in these countries, you might offend someone",
+ "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone",
+ "createdAt": "2021-11-15T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:59:35.148Z",
+ "publishedAt": "2022-09-22T12:35:53.899Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "category": {
+ "data": {
+ "id": 3,
+ "attributes": {
+ "name": "International",
+ "slug": "international",
+ "createdAt": "2021-11-09T13:33:20.123Z",
+ "updatedAt": "2021-11-09T13:33:20.123Z"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+```
+
+
+
+
+### Populate several levels deep for specific relations
+
+You can also populate specific relations several levels deep. For instance, when you populate a relation which itself populates another relation, you are populating 2 levels deep. Populating 2 levels deep is the example covered in this guide.
+
+:::caution
+There is no limit on the number of levels that can be populated. However, the deeper the populates, the more the request will take time to be performed.
+:::
+
+Since the REST API uses the [LHS bracket notation](https://christiangiacomi.com/posts/rest-design-principles/#lhs-brackets), (i.e., with square brackets `[]`), for instance if you want to populate a relation nested inside another relation, the parameter syntax would look like the following:
+
+`populate[first-level-relation-to-populate][populate][0]=second-level-relation-to-populate`
+
+:::tip
+The syntax for advanced query parameters can be quite complex to build manually. We recommend you use our [interactive query builder](/dev-docs/api/rest/interactive-query-builder) tool to generate the URL. For instance, the `/api/articles?populate[category][populate][0]=restaurants` URL used in the following examples has been generated by converting the following object using our tool:
+
+```json
+{
+ populate: {
+ category: {
+ populate: ['restaurants'],
+ },
+ },
+}
+```
+
+:::
+
+The [FoodAdvisor](https://github.com/strapi/foodadvisor) example application includes various levels of relations between content-types. For instance:
+
+- an `article` content-type includes a relation with the `category` content-type,
+- but a `category` can also be assigned to any `restaurant` content-type.
+
+With a single `GET` request to `/api/articles` and the appropriate populate parameters, you can return information about articles, restaurants, and categories simultaneously.
+
+Let's compare and explain the responses returned with `populate[0]=category` (1 level deep) and `populate[category][populate][0]=restaurants` (2 levels deep) when sending queries to FoodAdvisor:
+
+#### Example: With 1-level deep population
+
+When we only populate 1 level deep, asking for the categories associated to articles, we can get the following example response (highlighted lines show the `category` relations field):
+
+
+
+
+`GET /api/articles?populate[0]=category`
+
+
+
+
+
+```json {13-23,36-46,59-69,82-92}
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "category": {
+ "data": {
+ "id": 4,
+ "attributes": {
+ "name": "European",
+ "slug": "european",
+ "createdAt": "2021-11-09T13:33:20.123Z",
+ "updatedAt": "2021-11-09T13:33:20.123Z"
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": 2,
+ "attributes": {
+ "title": "What are chinese hamburgers and why aren't you eating them?",
+ "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
+ "createdAt": "2021-11-11T13:33:19.948Z",
+ "updatedAt": "2023-06-01T14:32:50.984Z",
+ "publishedAt": "2022-09-22T12:36:48.312Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "category": {
+ "data": {
+ "id": 13,
+ "attributes": {
+ "name": "Chinese",
+ "slug": "chinese",
+ "createdAt": "2021-11-09T13:33:20.123Z",
+ "updatedAt": "2021-11-09T13:33:20.123Z"
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": 3,
+ "attributes": {
+ "title": "7 Places worth visiting for the food alone",
+ "slug": "7-places-worth-visiting-for-the-food-alone",
+ "createdAt": "2021-11-12T13:33:19.948Z",
+ "updatedAt": "2023-06-02T11:30:00.075Z",
+ "publishedAt": "2023-06-02T11:30:00.075Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "category": {
+ "data": {
+ "id": 3,
+ "attributes": {
+ "name": "International",
+ "slug": "international",
+ "createdAt": "2021-11-09T13:33:20.123Z",
+ "updatedAt": "2021-11-09T13:33:20.123Z"
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": 4,
+ "attributes": {
+ "title": "If you don't finish your plate in these countries, you might offend someone",
+ "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone",
+ "createdAt": "2021-11-15T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:59:35.148Z",
+ "publishedAt": "2022-09-22T12:35:53.899Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "category": {
+ "data": {
+ "id": 3,
+ "attributes": {
+ "name": "International",
+ "slug": "international",
+ "createdAt": "2021-11-09T13:33:20.123Z",
+ "updatedAt": "2021-11-09T13:33:20.123Z"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+```
+
+
+
+
+#### Example: With 2-level deep population
+
+When we populate 2 levels deep, asking for the categories associated to articles, but also for restaurants associated to these categories, we can get the following example response.
+
+Notice that we now have the `restaurants` relation field included with the response inside the `category` relation (see highlighted lines):
+
+
+
+
+`GET /api/articles?populate[category][populate][0]=restaurants`
+
+
+
+
+
+```json {13-56}
+{{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "category": {
+ "data": {
+ "id": 4,
+ "attributes": {
+ "name": "European",
+ "slug": "european",
+ "createdAt": "2021-11-09T13:33:20.123Z",
+ "updatedAt": "2021-11-09T13:33:20.123Z",
+ "restaurants": {
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "name": "Mint Lounge",
+ "slug": "mint-lounge",
+ "price": "p3",
+ "createdAt": "2021-11-09T14:07:47.125Z",
+ "updatedAt": "2021-11-23T16:41:30.504Z",
+ "publishedAt": "2021-11-23T16:41:30.501Z",
+ "locale": "en"
+ }
+ },
+ {
+ "id": 9,
+ // truncated content
+ },
+ {
+ "id": 10,
+ // truncated content
+ },
+ {
+ "id": 12,
+ // truncated content
+ },
+ {
+ "id": 21,
+ // truncated content
+ },
+ {
+ "id": 26,
+ // truncated content
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": 2,
+ // truncated content
+ },
+ {
+ "id": 3,
+ // truncated content
+ },
+ {
+ "id": 4,
+ // truncated content
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+```
+
+
+
+
+### Populate components
+
+Components and dynamic zones are not included in responses by default and you need to explicitly populate each dynamic zones, components, and their nested components.
+
+Since the REST API uses the [LHS bracket notation](https://christiangiacomi.com/posts/rest-design-principles/#lhs-brackets), (i.e., with square brackets `[]`), you need to pass all elements in a `populate` array. Nested fields can also be passed, and the parameter syntax could look like the following:
+
+`populate[0]=a-first-field&populate[1]=a-second-field&populate[2]=a-third-field&populate[3]=a-third-field.a-nested-field&populate[4]=a-third-field.a-nested-component.a-nested-field-within-the-component`
+
+:::tip
+The syntax for advanced query parameters can be quite complex to build manually. We recommend you use our [interactive query builder](/dev-docs/api/rest/interactive-query-builder) tool to generate the URL. For instance, the `/api/articles?populate[0]=seo&populate[1]=seo.metaSocial&populate[2]=seo.metaSocial.image` URL used in the following examples has been generated by converting the following object using our tool:
+
+```json
+{
+ populate: [
+ 'seoData',
+ 'seoData.sharedImage',
+ 'seoData.sharedImage.media',
+ ],
+},
+```
+
+:::
+
+The [FoodAdvisor](https://github.com/strapi/foodadvisor) example application includes various components and even components nested inside other components. For instance:
+
+- an `article` content-type includes a `seo` component ,
+- the `seo` component includes a nested, repeatable `metaSocial` component ,
+- and the `metaSocial` component itself has several fields, including an `image` media field .
+
+![FoodAdvisor's SEO component structure in the Content-Type Builder](/img/assets/rest-api/ctb-article-components-structure.png)
+
+By default, none of these fields or components are included in the response of a `GET` request to `/api/articles`. But with the appropriate populate parameters, you can return all of them in a single request.
+
+Let's compare and explain the responses returned with `populate[0]=seo` (1st level component) and `populate[0]=seo&populate[1]=seo.metaSocial` (2nd level component nested within the 1st level component):
+
+#### Example: Only 1st level component
+
+When we only populate the `seo` component, we go only 1 level deep, and we can get the following example response. Highlighted lines show the `seo` component.
+
+Notice there's no mention of the `metaSocial` component nested within the `seo` component:
+
+
+
+
+`GET /api/articles?populate[0]=seo`
+
+
+
+
+
+```json {13-22}
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "seo": {
+ "id": 1,
+ "metaTitle": "Articles - FoodAdvisor",
+ "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor",
+ "keywords": "food",
+ "metaRobots": null,
+ "structuredData": null,
+ "metaViewport": null,
+ "canonicalURL": null
+ }
+ }
+ },
+ {
+ "id": 2,
+ // truncated content
+ },
+ {
+ "id": 3,
+ // truncated content
+ },
+ {
+ "id": 4,
+ // truncated content
+ },
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+```
+
+
+
+
+#### Example: 1st level and 2nd level component
+
+When we populate 2 levels deep, asking both for the `seo` component and the `metaSocial` component nested inside `seo`, we can get the following example response.
+
+Notice that we now have the `metaSocial` component-related data included with the response (see highlighted lines):
+
+
+
+
+`GET /api/articles?populate[0]=seo&populate[1]=seo.metaSocial`
+
+
+
+
+
+```json {13,22-29}
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "seo": {
+ "id": 1,
+ "metaTitle": "Articles - FoodAdvisor",
+ "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor",
+ "keywords": "food",
+ "metaRobots": null,
+ "structuredData": null,
+ "metaViewport": null,
+ "canonicalURL": null,
+ "metaSocial": [
+ {
+ "id": 1,
+ "socialNetwork": "Facebook",
+ "title": "Browse our best articles about food and restaurants ",
+ "description": "Discover our articles about food, restaurants, bars and more!"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "id": 2,
+ // truncated content
+ },
+ {
+ "id": 3,
+ // truncated content
+ },
+ {
+ "id": 4,
+ // truncated content
+ },
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+```
+
+
+
+
+### Populate dynamic zones
+
+Dynamic zones are highly dynamic content structures by essence.
+When populating dynamic zones, you can choose between the following 2 strategies:
+
+| Strategy name | Use case |
+| ---------------------------------------------------- | ------------------------------------------------------------- |
+| [Shared population](#shared-population-strategy) | Apply a unique behavior to all the dynamic zone's components. |
+| [Detailed population](#detailed-population-strategy) | Explicitly define what to populate with the response. |
+
+#### Shared population strategy
+
+With the shared population strategy, you apply the same population to all the components of a dynamic zone.
+
+For instance, in the [FoodAdvisor](https://github.com/strapi/foodadvisor) example application:
+
+- A `blocks` dynamic zone exists the `article` content-type .
+- The dynamic zone includes 3 different components: `relatedArticles` , `faq` , and `CtaCommandLine` . All components have a different data structure containing various fields.
+
+![FoodAdvisor's 'blocks' dynamic zone structure in the Content-Type Builder](/img/assets/rest-api/ctb-blocks-dynamic-zone-structure.png)
+
+By default, none of these fields or components are included in the response of a `GET` request to `/api/articles`. But with the appropriate populate parameters, you can return all of them in a single request. And instead of explicitly defining all the field names to populate, you can choose to use the shared population strategy to populate all fields of all components by passing `[populate=*]`.
+
+:::tip
+The syntax for advanced query parameters can be quite complex to build manually. We recommend you use our [interactive query builder](/dev-docs/api/rest/interactive-query-builder) tool to generate the URL. For instance, the `/api/articles?populate[blocks][populate]=*` URL used in the following example has been generated by converting the following object using our tool:
+
+```json
+{
+ populate: {
+ blocks: { // asking to populate the blocks dynamic zone
+ populate: '*' // populating all first-level fields in all components
+ }
+ },
+}
+```
+
+:::
+
+Let's compare and explain the responses returned with `populate[0]=blocks` (only populating the dynamic zone) and `populate[blocks][populate]=*` (populating the dynamic zone and applying a shared population strategy to all its components):
+
+##### Example: Populating only the dynamic zone
+
+When we only populate the `blocks` dynamic zone, we go only 1 level deep, and we can get the following example response. Highlighted lines show the `blocks` dynamic zone and the 2 components it includes:
+
+
+
+
+`GET /api/articles?populate[0]=blocks`
+
+
+
+
+
+```json {13-26}
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦" // truncated content
+ "blocks": [
+ {
+ "id": 2,
+ "__component": "blocks.related-articles"
+ },
+ {
+ "id": 2,
+ "__component": "blocks.cta-command-line",
+ "theme": "primary",
+ "title": "Want to give a try to a Strapi starter?",
+ "text": "β€οΈ",
+ "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git"
+ }
+ ]
+ }
+ },
+ {
+ "id": 2,
+ // β¦
+ },
+ {
+ "id": 3,
+ // β¦
+ },
+ {
+ "id": 4,
+ // β¦
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+```
+
+
+
+
+##### Example: Populating the dynamic zone and applying a shared strategy to its components
+
+When we populate the `blocks` dynamic zone and apply a shared population strategy to all its components with `[populate]=*`, we not only include components fields but also their 1st-level relations, as shown in the highlighted lines of the following example response:
+
+
+
+
+`GET /api/articles?populate[blocks][populate]=*`
+
+
+
+
+
+```json {13-63}
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "blocks": [
+ {
+ "id": 2,
+ "__component": "blocks.related-articles",
+ "header": {
+ "id": 2,
+ "theme": "primary",
+ "label": "More, I want more!",
+ "title": "Similar articles"
+ },
+ "articles": {
+ "data": [
+ {
+ "id": 2,
+ "attributes": {
+ "title": "What are chinese hamburgers and why aren't you eating them?",
+ "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
+ "createdAt": "2021-11-11T13:33:19.948Z",
+ "updatedAt": "2023-06-01T14:32:50.984Z",
+ "publishedAt": "2022-09-22T12:36:48.312Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ }
+ },
+ {
+ "id": 3,
+ "attributes": {
+ "title": "7 Places worth visiting for the food alone",
+ "slug": "7-places-worth-visiting-for-the-food-alone",
+ "createdAt": "2021-11-12T13:33:19.948Z",
+ "updatedAt": "2023-06-02T11:30:00.075Z",
+ "publishedAt": "2023-06-02T11:30:00.075Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ }
+ },
+ {
+ "id": 4,
+ "attributes": {
+ "title": "If you don't finish your plate in these countries, you might offend someone",
+ "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone",
+ "createdAt": "2021-11-15T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:59:35.148Z",
+ "publishedAt": "2022-09-22T12:35:53.899Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ }
+ }
+ ]
+ }
+ },
+ {
+ "id": 2,
+ "__component": "blocks.cta-command-line",
+ "theme": "primary",
+ "title": "Want to give a try to a Strapi starter?",
+ "text": "β€οΈ",
+ "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git"
+ }
+ ]
+ }
+ },
+ {
+ "id": 2,
+ // β¦
+ },
+ {
+ "id": 3,
+ // β¦
+ },
+ {
+ "id": 4,
+ // β¦
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+```
+
+
+
+
+#### Detailed population strategy
+
+With the detailed population strategy, you can define per-component populate queries using the `on` property.
+
+For instance, in the [FoodAdvisor](https://github.com/strapi/foodadvisor) example application:
+
+- A `blocks` dynamic zone exists the `article` content-type .
+- The dynamic zone includes 3 different components: `relatedArticles` , `faq` , and `CtaCommandLine` . All components have a different data structure containing various fields.
+- The `relatedArticles` component has an `articles` relation with the article content-type.
+
+![FoodAdvisor's 'blocks' dynamic zone structure in the Content-Type Builder](/img/assets/rest-api/ctb-blocks-dynamic-zone-structure-2.png)
+
+By default, none of the deeply nested fields or relations are included in the response of a `GET` request to `/api/articles`. With the appropriate populate parameters and by applying a detailed population strategy, you can return precisely the data you need.
+
+:::tip
+The syntax for advanced query parameters can be quite complex to build manually. We recommend you use our [interactive query builder](/dev-docs/api/rest/interactive-query-builder) tool to generate the URL. For instance, the `/api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=*` URL used in the following example has been generated by converting the following object using our tool:
+
+```json
+{
+ populate: {
+ blocks: { // asking to populate the blocks dynamic zone
+ on: { // using a detailed population strategy to explicitly define what you want
+ 'blocks.related-articles': {
+ populate: {
+ 'articles': {
+ populate: ['image']
+ }
+ }
+ },
+ 'blocks.cta-command-line': {
+ populate: '*'
+ }
+ },
+ },
+ },
+}
+```
+
+:::
+
+Let's compare and explain the responses returned with some examples of a shared population strategy and a detailed population strategy:
+
+##### Example: Shared population strategy
+
+When we populate the `blocks` dynamic zone and apply a shared population strategy to all its components with `[populate]=*`, we not only include components fields but also their 1st-level relations.
+
+Highlighted lines show that the response include the `articles` first-level relation with the `relatedArticles` component, and also data for all types of blocks, including the `faq` and `CtaCommandLine` blocks:
+
+
+
+
+
+`GET /api/articles?populate[blocks][populate]=*`
+
+
+
+
+
+```json {23-55,108-113}
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "blocks": [
+ {
+ "id": 2,
+ "__component": "blocks.related-articles",
+ "header": {
+ "id": 2,
+ "theme": "primary",
+ "label": "More, I want more!",
+ "title": "Similar articles"
+ },
+ "articles": {
+ "data": [
+ {
+ "id": 2,
+ "attributes": {
+ "title": "What are chinese hamburgers and why aren't you eating them?",
+ "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
+ "createdAt": "2021-11-11T13:33:19.948Z",
+ "updatedAt": "2023-06-01T14:32:50.984Z",
+ "publishedAt": "2022-09-22T12:36:48.312Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ }
+ },
+ {
+ "id": 3,
+ // β¦
+ },
+ {
+ "id": 4,
+ // β¦
+ }
+ ]
+ }
+ },
+ {
+ "id": 2,
+ "__component": "blocks.cta-command-line",
+ "theme": "primary",
+ "title": "Want to give a try to a Strapi starter?",
+ "text": "β€οΈ",
+ "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git"
+ }
+ ]
+ }
+ },
+ {
+ "id": 2,
+ // β¦
+ },
+ {
+ "id": 3,
+ "attributes": {
+ "title": "7 Places worth visiting for the food alone",
+ "slug": "7-places-worth-visiting-for-the-food-alone",
+ "createdAt": "2021-11-12T13:33:19.948Z",
+ "updatedAt": "2023-06-02T11:30:00.075Z",
+ "publishedAt": "2023-06-02T11:30:00.075Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "blocks": [
+ {
+ "id": 1,
+ "__component": "blocks.related-articles",
+ "header": {
+ "id": 1,
+ "theme": "primary",
+ "label": "More, I want more!",
+ "title": "Similar articles"
+ },
+ "articles": {
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ }
+ },
+ {
+ "id": 2,
+ // β¦
+ },
+ {
+ "id": 4,
+ // β¦
+ }
+ ]
+ }
+ },
+ {
+ "id": 1,
+ "__component": "blocks.faq",
+ "title": "Frequently asked questions",
+ "theme": "muted"
+ },
+ {
+ "id": 1,
+ "__component": "blocks.cta-command-line",
+ "theme": "secondary",
+ "title": "Want to give it a try with a brand new project?",
+ "text": "Up & running in seconds π",
+ "commandLine": "npx create-strapi-app my-project --quickstart"
+ }
+ ]
+ }
+ },
+ {
+ "id": 4,
+ // β¦
+ }
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+```
+
+
+
+
+##### Example: Detailed population strategy
+
+When we populate the `blocks` dynamic zone and apply a detailed population strategy, we explicitly define which data to populate.
+
+In the following example response, highlighted lines show differences with the shared population strategy:
+
+- We deeply populate the `articles` relation of the `relatedArticles` component, and even the `image` media field of the related article.
+
+- But because we have only asked to populate everything for the `CtaCommandLine` component and have not defined anything for the `faq` component, no data from the `faq` component is returned.
+
+
+
+
+
+`GET /api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=*`
+
+
+
+
+
+```json {16-17,29-34}
+{
+ "data": [
+ {
+ "id": 1,
+ "attributes": {
+ "title": "Here's why you have to try basque cuisine, according to a basque chef",
+ "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef",
+ "createdAt": "2021-11-09T13:33:19.948Z",
+ "updatedAt": "2023-06-02T10:57:19.584Z",
+ "publishedAt": "2022-09-22T09:30:00.208Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "blocks": [
+ {
+ "id": 2,
+ "__component": "blocks.related-articles",
+ "articles": {
+ "data": [
+ {
+ "id": 2,
+ "attributes": {
+ "title": "What are chinese hamburgers and why aren't you eating them?",
+ "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them",
+ "createdAt": "2021-11-11T13:33:19.948Z",
+ "updatedAt": "2023-06-01T14:32:50.984Z",
+ "publishedAt": "2022-09-22T12:36:48.312Z",
+ "locale": "en",
+ "ckeditor_content": "β¦", // truncated content
+ "image": {
+ "data": {
+ // β¦
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": 3,
+ // β¦
+ },
+ {
+ "id": 4,
+ // β¦
+ }
+ ]
+ }
+ },
+ {
+ "id": 2,
+ "__component": "blocks.cta-command-line",
+ "theme": "primary",
+ "title": "Want to give a try to a Strapi starter?",
+ "text": "β€οΈ",
+ "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git"
+ }
+ ]
+ }
+ },
+ {
+ "id": 2,
+ // β¦
+ },
+ {
+ "id": 3,
+ "attributes": {
+ "title": "7 Places worth visiting for the food alone",
+ "slug": "7-places-worth-visiting-for-the-food-alone",
+ "createdAt": "2021-11-12T13:33:19.948Z",
+ "updatedAt": "2023-06-02T11:30:00.075Z",
+ "publishedAt": "2023-06-02T11:30:00.075Z",
+ "locale": "en",
+ "ckeditor_content": "
There is no love sincerer than the love of food
said George Bernard Shaw, and let us also add that there's arguably no better way to explore a culture than to eat voraciously while traveling. Yes, there are many five-star restaurants worth booking an entire trip for, but it's equally important to savor classic treats from across the world.
Paella in Valencia, Spain
Itβs this classic rice-based dish that first pops to mind when thinking of Spanish gastronomy. For the best there is, head to the source: Valencia. And donβt forget to scrape the bottom of the pan for heavenly bites of crunchy rice, or socarrat: the most flavorful arroz ever to land on your palate.
Nasi Lemak in Malaysia
Malaysiaβs national dish, nasi lemak is a fragrant coconut-milk rice mixture, served with sambal sauce, fried crispy anchovies, toasted peanuts, and cucumber and cooked with screw pine (pandan) leaves. Available on almost every street corner, this much-loved classic hits all the notes.
Pintxos in San SebastiΓ‘n, Spain
Among the most highly ranked cities for Michelin-starred restaurants, San SebastiΓ‘n boasts pintxos (the equivalent of small tapas) with ΓΌber-creative takes on classics and beyond. Spainβs haute cuisine shines in this culinary paradise on the Basque coast.
Pastel de Nata in Lisbon
The most iconic Portuguese pastry, the pastel de nata is a sublime custard tart with hints of lemon, cinnamon, and vanilla. Buttery goodness in the middle, crunchy sweetness on topβwhatβs not to love?
Mole in Puebla, Mexico
Mole, a specialty in the Mexican city of Puebla, is a labor of love. The spicy-sweet combination of this rich, chocolate-colored sauce takes arduous preparation and packs ingredients such as ancho chiles, spices like anise and coriander, sesame seeds, almonds, peanuts, stale bread, brown sugar, raisins, chocolate, and ripe plantains. The culminating dish is fit for the gods.
Sichuan Hot Pot in China
This isnβt for the faint of heart. But if youβre an extreme spice lover, youβll welcome the tears that come from the hot potβs perfect nexus of pain and pleasure.
Tagine in Morocco
This slow-cooked savory stew, typically made with sliced meat, poultry, or fish and lots of herbs and spices, is true Moroccan soul food. Cooked for hours in a clay cooking pot with a conical lid (known as a tagine), this irresistible dish is served with couscous or bread and can be found all over Morocco.
",
+ "blocks": [
+ {
+ "id": 1,
+ "__component": "blocks.related-articles",
+ "articles": {
+ // β¦
+ }
+ },
+ {
+ "id": 1,
+ "__component": "blocks.cta-command-line",
+ "theme": "secondary",
+ "title": "Want to give it a try with a brand new project?",
+ "text": "Up & running in seconds π",
+ "commandLine": "npx create-strapi-app my-project --quickstart"
+ }
+ ]
+ }
+ },
+ {
+ "id": 4,
+ // β¦
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "page": 1,
+ "pageSize": 25,
+ "pageCount": 1,
+ "total": 4
+ }
+ }
+}
+```
+
+
+
+
diff --git a/docusaurus/docs/dev-docs/api/rest/parameters.md b/docusaurus/docs/dev-docs/api/rest/parameters.md
index 6d97f9bfcf..b453f338c1 100644
--- a/docusaurus/docs/dev-docs/api/rest/parameters.md
+++ b/docusaurus/docs/dev-docs/api/rest/parameters.md
@@ -28,3 +28,12 @@ Query parameters use the [LHS bracket syntax](https://christiangiacomi.com/posts
:::tip
A wide range of REST API parameters can be used and combined to query your content, which can result in long and complex query URLs. π You can use Strapi's [interactive query builder](/dev-docs/api/rest/interactive-query-builder) tool to build query URLs more conveniently. π€
:::
+
+:::warning
+In Strapi 4.13+, sending invalid query parameters will result in an error status instead of ignoring them. Please ensure that you are only querying fields that:
+- are in the correct format for the parameter
+- are not private or password fields
+- you have read permission on
+
+If you need your API to have the old behavior of ignoring invalid parameters, you will need to customize your controller to only sanitize and not validate.
+:::
diff --git a/docusaurus/docs/dev-docs/api/rest/populate-select.md b/docusaurus/docs/dev-docs/api/rest/populate-select.md
index 8ceea53a1d..3b787f6bd5 100644
--- a/docusaurus/docs/dev-docs/api/rest/populate-select.md
+++ b/docusaurus/docs/dev-docs/api/rest/populate-select.md
@@ -11,7 +11,7 @@ import QsForQueryBody from '/docs/snippets/qs-for-query-body.md'
# REST API: Population & Field Selection
-The [REST API](/dev-docs/api/rest) by default does not populate any relations, media fields, components, or dynamic zones. Use the [`populate` parameter](#population) to populate specific fields and the [`select` parameter](#field-selection) to return only specific fields with the query results.
+The [REST API](/dev-docs/api/rest) by default does not populate any relations, media fields, components, or dynamic zones. Use the [`populate` parameter](#population) to populate specific fields and the [`select` parameter](#field-selection) to return only specific fields with the query results. Ensure that the find permission is given to the field(s) for the relation(s) you populate.
:::tip
@@ -93,646 +93,48 @@ await request(`/api/users?${query}`);
-
## Population
-Queries can accept a `populate` parameter to populate various field types:
-
-- [relations & media fields](#relations--media-fields)
-- [components & dynamic zones](#components--dynamic-zones)
-- [creator fields](#populating-createdby-and-updatedby)
-
-It is also possible to [combine population with multiple operators](#combining-population-with-other-operators) among various other operators to have much more control over the population.
-
-:::note
-
-- By default Strapi will not populate any type of fields.
-- It's currently not possible to return just an array of IDs. This is something that is currently under discussion.
-
-:::
+The REST API by default does not populate any type of fields, so it will not populate relations, media fields, components, or dynamic zones unless you pass a `populate` parameter to populate various field types.
-### Relations & Media fields
-
-Queries can accept a `populate` parameter to explicitly define which fields to populate, with the following syntax option examples.
+The `populate` parameter can be used alone or [in combination with with multiple operators](#combining-population-with-other-operators) to have much more control over the population.
:::caution
-If the Users & Permissions plugin is installed, the `find` permission must be enabled for the content-types that are being populated. If a role doesn't have access to a content-type it will not be populated.
+The `find` permission must be enabled for the content-types that are being populated. If a role doesn't have access to a content-type it will not be populated (see [User Guide](/user-docs/users-roles-permissions/configuring-end-users-roles#editing-a-role) for additional information on how to enable `find` permissions for content-types).
:::
-
-
-
-#### Populate 1 level for all relations
-
-To populate one-level deep for all relations, use the `*` wildcard in combination with the `populate` parameter.
-
-
-
-
-
-
-
-
-`GET /api/articles?populate=*`
-
-
-
-
-
-```json
-{
- "data": [
- {
- "id": 1,
- "attributes": {
- "title": "Test Article",
- "slug": "test-article",
- "body": "Test 1",
- // ...
- "headerImage": {
- "data": {
- "id": 1,
- "attributes": {
- "name": "17520.jpg",
- "alternativeText": "17520.jpg",
- "formats": {
- // ...
- }
- // ...
- }
- }
- },
- "author": {
- // ...
- },
- "categories": {
- // ...
- }
- }
- }
- ],
- "meta": {
- // ...
- }
-}
-```
-
-
-
-
-
-
-
-
-
-```js
-const qs = require('qs');
-const query = qs.stringify(
- {
- populate: '*',
- },
- {
- encodeValuesOnly: true, // prettify URL
- }
-);
-
-await request(`/api/articles?${query}`);
-```
-
-
-
-
-
-
-
-
-
-#### Populate 1 level
-
-To populate only specific relations one-level deep, use one of the following method:
-
-- Use the populate parameter as an array and put the relation name inside.
-- Use the populate parameter as an object (using [LHS bracket notation](https://christiangiacomi.com/posts/rest-design-principles/#lhs-brackets), i.e., with square brackets `[]`)) and put the relation name as a key with one of the following values: `true, false, t, f, 1, 0`.
-
-
-
-
-
-
-
-
-`GET /api/articles?populate[0]=categories`
-
-
-
-
-
-```json
-{
- "data": [
- {
- "id": 1,
- "attributes": {
- "title": "Test Article",
- // ...
- "categories": {
- "data": [
- {
- "id": 1,
- "attributes": {
- "name": "Food"
- // ...
- }
- }
- ]
- }
- }
- }
- ],
- "meta": {
- // ...
- }
-}
-```
-
-
-
-
-
-
-
-
-
-```js
-// Array method
-const qs = require('qs');
-const query = qs.stringify(
- {
- populate: ['categories'],
- },
- {
- encodeValuesOnly: true, // prettify URL
- }
-);
-await request(`/api/articles?${query}`);
-```
-
-```js
-// Object method
-const qs = require('qs');
-const query = qs.stringify(
- {
- populate: {
- categories: true
- }
- },
- {
- encodeValuesOnly: true // prettify URL
- }
-);
-
-await request(`/api/articles?${query}`);
-```
-
-
-
-
-
-
-
-
-
-#### Populate 2 levels
-
-To populate specific relations, one or several levels deep, use the [LHS bracket notation](https://christiangiacomi.com/posts/rest-design-principles/#lhs-brackets) (i.e., with square brackets `[]`) for fields names in combination with the `populate` parameter.
-
-
-
:::note
-There is no limit on the number of levels that can be populated. However, the more nested populates there are, the more the request will take time to be performed.
+It's currently not possible to return just an array of ids with a request.
:::
-
-
-
-
-
-
-
-`GET /api/articles?populate[author][populate][0]=company`
-
-
+:::strapi Populating guides
-
+The [REST API guides](/dev-docs/api/rest/guides/intro) section includes more detailed information about various possible use cases for the populate parameter:
-```json
-{
- "data": [
- {
- "id": 1,
- "attributes": {
- "title": "Test Article",
- // ...
- "author": {
- "data": {
- "id": 1,
- "attributes": {
- "name": "Kai Doe",
- // ...
- "company": {
- "data": {
- "id": 1,
- "attributes": {
- "name": "Strapi"
- // ...
- }
- }
- }
- }
- }
- }
- }
- }
- ],
- "meta": {
- // ...
- }
-}
-```
-
-
-
-
-
-
-
-
-
-```js
-const qs = require('qs');
-const query = qs.stringify(
- {
- populate: {
- author: {
- populate: ['company'],
- },
- },
- },
- {
- encodeValuesOnly: true, // prettify URL
- }
-);
-await request(`/api/articles?${query}`);
-```
-
-
-
-
-
+- The [Understanding populate](/dev-docs/api/rest/guides/understanding-populate) guide explains in details how populate works, with diagrams, comparisons, and real-world examples.
+- The [How to populate creator fields](/dev-docs/api/rest/guides/populate-creator-fields) guide provides step-by-step instructions on how to add `createdBy` and `updatedBy` fields to your queries responses.
-### Components & Dynamic Zones
-
-The `populate` parameter is used to explicitly define which Dynamic zones, components, and nested components to populate.
-
-
-
-
-#### Example: Deeply populate a 2-level component & media
+The Strapi Blog also includes a tutorial on [how to populate and filter data with your queries](https://strapi.io/blog/demystifying-strapi-s-populate-and-filtering).
+:::
-To populate a 2-level component & its media, you need to explicitly ask for each element with the `populate` parameter, passing all elements in an array.
+The following table sums up possible populate use cases and their associated parameter syntaxes, and links to sections of the Understanding populate guide which includes more detailed explanations:
-
+| Use case | Example parameter syntax | Detailed explanations to read |
+|-----------| ---------------|-----------------------|
+| Populate everything, 1 level deep, including media fields, relations, components, and dynamic zones | `populate=*`| [Populate all relations and fields, 1 level deep](/dev-docs/api/rest/guides/understanding-populate#populate-all-relations-and-fields-1-level-deep) |
+| Populate one relation, 1 level deep | `populate[0]=a-relation-name`| [Populate 1 level deep for specific relations](/dev-docs/api/rest/guides/understanding-populate#populate-1-level-deep-for-specific-relations) |
+| Populate several relations, 1 level deep | `populate[0]=relation-name&populate[1]=another-relation-name&populate[2]=yet-another-relation-name`| [Populate 1 level deep for specific relations](/dev-docs/api/rest/guides/understanding-populate#populate-1-level-deep-for-specific-relations) |
+| Populate some relations, several levels deep | `populate[first-level-relation-to-populate][populate][0]=second-level-relation-to-populate`| [Populate several levels deep for specific relations](/dev-docs/api/rest/guides/understanding-populate#populate-several-levels-deep-for-specific-relations) |
+| Populate a component | `populate[0]=component-name`| [Populate components](/dev-docs/api/rest/guides/understanding-populate#populate-components) |
+| Populate a component and one of its nested components | `populate[0]=component-name&populate[1]=component-name.nested-component-name`| [Populate components](/dev-docs/api/rest/guides/understanding-populate#populate-components) |
+| Populate a dynamic zone (only its first-level elements) | `populate[0]=dynamic-zone-name`| [Populate dynamic zones](/dev-docs/api/rest/guides/understanding-populate#populate-dynamic-zones) |
+| Populate a dynamic zone and its nested elements and relations, using a unique, shared population strategy | `populate[dynamic-zone-name][populate]=*`| [Populate dynamic zones](/dev-docs/api/rest/guides/understanding-populate#shared-population-strategy) |
+| Populate a dynamic zone and its nested elements and relations, using a precisely defined, detailed population strategy | `populate[dynamic-zone-name][on][dynamic-zone-name.component-name][populate][relation-name][populate][0]=field-name`| [Populate dynamic zones](/dev-docs/api/rest/guides/understanding-populate#detailed-population-strategy) |
:::tip
The easiest way to build complex queries with multiple-level population is to use our [interactive query builder](/dev-docs/api/rest/interactive-query-builder) tool.
:::
-
-
-
-
-
-
-
-
-`GET /api/articles?populate[0]=seoData&populate[1]=seoData.sharedImage&populate[2]=seoData.sharedImage.media`
-
-
-
-
-
-```json
-{
- "data": [
- {
- "id": 1,
- "attributes": {
- "title": "Test Article",
- // ...
- "seoData": {
- "id": 1,
- "metaTitle": "Test Article",
- // ...
- "sharedImage": {
- "id": 1,
- "alt": "starSky",
- "media": {
- "data": [
- {
- "id": 1,
- "attributes": {
- "name": "17520.jpg",
- "formats": {
- // ...
- },
- // ...
- }
- }
- ]
- }
- }
- }
- }
- }
- ],
- "meta": {
- // ...
-}
-```
-
-
-
-
-
-
-
-
-
-```js
-const qs = require('qs');
-const query = qs.stringify(
- {
- populate: [
- 'seoData',
- 'seoData.sharedImage',
- 'seoData.sharedImage.media',
- ],
- },
- {
- encodeValuesOnly: true, // prettify URL
- }
-);
-
-await request(`/api/articles?${query}`);
-```
-
-
-
-
-
-
-
-
-
-#### Example: Deeply populate a dynamic zone with 2 components
-
-Dynamic zones are highly dynamic content structures by essence.
-When populating dynamic zones, you can choose between a shared population strategy or a detailed population strategy.
-
-In a shared population strategy, apply a unique behavior for all the dynamic zone's components.
-
-
-
-
-
-
-
-
-`GET /api/articles?populate[testDZ][populate]=*`
-
-
-
-
-
-```json
-{
- "data": [
- {
- "id": 1,
- "attributes": {
- "testString": "test1",
- // ...
- "testDZ": [
- {
- "id": 3,
- "__component": "test.test-compo",
- "testString": "test1",
- "testNestedCompo": {
- "id": 3,
- "testNestedString": "testNested1"
- },
- "otherField": "test"
- },
- {
- "id": 1,
- "__component": "test.test-compo2",
- "testInt": 1,
- "otherField": "test"
- }
- ]
- }
- }
- ],
- "meta": {
- // ...
- }
-}
-```
-
-
-
-
-
-
-
-
-
-```js
-const qs = require('qs');
-const query = qs.stringify(
- {
- populate: {
- testDZ: {
- populate: '*',
- },
- },
- },
- {
- encodeValuesOnly: true, // prettify URL
- }
-);
-
-await request(`/api/articles?${query}`);
-```
-
-
-
-
-
-
-
-
-With the detailed population strategy, define per-component populate queries using the `on` property.
-
-
-
-
-
-
-
-
-
-`GET /api/articles?populate[testDz][on][test.test-compo][fields][0]=testString&populate[testDz][on][test.test-compo][populate]=*&populate[testDz][on][test.test-compo2][fields][0]=testInt`
-
-
-
-
-
-```json
-{
- "data": [
- {
- "id": 1,
- "attributes": {
- "testString": "test1",
- // ...
- "testDZ": [
- {
- "id": 3,
- "__component": "test.test-compo",
- "testString": "test1",
- "testNestedCompo": {
- "testNestedString": "testNested1"
- }
- },
- {
- "id": 1,
- "__component": "test.test-compo2",
- "testInt": 1
- }
- ]
- }
- }
- ],
- "meta": {
- // ...
- }
-}
-```
-
-
-
-
-
-
-
-
-
-```js
-const qs = require('qs');
-const query = qs.stringify(
- {
- populate: {
- testDz: {
- on: {
- 'test.test-compo': {
- fields: ['testString'],
- populate: '*',
- },
- 'test.test-compo2': {
- fields: ['testInt'],
- },
- },
- },
- },
- },
- {
- encodeValuesOnly: true, // prettify URL
- }
-);
-
-await request(`/api/articles?${query}`);
-```
-
-
-
-
-
-
-### Populating createdBy and updatedBy
-
-The creator fields `createdBy` and `updatedBy` are removed from the REST API response by default. The `createdBy` and `updatedBy` fields can be returned in the REST API by activating the `populateCreatorFields` parameter at the content-type level.
-
-To add `createdBy` and `updatedBy` to the API response:
-
-1. Open the content-type `schema.json` file.
-2. Add `"populateCreatorFields": true` to the `options` object:
-
- ```json
- "options": {
- "draftAndPublish": true,
- "populateCreatorFields": true
- },
- ```
-
-3. Save the `schema.json`.
-4. Open the controller `[collection-name].js` file inside the corresponding API request.
-5. Add the following piece of code, and make sure you replace the `[collection-name].js` with proper collection name:
-
- ```js
- 'use strict';
- /**
- * [collection-name] controller
- */
- const { createCoreController } = require('@strapi/strapi').factories;
- module.exports = createCoreController('api::[collection-name].[collection-name]', ({ strapi }) => ({
- async find(ctx) {
- // Calling the default core action
- const { data, meta } = await super.find(ctx);
- const query = strapi.db.query('api::[collection-name].[collection-name]');
- await Promise.all(
- data.map(async (item, index) => {
- const foundItem = await query.findOne({
- where: {
- id: item.id,
- },
- populate: ['createdBy', 'updatedBy'],
- });
-
- data[index].attributes.createdBy = {
- id: foundItem.createdBy.id,
- firstname: foundItem.createdBy.firstname,
- lastname: foundItem.createdBy.lastname,
- };
- data[index].attributes.updatedBy = {
- id: foundItem.updatedBy.id,
- firstname: foundItem.updatedBy.firstname,
- lastname: foundItem.updatedBy.lastname,
- };
- })
- );
- return { data, meta };
- },
- }));
- ```
-
-REST API requests using the `populate` parameter that include the `createdBy` or `updatedBy` fields will now populate these fields.
-
-:::note
-
-The `populateCreatorFields` property is not available to the GraphQL API.
-:::
-
### Combining Population with other operators
By utilizing the `populate` operator it is possible to combine other operators such as [field selection](/dev-docs/api/rest/populate-select#field-selection), [filters](/dev-docs/api/rest/filters-locale-publication), and [sort](/dev-docs/api/rest/sort-pagination) in the population queries.
@@ -741,18 +143,10 @@ By utilizing the `populate` operator it is possible to combine other operators s
The population and pagination operators cannot be combined.
:::
-
-
-
#### Populate with field selection
`fields` and `populate` can be combined.
-
-
-
-
-
@@ -817,21 +211,10 @@ await request(`/api/articles?${query}`);
-
-
-
-
-
-
#### Populate with filtering
`filters` and `populate` can be combined.
-
-
-
-
-
@@ -901,6 +284,3 @@ await request(`/api/articles?${query}`);
```
-
-
-
diff --git a/docusaurus/docs/dev-docs/api/rest/relations.md b/docusaurus/docs/dev-docs/api/rest/relations.md
index cc0e3d85f5..f076a9b2d6 100644
--- a/docusaurus/docs/dev-docs/api/rest/relations.md
+++ b/docusaurus/docs/dev-docs/api/rest/relations.md
@@ -335,8 +335,8 @@ As `set` replaces all existing relations, it should not be used in combination w
:::note Omitting set
Omitting any parameter is equivalent to using `set`. For instance, the following 3 syntaxes are all equivalent:
-- `data: { categories: set: [{ id: 2 }, { id: 4 }] }}`
-- `data: { categories: set: [2, 4] }}`
+- `data: { categories: { set: [{ id: 2 }, { id: 4 }] }}`
+- `data: { categories: { set: [2, 4] }}`
- `data: { categories: [2, 4] }` (as used in the [REST API documentation](/dev-docs/api/rest#update-an-entry))
:::
diff --git a/docusaurus/docs/dev-docs/api/rest/sort-pagination.md b/docusaurus/docs/dev-docs/api/rest/sort-pagination.md
index 2b0213e321..c2c0483f2d 100644
--- a/docusaurus/docs/dev-docs/api/rest/sort-pagination.md
+++ b/docusaurus/docs/dev-docs/api/rest/sort-pagination.md
@@ -188,8 +188,7 @@ Queries can accept `pagination` parameters. Results can be paginated:
Pagination methods can not be mixed. Always use either `page` with `pageSize` **or** `start` with `limit`.
:::
-
-
+
### Pagination by page
@@ -199,7 +198,15 @@ To paginate results by page, use the following parameters:
| ----------------------- | ------- | ------------------------------------------------------------------------- | ------- |
| `pagination[page]` | Integer | Page number | 1 |
| `pagination[pageSize]` | Integer | Page size | 25 |
-| `pagination[withCount]` | Boolean | Adds the total numbers of entries and the number of pages to the response | True |
+| `pagination[withCount]` | Boolean | Adds the total numbers of entries and the number of pages to the response | true |
+
+
+
+
+
+**Example:**
+
+Using the `pagination[page]` and `pagination[pageSize]` parameters you can get results paginated by page:
@@ -257,8 +264,6 @@ await request(`/api/articles?${query}`);
-
-
### Pagination by offset
@@ -274,6 +279,13 @@ To paginate results by offset, use the following parameters:
The default and maximum values for `pagination[limit]` can be [configured in the `./config/api.js`](/dev-docs/configurations/api) file with the `api.rest.defaultLimit` and `api.rest.maxLimit` keys.
:::
+
+
+
+**Example:**
+
+Using the `pagination[start]` and `pagination[limit]` parameters you can get results paginated by offset:
+
diff --git a/docusaurus/docs/dev-docs/backend-customization.md b/docusaurus/docs/dev-docs/backend-customization.md
index 3350d112a9..f5c3cb70a7 100644
--- a/docusaurus/docs/dev-docs/backend-customization.md
+++ b/docusaurus/docs/dev-docs/backend-customization.md
@@ -38,11 +38,14 @@ Both global and route middlewares include an asynchronous callback function, `aw
* If a middleware returns nothing, the request will continue travelling through the various core elements of the back end (i.e., controllers, services, and the other layers that interact with the database).
* If a middleware returns before calling `await next()`, a response will be immediately sent, skipping the rest of the core elements. Then it will go back down the same chain it came up.
-
:::info
Please note that all customizations described in the pages of this section are only for the REST API. [GraphQL customizations](/dev-docs/plugins/graphql#customization) are described in the GraphQL plugin documentation.
:::
+:::tip Learn by example
+If you prefer learning by reading examples and understanding how they can be used in real-world use cases, the [Examples cookbook](/dev-docs/backend-customization/examples) section is another way at looking how the Strapi back end customization works.
+:::
+
## Interactive diagram
The following diagram represents how requests travel through the Strapi back end. You can click on any shape to jump to the relevant page in the documentation.
diff --git a/docusaurus/docs/dev-docs/backend-customization/controllers.md b/docusaurus/docs/dev-docs/backend-customization/controllers.md
index cf6f81f3bb..8f66373c0e 100644
--- a/docusaurus/docs/dev-docs/backend-customization/controllers.md
+++ b/docusaurus/docs/dev-docs/backend-customization/controllers.md
@@ -13,6 +13,12 @@ In most cases, the controllers will contain the bulk of a project's business log
The diagram represents a simplified version of how a request travels through the Strapi back end, with controllers highlighted. The backend customization introduction page includes a complete, interactive diagram.
+
+
+:::caution
+Before deciding to customize core controllers, please consider creating custom route middlewares (see [routes documentation](/dev-docs/backend-customization/routes)).
+:::
+
## Implementation
Controllers can be [generated or added manually](#adding-a-new-controller). Strapi provides a `createCoreController` factory function that automatically generates core controllers and allows building custom ones or [extend or replace the generated controllers](#extending-core-controllers).
@@ -30,42 +36,75 @@ A new controller can be implemented:
```js title="./src/api/restaurant/controllers/restaurant.js"
-
-const { createCoreController } = require('@strapi/strapi').factories;
-
-module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({
- // Method 1: Creating an entirely custom action
- async exampleAction(ctx) {
- try {
- ctx.body = 'ok';
- } catch (err) {
- ctx.body = err;
- }
- },
-
- // Method 2: Wrapping a core action (leaves core logic in place)
- async find(ctx) {
- // some custom logic here
- ctx.query = { ...ctx.query, local: 'en' }
-
- // Calling the default core action
- const { data, meta } = await super.find(ctx);
-
- // some more custom logic
- meta.date = Date.now()
-
- return { data, meta };
- },
-
- // Method 3: Replacing a core action with proper sanitization
- async find(ctx) {
- const sanitizedQueryParams = await this.sanitizeQuery(ctx);
- const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams);
- const sanitizedResults = await this.sanitizeOutput(results, ctx);
-
- return this.transformResponse(sanitizedResults, { pagination });
- }
-}));
+const { createCoreController } = require("@strapi/strapi").factories;
+
+module.exports = createCoreController(
+ "api::restaurant.restaurant",
+ ({ strapi }) => ({
+ /**
+ * Example 1: Modifying a Strapi controller function
+ *
+ * If you need to modify the input or output of a pre-defined Strapi controller method,
+ * write a method of the same name, and use `super` to call the parent method.
+ * */
+ async find(ctx) {
+ // your custom logic for modifying the input
+ ctx.query = { ...ctx.query, locale: "en" }; // force ctx.query.locale to 'en' regardless of what was requested
+
+ // Call the default parent controller action
+ const result = await super.find(ctx);
+
+ // your custom logic for modifying the output
+ result.meta.date = Date.now(); // change the date that is returned
+
+ return result;
+ },
+
+ /**
+ * Example 2: Replacing a Strapi controller function
+ *
+ * If you need to completely replace the behavior of a pre-defined Strapi controller method,
+ * you can do so by simply implementing a method of the same name.
+ *
+ * Caution: You will need to manage the security of the request and results on your own,
+ * as demonstrated in this example.
+ * */
+ async find(ctx) {
+ // validateQuery throws an error if any of the query params used are inaccessible to ctx.user
+ // That is, trying to access private fields, fields they don't have permission for, wrong data type, etc
+ await this.validateQuery(ctx);
+
+ // sanitizeQuery silently removes any query params that are invalid or the user does not have access to
+ // It is recommended to use sanitizeQuery even if validateQuery is used, as validateQuery allows
+ // a number of non-security-related cases such as empty objects in string fields to pass, while sanitizeQuery
+ // will remove them completely
+ const sanitizedQueryParams = await this.sanitizeQuery(ctx);
+
+ // Perform whatever custom actions are needed
+ const { results, pagination } = await strapi
+ .service("api::restaurant.restaurant")
+ .find(sanitizedQueryParams);
+
+ // sanitizeOutput removes any data that was returned by our query that the ctx.user should not have access to
+ const sanitizedResults = await this.sanitizeOutput(results, ctx);
+
+ // transformResponse correctly formats the data and meta fields of your results to return to the API
+ return this.transformResponse(sanitizedResults, { pagination });
+ },
+
+ /**
+ * Example 3: Writing your own new controller function
+ * If you need to create some new action that does not match one of the pre-configured Strapi methods,
+ * you can simply add the method with the desired name and implement whatever functionality you want.
+ *
+ * Caution: Similar to replacing a controller, you will need to manage the security of the request
+ * yourself, so remember to use sanitizers and validators as needed.
+ * */
+ async healthCheck(ctx) {
+ ctx.body = "ok";
+ },
+ })
+);
```
@@ -73,42 +112,79 @@ module.exports = createCoreController('api::restaurant.restaurant', ({ strapi })
```js title="./src/api/restaurant/controllers/restaurant.ts"
-
-import { factories } from '@strapi/strapi';
-
-export default factories.createCoreController('api::restaurant.restaurant', ({ strapi }) => ({
- // Method 1: Creating an entirely custom action
- async exampleAction(ctx) {
- try {
- ctx.body = 'ok';
- } catch (err) {
- ctx.body = err;
- }
- },
-
- // Method 2: Wrapping a core action (leaves core logic in place)
- async find(ctx) {
- // some custom logic here
- ctx.query = { ...ctx.query, local: 'en' }
-
- // Calling the default core action
- const { data, meta } = await super.find(ctx);
-
- // some more custom logic
- meta.date = Date.now()
-
- return { data, meta };
- },
-
- // Method 3: Replacing a core action with proper sanitization
- async find(ctx) {
- const sanitizedQueryParams = await this.sanitizeQuery(ctx);
- const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams);
- const sanitizedResults = await this.sanitizeOutput(results, ctx);
-
- return this.transformResponse(sanitizedResults, { pagination });
- }
-}));
+import { factories } from "@strapi/strapi";
+
+export default factories.createCoreController(
+ "api::restaurant.restaurant",
+ ({ strapi }) => ({
+ /**
+ * Example 1: Modifying a Strapi controller function
+ *
+ * If you need to modify the input or output of a pre-defined Strapi controller method,
+ * write a method of the same name, and use `super` to call the parent method.
+ * */
+ async find(ctx) {
+ // your custom logic for modifying the input
+ ctx.query = { ...ctx.query, locale: "en" }; // force ctx.query.locale to 'en' regardless of what was requested
+
+ // Call the default parent controller action
+ const result = await super.find(ctx);
+
+ // your custom logic for modifying the output
+ result.meta.date = Date.now(); // change the date that is returned
+
+ return result;
+ },
+
+ /**
+ * Example 2: Replacing a Strapi controller function
+ *
+ * If you need to completely replace the behavior of a pre-defined Strapi controller method,
+ * you can do so by simply implementing a method of the same name.
+ *
+ * Caution: You will need to manage the security of the request and results on your own,
+ * as demonstrated in this example.
+ * */
+ async find(ctx) {
+ // validateQuery throws an error if any of the query params used are inaccessible to ctx.user
+ // That is, trying to access private fields, fields they don't have permission for, wrong data type, etc
+ await this.validateQuery(ctx);
+
+ // sanitizeQuery silently removes any query params that are invalid or the user does not have access to
+ // It is recommended to use sanitizeQuery even if validateQuery is used, as validateQuery allows
+ // a number of non-security-related cases such as empty objects in string fields to pass, while sanitizeQuery
+ // will remove them completely
+ const sanitizedQueryParams = await this.sanitizeQuery(ctx);
+
+ // Perform whatever custom actions are needed
+ const { results, pagination } = await strapi
+ .service("api::restaurant.restaurant")
+ .find(sanitizedQueryParams);
+
+ // sanitizeOutput removes any data that was returned by our query that the ctx.user should not have access to
+ const sanitizedResults = await this.sanitizeOutput(results, ctx);
+
+ // transformResponse correctly formats the data and meta fields of your results to return to the API
+ return this.transformResponse(sanitizedResults, { pagination });
+ },
+
+ /**
+ * Example 3: Writing your own new controller function
+ * If you need to create some new action that does not match one of the pre-configured Strapi methods,
+ * you can simply add the method with the desired name and implement whatever functionality you want.
+ *
+ * Caution: Similar to replacing a controller, you will need to manage the security of the request
+ * yourself, so remember to use sanitizers and validators as needed.
+ * */
+ async healthCheck(ctx) {
+ try {
+ ctx.body = "ok";
+ } catch (err) {
+ ctx.body = err;
+ }
+ },
+ })
+);
```
@@ -127,23 +203,22 @@ A specific `GET /hello` [route](/dev-docs/backend-customization/routes) is defin
```js "title="./src/api/hello/routes/hello.js"
-
module.exports = {
routes: [
{
- method: 'GET',
- path: '/hello',
- handler: 'hello.index',
- }
- ]
-}
+ method: "GET",
+ path: "/hello",
+ handler: "hello.index",
+ },
+ ],
+};
```
```js "title="./src/api/hello/controllers/hello.js"
-
module.exports = {
- async index(ctx, next) { // called by GET /hello
- ctx.body = 'Hello World!'; // we could also send a JSON
+ async index(ctx, next) {
+ // called by GET /hello
+ ctx.body = "Hello World!"; // we could also send a JSON
},
};
```
@@ -153,23 +228,22 @@ module.exports = {
```js "title="./src/api/hello/routes/hello.ts"
-
export default {
routes: [
{
- method: 'GET',
- path: '/hello',
- handler: 'hello.index',
- }
- ]
-}
+ method: "GET",
+ path: "/hello",
+ handler: "hello.index",
+ },
+ ],
+};
```
```js title="./src/api/hello/controllers/hello.ts"
-
export default {
- async index(ctx, next) { // called by GET /hello
- ctx.body = 'Hello World!'; // we could also send a JSON
+ async index(ctx, next) {
+ // called by GET /hello
+ ctx.body = "Hello World!"; // we could also send a JSON
},
};
```
@@ -184,40 +258,65 @@ export default {
When a new [content-type](/dev-docs/backend-customization/models#content-types) is created, Strapi builds a generic controller with placeholder code, ready to be customized.
:::
-### Sanitization in controllers
+:::tip Tips
+- To see a possible advanced usage for custom controllers, read the [services and controllers](/dev-docs/backend-customization/examples/services-and-controllers) page of the backend customization examples cookbook.
+- If you want to implement unit testing to your controllers, this [blog post](https://strapi.io/blog/automated-testing-for-strapi-api-with-jest-and-supertest) should get you covered.
+:::
+
+### Sanitization and Validation in controllers
+
+Sanitization means that the object is βcleanedβ and returned.
+
+Validation means an assertion is made that the data is already clean and throws an error if something is found that shouldn't be there.
+
+In Strapi:
+
+- validation is applied on query parameters,
+- and only sanitization is applied to input data (create and update body data).
:::warning
-As of Strapi v4.8.0 and greater it's strongly recommended you sanitize your incoming request query and parameters utilizing the new `sanitizeQuery` function to prevent leaking of private data.
+It's strongly recommended you sanitize (v4.8.0+) and/or validate (v4.13.0+) your incoming request query utilizing the new `sanitizeQuery` and `validateQuery` functions to prevent the leaking of private data.
:::
#### Sanitization when utilizing controller factories
-Within the Strapi factories there are 3 functions exposed that can be used for sanitization:
+Within the Strapi factories the following functions are exposed that can be used for sanitization and validation:
| Function Name | Parameters | Description |
-|------------------|----------------------------|--------------------------------------------------------------------------------------|
+| ---------------- | -------------------------- | ------------------------------------------------------------------------------------ |
| `sanitizeQuery` | `ctx` | Sanitizes the request query |
| `sanitizeOutput` | `entity`/`entities`, `ctx` | Sanitizes the output data where entity/entities should be an object or array of data |
| `sanitizeInput` | `data`, `ctx` | Sanitizes the input data |
+| `validateQuery` | `ctx` | Validates the request query (throws an error on invalid params) |
+| `validateInput` | `data`, `ctx` | (EXPERIMENTAL) Validates the input data (throws an error on invalid data) |
These functions automatically inherit the sanitization settings from the model and sanitize the data accordingly based on the content-type schema and any of the content API authentication strategies, such as the Users & Permissions plugin or API tokens.
+:::warning
+Because these methods use the model associated with the current controller, if you query data that is from another model (i.e., doing a find for "menus" within a "restaurant" controller method), you must instead use the `@strapi/utils` tools, such as `sanitize.contentAPI.query` described in [Sanitizing Custom Controllers](#sanitize-validate-custom-controllers), or else the result of your query will be sanitized against the wrong model.
+:::
+
```js title="./src/api/restaurant/controllers/restaurant.js"
-
-const { createCoreController } = require('@strapi/strapi').factories;
-
-module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({
- async find(ctx) {
- const sanitizedQueryParams = await this.sanitizeQuery(ctx);
- const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams);
- const sanitizedResults = await this.sanitizeOutput(results, ctx);
-
- return this.transformResponse(sanitizedResults, { pagination });
- }
-}));
+const { createCoreController } = require("@strapi/strapi").factories;
+
+module.exports = createCoreController(
+ "api::restaurant.restaurant",
+ ({ strapi }) => ({
+ async find(ctx) {
+ await this.validateQuery(ctx);
+ const sanitizedQueryParams = await this.sanitizeQuery(ctx);
+ const { results, pagination } = await strapi
+ .service("api::restaurant.restaurant")
+ .find(sanitizedQueryParams);
+ const sanitizedResults = await this.sanitizeOutput(results, ctx);
+
+ return this.transformResponse(sanitizedResults, { pagination });
+ },
+ })
+);
```
@@ -225,55 +324,71 @@ module.exports = createCoreController('api::restaurant.restaurant', ({ strapi })
```js title="./src/api/restaurant/controllers/restaurant.ts"
-
-import { factories } from '@strapi/strapi';
-
-export default factories.createCoreController('api::restaurant.restaurant', ({ strapi }) => ({
- async find(ctx) {
- const sanitizedQueryParams = await this.sanitizeQuery(ctx);
- const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams);
- const sanitizedResults = await this.sanitizeOutput(results, ctx);
-
- return this.transformResponse(sanitizedResults, { pagination });
- }
-}));
+import { factories } from "@strapi/strapi";
+
+export default factories.createCoreController(
+ "api::restaurant.restaurant",
+ ({ strapi }) => ({
+ async find(ctx) {
+ const sanitizedQueryParams = await this.sanitizeQuery(ctx);
+ const { results, pagination } = await strapi
+ .service("api::restaurant.restaurant")
+ .find(sanitizedQueryParams);
+ const sanitizedResults = await this.sanitizeOutput(results, ctx);
+
+ return this.transformResponse(sanitizedResults, { pagination });
+ },
+ })
+);
```
-#### Sanitization when building custom controllers
+#### Sanitization and validation when building custom controllers {#sanitize-validate-custom-controllers}
-Within custom controllers, there are 3 primary functions exposed via the `@strapi/utils` package that can be used for sanitization:
+Within custom controllers, there are 5 primary functions exposed via the `@strapi/utils` package that can be used for sanitization and validation:
-| Function Name | Parameters | Description |
-|---------------------|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|
-| `contentAPI.input` | `data`, `schema`, `auth` | Sanitizes the request input including non-writable fields, removing restricted relations, and other nested "visitors" added by plugins |
-| `contentAPI.output` | `data`, `schema`, `auth` | Sanitizes the response output including restricted relations, private fields, passwords, and other nested "visitors" added by plugins |
-| `contentAPI.query` | `ctx.query`, `schema`, `auth` | Sanitizes the request query including filters, sort, fields, and populate |
+| Function Name | Parameters | Description |
+| ---------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `sanitize.contentAPI.input` | `data`, `schema`, `auth` | Sanitizes the request input including non-writable fields, removing restricted relations, and other nested "visitors" added by plugins |
+| `sanitize.contentAPI.output` | `data`, `schema`, `auth` | Sanitizes the response output including restricted relations, private fields, passwords, and other nested "visitors" added by plugins |
+| `sanitize.contentAPI.query` | `ctx.query`, `schema`, `auth` | Sanitizes the request query including filters, sort, fields, and populate |
+| `validate.contentAPI.query` | `ctx.query`, `schema`, `auth` | Validates the request query including filters, sort, fields (currently not populate) |
+| `validate.contentAPI.input` | `data`, `schema`, `auth` | (EXPERIMENTAL) Validates the request input including non-writable fields, removing restricted relations, and other nested "visitors" added by plugins |
:::note
-Depending on the complexity of your custom controllers, you may need additional sanitization that Strapi cannot currently account for especially when combining the data from multiple sources.
+Depending on the complexity of your custom controllers, you may need additional sanitization that Strapi cannot currently account for, especially when combining the data from multiple sources.
:::
```js title="./src/api/restaurant/controllers/restaurant.js"
-
-const { sanitize } = require('@strapi/utils')
-const { contentAPI } = sanitize;
+const { sanitize, validate } = require("@strapi/utils");
module.exports = {
async findCustom(ctx) {
- const contentType = strapi.contentType('api::test.test')
- const sanitizedQueryParams = await contentAPI.query(ctx.query, contentType, ctx.state.auth)
-
- const entities = await strapi.entityService.findMany(contentType.uid, sanitizedQueryParams)
-
- return await contentAPI.output(entities, contentType, ctx.state.auth);
- }
-}
+ const contentType = strapi.contentType("api::test.test");
+ await validate.contentAPI.query(ctx.query, contentType, {
+ auth: ctx.state.auth,
+ });
+ const sanitizedQueryParams = await sanitize.contentAPI.query(
+ ctx.query,
+ contentType,
+ { auth: ctx.state.auth }
+ );
+
+ const entities = await strapi.entityService.findMany(
+ contentType.uid,
+ sanitizedQueryParams
+ );
+
+ return await sanitize.contentAPI.output(entities, contentType, {
+ auth: ctx.state.auth,
+ });
+ },
+};
```
@@ -281,20 +396,31 @@ module.exports = {
```js title="./src/api/restaurant/controllers/restaurant.ts"
-
-import { sanitize } from '@strapi/utils';
-const { contentAPI } = sanitize;
+import { sanitize, validate } from "@strapi/utils";
export default {
async findCustom(ctx) {
- const contentType = strapi.contentType('api::test.test')
- const sanitizedQueryParams = await contentAPI.query(ctx.query, contentType, ctx.state.auth)
-
- const entities = await strapi.entityService.findMany(contentType.uid, sanitizedQueryParams)
-
- return await contentAPI.output(entities, contentType, ctx.state.auth);
- }
-}
+ const contentType = strapi.contentType("api::test.test");
+
+ await validate.contentAPI.query(ctx.query, contentType, {
+ auth: ctx.state.auth,
+ });
+ const sanitizedQueryParams = await sanitize.contentAPI.query(
+ ctx.query,
+ contentType,
+ { auth: ctx.state.auth }
+ );
+
+ const entities = await strapi.entityService.findMany(
+ contentType.uid,
+ sanitizedQueryParams
+ );
+
+ return await sanitize.contentAPI.output(entities, contentType, {
+ auth: ctx.state.auth,
+ });
+ },
+};
```
@@ -312,9 +438,15 @@ An action from a core controller can be replaced entirely by [creating a custom
When extending a core controller, you do not need to re-implement any sanitization as it will already be handled by the core controller you are extending. Where possible it's strongly recommended to extend the core controller instead of creating a custom controller.
:::
+
+
Collection type examples
+:::tip
+The [backend customization examples cookbook](/dev-docs/backend-customization/examples) shows how you can overwrite a default controller action, for instance for the [`create` action](/dev-docs/backend-customization/examples/services-and-controllers#custom-controller).
+:::
+
@@ -442,9 +574,9 @@ Controllers are declared and attached to a route. Controllers are automatically
```js
// access an API controller
-strapi.controller('api::api-name.controller-name');
+strapi.controller("api::api-name.controller-name");
// access a plugin controller
-strapi.controller('plugin::plugin-name.controller-name');
+strapi.controller("plugin::plugin-name.controller-name");
```
:::tip
diff --git a/docusaurus/docs/dev-docs/backend-customization/examples.md b/docusaurus/docs/dev-docs/backend-customization/examples.md
new file mode 100644
index 0000000000..fa2c766034
--- /dev/null
+++ b/docusaurus/docs/dev-docs/backend-customization/examples.md
@@ -0,0 +1,31 @@
+---
+title: Backend Customization Examples Cookbook
+description: Learn how to use the core backend features of Strapi with the FoodAdvisor deployment
+displayed_sidebar: devDocsSidebar
+pagination_prev: dev-docs/backend-customization
+pagination_next: dev-docs/backend-customization/examples/authentication
+---
+
+# Backend customization: An examples cookbook using FoodAdvisor
+
+The present section of the documentation is intended for developers who would like to get a deeper understanding of the Strapi back end customization possibilities.
+
+The section is a collection of examples that demonstrate how the core components of the back-end server of Strapi can be used in a real-world project. Front-end code that interacts with the back end may also be part of some examples, but displayed in collapsed blocks by default since front-end code examples are not the main focus of this cookbook.
+
+Examples are meant to extend the features of [FoodAdvisor](https://github.com/strapi/foodadvisor), the official Strapi demo application. FoodAdvisor builds a ready-made restaurants directory powered by a Strapi back end (included in the `/api` folder) and renders a [Next.js](https://nextjs.org/)-powered front-end website (included in the `/client` folder).
+
+:::prerequisites
+- π You have read the [Quick Start Guide](/dev-docs/quick-start) and/or understood that Strapi is a **headless CMS** A headless CMS is a Content Management System that separates the presentation layer (i.e., the front end, where content is displayed) from the back end (where content is managed).
Strapi is a headless CMS that provides:
a back-end server exposing an API for your content,
and a graphical user interface, called the admin panel, to manage the content.
The presentation layer should be handled by another framework, not by Strapi. that helps you create a data structure with the [Content-Type Builder](/user-docs/content-type-builder) and add some content through the [Content Manager](/user-docs/content-manager), then exposes the content through APIs.
+- π You have read the [back-end customization introduction](/dev-docs/backend-customization) to get a general understanding of what routes, policies, middlewares, controllers, and services are in Strapi.
+- π· If you want to test and play with the code examples by yourself, ensure you have cloned the [FoodAdvisor](https://github.com/strapi/foodadvisor) repository, setup the project, and started both the front-end and back-end servers. The Strapi admin panel should be accessible from [`localhost:1337/admin`](http://localhost:1337/admin) and the Next.js-based FoodAdvisor front-end website should be running on [`localhost:3000`](http://localhost:3000).
+:::
+
+This section can be read from start to finish, or you might want to jump directly to a specific page to understand how a given core element from the Strapi back end can be used to solve a real-world use case example:
+
+| I want to understand⦠| Dedicated page |
+|------------|---------------|
+| How to authenticate my queries | [Authentication flow with JWT](/dev-docs/backend-customization/examples/authentication) |
+| How and when to use custom controllers and services | [Custom controllers and services examples](/dev-docs/backend-customization/examples/services-and-controllers) |
+| How to use custom policies and send custom errors | [Custom policies examples](/dev-docs/backend-customization/examples/policies) |
+| How to configure and use custom routes | [Custom routes examples](/dev-docs/backend-customization/examples/routes) |
+| How and when to use custom global middlewares | [Custom middleware example](/dev-docs/backend-customization/examples/middlewares) |
diff --git a/docusaurus/docs/dev-docs/backend-customization/examples/authentication.md b/docusaurus/docs/dev-docs/backend-customization/examples/authentication.md
new file mode 100644
index 0000000000..b4645886fc
--- /dev/null
+++ b/docusaurus/docs/dev-docs/backend-customization/examples/authentication.md
@@ -0,0 +1,151 @@
+---
+title: Authentication flow with JWT
+description: Learn how to authenticate REST API queries using our FoodAdvisor example
+displayed_sidebar: devDocsSidebar
+pagination_prev: dev-docs/backend-customization/examples
+pagination_next: dev-docs/backend-customization/examples/services-and-controllers
+---
+
+
+# Examples cookbook: Authentication flow with JWT
+
+:::prerequisites
+This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/dev-docs/backend-customization/examples).
+:::
+
+**π Context:**
+
+Out of the box, the front-end website of [FoodAdvisor](https://github.com/strapi/foodadvisor) does not provide any log in functionality. Logging in is done by accessing Strapi's admin panel at [`localhost:1337/admin`](http://localhost:1337/admin`).
+
+
+
+
+
+Let's add a basic login page to the front-end, [Next.js](https://nextjs.org/)-powered website included in the `/client` folder of FoodAdvisor. The login page will be accessible at [`localhost:3000/auth/login`](http://localhost:3000/auth/login) and contain a typical email/password login form. This will allow programmatically authenticating API requests sent to Strapi.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+**π― Goal**:
+
+Create a front-end component to:
+
+1. to display a login form,
+2. send a request to the `/auth/local` route of the Strapi back-end server to authenticate,
+3. get a [JSON Web Token](https://en.wikipedia.org/wiki/JSON_Web_Token) (JWT),
+4. and store the JWT into the [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) property of your browser for later retrieval and authentication of our requests.
+
+
+
+
+
+
+
+Additional information about JWT authentication can be found in the [Users & Permissions plugin](/dev-docs/plugins/users-permissions) documentation.
+
+
+
+
+
+
+**π§βπ» Code example:**
+
+:::prerequisites
+The code example in this section uses the [formik](https://formik.org/) package. Install it using `yarn add formik` or `npm install formik` and restart the dev server.
+:::
+
+To achieve this, in the `/client` folder of the [FoodAdvisor](https://github.com/strapi/foodadvisor) project, you could create a `pages/auth/login.js` file that contains the following example code. Highlighted lines show the request sent to the `/auth/local` route provided by Strapi's Users & Permissions plugin:
+
+```jsx title="/client/pages/auth/login.js" {21-27}
+
+import React from 'react';
+import { useFormik } from 'formik';
+import { Button, Input } from '@nextui-org/react';
+import Layout from '@/components/layout';
+import { getStrapiURL } from '@/utils';
+
+const Login = () => {
+ const { handleSubmit, handleChange } = useFormik({
+ initialValues: {
+ identifier: '',
+ password: '',
+ },
+ onSubmit: async (values) => {
+ /**
+ * API URLs in Strapi are by default prefixed with /api,
+ * but because the API prefix can be configured
+ * with the rest.prefix property in the config/api.js file,
+ * we use the getStrapiURL() method to build the proper full auth URL.
+ **/
+ const res = await fetch(getStrapiURL('/auth/local'), {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(values),
+ });
+ /**
+ * Gets the JWT from the server response
+ */
+ const { jwt } = await res.json();
+ /**
+ * Stores the JWT in the localStorage of the browser.
+ * A better implementation would be to do this with an authentication context provider
+ * or something more sophisticated, but it's not the purpose of this tutorial.
+ */
+ localStorage.setItem('token', jwt);
+ },
+ });
+ /**
+ * The following code renders a basic login form
+ * accessible from the localhost:3000/auth/login page.
+ */
+ return (
+
+
+
+
+
+ );
+};
+
+export default Login;
+```
+
+
+
+:::strapi What's next?
+Learn more about how custom [services and controllers](/dev-docs/backend-customization/examples/services-and-controllers) can help you tweak a Strapi-based application.
+:::
diff --git a/docusaurus/docs/dev-docs/backend-customization/examples/middlewares.md b/docusaurus/docs/dev-docs/backend-customization/examples/middlewares.md
new file mode 100644
index 0000000000..9f25c97918
--- /dev/null
+++ b/docusaurus/docs/dev-docs/backend-customization/examples/middlewares.md
@@ -0,0 +1,249 @@
+---
+title: Custom middlewares
+description: Learn how to use custom middlewares using our FoodAdvisor example
+displayed_sidebar: devDocsSidebar
+pagination_prev: dev-docs/backend-customization/examples/routes
+---
+
+# Examples cookbook: Custom global middlewares
+
+:::prerequisites
+This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/dev-docs/backend-customization/examples).
+:::
+
+Out of the box, [FoodAdvisor](https://github.com/strapi/foodadvisor) does not provide any custom middlewares that could use incoming requests and perform some additional logic before executing the controller code.
+
+There are 2 types of middlewares in Strapi: **route middlewares** control access to a route while **global middlewares** have a wider scope (see reference documentation for [middlewares customization](/dev-docs/backend-customization/middlewares)).
+
+Custom route middlewares could be used instead of policies to control access to an endpoint (see [policies cookbook](/dev-docs/backend-customization/examples/policies)) and could modify the context before passing it down to further core elements of the Strapi server. This page will _not_ cover custom route middlewares but rather illustrate a more elaborated usage for **custom global middlewares**.
+
+## Populating an analytics dashboard in Google Sheets with a custom middleware
+
+**π Context:**
+
+In essence, a middleware gets executed between a request arriving at the server and the controller function getting executed. So, for instance, a middleware is a good place to perform some analytics.
+
+
+
+
+
+Letβs create a rudimentary example of an analytics dashboard made with Google Spreadsheets to have some insights on which restaurants pages of [FoodAdvisor](https://github.com/strapi/foodadvisor) are more visited.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+**π― Goals**:
+
+- Create some utility functions that interact with Google Sheets.
+- Create a custom Strapi middleware that will create and/or update an existing Google Sheet document every time we have an incoming request to a Restaurants page of the FoodAdvisor project.
+- Append the custom middleware to the route where we want it to get executed.
+
+
+
+
+
+
+
+Additional information can be found in the [middlewares customization](/dev-docs/backend-customization/middlewares) documentation.
+
+
+
+
+
+
+
+**π§βπ» Code example:**
+
+1. In the `/api` folder of the [FoodAdvisor](https://github.com/strapi/foodadvisor) project, create a `/restaurant/middlewares/utils.js` file with the following example code:
+
+
+ Example utility functions that could be used to read, write and update a Google spreadsheet:
+
+ The following code allows reading, writing, and updating a Google spreadsheet given an API Key read from a JSON file and a spreadsheet ID retrieved from the URL:
+
+ ![Google Spreadsheet URL](/img/assets/backend-customization/tutorial-spreadsheet-url.png)
+
+ Additional information can be found in the official [Google Sheets API documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values?hl=es-419).
+
+ ```jsx title="src/api/restaurant/middlewares/utils.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,
+ };
+ ```
+
+
+
+2. In the `/api` folder of the FoodAdvisor project, create a custom `analytics` middleware with the following code:
+
+ ```jsx title="src/api/restaurant/middlewares/analytics.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();
+ };
+ };
+ ```
+
+3. Configure the routes for the "Restaurants" content-type to execute the custom `analytics` middleware whenever a restaurant page is queried. To do so, use the following code:
+
+ ```jsx title="src/api/restaurant/routes/restaurant.js"
+
+ 'use strict';
+
+ const { createCoreRouter } = require('@strapi/strapi').factories;
+
+ module.exports = createCoreRouter('api::restaurant.restaurant', {
+ config: {
+ findOne: {
+ auth: false,
+ policies: [],
+ middlewares: ['api::restaurant.analytics'],
+ },
+ },
+ });
+ ```
+
diff --git a/docusaurus/docs/dev-docs/backend-customization/examples/policies.md b/docusaurus/docs/dev-docs/backend-customization/examples/policies.md
new file mode 100644
index 0000000000..b4220e8017
--- /dev/null
+++ b/docusaurus/docs/dev-docs/backend-customization/examples/policies.md
@@ -0,0 +1,389 @@
+---
+title: Custom policies
+description: Learn how to create custom policies using our FoodAdvisor example
+displayed_sidebar: devDocsSidebar
+pagination_prev: dev-docs/backend-customization/examples/services-and-controllers
+pagination_next: dev-docs/backend-customization/examples/routes
+---
+
+# Examples cookbook: Custom policies
+
+:::prerequisites
+This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/dev-docs/backend-customization/examples).
+:::
+
+Out of the box, [FoodAdvisor](https://github.com/strapi/foodadvisor) does not use any custom policies or route middlewares that could control access to content type endpoints.
+
+In Strapi, controlling access to a content-type endpoint can be done either with a policy or route middleware:
+
+- policies are read-only and allow a request to pass or return an error,
+- while route middlewares can perform additional logic.
+
+In our example, let's use a policy.
+
+## Creating a custom policy
+
+**π Context:**
+
+Let's say we would like to customize the backend of [FoodAdvisor](https://github.com/strapi/foodadvisor) to prevent restaurant owners from creating fake reviews for their businesses using a [form previously created](/dev-docs/backend-customization/examples/services-and-controllers#rest-api-queries-from-the-front-end) on the front-end website.
+
+
+
+
+
+**π― Goals**:
+
+1. Create a new folder for policies to apply only to the "Reviews" collection type.
+2. Create a new policy file.
+3. Use the `findMany()` method from the Entity Service API to get information about the owner of a restaurant when the `/reviews` endpoint is reached.
+4. Return an error if the authenticated user is the restaurant's owner, or let the request pass in other cases.
+
+
+
+
+
+
+
+Additional information can be found in the [Policies](/dev-docs/backend-customization/policies), [Routes](/dev-docs/backend-customization/routes), and [Entity Service API](/dev-docs/api/entity-service) documentation.
+
+
+
+
+
+
+
+**π§βπ» Code example:**
+
+In the `/api` folder of the [FoodAdvisor](https://github.com/strapi/foodadvisor) project, create a new `src/api/review/policies/is-owner-review.js` file with the following code:
+
+```jsx title="src/api/review/policies/is-owner-review.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;
+};
+```
+
+:::caution
+Policies or route middlewares should be declared in the configuration of a route to actually control access. Read more about routes in the [reference documentation](/dev-docs/backend-customization/routes) or see an example in the [routes cookbook](/dev-docs/backend-customization/examples/routes).
+:::
+
+## Sending custom errors through policies
+
+**π Context:**
+
+Out of the box, [FoodAdvisor](https://github.com/strapi/foodadvisor) sends a default error when a policy refuses access to a route. Let's say we want to customize the error sent when the [previously created custom policy](#creating-a-custom-policy) does not allow creating a review.
+
+
+
+
+
+**π― Goal:**
+
+Configure the custom policy to throw a custom error instead of the default error.
+
+
+
+
+
+
+
+Additional information can be found in the [Error handling](/dev-docs/error-handling) documentation.
+
+
+
+
+
+
+
+**π§βπ» Code example:**
+
+In the `/api` folder of the [FoodAdvisor](https://github.com/strapi/foodadvisor) project, update the [previously created `is-owner-review` custom policy](#creating-a-custom-policy) as follows (highlighted lines are the only modified lines):
+
+```jsx title="src/api/review/policies/is-owner-review.js" showLineNumbers
+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).
+ */
+ const error = new ApplicationError(
+ "The owner of the restaurant cannot submit reviews",
+ {
+ policy: "is-owner-review",
+ errCode: "RESTAURANT_OWNER_REVIEW", // can be useful for identifying different errors on the front end
+ }
+ );
+ error.name = "OwnerReviewError";
+ throw error;
+ // highlight-end
+ }
+
+ return true;
+};
+```
+
+
+Responses sent with default policy error vs. custom policy error:
+
+
+
+
+
+When a policy refuses access to a route and a default error is thrown, the following response will be sent when trying to query the content-type through the REST API:
+
+```jsx
+{
+ "data": null,
+ "error": {
+ "status": 403,
+ "name": "ForbiddenError",
+ "message": "Policy Failed",
+ "details": {}
+ }
+}
+```
+
+
+
+
+
+When a policy refuses access to a route and the custom policy throws the custom error defined in the code example above, the following response will be sent when trying to query the content-type through the REST API:
+
+Note that because `ForbiddenError` (403) is always replaced with a generic message, we used an `ApplicationError` (400) to send the custom message.
+
+```jsx
+{
+ "data": null,
+ "error": {
+ "status": 400,
+ "name": "OwnerReviewError",
+ "message": "The owner of the restaurant cannot submit reviews",
+ "details": {
+ "policy": "is-owner-review",
+ "errCode": "RESTAURANT_OWNER_REVIEW"
+ }
+ }
+}
+```
+
+
+
+
+
+
+
+
+
+### Using custom errors on the front end
+
+**π Context:**
+
+Out of the box, the Next.js-powered front-end website provided with [FoodAdvisor](https://github.com/strapi/foodadvisor) does not display errors or success messages on the front-end website when accessing content. For instance, the website will not inform the user when adding a new review with a [previously created form](/dev-docs/backend-customization/examples/services-and-controllers#rest-api-queries-from-the-front-end) is not possible.
+
+
+
+
+
+Let's say we want to customize the front end of FoodAdvisor to catch the custom error thrown by a [previously created custom policy](#creating-a-custom-policy) and display it to the user with a [React Hot Toast notification](https://github.com/timolins/react-hot-toast). As a bonus, another toast notification will be displayed when a review is successfully created.
+
+
+
+
+
+
+
+
+
+
+**π― Goals**:
+
+- Catch the error on the front-end website and display it within a notification.
+- Send another notification in case the policy allows the creation of a new review.
+
+**π§βπ» Code example:**
+
+In the `/client` folder of the [FoodAdvisor](https://github.com/strapi/foodadvisor) project, you could update the [previously created `new-review` component](/dev-docs/backend-customization/examples/services-and-controllers#rest-api-queries-from-the-front-end) as follows (modified lines are highlighted):
+
+
+Example front-end code to display toast notifications for custom errors or successful review creation:
+
+```jsx title="/client/components/pages/restaurant/RestaurantContent/Reviews/new-review.js" showLineNumbers
+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;
+```
+
+
+
+
+
+:::strapi What's next?
+Learn more about how to configure [custom routes](/dev-docs/backend-customization/examples/routes) to use your custom policies, and how these custom routes can be used to tweak a Strapi-based application.
+:::
diff --git a/docusaurus/docs/dev-docs/backend-customization/examples/routes.md b/docusaurus/docs/dev-docs/backend-customization/examples/routes.md
new file mode 100644
index 0000000000..b4239e6798
--- /dev/null
+++ b/docusaurus/docs/dev-docs/backend-customization/examples/routes.md
@@ -0,0 +1,71 @@
+---
+title: Custom routes
+description: Learn how to use custom routes using our FoodAdvisor example
+displayed_sidebar: devDocsSidebar
+pagination_prev: dev-docs/backend-customization/examples/policies
+pagination_next: dev-docs/backend-customization/examples/middlewares
+---
+
+# Examples cookbook: Custom routes
+
+:::prerequisites
+This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/dev-docs/backend-customization/examples).
+:::
+
+**π Context:**
+
+Out of the box, [FoodAdvisor](https://github.com/strapi/foodadvisor) does not control access to its content-type endpoints.
+
+Let's say we [previously created a policy](/dev-docs/backend-customization/examples/policies) to restrict access to the "Reviews" content-type to some conditions, for instance to prevent a restaurant's owner to create a review for their restaurants. We must now enable the policy on the route we use to create reviews.
+
+
+
+
+
+**π― Goals**:
+
+- Explicitly define a routes configuration for the "Reviews" content-type.
+- Configure the route used when creating a review to:
+ - bypass the default Strapi authentication system
+ - and restrict access depending on the [previously defined custom policy](/dev-docs/backend-customization/examples/policies).
+
+
+
+
+
+
+
+Additional information can be found in the [Policies](/dev-docs/backend-customization/policies) and [Routes](/dev-docs/backend-customization/routes) documentation.
+
+
+
+
+
+
+
+**π§βπ» Code example:**
+
+In the `/api` folder of the [FoodAdvisor](https://github.com/strapi/foodadvisor) project, replace the content of the `api/src/api/review/routes/review.js` file with the following code:
+
+```jsx title="src/api/review/routes/review.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: [],
+ },
+ },
+});
+```
+
+
+
+:::strapi What's next?
+Learn more about how to configure [custom middlewares](/dev-docs/backend-customization/examples/middlewares) to perform additional actions that extend your Strapi-based application.
+:::
diff --git a/docusaurus/docs/dev-docs/backend-customization/examples/services-and-controllers.md b/docusaurus/docs/dev-docs/backend-customization/examples/services-and-controllers.md
new file mode 100644
index 0000000000..c8f160e1db
--- /dev/null
+++ b/docusaurus/docs/dev-docs/backend-customization/examples/services-and-controllers.md
@@ -0,0 +1,487 @@
+---
+title: Custom services and controllers
+description: Learn how to authenticate use custom services and controllers using our FoodAdvisor example
+displayed_sidebar: devDocsSidebar
+pagination_prev: dev-docs/backend-customization/examples/authentication
+pagination_next: dev-docs/backend-customization/examples/policies
+---
+
+# Examples cookbook: Custom services and controllers
+
+:::prerequisites
+This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/dev-docs/backend-customization/examples).
+:::
+
+From the front-end website of [FoodAdvisor](https://github.com/strapi/foodadvisor), you can browse a list of restaurants accessible at [`localhost:3000/restaurants`](http://localhost:3000/restaurants). Clicking on any restaurant from the list will use the code included in the `/client` folder to display additional information about this restaurant. The content displayed on a restaurant page was created within Strapi's Content Manager and is retrieved by querying Strapi's REST API which uses code included in the `/api` folder.
+
+This page will teach about the following advanced topics:
+
+| Topic | Section |
+|------|---------|
+| Create a component that interacts with the backend of Strapi | [REST API queries from the front-end](#rest-api-queries-from-the-front-end) |
+| Understand how services and controllers can play together | [Controllers vs. services](#controllers-vs-services) |
+| Create custom services |
A [custom service](#custom-service-creating-a-review) that only uses the Entity Service API
Another more [advanced custom service](#custom-service-sending-an-email-to-the-restaurant-owner) that uses both Entity Service API and a Strapi plugin
|
+| Use services in a controller | [Custom controller](#custom-controller) |
+
+
+
+### REST API queries from the front end
+
+
+
+
+**π Context:**
+
+Restaurant pages on the front-end website of [FoodAdvisor](https://github.com/strapi/foodadvisor) include a Reviews section that is read-only. Adding reviews requires logging in to Strapi's admin panel and adding content to the "Reviews" collection type through the [Content Manager](/user-docs/content-manager).
+
+Let's add a small front-end component to restaurant pages. This component will allow a user to write a review directly from the front-end website.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+**π― Goals**:
+
+* Add a form to write a review.
+* Display the form on any restaurants page.
+* Send a POST request to Strapi's REST API when the form is submitted.
+* Use the [previously stored JWT](#authentication-flow-with-jwt) to authenticate the request.
+
+
+
+
+
+
+
+Additional information on endpoints for content types can be found in the [REST API](/dev-docs/api/rest#endpoints) documentation.
+
+
+
+
+
+
+
+**π§βπ» Code example:**
+
+In the `/client` folder of the [FoodAdvisor](https://github.com/strapi/foodadvisor) project, you could use the following code examples to:
+- create a new `pages/restaurant/RestaurantContent/Reviews/new-review.js` file,
+- and update the existing `components/pages/restaurant/RestaurantContent/Reviews/reviews.js`.
+
+
+Example front-end code to add a component for writing reviews and display it on restaurants pages:
+
+1. Create a new file in the `/client` folder to add a new component for writing reviews with the following code:
+
+ ```jsx title='/client/components/pages/restaurant/RestaurantContent/Reviews/new-review.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;
+ ```
+
+2. Display the new form component on any restaurants page by adding the highlighted lines (7, 8, and 13) to the code used to render restaurant's information:
+
+ ```jsx title='/client/components/pages/restaurant/RestaurantContent/Reviews/reviews.js' showLineNumbers
+ 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) => (
+ // β¦
+ ```
+
+
+
+
+
+### Controllers vs. Services
+
+Controllers could contain any business logic to be executed when the client requests a route. However, as your code grows bigger and becomes more structured, it is a best practice to split the logic into specific services that do only one thing well, then call the services from controllers.
+
+To illustrate the use of services, in this documentation the custom controller does not handle any responsibilities and delegates all the business logic to services.
+
+Let's say we would like to customize the back end of [FoodAdvisor](https://github.com/strapi/foodadvisor) to achieve the following scenario: when submitting the [previously added review form](#rest-api-queries-from-the-front-end) on the front-end website, Strapi will create a review in the back end and notify the restaurant owner by email. Translating this to Strapi back end customization means performing 3 actions:
+
+1. Creating a custom service to [create the review](#custom-service-creating-a-review).
+2. Creating a custom service to [send an email](#custom-service-sending-an-email-to-the-restaurant-owner).
+3. [Customizing the default controller](#custom-controller) provided by Strapi for the Review content-type to use the 2 new services.
+
+
+
+### Custom service: Creating a review
+
+**π Context:**
+
+By default, service files in Strapi includes basic boilerplate code that use the `createCoreService` factory function.
+
+Let's update the existing `review.js` service file for the "Reviews" collection type of [FoodAdvisor](https://github.com/strapi/foodadvisor) by replacing its code to create a review.
+
+
+
+
+
+**π― Goals**:
+
+- Declare a `create` method.
+- Grab context from the request.
+- Use the `findMany()` method from the EntityService API to find a restaurant.
+- Use the `create()` method from the EntityService API to append data to the restaurant, populating the restaurant owner.
+- Return the new review data.
+
+
+
+
+
+
+
+Additional information can be found in the [request context](/dev-docs/backend-customization/requests-responses), [services](/dev-docs/backend-customization/services) and [EntityService API](/dev-docs/api/entity-service) documentation.
+
+
+
+
+
+
+
+**π§βπ» Code example:**
+
+To create such a service, in the `/api` folder of the [FoodAdvisor](https://github.com/strapi/foodadvisor) project, replace the content of the `src/api/review/services/review.js` file with the following code:
+
+```jsx title="src/api/review/services/review.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;
+ },
+}));
+```
+
+:::tip Tips
+- In a controller's code, the `create` method from this service can be called with `strapi.service('api::review.review').create(ctx)` where `ctx` is the request's [context](/dev-docs/backend-customization/requests-responses).
+- The provided example code does not cover error handling. You should consider handling errors, for instance when the restaurant does not exist. Additional information can be found in the [Error handling](/dev-docs/error-handling) documentation.
+:::
+
+
+
+### Custom Service: Sending an email to the restaurant owner
+
+**π Context:**
+
+Out of the box, [FoodAdvisor](https://github.com/strapi/foodadvisor) does not provide any automated email service feature.
+
+Let's create an `email.js` service file to send an email. We could use it in a [custom controller](#custom-controller) to notify the restaurant owner whenever a new review is created on the front-end website.
+
+:::callout π€ Optional service
+This service is an advanced code example using the [Email](/dev-docs/plugins/email) plugin and requires understanding how [plugins](/dev-docs/plugins) and [providers](/dev-docs/providers) work with Strapi. If you don't need an email service to notify the restaurant's owner, you can skip this part and jump next to the custom [controller](#custom-controller) example.
+:::
+
+
+
+
+:::prerequisites
+- You have setup a [provider for the Email plugin](/dev-docs/plugins/email), for instance the [Sendmail](https://www.npmjs.com/package/@strapi/provider-email-sendmail) provider.
+- In Strapi's admin panel, you have [created an `Email` single type](/user-docs/content-type-builder/creating-new-content-type#creating-a-new-content-type) that contains a `from` Text field to define the sender email address.
+:::
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+**π― Goals**:
+
+- Create a new service file for the "Email" single type,
+- Declare a `send()` method for this service,
+- Grab the sender address stored in the Email single type using the Entity Service API,
+- Use email details (recipient's address, subject, and email body) passed when invoking the service's `send()` method to send an email using the Email plugin and a previously configured provider.
+
+
+
+
+
+
+
+Additional information can be found in the [Services](/dev-docs/backend-customization/services), [Entity Service API](/dev-docs/api/entity-service), [Email plugin](/dev-docs/plugins/email) and [Providers](/dev-docs/providers) documentation.
+
+
+
+
+
+
+
+**π§βπ» Code example:**
+
+To create such a service, in the `/api` folder of the [FoodAdvisor](https://github.com/strapi/foodadvisor) project, create a new `src/api/email/services/email.js` file with the following code:
+
+```jsx title="src/api/email/services/email.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,
+ });
+ },
+}));
+```
+
+:::tip
+In a controller's code, the `send` method from this email service can be called with `strapi.service('api::email.email).send(parameters)` where `parameters` is an object with the email's related information (recipient's address, subject, and email body).
+:::
+
+
+
+### Custom controller
+
+**π Context:**
+
+By default, controllers files in Strapi includes basic boilerplate code that use the `createCoreController` factory function. This exposes basic methods to create, retrieve, update, and delete content when reaching the requested endpoint. The default code for the controllers can be customized to perform any business logic.
+
+Let's customize the default controller for the "Reviews" collection type of [FoodAdvisor](https://github.com/strapi/foodadvisor) with the following scenario: upon a `POST` request to the `/reviews` endpoint, the controller calls previously created services to both [create a review](#custom-service-creating-a-review) and [send an email](#custom-service-sending-an-email-to-the-restaurant-owner) to the restaurant's owner.
+
+
+
+
+**π― Goals**:
+
+- Extend the existing controller for the "Reviews" collection type.
+- Declare a custom `create()` method.
+- Call previously created service(s).
+- Sanitize the content to be returned.
+
+
+
+
+
+
+
+Additional information can be found in the [controllers](/dev-docs/backend-customization/controllers) documentation.
+
+
+
+
+
+
+
+**π§βπ» Code example:**
+
+In the `/api` folder of the [FoodAdvisor](https://github.com/strapi/foodadvisor) 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](#custom-service-creating-a-review) or both custom services for the review creation and the [email notification](#custom-service-sending-an-email-to-the-restaurant-owner):
+
+
+
+
+```jsx title="src/api/review/controllers/review.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;
+ },
+}));
+```
+
+
+
+
+
+```jsx title="src/api/review/controllers/review.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;
+ },
+}));
+```
+
+
+
+
+
+
+
+
+
+
+:::strapi What's next?
+Learn more about how [custom policies](/dev-docs/backend-customization/examples/policies) can help you tweak a Strapi-based application and restrict access to some resources based on specific conditions.
+:::
diff --git a/docusaurus/docs/dev-docs/backend-customization/middlewares.md b/docusaurus/docs/dev-docs/backend-customization/middlewares.md
index 5ac46ffe68..fc76c972f7 100644
--- a/docusaurus/docs/dev-docs/backend-customization/middlewares.md
+++ b/docusaurus/docs/dev-docs/backend-customization/middlewares.md
@@ -123,6 +123,10 @@ export default () => {
The GraphQL plugin also allows [implementing custom middlewares](/dev-docs/plugins/graphql#middlewares), with a different syntax.
+:::tip
+To see a possible advanced usage for custom global middlewares, read the [middlewares](/dev-docs/backend-customization/examples/middlewares) page of the backend customization examples cookbook.
+:::
+
## Usage
Middlewares are called different ways depending on their scope:
@@ -134,3 +138,86 @@ Middlewares are called different ways depending on their scope:
:::tip
To list all the registered middlewares, run `yarn strapi middlewares:list`.
:::
+
+### Restricting content access with an "is-owner policy"
+
+It is often required that the author of an entry is the only user allowed to edit or delete the entry. In previous versions of Strapi, this was known as an "is-owner policy". With Strapi v4, the recommended way to achieve this behavior is to use a middleware.
+
+Proper implementation largely depends on your project's needs and custom code, but the most basic implementation could be achieved with the following procedure:
+
+1. From your project's folder, create a middleware with the Strapi CLI generator, by running the `yarn strapi generate` (or `npm run strapi generate`) command in the terminal.
+2. Select `middleware` from the list, using keyboard arrows, and press Enter.
+3. Give the middleware a name, for instance `isOwner`.
+4. Choose `Add middleware to an existing API` from the list.
+5. Select which API you want the middleware to apply.
+6. Replace the code in the `/src/api/[your-api-name]/middlewares/isOwner.js` file with the following, replacing `api::restaurant.restaurant` in line 22 with the identifier corresponding to the API you choose at step 5 (e.g., `api::blog-post.blog-post` if your API name is `blog-post`):
+
+ ```js showLineNumbers title="src/api/blog-post/middlewares/isOwner.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.entityService.findOne(
+ // highlight-start
+ // replace the next line with your proper content-type identifier
+ "api::restaurant.restaurant",
+ // highlight-end
+ 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();
+ }
+ };
+ };
+ ```
+
+7. Ensure the middleware is configured to apply on some routes. In the `config` object found in the `src/api/[your-apiβname]/routes/[your-content-type-name].js` file, define the methods (`find`, `findOne`, `read`, `update`, `delete`) for which you would like the middleware to apply, and declare the `isOwner` middleware for these routes.
For instance, if you wish to allow GET (i.e., `find` and `findOne` methods) and POST (i.e., `create` method) requests to any user for the `restaurant` content-type in the `restaurant` API, but would like to restrict PUT (i.e., `update` method) and DELETE requests only to the user who created the entry, you could use the following code in the `src/api/restaurant/routes/restaurant.js` file:
+
+ ```js title="src/api/restaurant/routes/restaurant.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"],
+ },
+ },
+ });
+ ```
+
+:::info
+You can find more information about route middlewares in the [routes documentation](/dev-docs/backend-customization/routes).
+:::
diff --git a/docusaurus/docs/dev-docs/backend-customization/models.md b/docusaurus/docs/dev-docs/backend-customization/models.md
index 1563e22b13..4a682165fe 100644
--- a/docusaurus/docs/dev-docs/backend-customization/models.md
+++ b/docusaurus/docs/dev-docs/backend-customization/models.md
@@ -55,7 +55,7 @@ The content-types use the following files:
These models files are stored in `./src/api/[api-name]/content-types/[content-type-name]/`, and any JavaScript or JSON file found in these folders will be loaded as a content-type's model (see [project structure](/dev-docs/project-structure)).
:::note
-In [TypeScript](/dev-docs/typescript.md)-enabled projects, schema typings can be generated using the `ts:generate-types` command.
+In [TypeScript](/dev-docs/typescript.md)-enabled projects, schema typings can be generated using the `ts:generate-types` command (e.g., `npm run strapi ts:generate-types` or `yarn strapi ts:generate-types`).
:::
### Components
@@ -83,7 +83,7 @@ General settings for the model can be configured with the following parameters:
| `kind`
_Optional, only for content-types_ | String | Defines if the content-type is:
a collection type (`collectionType`)
or a single type (`singleType`)
|
```json
-// ./api/[api-name]/content-types/restaurant/schema.json
+// ./src/api/[api-name]/content-types/restaurant/schema.json
{
"kind": "collectionType",
@@ -140,6 +140,10 @@ The `type` parameter of an attribute should be one of the following values:
| Special types unique to Strapi |
`media`
[`relation`](#relations)
[`customField`](#custom-fields)
[`component`](#components)
[`dynamiczone`](#dynamic-zones)
|
| Internationalization (i18n)-related types
_Can only be used if the [i18n plugin](/dev-docs/plugins/i18n.md) is installed_|
`locale`
`localizations`
|
+:::caution
+Never name a custom attribute `locale` because it could interfere with, and break, the [i18n](/dev-docs/plugins/i18n) feature.
+:::
+
#### Validations
Basic validations can be applied to attributes using the following parameters:
@@ -615,6 +619,10 @@ Lifecycle hooks can be customized declaratively or programmatically.
Lifecycles hooks are not triggered when using directly the [knex](https://knexjs.org/) library instead of Strapi functions.
:::
+:::tip
+Please refer to the [error handling](/dev-docs/error-handling#services-and-models-lifecycles) documentation to learn how to throw errors from lifecycle hooks.
+:::
+
### Available lifecycle events
The following lifecycle events are available:
@@ -653,10 +661,12 @@ Lifecycle hooks are functions that take an `event` parameter, an object with the
### Declarative and programmatic usage
-To configure a content-type lifecycle hook, create a `lifecycles.js` file in the `./api/[api-name]/content-types/[content-type-name]/` folder.
+To configure a content-type lifecycle hook, create a `lifecycles.js` file in the `./src/api/[api-name]/content-types/[content-type-name]/` folder.
Each event listener is called sequentially. They can be synchronous or asynchronous.
+#### Declarative usage
+
@@ -704,12 +714,14 @@ export default {
+#### Programmatic usage
+
Using the database layer API, it's also possible to register a subscriber and listen to events programmatically:
```js title="./src/index.js"
module.exports = {
async bootstrap({ strapi }) {
-// registering a subscriber
+ // registering a subscriber
strapi.db.lifecycles.subscribe({
models: [], // optional;
diff --git a/docusaurus/docs/dev-docs/backend-customization/policies.md b/docusaurus/docs/dev-docs/backend-customization/policies.md
index a477732af4..0932764110 100644
--- a/docusaurus/docs/dev-docs/backend-customization/policies.md
+++ b/docusaurus/docs/dev-docs/backend-customization/policies.md
@@ -75,6 +75,10 @@ export default (policyContext, config, { strapi }) => {
+:::tip
+To see a possible advanced usage for route policies, read the [policies](/dev-docs/backend-customization/examples/policies) page of the backend customization examples cookbook.
+:::
+
Policies can be configured using a `config` object:
@@ -201,7 +205,7 @@ module.exports = {
The `isAuthenticated` policy prodived with the `users-permissions` plugin
is executed before the `find` action in the `Restaurant.js` controller.
*/
- policies: ['plugins::users-permissions.isAuthenticated']
+ policies: ['plugin::users-permissions.isAuthenticated']
}
}
]
@@ -225,7 +229,7 @@ export default {
The `isAuthenticated` policy prodived with the `users-permissions` plugin
is executed before the `find` action in the `Restaurant.js` controller.
*/
- policies: ['plugins::users-permissions.isAuthenticated']
+ policies: ['plugin::users-permissions.isAuthenticated']
}
}
]
diff --git a/docusaurus/docs/dev-docs/backend-customization/requests-responses.md b/docusaurus/docs/dev-docs/backend-customization/requests-responses.md
index 109795fddd..3945996544 100644
--- a/docusaurus/docs/dev-docs/backend-customization/requests-responses.md
+++ b/docusaurus/docs/dev-docs/backend-customization/requests-responses.md
@@ -48,7 +48,7 @@ The `ctx.request` object contains the following parameters:
| `ctx.request.ips` | When `X-Forwarded-For` is present and `app.proxy` is enabled, an array of IPs is returned, ordered from upstream to downstream.
For example if the value were "client, proxy1, proxy2", you would receive the `["client", "proxy1", "proxy2"]` array. | `Array` |
| `ctx.request.method` | Request method (e.g., `GET`, `POST`). | `String` |
| `ctx.request.origin` | URL part before the first `/`. | `String` |
-| `ctx.request.params` | Query parameters sent in the URL.
For example, if the request URL includes includes something like `/restaurants?id`, the `?id` part creates an `id` parameter accessible through `ctx.request.params.id`. | `Object` |
+| `ctx.request.params` | Parameters sent in the URL.
For example, if the internal URL is `/restaurants/:id`, whatever you replace `:id` in the real request becomes accessible through `ctx.request.params.id`. | `Object` |
| `ctx.request.path` | Path of the requested resource, excluding the query parameters. | `String` |
| `ctx.request.protocol`| Protocol being used (e.g., `https` or `http`). | `String` |
| `ctx.request.query` | Strapi-specific [query parameters](#ctxrequestquery). | `Object` |
diff --git a/docusaurus/docs/dev-docs/backend-customization/routes.md b/docusaurus/docs/dev-docs/backend-customization/routes.md
index 2eb1463234..c8567b420b 100644
--- a/docusaurus/docs/dev-docs/backend-customization/routes.md
+++ b/docusaurus/docs/dev-docs/backend-customization/routes.md
@@ -15,7 +15,7 @@ Requests sent to Strapi on any URL are handled by routes. By default, Strapi gen
- with [policies](#policies), which are a way to block access to a route,
- and with [middlewares](#middlewares), which are a way to control and change the request flow and the request itself.
-Once a route exists, reaching it executes some code handled by a controller (see [controllers documentation](/dev-docs/backend-customization/controllers)).
+Once a route exists, reaching it executes some code handled by a controller (see [controllers documentation](/dev-docs/backend-customization/controllers)). To view all existing routes and their hierarchal order, you can run `yarn strapi routes:list` (see [CLI reference](/dev-docs/cli)).
+:::tip
+If you are getting an `ER_ACCESS_DENIED_ERROR message` and the `DATABASE_PASSWORD` value in your `.env` file includes special characters, try surrounding the password with single quotes. For instance, `DATABASE_PASSWORD=example-i-had-special-chars-like-#` should become `DATABASE_PASSWORD='example-i-had-special-chars-like-#'`.
+:::
+
### Environment variables for Strapi applications before `v4.6.2`
If you started your project with a version prior to `v4.6.2` you can convert your `database.js|database.ts` configuration file following this procedure:
@@ -545,5 +550,5 @@ $ GRANT ALL ON SCHEMA public TO my_strapi_db_user;
In addition to `client` values of '[postgres](https://www.npmjs.com/package/pg)', 'sqlite', and '[mysql](https://www.npmjs.com/package/mysql)', Strapi also allows a `client` value of '[mysql2](https://www.npmjs.com/package/mysql2)' for those who install and wish to use that package.
:::note
-`mysql2` is required for the `caching_sha2_password` auth method used by default in MySQL v8+. If you receive an `"ER_NOT_SUPPORTED_AUTH_MODE"` error when using the `mysql` driver, try adding the `mysql2` package to your project. You should then remove the deprecated `connectionString` parameter from your connection configuration in favor of the `username` and `password` values.
+`mysql2` is required for the `caching_sha2_password` auth method used by default in MySQL v8+. If you receive an `"ER_NOT_SUPPORTED_AUTH_MODE"` error when using the `mysql` driver, try adding the `mysql2` package to your project. You should then remove the deprecated `connectionString` parameter from your connection configuration in favor of the `user` and `password` values.
:::
diff --git a/docusaurus/docs/dev-docs/configurations/environment.md b/docusaurus/docs/dev-docs/configurations/environment.md
index 08cf1cbea0..6f2a8ebbdb 100644
--- a/docusaurus/docs/dev-docs/configurations/environment.md
+++ b/docusaurus/docs/dev-docs/configurations/environment.md
@@ -1,6 +1,6 @@
---
title: Environment variables
-displayed_sidebar: devDocsSidebar
+displayed_sidebar: devDocsConfigSidebar
---
# Environment configuration and variables
@@ -18,12 +18,14 @@ Strapi provides the following environment variables:
| `STRAPI_TELEMETRY_DISABLED` | Don't send telemetry usage data to Strapi | `Boolean` | `false` |
| `STRAPI_LICENSE` | The license key to activate the Enterprise Edition | `String` | `undefined` |
| `STRAPI_DISABLE_REMOTE_DATA_TRANSFER` | Disable the ability to use the [transfer feature](/dev-docs/data-management#transfer-data-using-the-cli-tool) | `Boolean` | `false` |
-| `NODE_ENV` | Type of environment where the application is running.
`production` enables specific behaviors (see [Node.js documentation](https://nodejs.dev/en/learn/nodejs-the-difference-between-development-and-production) for details) | `String` | `'development'` |
+| `NODE_ENV` | Type of environment where the application is running.
`production` enables specific behaviors (see [Node.js documentation](https://nodejs.org/en/learn/getting-started/nodejs-the-difference-between-development-and-production) for details) | `String` | `'development'` |
| `BROWSER` | Open the admin panel in the browser after startup | `Boolean` | `true` |
| `ENV_PATH` | Path to the file that contains your environment variables | `String` | `'./.env'` |
| `STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE`
_Optional_ | Initialization locale for the application, if the [Internationalization (i18n) plugin](/dev-docs/plugins/i18n) is installed and enabled on Content-Types (see [Configuration of i18n in production environments](/dev-docs/plugins/i18n#configuration-of-the-default-locale)) | `String` | `'en'` |
+| `STRAPI_ENFORCE_SOURCEMAPS` | Forces the bundler to emit source-maps, which is helpful for debugging errors in the admin app. | `boolean` | `false` |
| `FAST_REFRESH` | Use [react-refresh](https://github.com/pmmmwh/react-refresh-webpack-plugin) to enable "Fast Refresh" for near-instant feedback while developing the Strapi admin panel. | `boolean` | `true` |
+
:::tip
Prefixing an environment variable name with `STRAPI_ADMIN_` exposes the variable to the admin front end (e.g., `STRAPI_ADMIN_MY_PLUGIN_VARIABLE` is accessible through `process.env.STRAPI_ADMIN_MY_PLUGIN_VARIABLE`).
:::
diff --git a/docusaurus/docs/dev-docs/configurations/features.md b/docusaurus/docs/dev-docs/configurations/features.md
new file mode 100644
index 0000000000..5ec4ce3d9d
--- /dev/null
+++ b/docusaurus/docs/dev-docs/configurations/features.md
@@ -0,0 +1,111 @@
+---
+title: Features configuration
+sidebar_label: Features
+description: Enable experimental Strapi features
+displayed_sidebar: devDocsConfigSidebar
+---
+
+# Features configuration
+
+The `config/features.js|ts` file is used to enable feature flags. Currently this file only includes a `future` object used to enable experimental features through **future flags**.
+
+Some incoming Strapi features are not yet ready to be shipped to all users, but Strapi still offers community users the opportunity to provide early feedback on these new features or changes. With these experimental features, developers have the flexibility to choose and integrate new features and changes into their Strapi applications as they become available in the current major version as well as assist us in shaping these new features.
+
+Such experimental features are indicated by a badge throughout the documentation and enabling these features requires enabling the corresponding future flags. Future flags differ from features that are in alpha in that future flags are disabled by default.
+
+:::danger
+Enable future flags at your own risk. Experimental features may be subject to change or removal, may contain breaking changes, may be unstable or not fully ready for use, and some parts may still be under development or using mock data.
+:::
+
+
+
+
+## Enabling a future flag
+
+To enable a future flag:
+
+1. (_optional_) If the server is running, stop it with `Ctrl-C`.
+2. Open the `config/features.js|ts` file or create it if the file does not exist yet. The file will export a `future` object with all the future flags to enable.
+3. To enable a future flag, add its property name (see [full list](#available-future-flags)) to the `future` object and ensure the property's value is set to `true`. The following example shows how to enable the `contentReleasesScheduling` future flag:
+
+
+
+
+
+ ```ts title="/config/features.ts"
+ module.export = ({ env }) => ({
+ future: {
+ // You could also simply write: contentReleases: true
+ contentReleasesScheduling: env.bool('STRAPI_FUTURE_CONTENT_RELEASES_SCHEDULING', false),
+ },
+ })
+
+ ```
+
+ This example assumes that you have an `.env` environment file at the root of your application and that the file includes the following line:
+
+ ```json title=".env"
+ STRAPI_FUTURE_CONTENT_RELEASES_SCHEDULING=true
+ ```
+
+ If your environment file does not include this value, the `contentReleasesScheduling` future flag property value will default to `false` and the experimental feature will not be enabled.
+
+
+
+
+
+ ```ts title="/config/features.ts"
+ export default {
+ future: {
+ // You could also simply write: contentReleases: true
+ contentReleasesScheduling: env.bool('STRAPI_FUTURE_CONTENT_RELEASES_SCHEDULING', false),
+ },
+ };
+ ```
+
+ This example assumes that you have an `.env` environment file at the root of your application and that the file includes the following line:
+
+ ```json title=".env"
+ STRAPI_FUTURE_CONTENT_RELEASES_SCHEDULING=true
+ ```
+
+ If your environment file does not include this value, the `contentReleases` future flag property value will default to `false` and the experimental feature will not be enabled.
+
+
+
+
+4. Rebuild the admin panel and restart the server:
+
+
+
+
+ ```sh
+ yarn develop
+ ```
+
+
+
+ ```sh
+ npm run develop
+ ```
+
+
+
+
+## Future flags API
+
+Developers can use the following APIs to interact with future flags:
+
+- Features configuration is part of the `config` object and can be read with `strapi.config.get('features')` or with `strapi.features.config`.
+
+- `strapi.features.future` returns the `isEnabled()` that can be used to determine if a future flag is enabled, using the following method: `strapi.features.future.isEnabled('featureName')`.
+
+## Available future flags
+
+There are currently no available future flags. This section will be updated once new experimental features are available for testing.
+
+
diff --git a/docusaurus/docs/dev-docs/configurations/functions.md b/docusaurus/docs/dev-docs/configurations/functions.md
index 067ee6b880..3931fbd6cc 100644
--- a/docusaurus/docs/dev-docs/configurations/functions.md
+++ b/docusaurus/docs/dev-docs/configurations/functions.md
@@ -1,6 +1,6 @@
---
title: Lifecycle Functions
-displayed_sidebar: devDocsSidebar
+displayed_sidebar: devDocsConfigSidebar
description: Strapi includes lifecycle functions (e.g. register, bootstrap and destroy) that control the flow of your application.
---
@@ -11,6 +11,18 @@ The `./src/index.js` file (or `./src/index.ts` file in a [TypeScript-based](/dev
The functions can be synchronous, asynchronous, or return a promise.
+``` mermaid
+flowchart TB
+ A([The Strapi application starts.]) --> B{"register()"}
+ B -- The Strapi application is setup. --> C
+ C{"bootstrap()"} -- The Strapi back-end server starts. --> D
+ D(Request)
+ D
+ click B "#register"
+ click C "#bootstrap"
+ click D "/dev-docs/backend-customization/requests-responses"
+```
+
## Synchronous function
@@ -147,6 +159,8 @@ It can be used to:
- load some [environment variables](/dev-docs/configurations/environment)
- register a [custom field](/dev-docs/custom-fields) that would be used only by the current Strapi application.
+`register()` is the very first thing that happens when a Strapi application is starting. This happens _before_ any setup process and you don't have any access to database, routes, policies, or any other backend server elements within the `register()` function.
+
## Bootstrap
The `bootstrap` lifecycle function, found in `./src/index.js` (or in `./src/index.ts`), is called at every server start.
@@ -157,6 +171,12 @@ It can be used to:
- fill the database with some necessary data
- declare custom conditions for the [Role-Based Access Control (RBAC)](/dev-docs/configurations/rbac) feature
+The `bootstrapi()` function is run _before_ the back-end server starts but _after_ the Strapi application has setup, so you have access to anything from the `strapi` object.
+
+:::tip
+You can run `yarn strapi console` (or `npm run strapi console`) in the terminal and interact with the `strapi` object.
+:::
+
## Destroy
The `destroy` function, found in `./src/index.js` (or in `./src/index.ts`), is an asynchronous function that runs before the application gets shut down.
diff --git a/docusaurus/docs/dev-docs/configurations/guides/access-cast-environment-variables.md b/docusaurus/docs/dev-docs/configurations/guides/access-cast-environment-variables.md
new file mode 100644
index 0000000000..431928ed38
--- /dev/null
+++ b/docusaurus/docs/dev-docs/configurations/guides/access-cast-environment-variables.md
@@ -0,0 +1,101 @@
+---
+title: Access and cast environment variables
+# description: todo
+displayed_sidebar: devDocsConfigSidebar
+---
+
+# How to access and cast environment variables
+
+In most use cases there will be different configurations between environments (e.g. database credentials).
+
+Instead of writing those credentials into configuration files, variables can be defined in a `.env` file at the root of the application:
+
+```sh
+# path: .env
+
+DATABASE_PASSWORD=acme
+```
+
+To customize the path of the `.env` file to load, set an environment variable called `ENV_PATH` before starting the application:
+
+```sh
+ENV_PATH=/absolute/path/to/.env npm run start
+```
+
+## Accessing environment variables
+
+Variables defined in the `.env` file are accessible using `process.env.{variableName}` anywhere in configuration and application files.
+
+In configuration files, a `env()` utility allows defining defaults and [casting values](#casting-environment-variables):
+
+
+
+
+
+```js title="./config/database.js"
+
+module.exports = ({ env }) => ({
+ connections: {
+ default: {
+ settings: {
+ password: env('DATABASE_PASSWORD'),
+ },
+ },
+ },
+});
+```
+
+
+
+
+
+```js title="./config/database.ts"
+
+export default ({ env }) => ({
+ connections: {
+ default: {
+ settings: {
+ password: env('DATABASE_PASSWORD'),
+ },
+ },
+ },
+});
+```
+
+
+
+
+
+:::note
+The syntax `property-name: env('VAR', 'default-value')` uses the value stored in the `.env` file. If there is no specified value in the `.env` file the default value is used.
+:::
+
+## Casting environment variables
+
+The `env()` utility can be used to cast environment variables to different types:
+
+```js
+// 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')
+```
diff --git a/docusaurus/docs/dev-docs/configurations/guides/access-configuration-values.md b/docusaurus/docs/dev-docs/configurations/guides/access-configuration-values.md
new file mode 100644
index 0000000000..5560e01cf5
--- /dev/null
+++ b/docusaurus/docs/dev-docs/configurations/guides/access-configuration-values.md
@@ -0,0 +1,51 @@
+---
+title: Access configuration values from the code
+# description: todo
+displayed_sidebar: devDocsConfigSidebar
+---
+
+# How to access to configuration values from the code
+
+All the [configuration files](/dev-docs/configurations) are loaded on startup and can be accessed through the `strapi.config` configuration provider.
+
+If the `/config/server.js` file has the following configuration:
+
+ ```js
+ module.exports = {
+ host: '0.0.0.0',
+ };
+ ```
+
+then the `server.host` key can be accessed as:
+
+ ```js
+ strapi.config.get('server.host', 'defaultValueIfUndefined');
+ ```
+
+Nested keys are accessible with the [dot notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#dot_notation).
+
+:::note
+The filename is used as a prefix to access the configurations.
+:::
+
+Configuration files can either be `.js` or `.json` files.
+
+When using a `.js` file, the configuration can be exported:
+
+- either as an object:
+
+ ```js
+ module.exports = {
+ mySecret: 'someValue',
+ };
+ ```
+
+- or as a function returning a configuration object (recommended usage). The function will get access to the [`env` utility](#casting-environment-variables):
+
+ ```js
+ module.exports = ({ env }) => {
+ return {
+ mySecret: 'someValue',
+ };
+ };
+ ```
diff --git a/docusaurus/docs/dev-docs/configurations/middlewares.md b/docusaurus/docs/dev-docs/configurations/middlewares.md
index ce9e463dda..a4a0817f38 100644
--- a/docusaurus/docs/dev-docs/configurations/middlewares.md
+++ b/docusaurus/docs/dev-docs/configurations/middlewares.md
@@ -1,6 +1,6 @@
---
title: Middlewares
-displayed_sidebar: devDocsSidebar
+displayed_sidebar: devDocsConfigSidebar
description: Strapi offers a single entry point file for its middlewares configurations.
---
@@ -35,6 +35,7 @@ The `./config/middlewares.js` file exports an array, where order matters and con
module.exports = [
// The array is pre-populated with internal, built-in middlewares, prefixed by `strapi::`
+ 'strapi::logger',
'strapi::errors',
'strapi::security',
'strapi::cors',
@@ -67,7 +68,6 @@ module.exports = [
},
// remaining internal & built-in middlewares
- 'strapi::logger',
'strapi::query',
'strapi::body',
'strapi::session',
@@ -84,6 +84,7 @@ module.exports = [
export default [
// The array is pre-populated with internal, built-in middlewares, prefixed by `strapi::`
+ 'strapi::logger',
'strapi::cors',
'strapi::body',
'strapi::errors',
@@ -509,12 +510,10 @@ module.exports = {
'use strict';
-const {
- winston,
- formats: { prettyPrint, levelFilter },
-} = require('@strapi/logger');
+import winston from 'winston';
+import { prettyPrint, levelFilter } from 'winston.format';
-export default [
+export default {
transports: [
new winston.transports.Console({
level: 'http',
@@ -524,7 +523,7 @@ export default [
),
}),
],
-];
+};
```
@@ -852,7 +851,7 @@ module.exports = [
{
name: 'strapi::session',
config: {
- rolling: true
+ rolling: true,
renew: true
},
},
@@ -871,7 +870,7 @@ export default [
{
name: 'strapi::session',
config: {
- rolling: true
+ rolling: true,
renew: true
},
},
diff --git a/docusaurus/docs/dev-docs/configurations/plugins.md b/docusaurus/docs/dev-docs/configurations/plugins.md
index aaf0989ad0..10f1377a6b 100644
--- a/docusaurus/docs/dev-docs/configurations/plugins.md
+++ b/docusaurus/docs/dev-docs/configurations/plugins.md
@@ -1,13 +1,14 @@
---
title: Plugins configuration
-displayed_sidebar: devDocsSidebar
+sidebar_label: Plugins
+displayed_sidebar: devDocsConfigSidebar
description: Strapi plugins have a single entry point file to define their configurations.
---
# Plugins configuration
-Plugin configurations are stored in `./config/plugins.js` (see [project structure](/dev-docs/project-structure)). Each plugin can be configured with the following available parameters:
+Plugin configurations are stored in `/config/plugins.js|ts` (see [project structure](/dev-docs/project-structure)). Each plugin can be configured with the following available parameters:
| Parameter | Description | Type |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
@@ -15,6 +16,12 @@ Plugin configurations are stored in `./config/plugins.js` (see [project structur
| `config`
_Optional_ | Used to override default plugin configuration ([defined in strapi-server.js](/dev-docs/api/plugins/server-api#configuration)) | Object |
| `resolve` _Optional, only required for local plugins_ | Path to the plugin's folder | String |
+:::note
+Some features of Strapi are provided by plugins and the following plugins can also have specific configuration options: [GraphQL](#graphql-configuration) and [Upload](#upload-configuration).
+:::
+
+**Basic example custom configuration for plugins:**
+
@@ -36,7 +43,7 @@ module.exports = ({ env }) => ({
},
// disable a plugin
- myotherplugin: {
+ 'my-other-plugin': {
enabled: false, // plugin installed but disabled
},
});
@@ -63,7 +70,7 @@ export default ({ env }) => ({
},
// disable a plugin
- myotherplugin: {
+ 'my-other-plugin': {
enabled: false, // plugin installed but disabled
},
});
@@ -79,7 +86,7 @@ If no specific configuration is required, a plugin can also be declared with the
## GraphQL configuration
-The [GraphQL plugin](/dev-docs/plugins/graphql) has the following specific configuration options that should be declared in a `graphql.config` object. All parameters are optional:
+The [GraphQL plugin](/dev-docs/plugins/graphql) has the following specific configuration options that should be declared in a `graphql.config` object within the `config/plugins` file. All parameters are optional:
| Parameter | Description | Type | Default |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------- |
@@ -93,6 +100,8 @@ The [GraphQL plugin](/dev-docs/plugins/graphql) has the following specific confi
| `playgroundAlways` | Whether the playground should be publicly exposed.
Enabled by default in if `NODE_ENV` is set to `development`. | Boolean | `false` |
| `shadowCRUD` | Whether type definitions for queries, mutations and resolvers based on models should be created automatically (see [Shadow CRUD documentation](/dev-docs/plugins/graphql#shadow-crud)). | Boolean | `true` |
+**Example custom configuration**:
+
@@ -118,8 +127,7 @@ module.exports = () => ({
-```ts
-// path: ./config/plugins.ts
+```ts title="./config/plugins.ts"
export default () => ({
graphql: {
@@ -139,3 +147,81 @@ export default () => ({
+
+## Upload configuration
+
+The [Upload plugin](/dev-docs/plugins/upload) handles the [Media Library](/user-docs/media-library). When using the default upload provider, the following specific configuration options can be declared in an `upload.config` object within the `config/plugins` file. All parameters are optional:
+
+| Parameter | Description | Type | Default |
+| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------- | ------- |
+| `providerOptions.localServer` | Options that will be passed to [koa-static](https://github.com/koajs/static) upon which the Upload server is build.
(See [local server configuration](/dev-docs/plugins/upload#local-server) in Upload documentation for additional details) | Object | - |
+| `sizeLimit` | Maximum file size in bytes.
(See [max file size configuration](/dev-docs/plugins/upload#max-file-size) in Upload plugin documentation for additional information) | Integer | `209715200`
(200 MB in bytes, i.e., 200 x 1024 x 1024 bytes) |
+| `breakpoints` | Allows to override the breakpoints sizes at which responsive images are generated when the "Responsive friendly upload" option is set to `true`.
(See [responsive images configuration](/dev-docs/plugins/upload#responsive-images) in Upload plugin documentation for additional information) | Object | `{ large: 1000, medium: 750, small: 500 }` |
+
+:::note Notes
+* Some configuration options can also be set directly from the Admin Panel (see [User Guide](/user-docs/settings/media-library-settings)).
+* The Upload request timeout is defined in the server options, not in the Upload plugin options, as it's not specific to the Upload plugin but is applied to the whole Strapi server instance. See [upload request timeout configuration](/dev-docs/plugins/upload#upload-request-timeout) in the Upload documentation for additional details.
+* When using a different upload provider, additional configuration options might be available. For Upload providers maintained by Strapi, see the [Amazon S3 provider](https://market.strapi.io/providers/@strapi-provider-upload-aws-s3) and [Cloudinary provider](https://market.strapi.io/providers/@strapi-provider-upload-cloudinary) documentations for additional information about available options.
+:::
+
+**Example custom configuration**:
+
+The following is an example of a custom configuration for the Upload plugin when using the default upload provider:
+
+
+
+
+
+```js title="/config/plugins.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
+ },
+ },
+ },
+});
+```
+
+
+
+
+
+```ts title="/config/plugins.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
+ },
+ },
+ },
+})
+```
+
+
+
+
diff --git a/docusaurus/docs/dev-docs/configurations/public-assets.md b/docusaurus/docs/dev-docs/configurations/public-assets.md
index 4446afab79..df364b55e5 100644
--- a/docusaurus/docs/dev-docs/configurations/public-assets.md
+++ b/docusaurus/docs/dev-docs/configurations/public-assets.md
@@ -1,6 +1,7 @@
---
title: Public assets configuration
-displayed_sidebar: devDocsSidebar
+sidebar_label: Public assets
+displayed_sidebar: devDocsConfigSidebar
description: The public folder of Strapi is used for static files that you want to make accessible to the outside world.
---
diff --git a/docusaurus/docs/dev-docs/configurations/rbac.md b/docusaurus/docs/dev-docs/configurations/rbac.md
index 67757ee377..1da811a687 100644
--- a/docusaurus/docs/dev-docs/configurations/rbac.md
+++ b/docusaurus/docs/dev-docs/configurations/rbac.md
@@ -1,11 +1,12 @@
---
-title: Role-Based Access Control
-displayed_sidebar: devDocsSidebar
+title: Create new Role-Based Access Control (RBAC) conditions
+sidebar_label: Role-Based Access Control (RBAC)
+displayed_sidebar: devDocsConfigSidebar
description: In Strapi, RBAC is an approach to restricting access to some features of the admin panel to some users. The Community Edition of Strapi offers 3 default roles.
---
-# Role-Based Access Control (RBAC)
+# Create new Role-Based Access Control (RBAC) conditions
Role-Based Access Control (RBAC) is an approach to restricting access to some users. In a Strapi application, users of the admin panel are administrators. Their roles and permissions are [configured in the admin panel](/user-docs/users-roles-permissions/configuring-administrator-roles).
diff --git a/docusaurus/docs/dev-docs/configurations/server.md b/docusaurus/docs/dev-docs/configurations/server.md
index 5582328ffc..177906cc07 100644
--- a/docusaurus/docs/dev-docs/configurations/server.md
+++ b/docusaurus/docs/dev-docs/configurations/server.md
@@ -1,7 +1,8 @@
---
-title: Server
+title: Server configuration
+sidebar_label: Server
description: Strapi offers a single entry point file for its server configuration.
-displayed_sidebar: devDocsSidebar
+displayed_sidebar: devDocsConfigSidebar
---
@@ -35,6 +36,8 @@ The `./config/server.js` file can include the following parameters:
| `dirs` | Path configuration of different directories Strapi uses. | object | |
| `dirs.public` | Customize the path of the public folder. | string | `./public` |
| `webhooks.populateRelations` | Enable or disable receiving populated relations in webhooks | boolean | `true` |
+| `http` | Configuration of the http server used by Strapi | object | |
+| `http.serverOptions` | Options passed to http `createServer` | [http.serverOptions](https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener) | {} |
## Configurations
diff --git a/docusaurus/docs/dev-docs/configurations/sso.md b/docusaurus/docs/dev-docs/configurations/sso.md
index 5303d12c8e..20db87e737 100644
--- a/docusaurus/docs/dev-docs/configurations/sso.md
+++ b/docusaurus/docs/dev-docs/configurations/sso.md
@@ -1,6 +1,6 @@
---
-title: SSO
-displayed_sidebar: devDocsSidebar
+title: Single Sign-on (SSO)
+displayed_sidebar: devDocsConfigSidebar
description: Strapi's SSO allows you to configure additional sign-in and sign-up methods for your administration panel. It requires an Enterprise Edition with a Gold plan.
canonicalUrl: https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/sso.html
---
@@ -767,10 +767,6 @@ npm install --save passport-keycloak-oauth2-oidc
-:::caution
-When setting the `OKTA_DOMAIN` environment variable, make sure to include the protocol (e.g. `https://example.okta.com`). If you do not, you will end up in a redirect loop.
-:::
-
Configuration example for Keycloak (OpenID Connect):
@@ -892,6 +888,11 @@ npm install --save passport-okta-oauth20
+
+:::caution
+When setting the `OKTA_DOMAIN` environment variable, make sure to include the protocol (e.g. `https://example.okta.com`). If you do not, you will end up in a redirect loop.
+:::
+
Configuration example for Okta:
diff --git a/docusaurus/docs/dev-docs/configurations/typescript.md b/docusaurus/docs/dev-docs/configurations/typescript.md
index 0d138e032a..03e20f9e24 100644
--- a/docusaurus/docs/dev-docs/configurations/typescript.md
+++ b/docusaurus/docs/dev-docs/configurations/typescript.md
@@ -1,6 +1,7 @@
---
title: TypeScript configuration
-displayed_sidebar: devDocsSidebar
+sidebar_label: TypeScript
+displayed_sidebar: devDocsConfigSidebar
description: How to configure Strapi for TypeScript development.
---
@@ -23,40 +24,20 @@ TypeScript-enabled Strapi applications have a specific [project structure](/dev-
## Strapi-specific configuration for TypeScript
-:::caution π§ This feature is considered experimental.
-These settings are considered experimental and might have issues or break some features.
-:::
-
Types generated by Strapi are based on the user project structure. Once the type definitions are emitted into their dedicated files, Strapi reads the type definitions to adapt the autocompletion results accordingly.
-To avoid having to [manually generate types](/dev-docs/typescript#generate-typings-for-project-schemas) every time the server restarts, an optional `config/typescript.js|ts` configuration file can be added, which currently accepts only one parameter:
+To avoid having to [manually generate types](/dev-docs/typescript#generate-typings-for-project-schemas) every time the server restarts, TypeScript-based projects autogenerate the types for you. However, generating types is not a requirement for JavaScript-based projects, so if you want you can turn types autogeneration off by creating an optional `config/typescript.js|ts` configuration file and setting the `autogenerate` parameter to `false`:
| Parameter | Description | Type | Default |
| -------------- | -------------------------------------------------------------- | --------- | ------- |
-| `autogenerate` | Enable or disable automatic types generation on server restart | `Boolean` | `false` |
-
-**Example:**
-
-
-
-
+| `autogenerate` | Enable or disable automatic types generation on server restart | `Boolean` | `true` |
```js title="./config/typescript.js"
module.exports = ({ env }) => ({
- autogenerate: true,
-});
-```
-
-
-
-
-
-```ts title="./config/typescript.ts"
-export default ({ env }) => ({
- autogenerate: true,
+ autogenerate: false,
});
```
-
-
-
+:::caution π§ Experimental.
+Turning off types autogeneration for JavaScript projects is considered experimental and might have issues or break some features.
+:::
diff --git a/docusaurus/docs/dev-docs/custom-fields.md b/docusaurus/docs/dev-docs/custom-fields.md
index 140735b3b9..190a7269cd 100644
--- a/docusaurus/docs/dev-docs/custom-fields.md
+++ b/docusaurus/docs/dev-docs/custom-fields.md
@@ -11,15 +11,16 @@ import CustomFieldRequiresPlugin from '/docs/snippets/custom-field-requires-plug
Custom fields extend Strapiβs capabilities by adding new types of fields to content-types and components. Once created or added to Strapi via plugins, custom fields can be used in the Content-Type Builder and Content Manager just like built-in fields.
-The present documentation is intended for custom field creators: it describes which APIs and functions developers must use to create a new custom field. The [user guide](/user-docs/plugins/introduction-to-plugins.md#custom-fields) describes how to add and use custom fields from Strapi's admin panel.
+The present documentation is intended for custom field creators: it describes which APIs and functions developers must use to create a new custom field. The [User Guide](/user-docs/plugins/introduction-to-plugins.md#custom-fields) describes how to add and use custom fields from Strapi's admin panel.
It is recommended that you develop a dedicated [plugin](/dev-docs/plugins-development) for custom fields. Custom field plugins include both a server and admin panel part. The custom field must be registered in both parts before it is usable in Strapi's admin panel.
Once created and used, custom fields are defined like any other attribute in the model's schema. An attribute using a custom field will have its type represented as `customField` (i.e. `type: 'customField'`). Depending on the custom field being used a few additional properties may be present in the attribute's definition (see [models documentation](/dev-docs/backend-customization#custom-fields)).
:::note NOTES
-* Though the recommended way to add a custom field is through creating a plugin, app-specific custom fields can also be registered within the global `register` [function](/dev-docs/configurations/functions) found in `src/index.js` and `src/admin/app/js` files.
-* Custom fields can only be shared using plugins.
+
+- Though the recommended way to add a custom field is through creating a plugin, app-specific custom fields can also be registered within the global `register` [function](/dev-docs/configurations/functions) found in `src/index.js` and `src/admin/app/js` files.
+- Custom fields can only be shared using plugins.
:::
## Registering a custom field on the server
@@ -34,12 +35,12 @@ The `strapi.customFields` object exposes a `register()` method on the `Strapi` i
`strapi.customFields.register()` registers one or several custom field(s) on the server by passing an object (or an array of objects) with the following parameters:
-| Parameter | Description | Type |
-| --------------------------------- | --------------------------------------------------------------------------- | -------- |
-| `name` | The name of the custom field | `String` |
-| `plugin`
(_optional_) | The name of the plugin creating the custom fields | `String` |
-| `type` | The data type the custom field will use | `String` |
-| `inputSize`
(_optional_) | Parameters to define the width of a custom field's input in the admin panel | `Object` |
+| Parameter | Description | Type |
+| --------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------| -------- |
+| `name` | The name of the custom field | `String` |
+| `plugin`
(_optional_) | The name of the plugin creating the custom fields
βοΈ If defined, the `pluginId` value on the admin panel registration must have the same value (see [Registering a custom field in the admin panel](#registering-a-custom-field-in-the-admin-panel)) | `String` |
+| `type` | The data type the custom field will use | `String` |
+| `inputSize`
(_optional_) | Parameters to define the width of a custom field's input in the admin panel | `Object` |
The optional `inputSize` object, when specified, must contain all of the following parameters:
@@ -48,8 +49,11 @@ The optional `inputSize` object, when specified, must contain all of the followi
| `default` | The default size in columns that the input field will occupy in the 12-column grid in the admin panel. The value can either be `4`, `6`, `8` or `12`. | `Integer` |
| `isResizable` | Whether the input can be resized or not | `Boolean` |
-:::note
-Currently, custom fields cannot add new data types to Strapi and must use existing, built-in Strapi data types described in the [models' attributes](/dev-docs/backend-customization#model-attributes) documentation. Special data types unique to Strapi, such as relation, media, component, or dynamic zone data types, cannot be used in custom fields.
+:::note Current limitations
+Currently:
+* Custom fields cannot add new data types to Strapi and must use existing, built-in Strapi data types described in the [models' attributes](/dev-docs/backend-customization#model-attributes) documentation.
+* You also cannot modify an existing data type.
+* Special data types unique to Strapi, such as relation, media, component, or dynamic zone data types, cannot be used in custom fields.
:::
@@ -58,15 +62,15 @@ Currently, custom fields cannot add new data types to Strapi and must use existi
In the following example, the `color-picker` plugin was created using the CLI generator (see [plugins development](/dev-docs/plugins-development)):
```js title="./src/plugins/color-picker/server/register.js"
-
-'use strict';
+"use strict";
module.exports = ({ strapi }) => {
strapi.customFields.register({
- name: 'color',
- plugin: 'color-picker',
- type: 'string',
- inputSize: { // optional
+ name: "color",
+ plugin: "color-picker",
+ type: "string",
+ inputSize: {
+ // optional
default: 4,
isResizable: true,
},
@@ -77,14 +81,14 @@ module.exports = ({ strapi }) => {
The custom field could also be declared directly within the `strapi-server.js` file if you didn't have the plugin code scaffolded by the CLI generator:
```js title="./src/plugins/color-picker/strapi-server.js"
-
module.exports = {
register({ strapi }) {
strapi.customFields.register({
- name: 'color',
- plugin: 'color-picker',
- type: 'text',
- inputSize: { // optional
+ name: "color",
+ plugin: "color-picker",
+ type: "text",
+ inputSize: {
+ // optional
default: 4,
isResizable: true,
},
@@ -107,16 +111,16 @@ The `app.customFields` object exposes a `register()` method on the `StrapiApp` i
`app.customFields.register()` registers one or several custom field(s) in the admin panel by passing an object (or an array of objects) with the following parameters:
-| Parameter | Description | Type |
-| -------------------------------- | ------------------------------------------------------------------------ | --------------------- |
-| `name` | Name of the custom field | `String` |
-| `pluginId`
(_optional_) | Name of the plugin creating the custom field | `String` |
-| `type` | Existing Strapi data type the custom field will use
βοΈ Relations, media, components, or dynamic zones cannot be used. | `String` |
-| `icon`
(_optional_) | Icon for the custom field | `React.ComponentType` |
-| `intlLabel` | Translation for the name | [`IntlObject`](https://formatjs.io/docs/react-intl/) |
-| `intlDescription` | Translation for the description | [`IntlObject`](https://formatjs.io/docs/react-intl/) |
-| `components` | Components needed to display the custom field in the Content Manager (see [components](#components)) |
-| `options`
(_optional_) | Options to be used by the Content-type Builder (see [options](#options)) | `Object` |
+| Parameter | Description | Type |
+| -------------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------| ---------------------------------------------------- |
+| `name` | Name of the custom field | `String` |
+| `pluginId`
(_optional_) | Name of the plugin creating the custom field
βοΈ If defined, the `plugin` value on the server registration must have the same value (see [Registering a custom field on the server](#registering-a-custom-field-on-the-server)) | `String` |
+| `type` | Existing Strapi data type the custom field will use
βοΈ Relations, media, components, or dynamic zones cannot be used. | `String` |
+| `icon`
(_optional_) | Icon for the custom field | `React.ComponentType` |
+| `intlLabel` | Translation for the name | [`IntlObject`](https://formatjs.io/docs/react-intl/) |
+| `intlDescription` | Translation for the description | [`IntlObject`](https://formatjs.io/docs/react-intl/) |
+| `components` | Components needed to display the custom field in the Content Manager (see [components](#components)) |
+| `options`
(_optional_) | Options to be used by the Content-type Builder (see [options](#options)) | `Object` |
Example: Registering an example "color" custom field in the admin panel:
@@ -124,8 +128,7 @@ The `app.customFields` object exposes a `register()` method on the `StrapiApp` i
In the following example, the `color-picker` plugin was created using the CLI generator (see [plugins development](/dev-docs/plugins-development.md)):
```jsx title="./src/plugins/color-picker/admin/src/index.js"
-
-import ColorPickerIcon from './components/ColorPicker/ColorPickerIcon';
+import ColorPickerIcon from "./components/ColorPicker/ColorPickerIcon";
export default {
register(app) {
@@ -144,15 +147,18 @@ export default {
id: "color-picker.color.description",
defaultMessage: "Select any color",
},
- icon: ColorPickerIcon, // don't forget to create/import your icon component
+ icon: ColorPickerIcon, // don't forget to create/import your icon component
components: {
- Input: async () => import(/* webpackChunkName: "input-component" */ "./admin/src/components/Input"),
+ Input: async () =>
+ import(
+ /* webpackChunkName: "input-component" */ "./components/Input"
+ ),
},
options: {
// declare options here
},
});
- }
+ },
// ... bootstrap() goes here
};
@@ -170,36 +176,97 @@ export default {
In the following example, the `color-picker` plugin was created using the CLI generator (see [plugins development](/dev-docs/plugins-development.md)):
```jsx title="./src/plugins/color-picker/admin/src/index.js"
-
export default {
register(app) {
app.customFields.register({
// β¦
components: {
- Input: async () => import(/* webpackChunkName: "input-component" */ "./Input"),
- }
+ Input: async () =>
+ import(/* webpackChunkName: "input-component" */ "./Input"),
+ },
// β¦
});
- }
-}
+ },
+};
+```
+
+
+
+Custom field input components receive the following props:
+
+| Prop | Description | Type |
+| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
+| `attribute` | The attribute object with custom field's underlying Strapi type and options | `{ type: String, customField: String }` |
+| `description` | The field description set in [configure the view](/user-docs/content-manager/configuring-view-of-content-type#configuring-the-edit-view) | [`IntlObject`](https://formatjs.io/docs/react-intl/) |
+| `placeholder` | The field placeholder set in [configure the view](/user-docs/content-manager/configuring-view-of-content-type#configuring-the-edit-view) | [`IntlObject`](https://formatjs.io/docs/react-intl/) |
+| `hint` | The field description set in [configure the view](/user-docs/content-manager/configuring-view-of-content-type#configuring-the-edit-view) along with min/max [validation requirements](/dev-docs/backend-customization/models#validations) | `String` |
+| `name` | The field name set in the content-type builder | `String` |
+| `intlLabel` | The field name set in the content-type builder or configure the view | [`IntlObject`](https://formatjs.io/docs/react-intl/) |
+| `onChange` | The handler for the input change event. The `name` argument references the field name. The `type` argument references the underlying Strapi type | `({ target: { name: String value: unknown type: String } }) => void` |
+| `contentTypeUID` | The content-type the field belongs to | `String` |
+| `type` | The custom field uid, for example `plugin::color-picker.color` | `String` |
+| `value` | The input value the underlying Strapi type expects | `unknown` |
+| `required` | Whether or not the field is required | `boolean` |
+| `error` | Error received after validation | [`IntlObject`](https://formatjs.io/docs/react-intl/) |
+| `disabled` | Whether or not the input is disabled | `boolean` |
+
+As of Strapi v4.13.0, fields in the Content Manager can be auto-focussed via the `URLSearchParam` `field`. It's recommended that your input component is wrapped in React's [`forwardRef`](https://react.dev/reference/react/forwardRef) method; you should pass the corresponding `ref` to the `input` element.
+
+
+Example: A custom text input
+
+In the following example we're providing a custom text input that is controlled. All inputs should be controlled otherwise their data will not be submitted on save.
+
+```jsx title="./src/plugins//admin/src/components/Input.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;
```
:::tip
-The `Input` React component receives several props. The [`ColorPickerInput` file](https://github.com/strapi/strapi/blob/main/packages/plugins/color-picker/admin/src/components/ColorPicker/ColorPickerInput/index.js#L71-L82) in the Strapi codebase gives you an example of how they can be used.
+For a more detailed view of the props provided to the customFields and how they can be used check out the [`ColorPickerInput` file](https://github.com/strapi/strapi/blob/main/packages/plugins/color-picker/admin/src/components/ColorPickerInput.tsx#L80-L95) in the Strapi codebase.
:::
-
### Options
`app.customFields.register()` can pass an additional `options` object with the following parameters:
-| Options parameter | Description | Type |
-| -------------- | ------------------------------------------------------------------------------- | ----------------------- |
-| `base` | Settings available in the _Base settings_ tab of the field in the Content-type Builder | `Object` or `Array of Objects` |
-| `advanced` | Settings available in the _Advanced settings_ tab of the field in the Content-type Builder | `Object` or `Array of Objects` |
-| `validator` | Validator function returning an object, used to sanitize input. Uses a [`yup` schema object](https://github.com/jquense/yup/tree/pre-v1). | `Function` |
+| Options parameter | Description | Type |
+| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
+| `base` | Settings available in the _Base settings_ tab of the field in the Content-type Builder | `Object` or `Array of Objects` |
+| `advanced` | Settings available in the _Advanced settings_ tab of the field in the Content-type Builder | `Object` or `Array of Objects` |
+| `validator` | Validator function returning an object, used to sanitize input. Uses a [`yup` schema object](https://github.com/jquense/yup/tree/pre-v1). | `Function` |
Both `base` and `advanced` settings accept an object or an array of objects, each object being a settings section. Each settings section could include:
@@ -211,7 +278,7 @@ Each object in the `items` array can contain the following parameters:
| Items parameter | Description | Type |
| --------------- | ------------------------------------------------------------------ | ---------------------------------------------------- |
| `name` | Label of the input. Must use the `options.settingName` format. | `String` |
-| `description` | Description of the input to use in the Content-type Builder | `String` |
+| `description` | Description of the input to use in the Content-type Builder | [`IntlObject`](https://formatjs.io/docs/react-intl/) |
| `intlLabel` | Translation for the label of the input | [`IntlObject`](https://formatjs.io/docs/react-intl/) |
| `type` | Type of the input (e.g., `select`, `checkbox`) | `String` |
@@ -221,7 +288,6 @@ Each object in the `items` array can contain the following parameters:
In the following example, the `color-picker` plugin was created using the CLI generator (see [plugins development](/dev-docs/plugins-development.md)):
```jsx title="./src/plugins/color-picker/admin/src/index.js"
-
// imports go here (ColorPickerIcon, pluginId, yup packageβ¦)
export default {
@@ -229,7 +295,7 @@ export default {
// ... app.addMenuLink() goes here
// ... app.registerPlugin() goes here
app.customFields.register({
- // β¦
+ // β¦
options: {
base: [
/*
@@ -237,11 +303,13 @@ export default {
of the field in the Content-Type Builder
*/
{
- sectionTitle: { // Add a "Format" settings section
- id: 'color-picker.color.section.format',
- defaultMessage: 'Format',
+ sectionTitle: {
+ // Add a "Format" settings section
+ id: "color-picker.color.section.format",
+ defaultMessage: "Format",
},
- items: [ // Add settings items to the section
+ items: [
+ // Add settings items to the section
{
/*
Add a "Color format" dropdown
@@ -249,31 +317,32 @@ export default {
for the color value: hexadecimal or RGBA
*/
intlLabel: {
- id: 'color-picker.color.format.label',
- defaultMessage: 'Color format',
+ 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
+ name: "options.format",
+ type: "select",
+ value: "hex", // option selected by default
+ options: [
+ // List all available "Color format" options
{
- key: 'hex',
- defaultValue: 'hex',
- value: 'hex',
+ key: "hex",
+ defaultValue: "hex",
+ value: "hex",
metadatas: {
intlLabel: {
- id: 'color-picker.color.format.hex',
- defaultMessage: 'Hexadecimal',
+ id: "color-picker.color.format.hex",
+ defaultMessage: "Hexadecimal",
},
},
},
{
- key: 'rgba',
- value: 'rgba',
+ key: "rgba",
+ value: "rgba",
metadatas: {
intlLabel: {
- id: 'color-picker.color.format.rgba',
- defaultMessage: 'RGBA',
+ id: "color-picker.color.format.rgba",
+ defaultMessage: "RGBA",
},
},
},
@@ -288,21 +357,22 @@ export default {
of the field in the Content-Type Builder
*/
],
- validator: args => ({
+ validator: (args) => ({
format: yup.string().required({
- id: 'options.color-picker.format.error',
- defaultMessage: 'The color format is required',
+ id: "options.color-picker.format.error",
+ defaultMessage: "The color format is required",
}),
- })
+ }),
},
});
- }
-}
+ },
+};
```
+
:::tip
-The Strapi codebase gives an example of how settings objects can be described: check the [`baseForm.js`](https://github.com/strapi/strapi/blob/main/packages/core/content-type-builder/admin/src/components/FormModal/attributes/baseForm.js) file for the `base` settings and the [`advancedForm.js`](https://github.com/strapi/strapi/blob/main/packages/core/content-type-builder/admin/src/components/FormModal/attributes/advancedForm.js) file for the `advanced` settings. The base form lists the settings items inline but the advanced form gets the items from an [`attributeOptions.js`](https://github.com/strapi/strapi/blob/main/packages/core/content-type-builder/admin/src/components/FormModal/attributes/attributeOptions.js) file.
+The Strapi codebase gives an example of how settings objects can be described: check the [`baseForm.ts`](https://github.com/strapi/strapi/blob/main/packages/core/content-type-builder/admin/src/components/FormModal/attributes/baseForm.ts) file for the `base` settings and the [`advancedForm.js`](https://github.com/strapi/strapi/blob/main/packages/core/content-type-builder/admin/src/components/FormModal/attributes/advancedForm.ts) file for the `advanced` settings. The base form lists the settings items inline but the advanced form gets the items from an [`attributeOptions.js`](https://github.com/strapi/strapi/blob/main/packages/core/content-type-builder/admin/src/components/FormModal/attributes/attributeOptions.js) file.
:::
diff --git a/docusaurus/docs/dev-docs/customization.md b/docusaurus/docs/dev-docs/customization.md
new file mode 100644
index 0000000000..8dbcafbe18
--- /dev/null
+++ b/docusaurus/docs/dev-docs/customization.md
@@ -0,0 +1,44 @@
+---
+title: Customization
+# description: todo
+displayed_sidebar: devDocsSidebar
+pagination_next: dev-docs/plugins
+---
+
+# Customization
+
+Strapi includes 2 main components:
+
+- The back-end part of Strapi is a **server** that receives requests and handles them to return responses that can surface the data you built and saved through the Content-Type Builder and Content Manager. The backend server is described in more details in the [Backend Customization introduction](/dev-docs/backend-customization). Most of the parts of the backend server can be customized.
+
+- The front-end, user-facing part of Strapi is called the **admin panel**. The admin panel is the graphical user interface (GUI) that you use to build a data structure, create and manage content, and perform various other actions that can be managed by built-in or 3rd-party plugins. Some parts of the admin panel can be customized.
+
+From a bigger picture, this is how Strapi integrates in a typical, generic setup: Strapi includes 2 parts, a back-end server and an admin panel, and interact with a database (which stores data) and an external, front-end application that displays your data. Both parts of Strapi can be customized to some extent.
+
+```mermaid
+
+ flowchart LR
+ database[(Database)] --> backend[Back-end server]
+ subgraph Strapi
+ direction BT
+ backend --> frontend[Admin Panel]
+ frontend
+ end
+ frontend --> external{{External, front-end applications}}
+```
+
+
+
+Click on any of the following cards to learn more about customization possibilities:
+
+
+
+
+
+
+
+:::info
+Customizing the database or the external, front-end application are outside of the scope of the present documentation section.
+- You can learn more about databases usage with Strapi by reading the installation documentation, which lists [supported databases](/dev-docs/installation/cli#preparing-the-installation), and the configuration documentation, which describes how to [configure a database](/dev-docs/configurations/database) with your project.
+- You can learn more about how external front-end applications can interact with Strapi by reading the [integration guides](/dev-docs/integrations).
+:::
diff --git a/docusaurus/docs/dev-docs/data-management/transfer.md b/docusaurus/docs/dev-docs/data-management/transfer.md
index 8821ce1eae..cfcdfe5b02 100644
--- a/docusaurus/docs/dev-docs/data-management/transfer.md
+++ b/docusaurus/docs/dev-docs/data-management/transfer.md
@@ -18,15 +18,15 @@ The `strapi transfer` command streams your data from one Strapi instance to anot
:::caution
-- If you are using an SQLite database in the destination instance other database connections will be blocked while the `transfer` operation is running.
-- Assets that are contained in the local Media Library provider are transferred to the same provider in the remote instance. This means that if you use the default Media Library locally and an S3 bucket in your remote instance, the `transfer` command does not add assets to your S3 bucket.
-
+* If you are using an SQLite database in the destination instance other database connections will be blocked while the `transfer` operation is running.
+* Admin users and API tokens are not transferred.
+* If you use websockets or Socket.io in your projects, the transfer command will fail. You will need to **temporarily disable websockets or Socket.io** or ensure that your websocket server is running on a different port than the Strapi server, or a on a specific route within Strapi to use the transfer command.
:::
The CLI command consists of the following arguments:
| Option | Description |
-| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+|----------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| `--to` | Full URL of the `/admin` endpoint on the destination Strapi instance (e.g. `--to https://my-beautiful-strapi-website/admin`) |
| `ββtoβtoken` | Transfer token from the Strapi destination instance. |
| `--from` | Full URL of the `/admin` endpoint of the remote Strapi instance to pull data from (e.g., `--from https://my-beautiful-strapi-website/admin`) |
@@ -34,6 +34,7 @@ The CLI command consists of the following arguments:
| `--force` | Automatically answer "yes" to all prompts, including potentially destructive requests, and run non-interactively. |
| `--exclude` | Exclude data using comma-separated data types. The available types are: `content`, `files`, and `config`. |
| `--only` | Include only these data. The available types are: `content`, `files`, and `config`. |
+| `--throttle` | Time in milliseconds to inject an artificial delay between the "chunks" during a transfer. |
:::caution
Either `--to` or `--from` is required.
@@ -80,7 +81,7 @@ Initiating a data transfer depends on whether you want to push data to a remote
1. Start the Strapi server for the destination instance.
2. In a new terminal window, navigate to the root directory of the source instance.
- 3. Run the following minimal command to initiate the transfer:
+ 3. Run the following minimal command to initiate the transfer, ensuring `destinationURL` is the full URL to the admin panel (i.e., the URL includes the `/admin` part):
@@ -111,7 +112,7 @@ Initiating a data transfer depends on whether you want to push data to a remote
1. Start the Strapi server for the source instance.
2. In a new terminal window, navigate to the root directory of the destination instance.
-3. Run the following minimal command to initiate the transfer:
+ 3. Run the following minimal command to initiate the transfer, ensuring `remoteURL` is the full URL to the admin panel (i.e., the URL includes the `/admin` part):
diff --git a/docusaurus/docs/dev-docs/database-migrations.md b/docusaurus/docs/dev-docs/database-migrations.md
index 2f22312e60..1e1d825baa 100644
--- a/docusaurus/docs/dev-docs/database-migrations.md
+++ b/docusaurus/docs/dev-docs/database-migrations.md
@@ -74,3 +74,35 @@ module.exports = {
```
+
+### Using Strapi Instance for migrations
+
+:::danger
+If a user opts not to use Knex directly for migrations and instead utilizes the Strapi instance, it is important to wrap the migration code with `strapi.db.transaction()`. Failure to do so may result in migrations not rolling back if an error occurs.
+:::
+
+
+Example of migration file with Strapi instance
+
+```jsx title="./database/migrations/2022.05.10T00.00.00.name-of-my-migration.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();
+ });
+ },
+};
+```
+
+
+
diff --git a/docusaurus/docs/dev-docs/deployment.md b/docusaurus/docs/dev-docs/deployment.md
index ce42f75af8..75923b8a9e 100644
--- a/docusaurus/docs/dev-docs/deployment.md
+++ b/docusaurus/docs/dev-docs/deployment.md
@@ -7,6 +7,9 @@ description: Learn how to develop locally with Strapi and deploy Strapi with var
import DatabaseRequire from '/docs/snippets/database-require.md'
import HardwareRequire from '/docs/snippets/hardware-require.md'
import OperatingSystemRequire from '/docs/snippets/operating-system-require.md'
+import ConsiderStrapiCloud from '/docs/snippets/consider-strapi-cloud.md'
+import CommunityGuides from '/docs/snippets/community-deployment-guides.md'
+import InstallPrereq from '/docs/snippets/installation-prerequisites.md'
# Deployment
@@ -15,11 +18,7 @@ Strapi provides many deployment options for your project or application. Your St
The following documentation covers how to develop locally with Strapi and deploy Strapi with several common hosting options.
:::callout βοΈ Strapi Cloud
-Don't want to deploy Strapi by yourself? Use [Strapi Cloud](/cloud/intro) to easily deploy and host your project.
-:::
-
-:::strapi Community Guides
-In addition to the official deployment guides maintained by Strapi that are found here, community-maintained guides for additional providers are available in the [Strapi Forum](https://forum.strapi.io/c/community-guides/28).
+You can use [Strapi Cloud](/cloud/intro) to quickly deploy and host your project.
:::
:::tip
@@ -34,12 +33,11 @@ Another possible workflow is to first create the data structure locally, push yo
To provide the best possible environment for Strapi the following requirements apply to development (local) and staging and production workflows.
-- Node LTS (currently v16, v18, and v20) **Odd-number releases of Node are not supported (e.g. v17, v19).**
-- NPM v6 (or the version shipped with the LTS Node versions)
+
- Standard build tools for your OS (the `build-essentials` package on most Debian-based systems)
- Hardware specifications for your server (CPU, RAM, storage):
-
+
- A supported database version:
@@ -50,7 +48,7 @@ Deploying databases along with Strapi is covered in the [databases guide](/dev-d
- A supported operating system:
-
+
### Application Configuration
@@ -58,20 +56,19 @@ Deploying databases along with Strapi is covered in the [databases guide](/dev-d
We recommend using environment variables to configure your application based on the environment, for example:
-```js
-// Path: ./config/server.js
+```js title="/config/server.js"
module.exports = ({ env }) => ({
- host: env('APP_HOST', '0.0.0.0'),
- port: env.int('NODE_PORT', 1337),
+ host: env('HOST', '0.0.0.0'),
+ port: env.int('PORT', 1337),
});
```
Then you can create a `.env` file or directly set environment variables in your chosen deployment platform:
```
-APP_HOST=10.0.0.1
-NODE_PORT=1338
+HOST=10.0.0.1
+PORT=1338
```
:::tip
@@ -185,8 +182,18 @@ For more information, consult the [TypeScript documentation](/dev-docs/typescrip
If you want to host the administration on another server than the API, [please take a look at this dedicated section](/dev-docs/admin-panel-customization#deployment).
-## Deployment Guides
+## Deployment guides
+
+Click on any of the following cards to read manual guides for deployment and optional software:
+
+
+
+
+
+
+
+
-Manual guides for deployment and optional software:
+
-
+
diff --git a/docusaurus/docs/dev-docs/deployment/amazon-aws.md b/docusaurus/docs/dev-docs/deployment/amazon-aws.md
index f1952f6ea5..90f2100cd1 100644
--- a/docusaurus/docs/dev-docs/deployment/amazon-aws.md
+++ b/docusaurus/docs/dev-docs/deployment/amazon-aws.md
@@ -5,11 +5,18 @@ description: Learn in this guide how to deploy your Strapi application on AWS EC
---
+import ConsiderStrapiCloud from '/docs/snippets/consider-strapi-cloud.md'
+
# Amazon AWS
This is a step-by-step guide for deploying a Strapi project to [Amazon AWS EC2](https://aws.amazon.com/ec2/) inside your [AWS VPC](https://aws.amazon.com/vpc/). This guide will connect to an [Amazon AWS RDS](https://aws.amazon.com/rds/) for managing and hosting the database. Optionally, this guide will show you how to connect host and serve images on [Amazon AWS S3](https://aws.amazon.com/s3/).
-Prior to starting this guide, you should have created a [Strapi project](/dev-docs/quick-start), to use for deploying on AWS. And have read through the [configuration](/dev-docs/deployment#application-configuration) section.
+:::prerequisites
+- You have created a [Strapi project](/dev-docs/quick-start), to use for deploying on AWS.
+- You have read through the [configuration](/dev-docs/deployment#application-configuration) section.
+:::
+
+
### Amazon AWS Install Requirements and creating an IAM non-root user
@@ -248,13 +255,20 @@ ubuntu@ip-1.2.3.4:~$
#### 3. Install **Node.js** with **npm**:
-Strapi currently supports `Node.js` `v16.x.x`, `v18.x.x`, and `v20.x.x`. The following steps will install Node.js onto your EC2 server.
+The following steps will install Node.js onto your EC2 server.
```bash title="example using Node.js 20"
cd ~
-curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash -
+sudo apt-get update
+...
+sudo apt-get install -y ca-certificates curl gnupg
...
-sudo apt-get install nodejs
+sudo mkdir -p /etc/apt/keyrings
+curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
+NODE_MAJOR=20
+sudo apt-get update
+...
+sudo apt-get install nodejs -y
...
node -v && npm -v
```
@@ -406,12 +420,14 @@ module.exports = ({ env }) => ({
config: {
provider: 'aws-s3',
providerOptions: {
- accessKeyId: env('AWS_ACCESS_KEY_ID'),
- secretAccessKey: env('AWS_ACCESS_SECRET'),
- region: env('AWS_REGION'),
- params: {
+ s3Options: {
+ accessKeyId: env('AWS_ACCESS_KEY_ID'),
+ secretAccessKey: env('AWS_ACCESS_SECRET'),
+ region: env('AWS_REGION'),
+ params: {
Bucket: env('AWS_BUCKET_NAME'),
- },
+ },
+ }
},
// These parameters could solve issues with ACL public-read access β see [this issue](https://github.com/strapi/strapi/issues/5868) for details
actionOptions: {
@@ -437,12 +453,14 @@ export default ({ env }) => ({
config: {
provider: 'aws-s3',
providerOptions: {
- accessKeyId: env('AWS_ACCESS_KEY_ID'),
- secretAccessKey: env('AWS_ACCESS_SECRET'),
- region: env('AWS_REGION'),
- params: {
- Bucket: env('AWS_BUCKET_NAME'),
- },
+ s3Options: {
+ accessKeyId: env('AWS_ACCESS_KEY_ID'),
+ secretAccessKey: env('AWS_ACCESS_SECRET'),
+ region: env('AWS_REGION'),
+ params: {
+ Bucket: env('AWS_BUCKET_NAME'),
+ },
+ }
},
},
}
@@ -725,7 +743,7 @@ http
.digest('hex');
if (req.headers['x-hub-signature'] == sig) {
- exec(`cd ${repo} && git pull && ${PM2_CMD}`, (error, stdout, stderr) => {
+ exec(`cd ${repo} && git pull && NODE_ENV=production npm run build && ${PM2_CMD}`, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
diff --git a/docusaurus/docs/dev-docs/deployment/azure.md b/docusaurus/docs/dev-docs/deployment/azure.md
index 70c9a8461f..22168e4f44 100644
--- a/docusaurus/docs/dev-docs/deployment/azure.md
+++ b/docusaurus/docs/dev-docs/deployment/azure.md
@@ -5,6 +5,7 @@ description: Learn in this guide how to deploy your Strapi application on Micros
---
import DatabaseRequire from '/docs/snippets/database-require.md'
+import ConsiderStrapiCloud from '/docs/snippets/consider-strapi-cloud.md'
# Azure
@@ -17,6 +18,9 @@ This is a step-by-step guide for deploying a Strapi project to [Azure](https://a
* You must have an [Azure account](https://azure.microsoft.com/free/) before doing these steps.
:::
+
+
+
## PaaS Deployment Guides
There are 3 ways which you can deploy the Azure resources:
@@ -333,18 +337,16 @@ sudo apt install libpng-dev build-essential -y
For Node.js it is recommended you use the [official source](https://github.com/nodesource/distributions/blob/master/README.md#debinstall), per the instructions we will use the following commands:
```bash
-curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
-sudo apt install nodejs -y
+curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - &&\
+sudo apt-get install -y nodejs
```
-Likewise for Yarn we will use the instructions from the [Yarn documentation](https://classic.yarnpkg.com/en/docs/install/#debian-stable):
+Likewise for Yarn we will use the instructions from the [Yarn documentation](https://yarnpkg.com/getting-started/install):
```bash
-curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
-echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
-
-sudo apt update
-sudo apt install yarn -y
+corepack enable
+yarn set version stable
+yarn install
```
To verify you have everything installed properly you can run the following:
@@ -695,4 +697,4 @@ There are many different types of proxy services you could use, anything from lo
#### 3. File upload providers
-There are many options for storing files outside of your virtual machine, Strapi have built a few and the community is constantly building new ones. See the [following guide](/dev-docs/providers) on searching for options as well as installing them.
+There are many options for storing files outside of your virtual machine, Strapi have built a few and the community is constantly building new ones. See the [following guide](/dev-docs/providers) on searching for options as well as installing them.
\ No newline at end of file
diff --git a/docusaurus/docs/dev-docs/deployment/digitalocean-app-platform.md b/docusaurus/docs/dev-docs/deployment/digitalocean-app-platform.md
index c138afc01a..cd3dbe8eaa 100644
--- a/docusaurus/docs/dev-docs/deployment/digitalocean-app-platform.md
+++ b/docusaurus/docs/dev-docs/deployment/digitalocean-app-platform.md
@@ -5,6 +5,8 @@ displayed_sidebar: devDocsSidebar
---
+import ConsiderStrapiCloud from '/docs/snippets/consider-strapi-cloud.md'
+
# Deploy to the DigitalOcean App Platform
The purpose of this guide is to allow users to deploy Strapi applications on the DigitalOcean App Platform. This guide uses the PostgreSQL development database provided by DigitalOcean, so applications can be tested in a deployed environment. At the end of the guide there is information on how to connect a Strapi application to a DigitalOcean Managed Database. Additional information about [migrating local database content to a production database](https://docs.digitalocean.com/products/databases/postgresql/how-to/import-databases/) and other deployment topics are provided in the [DigitalOcean documentation](https://docs.digitalocean.com/).
@@ -13,6 +15,8 @@ The purpose of this guide is to allow users to deploy Strapi applications on the
Strapi maintains deployment guides to assist users in deploying projects. Since there are frequent updates to Strapi and to the hosting provider platforms, the guides are sometimes out of date. If you encounter an issue deploying your project following this guide, please [open an issue on GitHub](https://github.com/strapi/documentation/issues) or [submit a pull request](https://github.com/strapi/documentation/pulls) to improve the documentation.
:::
+
+
## Prepare the deployment
Prior to starting the deployment process each user needs:
diff --git a/docusaurus/docs/dev-docs/deployment/digitalocean.md b/docusaurus/docs/dev-docs/deployment/digitalocean.md
index 1b1dc0b7e0..140cd34fd6 100644
--- a/docusaurus/docs/dev-docs/deployment/digitalocean.md
+++ b/docusaurus/docs/dev-docs/deployment/digitalocean.md
@@ -5,6 +5,8 @@ description: Learn in this guide how to deploy your Strapi application on Digita
---
+import ConsiderStrapiCloud from '/docs/snippets/consider-strapi-cloud.md'
+
# DigitalOcean Droplets
This is a step-by-step guide for deploying a Strapi project to a [DigitalOcean Droplet](https://www.digitalocean.com/docs/droplets/). Alternatively, you can also choose to deploy to DigitalOcean's Platform-as-a-Service (PaaS) called [App Platform](/dev-docs/deployment/digitalocean-app-platform) if database-related requirements and budget better fit with your use case.
@@ -21,6 +23,8 @@ This guide covers hosting the database on a DigitalOcean Droplet. Another option
When creating your Strapi project, don't use the `--quickstart` flag as the quick start installation uses SQLite, which is not desired for remote hosting.
:::
+
+
### Create a "Droplet"
DigitalOcean calls a virtual private server, a [Droplet](https://www.digitalocean.com/docs/droplets/). Create a new Droplet to host your Strapi project:
@@ -140,7 +144,7 @@ Note the database name, username and password for later use.
:::prerequisites
- You must have `git` [installed and set-up locally](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup).
-- Git should be [initialized](https://git-scm.com/docs/git-init) for your previsouly created Strapi project.
+- Git should be [initialized](https://git-scm.com/docs/git-init) for your previously created Strapi project.
:::
1. Replace the content of the `config/database.js` with the following:
@@ -269,6 +273,16 @@ Your Strapi project is now installed on your DigitalOcean Droplet. Before being
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
```
+ * Nginx has a [configuration setting](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size) called `client_max_body_size` that will prevent file uploads greater than the specified amount. This will need to be adjusted since its default is only 1MB. Note that strapi middleware is already in charge of parsing requests of file sizes up to 200MB.
+ ```
+ ...
+ http {
+ ...
+ client_max_body_size 200m; # Or 0 to disable
+ ...
+ }
+ ...
+ ```
2. Close the port to outside traffic by running the following commands:
@@ -448,7 +462,7 @@ More information can be found on webhooks in general in the [GitHub documentatio
.digest('hex');
if (req.headers['x-hub-signature'] == sig) {
- exec(`cd ${repo} && git pull && ${PM2_CMD}`, (error, stdout, stderr) => {
+ exec(`cd ${repo} && git pull && NODE_ENV=production npm run build && ${PM2_CMD}`, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
diff --git a/docusaurus/docs/dev-docs/deployment/heroku.md b/docusaurus/docs/dev-docs/deployment/heroku.md
index 37ff043d42..b47076562d 100644
--- a/docusaurus/docs/dev-docs/deployment/heroku.md
+++ b/docusaurus/docs/dev-docs/deployment/heroku.md
@@ -5,6 +5,8 @@ displayed_sidebar: devDocsSidebar
---
+import ConsiderStrapiCloud from '/docs/snippets/consider-strapi-cloud.md'
+
# Deploy to Heroku
The purpose of this guide is to allow users to deploy Strapi applications on Heroku. This guide uses the Heroku CLI tool with a PostgreSQL database provided by Heroku. There are other options for how to deploy to Heroku available in the [Heroku documentation](https://devcenter.heroku.com/categories/data-management).
@@ -23,6 +25,8 @@ Prior to starting the deployment process, each user needs:
- [Git version control](https://docs.github.com/en/get-started/quickstart/set-up-git),
- an existing Strapi application.
+
+
## Setup a Strapi project for deployment
Strapi uses [environment configurations](/dev-docs/configurations/environment) to maintain multiple environments inside a single application. This section describes how to set up a production environment in a Strapi application.
@@ -161,8 +165,7 @@ npm install pg && npm install pg-connection-string
-
-
+
7. Add `package-lock.json` to the end of the `.gitignore` file at the root of the Strapi project:
```sh
@@ -191,14 +194,14 @@ Deploying to Heroku requires installing the CLI tool, creating an App, connectin
### Install and use the Heroku CLI
1. Use the following OS-specific installation instructions to install the Heroku CLI tool:
-
+
Run the following from a terminal:
```bash
-sudo snap install --classic heroku
+curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
```
diff --git a/docusaurus/docs/dev-docs/deployment/hosting-guides.md b/docusaurus/docs/dev-docs/deployment/hosting-guides.md
index 0b21d280d9..cb3883d0fe 100644
--- a/docusaurus/docs/dev-docs/deployment/hosting-guides.md
+++ b/docusaurus/docs/dev-docs/deployment/hosting-guides.md
@@ -4,8 +4,25 @@ description: Learn in this guide how to deploy your Strapi application.
displayed_sidebar: devDocsSidebar
---
+import ConsiderStrapiCloud from '/docs/snippets/consider-strapi-cloud.md'
+import CommunityGuides from '/docs/snippets/community-deployment-guides.md'
+
# Hosting Provider Guides
-Manual guides for deployment on the following platforms:
+:::prerequisites
+* Your Strapi project is [created](/dev-docs/installation) and its code is hosted on GitHub.
+* You have read the [general deployment guidelines](/dev-docs/deployment#general-guidelines).
+:::
+
+To manually deploy your Strapi project on 3rd-party platforms, click on any of the following guides:
+
+
+
+
+
+
+
+
-
+
+
diff --git a/docusaurus/docs/dev-docs/deployment/optional-software-guides.md b/docusaurus/docs/dev-docs/deployment/optional-software-guides.md
index f635256b2e..440751be14 100644
--- a/docusaurus/docs/dev-docs/deployment/optional-software-guides.md
+++ b/docusaurus/docs/dev-docs/deployment/optional-software-guides.md
@@ -3,8 +3,19 @@ title: Optional Software Guides
description: Learn how to use proxy and process managers in your deployed Strapi application .
displayed_sidebar: devDocsSidebar
---
-## Optional Software Guides
+# Optional Software Guides
-Additional guides for optional software additions that compliment or improve the deployment process when using Strapi in a production or production-like environment.
+:::prerequisites
+* Your Strapi project is [created](/dev-docs/installation) and its code is hosted on GitHub.
+* You have read the [general deployment guidelines](/dev-docs/deployment#general-guidelines).
+* You have deployed Strapi on a [3rd-party platform](/dev-docs/deployment/hosting-guides) or on your own server.
+:::
-
\ No newline at end of file
+The following guides are for optional software additions that compliment or improve the [deployment](/dev-docs/deployment) process when using Strapi in a production or production-like environment:
+
+
+
+
+
+
+
diff --git a/docusaurus/docs/dev-docs/deployment/process-manager.md b/docusaurus/docs/dev-docs/deployment/process-manager.md
index a4705d5069..da36a7908a 100644
--- a/docusaurus/docs/dev-docs/deployment/process-manager.md
+++ b/docusaurus/docs/dev-docs/deployment/process-manager.md
@@ -62,9 +62,9 @@ strapi().start();
```ts title="path: ./server.js"
-const strapi = require('@strapi/strapi');
- const app = strapi({ distDir: '' });
- app.start();
+const strapi = require("@strapi/strapi");
+const app = strapi({ distDir: "./dist" });
+app.start();
```
diff --git a/docusaurus/docs/dev-docs/deployment/snippets-proxy/admin-redirect.md b/docusaurus/docs/dev-docs/deployment/snippets-proxy/admin-redirect.md
index 37004b5fc8..98ffb2d138 100644
--- a/docusaurus/docs/dev-docs/deployment/snippets-proxy/admin-redirect.md
+++ b/docusaurus/docs/dev-docs/deployment/snippets-proxy/admin-redirect.md
@@ -9,7 +9,7 @@ This sample configuration expects that the admin panel is accessible on `/admin`
```js title="./config/middlewares.js"
module.exports = ({ env }) => [
// ...
- { resolve: './src/middlewares/admin-redirect' },
+ { resolve: '../src/middlewares/admin-redirect' },
];
```
diff --git a/docusaurus/docs/dev-docs/error-handling.md b/docusaurus/docs/dev-docs/error-handling.md
index e4895a65dd..d8694d2c40 100644
--- a/docusaurus/docs/dev-docs/error-handling.md
+++ b/docusaurus/docs/dev-docs/error-handling.md
@@ -145,6 +145,7 @@ See the [default error classes](#default-error-classes) section for more informa
Example: Throwing an error in a service
+
This example shows wrapping a [core service](/dev-docs/backend-customization/services#extending-core-services) and doing a custom validation on the `create` method:
@@ -398,12 +399,14 @@ throw new NotFoundError('These are not the droids you are looking for');
-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:
+The `ForbiddenError` class is a specific error class ([error 403](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403)) 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 | `Forbidden access` |
+Note: `ForbiddenError` message contents will not be displayed to the Content API and will be returned to the user as an empty `ForbiddenError`
+
```js
throw new ForbiddenError('Ah ah ah, you didn\'t say the magic word');
```
@@ -412,12 +415,14 @@ throw new ForbiddenError('Ah ah ah, you didn\'t say the magic word');
-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:
+The `UnauthorizedError` class is a specific error class ([error 401](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401)) 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 | `Unauthorized` |
+Note: `UnauthorizedError` message contents will not be displayed to the Content API and will be returned to the user as an empty `UnauthorizedError`
+
```js
throw new UnauthorizedError('You shall not pass!');
```
@@ -465,6 +470,8 @@ The `PolicyError` class is a specific error designed to be used with [route poli
throw new PolicyError('Something went wrong', { policy: 'my-policy' });
```
+Note: Because `PolicyError` extends `ForbiddenError`, it will not be displayed to the Content API and will be returned to the user as an empty `ForbiddenError` and you will need to use a different error type in your policy if you want it to be visible in the Content API.
+
diff --git a/docusaurus/docs/dev-docs/faq.md b/docusaurus/docs/dev-docs/faq.md
index 825ef4ee02..4ace16a1a0 100644
--- a/docusaurus/docs/dev-docs/faq.md
+++ b/docusaurus/docs/dev-docs/faq.md
@@ -37,6 +37,10 @@ If you used `--quickstart` to create your Strapi project, by default this uses t
It is recommended you use a database add-on like Heroku's PostgreSQL. For file uploads, you will need to use one of the 3rd party providers such as Cloudinary or AWS S3.
+## How can I upgrade my free Strapi Cloud trial to a paid plan?
+
+Strapi Cloud provides a free, 14-day trial. Whenever you're ready to upgrade to one of the [paid plans](https://strapi.io/pricing-cloud), please use the _Plans_ section of your Strapi Cloud project's settings (see [Cloud documentation](/cloud/projects/settings#upgrading-to-another-plan) for more details).
+
## Can Strapi be run in serverless environments?
Strapi is not well suited for serverless environments due to how the application is structured. Several actions happen while Strapi is booting that can take several seconds. Serverless deployment usually requires an application to cold boot very quickly. Strapi is designed to run as an always-on service, and we don't plan to decrease the cold boot time for the foreseeable future. Therefore, running Strapi in serverless environments is not a great experience, as every request will take seconds to respond to instead of milliseconds. Choosing between a cold boot or a warm boot is an architectural decision that many software developers need to take from a very early stage, so please consider this when choosing to use Strapi.
diff --git a/docusaurus/docs/dev-docs/installation.md b/docusaurus/docs/dev-docs/installation.md
index 554f13120b..6f3f30814f 100644
--- a/docusaurus/docs/dev-docs/installation.md
+++ b/docusaurus/docs/dev-docs/installation.md
@@ -1,12 +1,13 @@
---
title: Installation
description: Learn many different options to install Strapi and getting started on using it.
+pagination_next: dev-docs/installation/cli
---
# Installation
-Strapi projects and applications can be installed either locally on a computer or on a remote server. The following installation guide provides step-by-step instructions on how to install and create a new Strapi project on your local machine.
+Strapi projects can be installed either locally on a computer or on a remote server. The following installation guide provides step-by-step instructions on how to install and create a new Strapi project on your local machine.
diff --git a/docusaurus/docs/dev-docs/installation/cli.md b/docusaurus/docs/dev-docs/installation/cli.md
index 710f60858f..dcd4100067 100644
--- a/docusaurus/docs/dev-docs/installation/cli.md
+++ b/docusaurus/docs/dev-docs/installation/cli.md
@@ -79,6 +79,8 @@ The above installation guide only covers the basic installation option using the
- `--typescript`/`--ts`: Create a project in [TypeScript](/dev-docs/typescript).
- `--no-run`: Prevent Strapi from automatically starting the server (useful in combination with `--quickstart`).
+- `--skip-cloud`: Automatically answers "Skip" to the Login/Signup question, which prevents the installation script from login into Strapi Cloud (useful in combination with `--quickstart`).
+
For more information on available flags, see our [CLI documentation](/dev-docs/cli).
Strapi also offers a starters CLI to create a project with a pre-made frontend application (see [our dedicated blog post](https://strapi.io/blog/announcing-the-strapi-starter-cli)).
@@ -89,6 +91,36 @@ Experimental Strapi versions are released every Tuesday through Saturday at midn
Please use these experimental builds at your own risk. It is not recommended to use them in production.
:::
+### Skipping the Strapi Cloud login step
+
+When the installation script runs, the terminal will first ask you if you want to login/signup. Choosing `Login/signup` will create a free, 14-day trial [Strapi Cloud](/cloud/intro#what-is-strapi-cloud) project as described in the [Quick Start Guide](/dev-docs/quick-start).
+
+If you prefer skipping this Strapi Cloud login part, use the arrow keys to select `Skip`. The script will resume and create a local project. To deploy this project and host it online, you could later choose to:
+- host it yourself by pushing the project's code to a repository (e.g., on GitHub) before following a [3rd-party deployment guide](/dev-docs/deployment),
+- or use the [Cloud CLI](/cloud/cli/cloud-cli) commands to login to Strapi Cloud and deploy your project there.
+
+If you want to host your project yourself and are not already familiar with GitHub, the following togglable content should get you startedπ.
+
+
+Steps required to push your Strapi project code to GitHub:
+
+1. In the terminal, ensure you are still in the folder that hosts the Strapi project you created.
+2. Run the `git init` command to initialize git for this folder.
+3. Run the `git add .` command to add all modified files to the git index.
+4. Run the `git commit -m "Initial commit"` command to create a commit with all the added changes.
+5. Log in to your GitHub account and [create a new repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/quickstart-for-repositories). Give the new repository a name, for instance `my-first-strapi-project`, and remember this name.
+6. Go back to the terminal and push your local repository to GitHub:
+
+ a. Run a command similar to the following: `git remote add origin git@github.com:yourname/my-first-strapi-project.git`, ensuring you replace `yourname` by your own GitHub profile name, and `my-first-strapi-project` by the actual name you used at step 4.
+
+ b. Run the `git push --set-upstream origin main` command to finally push the commit to your GitHub repository.
+
+Additional information about using git with the command line interface can be found in the [official GitHub documentation](https://docs.github.com/en/migrations/importing-source-code/using-the-command-line-to-import-source-code/adding-locally-hosted-code-to-github#adding-a-local-repository-to-github-using-git).
+
+
+
+
+
## Running Strapi
To start the Strapi application, run the following command in the project folder:
@@ -112,3 +144,9 @@ npm run develop
+
+:::info Where is my content?
+For self-hosted Strapi projects, all your content is saved in a database file (by default, SQLite) found in the `.tmp` subfolder in your project's folder. So anytime you start the Strapi application from the folder where you created your Strapi project, your content will be available (see [database configuration](/dev-docs/configurations/database) for additional information).
+
+If the content was added to a Strapi Cloud project, it is stored in the database managed with your Strapi Cloud project (see [advanced database configuration for Strapi Cloud](/cloud/advanced/database) for additional information).
+:::
diff --git a/docusaurus/docs/dev-docs/installation/docker.md b/docusaurus/docs/dev-docs/installation/docker.md
index cde038c2d9..0e6a1bf4a5 100644
--- a/docusaurus/docs/dev-docs/installation/docker.md
+++ b/docusaurus/docs/dev-docs/installation/docker.md
@@ -12,6 +12,10 @@ import DockerEnvTable from '/docs/snippets/docker-env-table.md'
Strapi does not build any official container images. The following instructions are provided as a courtesy to the community. If you have any questions please reach out on [Discord](https://discord.strapi.io).
:::
+:::danger
+Strapi applications are not meant to be connected to a pre-existing database, not created by a Strapi application, nor connected to a Strapi v3 database. The Strapi team will not support such attempts. Attempting to connect to an unsupported database may, and most likely will, result in lost data such as dropped tables.
+:::
+
The following documentation will guide you through building a custom [Docker](https://www.docker.com/) container with an existing Strapi project.
Docker is an open platform that allows developing, shipping, and running applications by using containers (i.e. packages containing all the parts an application needs to function, such as libraries and dependencies). Containers are isolated from each other and bundle their own software, libraries, and configuration files; they can communicate with each other through well-defined channels.
@@ -53,14 +57,15 @@ Sample `Dockerfile`:
```dockerfile title="./Dockerfile"
-FROM node:18-alpine
+FROM node:18-alpine3.18
# 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
+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
ENV PATH /opt/node_modules/.bin:$PATH
@@ -80,12 +85,13 @@ CMD ["yarn", "develop"]
```dockerfile title="./Dockerfile"
FROM node:18-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
+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 install
ENV PATH /opt/node_modules/.bin:$PATH
@@ -316,6 +322,40 @@ networks:
+### (Optional) .dockerignore
+
+When building Docker images, it's essential to include only the files necessary for running the application inside the container. This is where the `.dockerignore` file comes into play. Similar to how `.gitignore` works for Git, specifying files and directories that should not be tracked or uploaded, `.dockerignore` tells Docker which files and directories to ignore when building an image.
+
+Sample `.dockerignore`:
+
+```bash
+.tmp/
+.cache/
+.git/
+.env
+build/
+node_modules/
+# Ignoring folders that might be used in starter templates
+data/
+backup/
+```
+
+#### Why Use .dockerignore?
+
+Including unnecessary files in a Docker build context can significantly slow down the build process. By excluding files and directories like .tmp, .cache, .git, build, node_modules, and .env through .dockerignore, the amount of data sent to the Docker daemon is reduced. This leads to faster build times since Docker has fewer files to process and include in the image.
+
+#### Security
+
+Excluding files and directories can also enhance the security of the Docker image. Sensitive files, such as `.env`, which might contain environment-specific secrets or credentials, should not be included in Docker images. This prevents accidental exposure of sensitive information.
+
+#### Cleaner Images
+
+A Docker image cluttered with unnecessary files can lead to potential confusion and issues, especially when the image is shared across teams or used in production. By keeping the image clean and focused only on the essentials, it becomes easier to maintain and troubleshoot.
+
+#### Reduced Image Size
+
+Smaller Docker images are more efficient to store, transfer, and launch. By excluding non-essential files, the final image size can be significantly reduced, leading to quicker pull and start times, especially in distributed and cloud environments.
+
## Production Environments
The Docker image in production is different from the one used in development/staging environments because of the differences in the admin build process in addition to the command used to run the application. Typical production environments will use a reverse proxy to serve the application and the admin panel. The Docker image is built with the production build of the admin panel and the command used to run the application is `strapi start`.
@@ -334,12 +374,12 @@ The following `Dockerfile` can be used to build a production Docker image for a
```dockerfile title="./Dockerfile.prod"
# Creating multi-stage build for production
FROM node:18-alpine as build
-RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev > /dev/null 2>&1
-ARG NODE_ENV=production
-ENV NODE_ENV=${NODE_ENV}
+RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev git > /dev/null 2>&1
+ENV NODE_ENV=production
WORKDIR /opt/
COPY package.json yarn.lock ./
+RUN yarn global add node-gyp
RUN yarn config set network-timeout 600000 -g && yarn install --production
ENV PATH /opt/node_modules/.bin:$PATH
WORKDIR /opt/app
@@ -349,8 +389,7 @@ RUN yarn build
# Creating final production image
FROM node:18-alpine
RUN apk add --no-cache vips-dev
-ARG NODE_ENV=production
-ENV NODE_ENV=${NODE_ENV}
+ENV NODE_ENV=production
WORKDIR /opt/
COPY --from=build /opt/node_modules ./node_modules
WORKDIR /opt/app
@@ -370,13 +409,14 @@ CMD ["yarn", "start"]
```dockerfile title="./Dockerfile.prod"
# Creating multi-stage build for production
FROM node:18-alpine as build
-RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev > /dev/null 2>&1
+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 config set network-timeout 600000 -g && npm install --only=production
+RUN npm install -g node-gyp
+RUN npm config set fetch-retry-maxtimeout 600000 -g && npm install --only=production
ENV PATH /opt/node_modules/.bin:$PATH
WORKDIR /opt/app
COPY . .
diff --git a/docusaurus/docs/dev-docs/installation/templates.md b/docusaurus/docs/dev-docs/installation/templates.md
deleted file mode 100644
index 959adafe76..0000000000
--- a/docusaurus/docs/dev-docs/installation/templates.md
+++ /dev/null
@@ -1 +0,0 @@
-(Coming soonβ¦)
diff --git a/docusaurus/docs/dev-docs/integrations/11ty.md b/docusaurus/docs/dev-docs/integrations/11ty.md
index e5a206c8e5..52fc39fc8f 100644
--- a/docusaurus/docs/dev-docs/integrations/11ty.md
+++ b/docusaurus/docs/dev-docs/integrations/11ty.md
@@ -235,7 +235,8 @@ pagination:
```md
---
-title: Restaurant
+eleventyComputed:
+ title: {{ restaurant.attributes.name }}
layout: default.liquid
pagination:
data: restaurants.data
@@ -355,7 +356,8 @@ module.exports = async () => {
```md
---
-title: Category
+eleventyComputed:
+ title: {{ category.attributes.name }}
layout: default.liquid
pagination:
data: categories.data
diff --git a/docusaurus/docs/dev-docs/integrations/next-js.md b/docusaurus/docs/dev-docs/integrations/next-js.md
index 660fc5a227..a769485232 100644
--- a/docusaurus/docs/dev-docs/integrations/next-js.md
+++ b/docusaurus/docs/dev-docs/integrations/next-js.md
@@ -7,7 +7,7 @@ description: Integrate Strapi with Next.js.
# Getting Started with Next.js
-This integration guide follows the [Quick Start Guide](/dev-docs/quick-start) and assumes you have you have fully completed the "Hands-on" path. You should be able to consume the API by browsing the URL [http://localhost:1337/api/restaurants](http://localhost:1337/api/restaurants).
+This integration guide follows the [Quick Start Guide](/dev-docs/quick-start) and assumes you have fully completed the "Hands-on" path. You should be able to consume the API by browsing the URL [http://localhost:1337/api/restaurants](http://localhost:1337/api/restaurants).
If you haven't gone through the Quick Start Guide, the way you request a Strapi API with [Next.js](https://nextjs.org/) remains the same except that you do not fetch the same content.
@@ -60,6 +60,10 @@ No installation needed.
+:::tip
+If you're getting `localhost` errors with Axios, but your requests work fine by replacing localhost with 127.0.0.1, please ensure this is not a CORS issue (see additional details in the [axios GitHub](https://github.com/axios/axios/issues/4837#issuecomment-1198487683)).
+:::
+
## GET Request your collection type
Execute a `GET` request on the `restaurant` collection type in order to fetch all your restaurants.
diff --git a/docusaurus/docs/dev-docs/integrations/nuxt-js.md b/docusaurus/docs/dev-docs/integrations/nuxt-js.md
index 8ebc406baf..598acb9da0 100644
--- a/docusaurus/docs/dev-docs/integrations/nuxt-js.md
+++ b/docusaurus/docs/dev-docs/integrations/nuxt-js.md
@@ -332,6 +332,7 @@ const createRestaurant = async () => {
name: name.value,
description: description.value
})
+}
```
diff --git a/docusaurus/docs/dev-docs/intro.md b/docusaurus/docs/dev-docs/intro.md
index b3ae5edd5c..9315e9597b 100644
--- a/docusaurus/docs/dev-docs/intro.md
+++ b/docusaurus/docs/dev-docs/intro.md
@@ -7,7 +7,17 @@ sidebar_position: 1
# Welcome to the Strapi Developer Docs!
-This documentation contains all technical documentation related to the setup, deployment, update and customization of your Strapi application.
+
+
+The documentation for Strapi contains 3 main sections, accessible from the top navigation bar:
+
+- π§βπ» The **Developer Docs** contain all the technical information related to the setup, advanced usage, customization, and update of your Strapi v4 application.
+- π§βπ« The **[User Guide](/user-docs/intro)** is all about using Strapi's admin panel.
+- βοΈ The **[Strapi Cloud](/cloud/intro)** documentation is about deploying your Strapi application to Strapi Cloud and managing your Strapi Cloud projects and settings.
+
+
+
+This documentation contains official technical documentation related to the setup, deployment, update and customization of your Strapi v4 application.
:::strapi Can't wait to start using Strapi?
You can directly head to the [Quick Start](./quick-start.md)! If demos are more your thing, we have a [video demo](https://youtu.be/zd0_S_FPzKg), or you can request a [live demo](https://strapi.io/demo)!
diff --git a/docusaurus/docs/dev-docs/migration-guides.md b/docusaurus/docs/dev-docs/migration-guides.md
index ce7063c924..15dcb0cc20 100644
--- a/docusaurus/docs/dev-docs/migration-guides.md
+++ b/docusaurus/docs/dev-docs/migration-guides.md
@@ -28,6 +28,8 @@ If there is no specific migration guide between your current version and the lat
- [Migration guide from 4.5.1 to 4.6.1](/dev-docs/migration/v4/migration-guide-4.5.1-to-4.6.1)
- [Migration guide from 4.6.1 to 4.7.0](/dev-docs/migration/v4/migration-guide-4.6.1-to-4.7.0)
- [Migration guide from 4.7.0 to 4.11.4](/dev-docs/migration/v4/migration-guide-4.7.0-to-4.11.4)
+- [Migration guide from 4.11.4 to 4.14.0](/dev-docs/migration/v4/migration-guide-4.11.4-to-4.14.0)
+- [Migration guide from 4.14.0 to 4.15.5](/dev-docs/migration/v4/migration-guide-4.14.0-to-4.15.5)
## v3 to v4 migration guides
diff --git a/docusaurus/docs/dev-docs/migration/v4/migration-guide-4.11.4-to-4.14.0.md b/docusaurus/docs/dev-docs/migration/v4/migration-guide-4.11.4-to-4.14.0.md
new file mode 100644
index 0000000000..5eba4e6563
--- /dev/null
+++ b/docusaurus/docs/dev-docs/migration/v4/migration-guide-4.11.4-to-4.14.0.md
@@ -0,0 +1,78 @@
+---
+title: Migrate from 4.11.4 to 4.14.0
+displayed_sidebar: devDocsSidebar
+description: Learn how you can migrate your Strapi application from 4.14.0 to 4.14.0.
+---
+
+import PluginsCaution from '/docs/snippets/migrate-plugins-extension-caution.md'
+import BuildCommand from '/docs/snippets/build-npm-yarn.md'
+import DevelopCommand from '/docs/snippets/develop-npm-yarn.md'
+import InstallCommand from '/docs/snippets/install-npm-yarn.md'
+
+# v4.11.4 to v4.14.0 migration guide
+
+The Strapi v4.11.4 to v4.14.0 migration guide upgrades v4.11.4 to v4.14.0. Strapi v4.14.0 migrated the `strapi/strapi` package to TypeScript and introduced a new `@strapi/typings` package. This migration is required if you have used TypeScript in your project prior to 4.14.0. The migration guide consists of:
+
+- Upgrading the application dependencies
+- Generating your TypeScript typings
+- Reinitializing the application
+
+
+
+
+## Upgrading the application dependencies to 4.14.0
+
+:::prerequisites
+Stop the server before starting the upgrade.
+:::
+
+1. Upgrade all of the Strapi packages in `package.json` to `4.14.0`:
+
+ ```json title="path: package.json"
+ {
+ // ...
+ "dependencies": {
+ "@strapi/strapi": "4.14.0",
+ "@strapi/plugin-users-permissions": "4.14.0",
+ "@strapi/plugin-i18n": "4.14.0"
+ // ...
+ }
+ }
+ ```
+
+2. Save the edited `package.json` file.
+
+3. Run the install command:
+
+
+## Generate TypeScript typings
+
+Generate your TypeScript typings by running the following command in the terminal:
+
+
+
+
+
+```bash
+yarn strapi ts:generate-types
+```
+
+
+
+
+
+```bash
+npm run strapi ts:generate-types
+```
+
+
+
+
+
+## Rebuild the application
+
+
+
+## Restart the application
+
+
diff --git a/docusaurus/docs/dev-docs/migration/v4/migration-guide-4.14.0-to-4.15.5.md b/docusaurus/docs/dev-docs/migration/v4/migration-guide-4.14.0-to-4.15.5.md
new file mode 100644
index 0000000000..037d6199f4
--- /dev/null
+++ b/docusaurus/docs/dev-docs/migration/v4/migration-guide-4.14.0-to-4.15.5.md
@@ -0,0 +1,94 @@
+---
+title: Migrate from 4.14.0 to 4.15.5
+displayed_sidebar: devDocsSidebar
+description: Learn how you can migrate your Strapi application from 4.14.0 to 4.15.5.
+---
+
+import PluginsCaution from '/docs/snippets/migrate-plugins-extension-caution.md'
+import BuildCommand from '/docs/snippets/build-npm-yarn.md'
+import DevelopCommand from '/docs/snippets/develop-npm-yarn.md'
+import InstallCommand from '/docs/snippets/install-npm-yarn.md'
+
+# v4.14.0 to v4.15.5 migration guide
+
+The present migration guide upgrades Strapi v4.14.0 to v4.15.5. Strapi v4.15.5 updated the [loading order](/dev-docs/configurations/middlewares#loading-order) of the middlewares, ensuring the [`logger` middleware](/dev-docs/configurations/middlewares#logger) is loaded first. The migration guide consists of:
+
+- Upgrading the application dependencies
+- Manually updating the loading order of middlewares in the configuration file
+- Reinitializing the application
+
+
+
+## Upgrading the application dependencies to 4.15.5
+
+:::prerequisites
+Stop the server before starting the upgrade.
+:::
+
+1. Upgrade all of the Strapi packages in `package.json` to 4.15.5:
+
+ ```json title="path: package.json"
+ {
+ // ...
+ "dependencies": {
+ "@strapi/strapi": "4.15.5",
+ "@strapi/plugin-users-permissions": "4.15.5",
+ "@strapi/plugin-i18n": "4.15.5",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0",
+ "react-router-dom": "5.3.4",
+ "styled-components": "5.3.3"
+
+ // ...
+ }
+ }
+ ```
+
+2. Save the edited `package.json` file.
+
+3. Run the install command:
+
+
+## Manually update the loading order of middlewares
+
+Manually update the [`config/middlewares.js` configuration file](/dev-docs/configurations/middlewares) to ensure that `strapi::logger` is the first item in the array:
+
+
+
+
+
+```js title="./config/middlewares.js" {3}
+module.exports = [
+ "strapi::logger",
+ "strapi::errors",
+ "strapi::security",
+ "strapi::cors",
+ // β¦
+];
+```
+
+
+
+
+
+```ts title="./config/middlewares.ts" {3}
+export default [
+ "strapi::logger",
+ "strapi::cors",
+ "strapi::body",
+ "strapi::errors",
+ // β¦
+];
+```
+
+
+
+
+
+## Rebuild the application
+
+
+
+## Restart the application
+
+
diff --git a/docusaurus/docs/dev-docs/migration/v4/migration-guide-4.5.1-to-4.6.1.md b/docusaurus/docs/dev-docs/migration/v4/migration-guide-4.5.1-to-4.6.1.md
index 548c9243b1..4a26c130fe 100644
--- a/docusaurus/docs/dev-docs/migration/v4/migration-guide-4.5.1-to-4.6.1.md
+++ b/docusaurus/docs/dev-docs/migration/v4/migration-guide-4.5.1-to-4.6.1.md
@@ -47,6 +47,94 @@ Stop the server before starting the upgrade.
If the operation doesn't work, try removing your `yarn.lock` or `package-lock.json`. If that doesn't help, remove the `node_modules` folder as well and try again.
:::
+## Migrate Audit Log Tables (optional & EE only)
+
+:::caution
+This specific section only applies to Strapi Enterprise customers or Cloud customers who have the Audit Logs feature enabled.
+:::
+
+If you updated to v4.6.0 originally and are now updating to v4.6.1 while being a Strapi Enterprise customer, there was a small breaking change with the table names for Audit Logs. This is only relevant if you are using the Audit Logs feature.
+
+Between v4.6.0 and v4.6.1 the Audit log table names changed from `audit_logs` to `strapi_audit_logs` and likewise the link table changed from `audit_logs_user_links` to `strapi_audit_logs_user_links`.
+
+To migrate these tables properly without losing your audit log data:
+
+1. Make a backup of the database in case something unexpected happens.
+2. In the `./database/migrations` folder, create a file named: `2023.02.08T00.00.00.update-audit-logs-tables.js`.
+3. Copy and paste the following code into the previously created file:
+
+```js
+const tables = {
+ auditLogs: {
+ fq: ["audit_logs_created_by_id_fk", "audit_logs_updated_by_id_fk"],
+ indexes: ["audit_logs_created_by_id_fk", "audit_logs_updated_by_id_fk"],
+ tableOld: "audit_logs",
+ tableNew: "strapi_audit_logs",
+ },
+ auditLogsUser: {
+ fq: ["audit_logs_user_links_fk", "audit_logs_user_links_inv_fk"],
+ indexes: ["audit_logs_user_links_fk", "audit_logs_user_links_inv_fk"],
+ tableOld: "audit_logs_user_links",
+ tableNew: "strapi_audit_logs_user_links",
+ },
+};
+
+module.exports = {
+ async up(knex) {
+ // Drop all of the schema entries we cache
+ await knex.from("strapi_database_schema").delete();
+
+ // Rename the auditLog table
+ if (await knex.schema.hasTable(tables.auditLogs.tableOld)) {
+ await knex.schema.renameTable(
+ tables.auditLogs.tableOld,
+ tables.auditLogs.tableNew
+ );
+ }
+
+ // Rename the auditLogUser table
+ if (await knex.schema.hasTable(tables.auditLogsUser.tableOld)) {
+ await knex.schema.renameTable(
+ tables.auditLogsUser.tableOld,
+ tables.auditLogsUser.tableNew
+ );
+ }
+
+ try {
+ // Drop the auditLog table fq and indexes
+ for (const fq of tables.auditLogs.fq) {
+ await knex.schema.alterTable(tables.auditLogs.tableNew, (table) => {
+ table.dropForeign([], fq);
+ });
+ }
+
+ for (const index of tables.auditLogs.indexes) {
+ await knex.schema.alterTable(tables.auditLogs.tableNew, (table) => {
+ table.dropIndex([], index);
+ });
+ }
+
+ // Drop the auditLogUser table fq and indexes
+ for (const fq of tables.auditLogsUser.fq) {
+ await knex.schema.alterTable(tables.auditLogsUser.tableNew, (table) => {
+ table.dropForeign([], fq);
+ });
+ }
+
+ for (const index of tables.auditLogsUser.indexes) {
+ await knex.schema.alterTable(tables.auditLogsUser.tableNew, (table) => {
+ table.dropIndex([], index);
+ });
+ }
+ } catch (e) {
+ console.log(e);
+ }
+ },
+};
+```
+
+5. Save the file.
+
## Changing the webhooks configuration (optional)
By default, and for backward compatibility reasons, webhooks will receive the entity with its relations populated again. We do recommend to disable this behavior if you were not using it, as it may cause performance issues when having many relations. If you need populated relations in your webhook, we recommend doing a separate query in your webhook listener to fetch the entity only with the necessary data.
diff --git a/docusaurus/docs/dev-docs/plugins-extension.md b/docusaurus/docs/dev-docs/plugins-extension.md
index c4a5bab2c2..b4467a4911 100644
--- a/docusaurus/docs/dev-docs/plugins-extension.md
+++ b/docusaurus/docs/dev-docs/plugins-extension.md
@@ -21,14 +21,14 @@ Plugin extensions code is located in the `./src/extensions` folder (see [project
```bash
/extensions
/some-plugin-to-extend
- strapi-server.js
+ strapi-server.js|ts
/content-types
/some-content-type-to-extend
model.json
/another-content-type-to-extend
model.json
/another-plugin-to-extend
- strapi-server.js
+ strapi-server.js|ts
```
@@ -39,13 +39,13 @@ Plugins can be extended in 2 ways:
## Extending a plugin's content-types
-A plugin's Content-Types can be extended in 2 ways: using the programmatic interface within `strapi-server.js` and by overriding the content-types schemas.
+A plugin's Content-Types can be extended in 2 ways: using the programmatic interface within `strapi-server.js|ts` and by overriding the content-types schemas.
The final schema of the content-types depends on the following loading order:
1. the content-types of the original plugin,
2. the content-types overridden by the declarations in the [schema](/dev-docs/backend-customization/models#model-schema) defined in `./src/extensions/plugin-name/content-types/content-type-name/schema.json`
-3. the content-types declarations in the [`content-types` key exported from `strapi-server.js`](/dev-docs/api/plugins/server-api#content-types)
+3. the content-types declarations in the [`content-types` key exported from `strapi-server.js|ts`](/dev-docs/api/plugins/server-api#content-types)
4. the content-types declarations in the [`register()` function](/dev-docs/configurations/functions#register) of the Strapi application
To overwrite a plugin's [content-types](/dev-docs/backend-customization/models):
@@ -63,9 +63,13 @@ When a Strapi application is initializing, plugins, extensions and global lifecy
1. Plugins are loaded and their interfaces are exposed.
2. Files in `./src/extensions` are loaded.
-3. The `register()` and `bootstrap()` functions in `./src/index.js` are called.
+3. The `register()` and `bootstrap()` functions in `./src/index.js|ts` are called.
-A plugin's interface can be extended at step 2 (i.e. within `./src/extensions`) or step 3 (i.e. inside `./src/index.js`).
+A plugin's interface can be extended at step 2 (i.e. within `./src/extensions`) or step 3 (i.e. inside `./src/index.js|ts`).
+
+:::note
+If your Strapi project is TypeScript-based, please ensure that the `index` file has a TypeScript extension (i.e., `src/index.ts`) otherwise it will not be compiled.
+:::
### Within the extensions folder
@@ -73,13 +77,13 @@ To extend a plugin's server interface using the `./src/extensions` folder:
1. _(optional)_ Create the `./src/extensions` folder at the root of the app, if the folder does not already exist.
2. Create a subfolder with the same name as the plugin to be extended.
-3. Create a `strapi-server.js` file to extend a plugin's back end using the [Server API](/dev-docs/api/plugins/server-api).
+3. Create a `strapi-server.js|ts` file to extend a plugin's back end using the [Server API](/dev-docs/api/plugins/server-api).
4. Within this file, define and export a function. The function receives the `plugin` interface as an argument so it can be extended.
Example of backend extension
-```js title="./src/extensions/some-plugin-to-extend/strapi-server.js"
+```js title="./src/extensions/some-plugin-to-extend/strapi-server.js|ts"
module.exports = (plugin) => {
plugin.controllers.controllerA.find = (ctx) => {};
@@ -99,12 +103,12 @@ module.exports = (plugin) => {
### Within the register and bootstrap functions
-To extend a plugin's interface within `./src/index.js`, use the `bootstrap()` and `register()` [functions](/dev-docs/configurations/functions) of the whole project, and access the interface programmatically with [getters](/dev-docs/api/plugins/server-api#usage).
+To extend a plugin's interface within `./src/index.js|ts`, use the `bootstrap()` and `register()` [functions](/dev-docs/configurations/functions) of the whole project, and access the interface programmatically with [getters](/dev-docs/api/plugins/server-api#usage).
-Example of extending a plugin's content-type within ./src/index.js
+Example of extending a plugin's content-type within ./src/index.js|ts
-```js title="./src/index.js"
+```js title="./src/index.js|ts"
module.exports = {
register({ strapi }) {
diff --git a/docusaurus/docs/dev-docs/plugins.md b/docusaurus/docs/dev-docs/plugins.md
index 9020c82a2d..cd69fe4beb 100644
--- a/docusaurus/docs/dev-docs/plugins.md
+++ b/docusaurus/docs/dev-docs/plugins.md
@@ -1,33 +1,31 @@
---
-title: Strapi plugins - Strapi Developer Docs
+title: Plugins
description: Strapi comes with built-in plugins (i18n, GraphQL, Users & Permissions, Upload, API documentation, and Email) and you can install plugins as npm packages.
displayed_sidebar: devDocsSidebar
-
+pagination_prev: dev-docs/backend-customization
+pagination_next: dev-docs/plugins/using-plugins
---
# Strapi plugins
-Strapi comes with these officially supported plugins:
-
-
-
-## Automatic plugins discovery
-
-Strapi automatically loads plugins installed with npm. Under the hood, Strapi scans every `package.json` file of the project dependencies, and looks for the following declaration:
+:::strapi Dev Docs vs. User Guide
+The present section is about the developer-oriented aspects of Strapi plugins. To learn how to install and use plugins from the Strapi admin panel, please read the [User Guide](/user-docs/plugins).
+:::
-```json
-"strapi": {
- "kind": "plugin"
-}
-```
+Strapi's core features can be extended with plugins, and your experience with Strapi plugins will fall under the following 4 use cases:
-Installed plugins can also be manually enabled or disabled.
+- You will use one of the **built-in plugins** officially maintained by Strapi. Some built-in plugins can already be pre-installed when you create a new Strapi project.
+- You might want to browse **3rd-party plugins** for additional features. 3rd-party plugins can be browsed from the admin panel or from the [Marketplace website](https://market.strapi.io) and installed with the command line interface.
+- You might want to **develop your own plugins**. The plugins you develop can be specific to your Strapi project β these plugins are called "local plugins", or can be submitted to the Marketplace if meant to be shared with the Strapi community.
+- You might want to **extend an existing plugin** for a specific Strapi project. With this last use case, please proceed carefully, knowing that extensions might break with future updates from the plugin maintainer.
-## Manual enabling/disabling
+Choose one of the following documentation sections from the table, depending on your profile and use case:
-To disable a plugin without uninstalling it, switch its `enabled` key to `false` in the [`/config/plugins.js` file](/dev-docs/configurations/plugins).
+| As a⦠| I want to⦠| Recommended section to read |
+|-------------|---------------|-----------------------------|
+| User | Discover and install built-in and 3rd-party plugins | [User Guide > Plugins](/user-docs/plugins) |
+| Developer | Setup, configure, and use Strapi built-in plugins | [Dev Docs > Using plugins](/dev-docs/plugins/using-plugins) |
+| Developer | Create my own plugin and submit it to the Marketplace | [Dev Docs > Developing plugins](/dev-docs/plugins/developing-plugins) |
+| Developer | Extend an existing plugin to customize it for a specific Strapi projectοΈ | [Dev Docs > Extending plugins](/dev-docs/plugins-extension) |
-:::strapi To go further
-* To know more about plugins installation, see the [User guide](/user-docs/plugins).
-* Existing plugins can be [extended](/dev-docs/plugins-extension), or you can even [create your own](/dev-docs/plugins-development)!
-:::
+
diff --git a/docusaurus/docs/dev-docs/plugins/developing-plugins.md b/docusaurus/docs/dev-docs/plugins/developing-plugins.md
new file mode 100644
index 0000000000..a1bd5e7e3f
--- /dev/null
+++ b/docusaurus/docs/dev-docs/plugins/developing-plugins.md
@@ -0,0 +1,50 @@
+---
+title: Developing plugins
+description: Generation introduction about Strapi plugins development
+displayed_sidebar: devDocsSidebar
+pagination_prev: dev-docs/plugins
+pagination_next: dev-docs/plugins/development/create-a-plugin
+---
+
+# Developing Strapi plugins
+
+:::info
+This section is about developing Strapi plugins to use them as local plugins or to submit them to the Marketplace. Not what you're looking for? Read the [plugins introduction](/dev-docs/plugins) and find your use case and recommended section to read from there.
+:::
+
+Strapi allows the development of plugins that work exactly like the built-in plugins or 3rd-party plugins available from the [Marketplace](https://market.strapi.io). Once created, your plugin can be:
+
+- used as a local plugin, working only with a specific Strapi project,
+- or [submitted to the Marketplace](https://market.strapi.io/submit-plugin) to be shared with the community.
+
+π To start developing a Strapi plugin:
+
+1. [Create a plugin](/dev-docs/plugins/development/create-a-plugin) using the CLI-based generator.
+2. Learn more about the [structure of a plugin](/dev-docs/plugins/development/plugin-structure).
+3. Get an overview of the [plugin APIs](#plugin-apis) to add features to your plugin.
+4. Read some [guides](#guides) based on your use case(s).
+
+## Plugin APIs
+
+Strapi provides the following programmatic APIs for plugins to hook into some of Strapi's features:
+
+
+
+
+
+
+:::strapi Custom fields plugins
+Plugins can also be used to add [custom fields](/dev-docs/custom-fields) to Strapi.
+:::
+
+## Guides
+
+
+
+
+
+
+
+:::strapi Additional resources
+The Strapi blog features a [tutorial series](https://strapi.io/blog/how-to-create-a-strapi-v4-plugin-server-customization-4-6) about creating a Strapi v4 'Todo' plugin. The [contributors documentation](https://contributor.strapi.io/) can also include additional information useful while developing a Strapi plugin.
+:::
diff --git a/docusaurus/docs/dev-docs/plugins/development/create-a-plugin.md b/docusaurus/docs/dev-docs/plugins/development/create-a-plugin.md
new file mode 100644
index 0000000000..b8ba21bb57
--- /dev/null
+++ b/docusaurus/docs/dev-docs/plugins/development/create-a-plugin.md
@@ -0,0 +1,313 @@
+---
+title: Plugin creation & setup
+description: Learn how to create a Strapi plugin and how to start the development servers
+pagination_next: dev-docs/plugins/development/plugin-structure
+---
+
+# Plugin creation and setup
+
+To start developing a Strapi plugin, you need to:
+
+1. create the plugin,
+2. enable the plugin,
+3. install dependencies, build the admin panel, and start the server(s).
+
+:::prerequisites
+You created a Strapi project.
+
+
+Use the CLI to create a project:
+
+Run the corresponding command in a terminal window, replacing `my-project` with the name of your choice:
+
+
+
+
+
+```bash
+yarn create strapi-app my-project --quickstart
+```
+
+
+
+
+
+```bash
+npx create-strapi-app@latest my-project --quickstart
+```
+
+
+
+
+
+More details can be found in the [CLI installation guide](/dev-docs/installation/cli).
+
+
+:::
+
+## Create the plugin using the CLI generator
+
+The fastest way to create a Strapi plugin is to use the CLI generator. To do so:
+
+1. Navigate to the root of an existing Strapi project, or create a new one.
+2. Run the following command in a terminal window to start the interactive CLI:
+
+
+
+
+ ```sh
+ yarn strapi generate plugin
+ ```
+
+
+
+
+
+ ```sh
+ npm run strapi generate plugin
+ ```
+
+
+
+
+4. Choose either `JavaScript` or `TypeScript` for the plugin language.
+
+:::callout π§ Experimental plugin CLI
+Strapi now also offers a [plugin CLI](/dev-docs/plugins/development/plugin-cli), but use it at your own risk as it's still experimental.
+:::
+
+## Enable the plugin
+
+Once the `strapi generate plugin` CLI script has finished running, the minimum required code for the plugin to work is created for you, but the plugin is not enabled yet.
+
+To enable a plugin:
+
+1. If it does not exist already, create the **plugins configuration file** file at the root of the Strapi project.
+2. Enable the plugin by adding the following code to the plugins configuration file:
+
+
+
+
+ ```js title="./config/plugins.js"
+ module.exports = {
+ // ...
+ "my-plugin": {
+ // name of your plugin, kebab-cased
+ enabled: true,
+ resolve: "./src/plugins/my-plugin", // path to the plugin folder
+ },
+ // ...
+ };
+ ```
+
+
+
+
+
+ ```js title=./config/plugins.ts
+ export default {
+ // ...
+ "my-plugin": {
+ enabled: true,
+ resolve: "./src/plugins/my-plugin", // path to plugin folder
+ },
+ // ...
+ };
+ ```
+
+
+
+
+:::tip
+If you plan to use the plugin outside the Strapi project it was created in, move your plugin file outside the Strapi project and change the `resolve` value to the absolute directory path of your plugin.
+:::
+
+## Install dependencies, build the admin panel, and start servers
+
+Once the plugin code has been generated and the plugin is enabled, the next steps slighly differ depending on whether you created a vanilla JavaScript-based plugin or a TypeScript-based plugin (see [step 3](#create-the-plugin-using-the-cli-generator) of the CLI generator instructions).
+
+
+
+
+
+1. Navigate to the folder of the plugin. If created from a Strapi project using the CLI generator, plugins are located in the `src/plugins` folder (see [project structure](/dev-docs/project-structure)).
+
+2. Run the following command in the newly-created plugin directory to install plugin dependencies:
+
+
+
+
+ ```sh
+ yarn
+ ```
+
+
+
+
+
+ ```sh
+ npm install
+ ```
+
+
+
+
+3. Navigate back to the Strapi project root with `cd ../../..` and run the following command to build the admin panel and start the server(s):
+
+
+
+
+ ```sh
+ yarn develop
+ ```
+
+
+
+
+
+ ```sh
+ npm run develop
+ ```
+
+
+
+
+
+
+
+
+1. Navigate to the folder of the plugin. If created from a Strapi project using the CLI generator, plugins are located in the `src/plugins` folder (see [project structure](/dev-docs/project-structure)).
+
+2. Run the following command in the newly-created plugin directory to install plugin dependencies:
+
+
+
+
+ ```sh
+ yarn
+ ```
+
+
+
+
+
+ ```sh
+ npm install
+ ```
+
+
+
+
+3. Still in the plugin directory (e.g., `src/plugins/my-plugin`), run the following command:
+
+
+
+
+ ```sh
+ yarn build
+ ```
+
+
+
+
+
+ ```sh
+ npm run build
+ ```
+
+
+
+
+ This step transpiles the TypeScript files and outputs the JavaScript files to a `dist` directory that is unique to the plugin.
+
+4. Navigate back to the Strapi project root with `cd ../../..` and run the following command to build the admin panel and start the server(s):
+
+
+
+
+ ```sh
+ yarn develop
+ ```
+
+
+
+
+
+ ```sh
+ npm run develop
+ ```
+
+
+
+
+
+
+
+You should now be ready to start developing your plugin.
+
+:::strapi What to read next?
+You can either jump to the [plugin structure](/dev-docs/plugins/development/plugin-structure) documentation or read the [servers and hot reloading](#servers-and-hot-reloading) section to learn more about different ways to start the server.
+:::
+
+### Servers and hot reloading
+
+Strapi itself is **headless** . The admin panel is completely separate from the server.
+
+```mermaid
+graph LR
+ A{Server} -->|Axios instance| B{Admin Panel}
+ B --> A
+```
+
+The server can be started in 2 different ways: you can run the backend server only or start both the server and admin panel servers.
+
+#### Start only the backend server
+
+To start only the backend server, run the following command:
+
+
+
+
+
+```bash
+yarn develop
+```
+
+
+
+
+
+```bash
+npm run develop
+```
+
+
+
+
+
+This will run the server on `localhost:1337` and enable hot reloading only on the back-end server, i.e. it will only auto-reload when changes are made to the server. If you are only doing development in the `./server` directory of your plugin, this will be faster.
+
+#### Start both the backend and admin panel servers
+
+If you are doing development on both the `/server` and `/admin` directories of your plugin, run the following command:
+
+
+
+
+
+```bash
+yarn develop --watch-admin
+```
+
+
+
+
+
+```bash
+npm run develop -- --watch-admin
+```
+
+
+
+
+This will run the server on `localhost:1337` and enable hot reloading on both the back-end and front-end servers, i.e.it will auto-reload when changes are made to the server or the admin panel of Strapi.
diff --git a/docusaurus/docs/dev-docs/plugins/development/plugin-cli.md b/docusaurus/docs/dev-docs/plugins/development/plugin-cli.md
new file mode 100644
index 0000000000..7726a84d79
--- /dev/null
+++ b/docusaurus/docs/dev-docs/plugins/development/plugin-cli.md
@@ -0,0 +1,87 @@
+---
+title: Plugin CLI
+description: Reference documentation for Strapi's Plugin CLI commands
+displayed_sidebar: devDocsSidebar
+---
+
+# Plugin CLI reference
+
+:::caution
+The Plugin CLI is currently experimental.
+:::
+
+The Plugin CLI is set of commands orientated around developing plugins to use them as local plugins or to publish them on NPM and/or submit them to the Marketplace.
+
+The present documentation lists the available Plugin CLI commands. The [associated guide](/dev-docs/plugins/guides/use-the-plugin-cli) illustrates how to use these commands to create a plugin from scratch, link it to an existing project, and publish it.
+
+## strapi plugin:init
+
+Create a new plugin at a given path.
+
+```bash
+strapi plugin:init
+```
+
+| Arguments | Type | Description | Default |
+| --------- | :----: | --------------------| ------------------------- |
+| `path` | string | Path to the plugin | `./src/plugins/my-plugin` |
+
+| Option | Type | Description | Default |
+| ------------- | :--: | ---------------------------------------- |---------|
+| `-d, --debug` | - | Enable debugging mode with verbose logs | false |
+| `--silent` | - | Do not log anything | false |
+
+## strapi plugin:build
+
+Bundle the strapi plugin for publishing.
+
+```bash
+strapi plugin:build
+```
+
+| Option | Type | Description | Default |
+| -------------- | :----: | ----------------------------------------------------------------------------------------------------------------- | --------|
+| `--force` | string | Automatically answer "yes" to all prompts, including potentially destructive requests, and run non-interactively. | - |
+| `-d, --debug` | - | Enable debugging mode with verbose logs | false |
+| `--silent` | - | Do not log anything | false |
+| `--minify` | - | Minify the output | true |
+| `--sourcemaps` | - | Produce sourcemaps | false |
+
+## strapi plugin:link-watch
+
+Recompiles the plugin automatically on changes and runs `yalc push --publish`.
+
+```bash
+strapi plugin:link-watch
+```
+
+| Option | Type | Description | Default |
+| ------------- | :--: | -------------------------------------------------------- | --------|
+| `-d, --debug` | - | Enable debugging mode with verbose logs | false |
+| `--silent` | - | Do not log anything | false |
+
+## strapi plugin:watch
+
+Watch and compile the Strapi plugin for local development.
+
+```bash
+strapi plugin:watch
+```
+
+| Option | Type | Description | Default |
+| ------------- | :--: | -------------------------------------------------------- |---------|
+| `-d, --debug` | - | Enable debugging mode with verbose logs | false |
+| `--silent` | - | Do not log anything | false |
+
+## strapi plugin:verify
+
+Verify the output of the plugin before publishing it.
+
+```bash
+strapi plugin:verify
+```
+
+| Option | Type | Description | Default |
+| ------------- | :--: | -------------------------------------------------------- | --------|
+| `-d, --debug` | - | Enable debugging mode with verbose logs | false |
+| `--silent` | - | Do not log anything | false |
diff --git a/docusaurus/docs/dev-docs/plugins/development/plugin-structure.md b/docusaurus/docs/dev-docs/plugins/development/plugin-structure.md
new file mode 100644
index 0000000000..ea99964e7c
--- /dev/null
+++ b/docusaurus/docs/dev-docs/plugins/development/plugin-structure.md
@@ -0,0 +1,39 @@
+---
+title: Plugin structure
+description: Learn more about the structure of a Strapi plugin
+displayed_sidebar: devDocsSidebar
+---
+
+import InteractivePluginStructure from '@site/src/components/PluginStructure.js'
+
+# Plugin structure
+
+When [creating a plugin with the CLI generator](/dev-docs/plugins/development/create-a-plugin), Strapi generates the following boilerplate structure for you in the `./src/plugins/my-plugin` folder:
+
+
+
+A Strapi plugin is divided into 2 parts, each living in a different folder and offering a different API:
+
+| Plugin part | Description | Folder | API |
+|-------------|-------------|--------------|-----|
+| Admin panel | Includes what will be visible in the [admin panel](/user-docs/intro) (components, navigation, settings, etc.) | `/admin` |[Admin Panel API](/dev-docs/api/plugins/admin-panel-api)|
+| Backend server | Includes what relates to the [backend server](/dev-docs/backend-customization) (content-types, controllers, middlewares, etc.) |`/server` |[Server API](/dev-docs/api/plugins/server-api)|
+
+
+
+:::note Notes about the usefulness of the different parts for your specific use case
+- **Server-only plugin**: You can create a plugin that will just use the server part to enhance the API of your application. For instance, this plugin could have its own visible or invisible content-types, controller actions, and routes that are useful for a specific use case. In such a scenario, you don't need your plugin to have an interface in the admin panel.
+
+- **Admin panel plugin vs. application-specific customization**: You can create a plugin to inject some components into the admin panel. However, you can also achieve this by creating a `./src/admin/app.js` file and invoking the `bootstrap` lifecycle function to inject your components. In this case, deciding whether to create a plugin depends on whether you plan to reuse and distribute the code or if it's only useful for a unique Strapi application.
+:::
+
+
+
+:::strapi What to read next?
+The next steps of your Strapi plugin development journey will require you to use any of the Strapi plugins APIs.
+
+2 different types of resources help you understand how to use the plugin APIs:
+
+- The reference documentation for the [Admin Panel API](/dev-docs/api/plugins/admin-panel-api) and [Server API](/dev-docs/api/plugins/server-api) give an overview of what is possible to do with a Strapi plugin.
+- [Guides](/dev-docs/plugins/developing-plugins#guides) cover some specific, use-case based examples.
+:::
diff --git a/docusaurus/docs/dev-docs/plugins/email.md b/docusaurus/docs/dev-docs/plugins/email.md
index 3222690232..dbf782113f 100644
--- a/docusaurus/docs/dev-docs/plugins/email.md
+++ b/docusaurus/docs/dev-docs/plugins/email.md
@@ -16,7 +16,7 @@ The Email plugin requires a provider and a provider configuration in the `config
:::
:::note
-[`Sendmail`](https://www.npmjs.com/package/sendmail) is the default email provider in the Strapi Email plugin. It provides functionality for the local development environment but is not production-ready in the default configuration. For production stage applications you need to further configure `Sendmail` or change providers. The [Providers](/dev-docs/providers) documentation has instructions for changing providers, configuring providers, and creating a new email provider.
+[`Sendmail`](https://www.npmjs.com/package/sendmail) is the default email provider in the Strapi Email plugin. It provides functionality for the local development environment but is not production-ready in the default configuration. For production stage applications you need to further configure `Sendmail` (refer to its [README on npm](https://www.npmjs.com/package/sendmail)) or change providers. The [Providers](/dev-docs/providers) documentation has instructions for changing providers, configuring providers, and creating a new email provider.
:::
## Sending emails with a controller or service
diff --git a/docusaurus/docs/dev-docs/plugins/graphql.md b/docusaurus/docs/dev-docs/plugins/graphql.md
index 4d6fa54f9b..34b1b0916e 100644
--- a/docusaurus/docs/dev-docs/plugins/graphql.md
+++ b/docusaurus/docs/dev-docs/plugins/graphql.md
@@ -42,17 +42,18 @@ npm run strapi install graphql
Then, start your app and open your browser at [http://localhost:1337/graphql](http://localhost:1337/graphql). You should now be able to access the **GraphQL Playground** that will help you to write your GraphQL queries and mutations.
:::note
-The GraphQL Playground is enabled by default for both the development and staging environments, but disabled in production environments. Set the `playgroundAlways` configuration option to `true` to also enable the GraphQL Playground in production environments (see [plugins configuration documentation](/dev-docs/configurations/plugins#graphql-configuration)).
+- The GraphQL Playground is enabled by default for both the development and staging environments but disabled in production environments. Set the `playgroundAlways` configuration option to `true` to also enable the GraphQL Playground in production environments (see [plugins configuration documentation](/dev-docs/configurations/plugins#graphql-configuration)).
+- If the GraphQL Playground does not appear to be accessible, please ensure that the GraphQL plugin is enabled: `graphql: true` should be added to the `config/plugins.js|ts` configuration file; if not, edit the file to add this line (see [plugins configuration](/dev-docs/configurations/plugins) for more details).
:::
## Configuration
-Plugins configuration are defined in the `config/plugins.js` file. This configuration file can include a `graphql.config` object to define specific configurations for the GraphQL plugin (see [plugins configuration documentation](/dev-docs/configurations/plugins#graphql-configuration)).
+Plugins configuration are defined in the `config/plugins.js|ts` file. This configuration file can include a `graphql.config` object to define specific configurations for the GraphQL plugin (see [plugins configuration documentation](/dev-docs/configurations/plugins#graphql-configuration)).
[Apollo Server](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#apolloserver) options can be set with the `graphql.config.apolloServer` [configuration object](/dev-docs/configurations/plugins#graphql-configuration). Apollo Server options can be used for instance to enable the [tracing feature](https://www.apollographql.com/docs/federation/metrics/), which is supported by the GraphQL playground to track the response time of each part of your query. From `Apollo Server` version 3.9 default cache option is `cache: 'bounded'`. You can change it in the `apolloServer` configuration. For more information visit [Apollo Server Docs](https://www.apollographql.com/docs/apollo-server/performance/cache-backends/).
:::caution
-The maximum number of items returned by the response is limited to 100 by default. This value can be changed using the `amountLimit` configuration option, but should only be changed after careful consideration: a large query can cause a DDoS (Distributed Denial of Service) and may cause abnormal load on your Strapi server, as well as your database server.
+The maximum number of items returned by the response is limited to 100 by default. This value can be changed using the `defaultLimit` configuration option, but should only be changed after careful consideration: a large query can cause a DDoS (Distributed Denial of Service) and may cause abnormal load on your Strapi server, as well as your database server.
:::
@@ -639,7 +640,7 @@ The `context` object gives access to:
* Koa's [context](https://koajs.com/#context) with `context.http` and [state](https://koajs.com/#ctx-state) with `context.state`.
- Example of a custom GraphQL policy applied to a resolver
+ Example of GraphQL policies applied to resolvers
@@ -663,6 +664,15 @@ module.exports = {
*/
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,
},
@@ -694,6 +704,15 @@ export default {
*/
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,
},
@@ -709,11 +728,11 @@ export default {
-#### Middlewares
+##### Middlewares
-[Middlewares](/dev-docs/backend-customization/middlewares) can be applied to a GraphQL resolver through the `resolversConfig.[MyResolverName].middlewares` key.
+[Middlewares](/dev-docs/backend-customization/middlewares) can be applied to a GraphQL resolver through the `resolversConfig.[MyResolverName].middlewares` key. The only difference between the GraphQL and REST implementations is that the `config` key becomes `options`.
-The `middlewares` key is an array accepting a list of middlewares, each item in this list being either a reference to an already registered policy or an implementation that is passed directly (see [middlewares configuration documentation](/dev-docs/backend-customization/routes#middlewares)).
+The `middlewares` key is an array accepting a list of middlewares, each item in this list being either a reference to an already registered middleware or an implementation that is passed directly (see [middlewares configuration documentation](/dev-docs/backend-customization/routes#middlewares)).
Middlewares directly implemented in `resolversConfig` can take the GraphQL resolver's [`parent`, `args`, `context` and `info` objects](https://www.apollographql.com/docs/apollo-server/data/resolvers/#resolver-arguments) as arguments.
@@ -722,7 +741,7 @@ Middlewares with GraphQL can even act on nested resolvers, which offer a more gr
:::
- Examples of custom GraphQL middlewares applied to a resolver
+ Examples of GraphQL middlewares applied to a resolver
@@ -771,6 +790,17 @@ module.exports = {
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,
},
@@ -827,6 +857,18 @@ export default {
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,
},
diff --git a/docusaurus/docs/dev-docs/plugins/guides/marketplace.md b/docusaurus/docs/dev-docs/plugins/guides/marketplace.md
new file mode 100644
index 0000000000..3ce1887eb6
--- /dev/null
+++ b/docusaurus/docs/dev-docs/plugins/guides/marketplace.md
@@ -0,0 +1,203 @@
+---
+title: Publishing a Strapi plugin to the Marketplace
+# description: todo
+displayed_sidebar: devDocsSidebar
+---
+
+# Publishing your Strapi plugin
+
+_Coming soonβ¦_
+
+:::tip
+Check [this blog post](https://strapi.io/blog/how-to-create-a-strapi-v4-plugin-publish-on-npm-6-6) to learn how to publish your Strapi plugin on npm.
+:::
+
+
+
+
diff --git a/docusaurus/docs/dev-docs/plugins/guides/pass-data-from-server-to-admin.md b/docusaurus/docs/dev-docs/plugins/guides/pass-data-from-server-to-admin.md
new file mode 100644
index 0000000000..9f90feeda1
--- /dev/null
+++ b/docusaurus/docs/dev-docs/plugins/guides/pass-data-from-server-to-admin.md
@@ -0,0 +1,103 @@
+---
+title: How to pass data from server to admin panel with a Strapi plugin
+description: Learn how to pass data from server to admin panel with a Strapi plugin
+sidebar_label: Pass data from server to admin
+displayed_sidebar: devDocsSidebar
+---
+
+# How to pass data from server to admin panel with a Strapi plugin
+
+Plugins in Strapi help you add additional features to the existing core set of built-in features. They can be used to extend the API, customize the admin panel, and more. In many cases, you would like your plugin to store data for later retrieval, and to access this data.
+
+When [developing a Strapi plugin](/dev-docs/plugins/developing-plugins) you might want to pass data from the `/server` to the `/admin` folder. Within the `/server` folder you have access to the Strapi object and can do database queries whereas in the `/admin` folder you can't.
+
+Strapi is **headless** . The admin panel is completely separate from the server.
+
+Passing data from the `/server` to the `/admin` folder can be done using the admin panel's Axios instance:
+
+```mermaid
+graph LR
+ A{Server} -->|Axios instance| B{Admin Panel}
+ B --> A
+```
+
+To pass data from the `/server` to `/admin` folder you would first [create a custom admin route](#create-a-custom-admin-route) and then [get the data returned in the admin panel](#get-the-data-in-the-admin-panel).
+
+## Create a custom admin route
+
+Admin routes are like the routes that you would have for any controller, except that the `type: 'admin'` declaration hides them from the general API router, and allows you to access them from the admin panel.
+
+The following code will declare a custom admin route for the `my-plugin` plugin:
+
+```js title="/my-plugin/server/routes/index.js"
+module.exports = {
+ 'pass-data': {
+ type: 'admin',
+ routes: [
+ {
+ method: 'GET',
+ path: '/pass-data',
+ handler: 'myPluginContentType.index',
+ config: {
+ policies: [],
+ auth: false,
+ },
+ },
+ ]
+ }
+ // ...
+};
+```
+
+This route will call the `index` method of the `myPluginContentType` controller when you send a GET request to the `/my-plugin/pass-data` URL endpoint.
+
+Let's create a basic custom controller that simply returns a simple text:
+
+```js title="/my-plugin/server/controllers/my-plugin-content-type.js"
+'use strict';
+
+module.exports = {
+ async index(ctx) {
+ ctx.body = 'You are in the my-plugin-content-type controller!';
+ }
+}
+```
+
+This means that when sending a GET request to the `/my-plugin/pass-data` URL endpoint, you should get the `You are in the my-plugin-content-type controller!` text returned with the response.
+
+## Get the data in the admin panel
+
+Any request sent from an admin panel component to the endpoint for which we defined the custom route `/my-plugin/pass-data` should now return the text message returned by the custom controller.
+
+So for instance, if you create an `/admin/src/api/foobar.js` file and copy and paste the following code example:
+
+```js title="/my-plugin/admin/src/api/foobar.js"
+import axios from 'axios';
+
+const foobarRequests = {
+ getFoobar: async () => {
+ const data = await axios.get(`/my-plugin/pass-data`);
+ return data;
+ },
+};
+export default foobarRequests;
+```
+
+You will be able to use `foobarRequests.getFoobar()` in the code of an admin panel component and have it return the `You are in the my-plugin-content-type controller!` text with the data.
+
+For instance, within a React component, you could use `useEffect` to get the data after the component initializes:
+
+```js title="/my-plugin/admin/src/components/MyComponent/index.js"
+import foobarRequests from "../../api/foobar";
+const [foobar, setFoobar] = useState([]);
+
+// β¦
+useEffect(() => {
+ foobarRequests.getFoobar().then(res => {
+ setSchemas(res.data);
+ });
+}, [setFoobar]);
+// β¦
+```
+
+This would set the `You are in the my-plugin-content-type controller!` text within the `foobar` data of the component's state.
diff --git a/docusaurus/docs/dev-docs/plugins/guides/store-and-access-data.md b/docusaurus/docs/dev-docs/plugins/guides/store-and-access-data.md
new file mode 100644
index 0000000000..a9ff344ef2
--- /dev/null
+++ b/docusaurus/docs/dev-docs/plugins/guides/store-and-access-data.md
@@ -0,0 +1,178 @@
+---
+title: How to store and access data from a Strapi plugin
+description: Learn how to store and access data from a Strapi plugin
+sidebar_label: Store and access data
+displayed_sidebar: devDocsSidebar
+---
+
+# How to store and access data from a Strapi plugin
+
+[Plugins](/dev-docs/plugins/developing-plugins) in Strapi help you add additional features to the existing core set of built-in features. They can be used to extend the API, customize the admin panel, and more. In many cases, you would like your plugin to store data for later retrieval, and to access this data.
+
+To store data with a Strapi plugin, use a plugin content-type. Plugin content-types work exactly like other [content-types](/dev-docs/backend-customization/models). Once the content-type is [created](#create-a-content-type-for-your-plugin), you can start [interacting with the data](#interact-with-data-from-the-plugin).
+
+## Create a content-type for your plugin
+
+To create a content-type with the CLI generator, run the following command in a terminal:
+
+
+
+
+```bash
+yarn strapi generate content-type
+```
+
+
+
+
+
+```bash
+npm run strapi generate content-type
+```
+
+
+
+
+The generator CLI is interactive and asks a few questions about the content-type and the attributes it will contain. Answer the first questions, then for the `Where do you want to add this model?` question, choose the `Add model to existing plugin` option and type the name of the related plugin when asked.
+
+
+
+
+
+The CLI will generate some code required to use your plugin, which includes the following:
+
+- the [content-type schema](/dev-docs/backend-customization/models#model-schema)
+- and a basic [controller](/dev-docs/backend-customization/controllers), [service](/dev-docs/backend-customization/services), and [route](/dev-docs/backend-customization/routes) for the content-type
+
+:::tip
+You may want to create the whole structure of your content-types either entirely with the CLI generator or by directly creating and editing `schema.json` files. We recommend you first create a simple content-type with the CLI generator and then leverage the [Content-Type Builder](/user-docs/content-type-builder) in the admin panel to edit your content-type.
+
+If your content-type is not visible in the admin panel, you might need to set the `content-manager.visible` and `content-type-builder.visible` parameters to `true` in the `pluginOptions` object of the content-type schema:
+
+
+Making a plugin content-type visible in the admin panel:
+
+The following highlighted lines in an example `schema.json` file show how to make a plugin content-type visible to the Content-Type Builder and Content-Manager:
+
+```json title="/server/content-types/my-plugin-content-type/schema.json" {13-20} showLineNumbers
+{
+ "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
+
+The CLI generator might not have imported all the related content-type files for your plugin, so you might have to make the following adjustments after the `strapi generate content-type` CLI command has finished running:
+
+1. In the `/server/index.js` file, import the content-types:
+
+ ```js {7,22} showLineNumbers title="/server/index.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,
+ };
+
+ ```
+
+2. In the `/server/content-types/index.js` file, import the content-type folder:
+
+ ```js title="/server/content-types/index.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'),
+ };
+ ```
+
+3. Ensure that the `/server/content-types/[your-content-type-name]` folder contains not only the `schema.json` file generated by the CLI, but also an `index.js` file that exports the content-type with the following code:
+
+ ```js title="/server/content-types/my-plugin-content-type/index.js
+ 'use strict';
+
+ const schema = require('./schema');
+
+ module.exports = {
+ schema,
+ };
+ ```
+
+## Interact with data from the plugin
+
+Once you have created a content-type for your plugin, you can create, read, update, and delete data.
+
+:::note
+A plugin can only interact with data from the `/server` folder. If you need to update data from the admin panel, please refer to the [passing data guide](/dev-docs/plugins/guides/pass-data-from-server-to-admin).
+:::
+
+To create, read, update, and delete data, you can use either the [Entity Service API](/dev-docs/api/entity-service) or the [Query Engine API](/dev-docs/api/query-engine). While it's recommended to use the Entity Service API, especially if you need access to components or dynamic zones, the Query Engine API is useful if you need unrestricted access to the underlying database.
+
+Use the `plugin::your-plugin-slug.the-plugin-content-type-name` syntax for content-type identifiers in Entity Service and Query Engine API queries.
+
+**Example:**
+
+Here is how to find all the entries for the `my-plugin-content-type` collection type created for a plugin called `my-plugin`:
+
+```js
+// Using the Entity Service API
+let data = await strapi.entityService.findMany('plugin::my-plugin.my-plugin-content-type');
+
+// Using the Query Engine API
+let data = await strapi.db.query('plugin::my-plugin.my-plugin-content-type').findMany();
+````
+
+:::tip
+You can access the database via the `strapi` object which can be found in `middlewares`, `policies`, `controllers`, `services`, as well as from the `register`, `boostrap`, `destroy` lifecycle functions.
+:::
diff --git a/docusaurus/docs/dev-docs/plugins/guides/use-the-plugin-cli.md b/docusaurus/docs/dev-docs/plugins/guides/use-the-plugin-cli.md
new file mode 100644
index 0000000000..a26e79ba4e
--- /dev/null
+++ b/docusaurus/docs/dev-docs/plugins/guides/use-the-plugin-cli.md
@@ -0,0 +1,185 @@
+---
+title: How to use the Plugin CLI to build and publish a Strapi plugin
+description: Learn how to use the Plugin CLI to build and publish a Strapi plugin
+sidebar_label: Use the Plugin CLI
+displayed_sidebar: devDocsSidebar
+---
+
+# How to use the Plugin CLI to create and publish a Strapi plugin
+
+:::caution
+The Plugin CLI is currently experimental.
+:::
+
+The Plugin CLI is set of commands orientated around developing plugins to use them as local plugins or to publish them on NPM and/or submit them to the Marketplace.
+
+As opposed to the `strapi generate plugin` command (see [plugin creation and setup](/dev-docs/plugins/development/create-a-plugin)) you do not need to set up a Strapi project to use the Plugin CLI.
+
+The present guide covers creating a plugin from scratch, linking it to an existing Strapi project, and publishing the plugin. If you already have an existing plugin, you can instead retrofit the plugin setup to utilise the Plugin CLI commands (please refer to the [Plugin CLI reference](/dev-docs/plugins/development/plugin-cli) for a full list of available commands).
+
+## Getting started with the Plugin CLI
+
+:::note
+This guide assumes you want to develop a plugin external to your Strapi project. However, the steps largely remain the same if you want to develop a plugin within your existing project. If you are not [using a monorepo](#working-with-the-plugin-cli-in-a-monorepo-environment) the steps are exactly the same.
+:::
+
+### Creating the plugin
+
+To create your plugin, ensure you are in the parent directory of where you want it to be created and run the following command:
+
+
+
+
+
+```bash
+yarn dlx @strapi/strapi plugin:init my-strapi-plugin
+```
+
+
+
+
+
+```bash
+npx @strapi/strapi plugin:init my-strapi-plugin
+```
+
+
+
+
+
+The path `my-strapi-plugin` can be replaced with whatever you want to call your plugin, including the path to where it should be created (e.g., `code/strapi-plugins/my-new-strapi-plugin`).
+
+You will be ran through a series of prompts to help you setup your plugin. If you selected yes to all options the final structure will be similar to the default [plugin structure](/dev-docs/plugins/development/plugin-structure).
+
+### Linking the plugin to your project
+
+Once you created your plugin, you will want to register it with the [yalc](https://github.com/wclr/yalc) repository (it's local to your machine). To do this, run the following command:
+
+
+
+
+
+```bash
+cd my-strapi-plugin
+yarn install
+yarn link-watch
+```
+
+
+
+
+
+```bash
+cd my-strapi-plugin
+npm install
+npm run link-watch
+```
+
+
+
+
+
+This will then produce an output explaing how to link your plugin to your Strapi project. Open a new terminal window to do the next steps:
+
+
+
+
+
+```bash
+cd /path/to/strapi/project
+yarn dlx yalc add --link my-strapi-plugin && yarn install
+```
+
+
+
+
+
+```bash
+cd /path/to/strapi/project
+npx yalc add --link my-strapi-plugin && npm run install
+```
+
+
+
+
+
+:::note
+In the above examples we use the name of the plugin when linking it to the project. This is the name of the package, not the name of the folder.
+:::
+
+Because this plugin is installed via `node_modules` you won't need to explicity add it to your `plugins` [configuration file](/dev-docs/configurations/plugins), so running the [`develop command`](../../cli.md#strapi-develop) will automatically pick up your plugin. However, to improve your experience we recommend you run Strapi with the `--watch-admin` flag so that your admin panel is automatically rebuilt when you make changes to your plugin:
+
+
+
+
+
+```bash
+yarn develop --watch-admin
+```
+
+
+
+
+
+```bash
+npm run develop --watch-admin
+```
+
+
+
+
+
+You are now ready to develop your plugin how you see fit! If you are making server changes, you will need to restart your server for them to take effect.
+
+### Building the plugin for publishing
+
+When you are ready to publish your plugin, you will need to build it. To do this, run the following command:
+
+
+
+
+
+```bash
+yarn build && yarn verify
+```
+
+
+
+
+
+```bash
+npm run build && npm run verify
+```
+
+
+
+
+
+The above commands will not only build the plugin, but also verify that the output is valid and ready to be published. You can then publish your plugin to NPM as you would any other package.
+
+## Working with the plugin CLI in a monorepo environment
+
+If you are working with a monorepo environment to develop your plugin, you don't need to use the `link-watch` command because the monorepo workspace setup will handle the symlink. You can use the `watch` command instead.
+
+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:
+
+```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;
+};
+```
+
+:::caution
+Because the server looks at the `strapi-server.js` file to import your plugin code, you must use the `watch` command otherwise the code will not be transpiled and the server will not be able to find your plugin.
+:::
diff --git a/docusaurus/docs/dev-docs/plugins/upload.md b/docusaurus/docs/dev-docs/plugins/upload.md
index dabd04d69d..bf3da3c9c7 100644
--- a/docusaurus/docs/dev-docs/plugins/upload.md
+++ b/docusaurus/docs/dev-docs/plugins/upload.md
@@ -8,7 +8,7 @@ description: Upload any kind of file on your server or external providers.
The Upload plugin is the backend powering the Media Library plugin available by default in the Strapi admin panel. Using either the Media Library from the admin panel or the upload API directly, you can upload any kind of file for use in your Strapi application.
-By default Strapi provides a [provider](/dev-docs/providers) that uploads files to a local directory. Additional providers are available should you want to upload your files to another location.
+By default Strapi provides a [provider](/dev-docs/providers) that uploads files to a local directory, which by default will be `public/uploads/` in your Strapi project. Additional providers are available should you want to upload your files to another location.
The providers maintained by Strapi include:
@@ -84,7 +84,7 @@ You can pass configuration to the middleware directly by setting it in the [`bod
-
+
```js title="path: ./config/middlewares.js"
@@ -107,7 +107,7 @@ module.exports = [
-
+
```js title="path: ./config/middlewares.ts"
@@ -136,7 +136,7 @@ In addition to the middleware configuration, you can pass the `sizeLimit`, which
-
+
```js title="path: ./config/plugins.js"
@@ -152,7 +152,7 @@ module.exports = {
-
+
```js title="path: ./config/plugins.ts"
@@ -160,9 +160,7 @@ export default {
// ...
upload: {
config: {
- providerOptions: {
- sizeLimit: 250 * 1024 * 1024 // 256mb in bytes
- }
+ sizeLimit: 250 * 1024 * 1024 // 256mb in bytes
}
}
};
@@ -172,6 +170,51 @@ export default {
+### Upload request timeout
+
+By default, the value of `strapi.server.httpServer.requestTimeout` is set to 330 seconds. This includes uploads. To make it possible for users with slow internet connection to upload large files, it might be required to increase this timeout limit. The recommended way to do it is by setting the `http.serverOptions.requestTimeout` parameter in the `config/server.js|ts` file (see [server configuration](/dev-docs/configurations/server).
+An alternate method is to set the `requestTimeout` value in the `bootstrap` function that runs before Strapi gets started. This is useful in cases where it needs to change programmatically β for example, to temporarily disable and re-enable it:
+
+
+
+
+
+
+```js title="path: ./index.js"
+
+module.exports = {
+
+ //...
+
+ bootstrap({ strapi }) {
+ // Set the requestTimeout to 1,800,000 milliseconds (30 minutes):
+ strapi.server.httpServer.requestTimeout = 30 * 60 * 1000;
+ },
+};
+```
+
+
+
+
+
+```ts title="path: ./index.ts"
+
+export default {
+
+ //...
+
+ bootstrap({ strapi }) {
+ // Set the requestTimeout to 1,800,000 milliseconds (30 minutes):
+ strapi.server.httpServer.requestTimeout = 30 * 60 * 1000;
+ },
+};
+```
+
+
+
+
+
+
### Responsive Images
When the `Enable responsive friendly upload` setting is enabled in the settings panel the plugin will generate the following responsive image sizes:
@@ -186,7 +229,7 @@ These sizes can be overridden in `./config/plugins.js`:
-
+
```js title="path: ./config/plugins.js"
@@ -207,7 +250,7 @@ module.exports = ({ env }) => ({
-
+
```js title="path: ./config/plugins.ts"
@@ -555,3 +598,32 @@ const response = await fetch(`http://localhost:1337/api/upload?id=${fileId}`, {
});
```
+
+### Upload single file from an API controller
+
+Add a file to Media Library from the backend.
+
+```js
+async create(ctx) {
+
+ // ...
+
+ const { body, files } = ctx.request;
+
+ const file = files["files.uploadedFile"];
+
+ const createdFiles = await strapi.plugins.upload.services.upload.upload({
+ data: {
+ fileInfo: {
+ name: "Name",
+ caption: "Caption",
+ alternativeText: "Alternative Text",
+ },
+ },
+ files: file,
+ });
+
+ // ...
+
+},
+```
diff --git a/docusaurus/docs/dev-docs/plugins/users-permissions.md b/docusaurus/docs/dev-docs/plugins/users-permissions.md
index 6242a87d06..b72ee13c79 100644
--- a/docusaurus/docs/dev-docs/plugins/users-permissions.md
+++ b/docusaurus/docs/dev-docs/plugins/users-permissions.md
@@ -201,10 +201,57 @@ Setting JWT expiry for more than 30 days is **not recommended** due to security
### Registration
-Creates a new user in the database with a default role as 'registered'.
+#### Configuration
+
+If you have added any additional fields to your user model that need to be accepted on registration, they need to be added to the list of allowed fields in the `register` configuration option, otherwise they will not be accepted.
+
+For example, if you have added a field called "nickname" that you wish to accept from the API on user registration:
+
+
+
+
+
+```js title="./config/plugins.js"
+module.exports = ({ env }) => ({
+ // ...
+ "users-permissions": {
+ config: {
+ register: {
+ allowedFields: ["nickname"],
+ },
+ },
+ },
+ // ...
+});
+```
+
+
+
+
+
+```ts title="./config/plugins.ts"
+export default ({ env }) => ({
+ // ...
+ "users-permissions": {
+ config: {
+ register: {
+ allowedFields: ["nickname"],
+ },
+ },
+ },
+ // ...
+});
+```
+
+
+
+
+
#### Usage
+Creates a new user in the database with a default role as 'registered'.
+
```js
import axios from 'axios';
@@ -249,7 +296,7 @@ Let's say that:
7. The frontend (`http://website.com/connect/github/redirect`) calls the backend with `https://strapi.website.com/api/auth/github/callback?access_token=eyfvg` that returns the Strapi user profile with its `jwt`. (Under the hood, the backend asks Github for the user's profile and a match is done on Github user's email address and Strapi user's email address).
8. The frontend now possesses the user's `jwt`, which means the user is connected and the frontend can make authenticated requests to the backend!
-An example of a frontend app that handles this flow can be found here: [react login example app](https://github.com/strapi/strapi-examples/tree/master/login-react).
+An example of a frontend app that handles this flow can be found here: [react login example app](https://github.com/strapi/strapi-examples/tree/master/examples/login-react).
#### Setting up the server url
@@ -296,7 +343,7 @@ Later you will give this url to your provider. For development, some provi
Instead of a generic explanation we decided to show an example for each provider.
-In the following examples, the frontend app will be the [react login example app](https://github.com/strapi/strapi-examples/tree/master/login-react).
+In the following examples, the frontend app will be the [react login example app](https://github.com/strapi/strapi-examples/tree/master/examples/login-react).
It (the frontend app) will be running on `http://localhost:3000`.
Strapi (the backend) will be running on `http://localhost:1337`.
@@ -313,7 +360,7 @@ Use `ngrok` to serve the backend app.
ngrok http 1337
```
-Don't forget to update the server url in the backend config file `config/server.js` and the server url in your frontend app (environment variable `REACT_APP_BACKEND_URL` if you use [react login example app](https://github.com/strapi/strapi-examples/tree/master/login-react)) with the generated ngrok url.
+Don't forget to update the server url in the backend config file `config/server.js` and the server url in your frontend app (environment variable `REACT_APP_BACKEND_URL` if you use [react login example app](https://github.com/strapi/strapi-examples/tree/master/examples/login-react)) with the generated ngrok url.
Github configuration
@@ -349,7 +396,7 @@ Use `ngrok` to serve the backend app.
ngrok http 1337
```
-Don't forget to update the server url in the backend config file `config/server.js` and the server url in your frontend app (environment variable `REACT_APP_BACKEND_URL` if you use [react login example app](https://github.com/strapi/strapi-examples/tree/master/login-react)) with the generated ngrok url.
+Don't forget to update the server url in the backend config file `config/server.js` and the server url in your frontend app (environment variable `REACT_APP_BACKEND_URL` if you use [react login example app](https://github.com/strapi/strapi-examples/tree/master/examples/login-react)) with the generated ngrok url.
Facebook configuration
@@ -450,7 +497,7 @@ The use of `ngrok` is not needed.
- **Client ID**: fill in the **App client id** (`5bd7a786qdupjmi0b3s10vegdt`)
- **Client Secret**: fill in the **App client secret** (`19c5c78dsfsdfssfsdfhpdb4nkpb145vesdfdsfsffgh7vwd6g45jlipbpb`)
- **Host URI (Subdomain)**: fill in the URL value that you copied earlier (`myapp67b50345-67b50b17-local.auth.eu-central-1.amazoncognito.com`)
- - **The redirect URL to your front-end app**: if you are using strapi react-login [https://github.com/strapi/strapi-examples/tree/master/login-react/](https://github.com/strapi/strapi-examples/tree/master/login-react/) use `http://localhost:3000/connect/cognito/redirect` but if you do not yet have a front-end app to test your Cognito configuration you can then use the following URL `http://localhost:1337/api/auth/cognito/callback`
+ - **The redirect URL to your front-end app**: if you are using strapi react-login [https://github.com/strapi/strapi-examples/tree/master/examples/login-react/](https://github.com/strapi/strapi-examples/tree/master/examples/login-react/) use `http://localhost:3000/connect/cognito/redirect` but if you do not yet have a front-end app to test your Cognito configuration you can then use the following URL `http://localhost:1337/api/auth/cognito/callback`
@@ -465,7 +512,7 @@ Use `ngrok` to serve the backend app.
ngrok http 1337
```
-Don't forget to update the server url in the backend config file `config/server.js` and the server url in your frontend app (environment variable `REACT_APP_BACKEND_URL` if you use [react login example app](https://github.com/strapi/strapi-examples/tree/master/login-react)) with the generated ngrok url.
+Don't forget to update the server url in the backend config file `config/server.js` and the server url in your frontend app (environment variable `REACT_APP_BACKEND_URL` if you use [react login example app](https://github.com/strapi/strapi-examples/tree/master/examples/login-react)) with the generated ngrok url.
Twitter configuration
@@ -568,7 +615,7 @@ Use `ngrok` to serve the backend app.
ngrok http 1337
```
-Don't forget to update the server url in the backend config file `config/server.js` and the server url in your frontend app (environment variable `REACT_APP_BACKEND_URL` if you use [react login example app](https://github.com/strapi/strapi-examples/tree/master/login-react)) with the generated ngrok url.
+Don't forget to update the server url in the backend config file `config/server.js` and the server url in your frontend app (environment variable `REACT_APP_BACKEND_URL` if you use [react login example app](https://github.com/strapi/strapi-examples/tree/master/examples/login-react)) with the generated ngrok url.
Instagram configuration
@@ -781,7 +828,7 @@ The use of `ngrok` is not needed.
- Client ID: ``
- Client Secret: ``
- Subdomain: ``, example it is the part in bold in the following url: https://**my-tenant.eu**.auth0.com/
- - The redirect URL to your front-end app: `http://localhost:3000/connect/auth0`
+ - The redirect URL to your front-end app: `http://localhost:3000/connect/auth0/redirect`
@@ -792,11 +839,11 @@ The use of `ngrok` is not needed.
Patreon does not accept `localhost` urls.
Use `ngrok` to serve the backend app.
-```
+```bash
ngrok http 1337
```
-Don't forget to update the server url in the Strapi config file `./config/server.js` and the server URL in your frontend app (environment variable `REACT_APP_BACKEND_URL` if you use [react login example app](https://github.com/strapi/strapi-examples/tree/master/login-react)) with the generated ngrok URL.
+Don't forget to update the server url in the Strapi config file `./config/server.js` and the server URL in your frontend app (environment variable `REACT_APP_BACKEND_URL` if you use [react login example app](https://github.com/strapi/strapi-examples/tree/master/examples/login-react)) with the generated ngrok URL.
Patreon configuration
@@ -806,9 +853,9 @@ Don't forget to update the server url in the Strapi config file `./config/server
- Click on "Create Client"
- Enter the details of your organization and website.
- There is a drop-down for "App Category" but no explanation of what the different categories mean.
-"Community" seems to work fine.
+"Community" seems to work fine.
- You can choose either version 1 or version 2 of the API - neither are actively developed.
-Version 2 is probably the best choice. See their
+Version 2 is probably the best choice. See their
[developer docs](https://docs.patreon.com/#introduction) for more detail.
- Under "Redirect URI's" enter `https://your-site.com/api/connect/patreon/callback`
- Save the client details and you will then see the Client ID and Client Secret.
@@ -822,11 +869,44 @@ Version 2 is probably the best choice. See their
- Client ID: `` - as above
- Client Secret: `` - as above
+
+
+
+
+
Using ngrok
+
+Keycloak accepts the `localhost` urls.
+The use of `ngrok` is not needed.
+
+
Keycloak configuration
+
+- Visit your Keycloak admin dashboard
+- If you don't already have a realm, you'll want to create one
+- In the Clients section of your realm, create a new client
+- Under the capability config, ensure you set `Client Authentication` to on to ensure you can create a private key
+- Under the access settings, ensure you set the following values:
+ - **Valid redirect URIs**: `http://localhost:1337/api/connect/keycloak/callback` and `http://localhost:1337/api/connect/keycloak`
+ - **Allowed Web Origins**: `http://localhost:3000` and `http://localhost:1337`
+- In the Client Scopes section, ensure you have the `email` and `profile` scopes set to default
+- In the Client Scopes section, ensure you have the `openid` scope set to default, if you don't have this you will need to manually create it in the global Client Scopes
+
+
π‘ The box can be expanded by clicking on **Expand** in the bottom bar. It displays side by side, at the same time, the textbox that you can edit and the preview. |
+| Rich text (Markdown) | Write your textual content in the editor, in Markdown. Some basic formatting options (titles, bold, italics, underline) are available in the top bar of the editor to apply to selected text. A **Preview mode/Markdown mode** button to switch between modes is also available.
π‘ The box can be expanded by clicking on **Expand** in the bottom bar. It displays side by side, at the same time, the textbox that you can edit and the preview. |
+| Rich text (Blocks) | Write and manage your content in the editor, which automatically renders live all additions/updates. In the Blocks editor, paragraphs behave as blocks of text: hovering on a paragraph will display an icon ![Reorder icon](/img/assets/icons/reorder.svg) on which to click to reorder the content. Options to format or enrich the content are also accessible from the top bar of the editor (basic formatting options, code, links, image etc.).
π‘ In the Blocks editor, you can use text formatting keyboard shortcuts (e.g. `ctrl + B` to apply bold formatting on selected content) as well as Markdown shortcuts. List of available Markdown shortcuts in Blocks
`#` to `######` to convert to H1 to H6 headings
`>` to convert to a quote
`-`, `*` or `+` to convert to a bullet list
`1.` to convert to a numbered list
`![` to add an image
``` to convert to a code block
π‘ You can also click on the **Expand** button in the bottom right corner to expand the Rich text field. |
| Number | Write your number in the textbox. Up and down arrows, displayed on the right of the box, allow to increase or decrease the current number indicated in the textbox. |
| Date | 1. Click the date and/or time box. 2. Type the date and time or choose a date using the calendar and/or a time from the list. The calendar view fully supports keyboard-based navigation.
| Boolean | Click on **OFF** or **ON**. |
@@ -141,3 +142,4 @@ You can also use the keyboard to reorder components: focus the component using T
:::note
Unlike regular fields, the order of the fields and components inside a dynamic field is important. It should correspond exactly to how end users will read/see the content.
:::
+
diff --git a/docusaurus/docs/user-docs/content-type-builder/configuring-fields-content-type.md b/docusaurus/docs/user-docs/content-type-builder/configuring-fields-content-type.md
index af00878809..1dfeec4cc0 100644
--- a/docusaurus/docs/user-docs/content-type-builder/configuring-fields-content-type.md
+++ b/docusaurus/docs/user-docs/content-type-builder/configuring-fields-content-type.md
@@ -8,7 +8,7 @@ sidebar_position: 3
The Content-type Builder is only accessible to create and update content-types when your Strapi application is in a development environment, else it will be in a read-only mode in other environments.
:::
-Content-types are composed of one or several fields. Each field is designed to contain specific kind of data, filled up in the Content Manager (see [Writing content](#)).
+Content-types are composed of one or several fields. Each field is designed to contain specific kind of data, filled up in the Content Manager (see [Writing content](/user-docs/content-manager/writing-content.md)).
In the Content-type Builder, fields can be added at the creation of a new content-type or component, or afterward when a content-type or component is edited or updated. The following documentation lists all existing regular fields but also tackles the specificities of components and dynamic zones. For each, you will find a definition, explanation of the form they take once in the Content Manager, and instructions to configure them.
@@ -18,13 +18,16 @@ Depending on what content-type or component is being created or edited, not all
+:::caution
+Never name a field `locale` because it could interfere with, and break, the [i18n](/user-docs/settings/internationalization) feature.
+:::
+
## Regular fields
### Text
@@ -59,17 +62,17 @@ The Text field displays a textbox that can contain small text. This field can be
-### Rich Text
+### Rich Text (Markdown)
-The Rich Text field displays an editor with formatting options to manage rich text. This field can be used for long written content.
+The Rich Text (Markdown) field displays an editor with basic formatting options to manage rich text written in Markdown. This field can be used for long written content.
-| Setting name | Instructions |
-|--------------|----------------------------------------|
-| Name | Write the name of the Rich Text field. |
+| Setting name | Instructions |
+|--------------|---------------------------------------------------|
+| Name | Write the name of the Rich Text (Markdown) field. |
@@ -81,7 +84,6 @@ The Rich Text field displays an editor with formatting options to manage rich te
| Private field | Tick to make the field private and prevent it from being found via the API. |
| Enable localization for this field | (if the [Internationalization plugin](/user-docs/plugins/strapi-plugins#-internationalization-plugin) is installed and localization is enabled for the content-type) Allow the field to have a different value per locale. |
| Required field | Tick to prevent creating or saving an entry if the field is not filled in. |
-| Unique field | Tick to prevent another field to be identical to this one. |
| Maximum length | Tick to define a maximum number of characters allowed. |
| Minimum length | Tick to define a minimum number of characters allowed. |
@@ -89,6 +91,35 @@ The Rich Text field displays an editor with formatting options to manage rich te
+### Rich Text (Blocks)
+
+The Rich Text (Blocks) field displays an editor with live rendering and various options to manage rich text. This field can be used for long written content, even including images and code.
+
+
+
+
+
+| Setting name | Instructions |
+|--------------|-------------------------------------------------|
+| Name | Write the name of the Rich Text (Blocks) field. |
+
+
+
+
+
+| Setting name | Instructions |
+|----------------|-----------------------------------------------------------------------------|
+| Private field | Tick to make the field private and prevent it from being found via the API. |
+| Required field | Tick to prevent creating or saving an entry if the field is not filled in. |
+
+
+
+
+
+:::strapi Front-end renderers
+If using the Blocks editor, we recommend that you also use the official [Strapi Blocks React Renderer](https://github.com/strapi/blocks-react-renderer) to render the content in a React frontend. Other community-supported renderers are also available on [npm](https://www.npmjs.com/search?q=strapi%20blocks%20renderer).
+:::
+
### Number
The Number field displays a field for any kind of number: integer, decimal and float.
diff --git a/docusaurus/docs/user-docs/content-type-builder/managing-content-types.md b/docusaurus/docs/user-docs/content-type-builder/managing-content-types.md
index ec1ac2a845..cbb89d64c1 100644
--- a/docusaurus/docs/user-docs/content-type-builder/managing-content-types.md
+++ b/docusaurus/docs/user-docs/content-type-builder/managing-content-types.md
@@ -1,3 +1,5 @@
+import ScreenshotNumberReference from '/src/components/ScreenshotNumberReference.jsx';
+
# Managing content-types
:::note development-only
@@ -20,11 +22,11 @@ Managing a content-type or component can include editing the general settings an
}}
/>
-- Next to the name and optional description of the content-type or component, an ![Edit icon](/img/assets/icons/edit.svg) **Edit** button (1) allows to access the [general settings](#editing-content-type-or-component-settings) of the content-type or component.
+- Next to the name and optional description of the content-type or component, an ![Edit icon](/img/assets/icons/edit.svg) **Edit** button allows to access the [general settings](#editing-content-type-or-component-settings) of the content-type or component.
- In the top right corner:
- - the **Add new field** and **Save** buttons (2) allow to respectively add another field to the content-type or component (see [Configuring fields for content-types](/user-docs/content-type-builder/configuring-fields-content-type)), or save any ongoing modification.
+ - the **Add new field** and **Save** buttons allow to respectively add another field to the content-type or component (see [Configuring fields for content-types](/user-docs/content-type-builder/configuring-fields-content-type)), or save any ongoing modification.
- the **Configure the view** button allows to access the view configuration interface (see [Configuring the edit view](/user-docs/content-manager/configuring-view-of-content-type#configuring-the-edit-view))
-- Below the previous editing options, a table (3) lists all the fields created and configured for the content-type or component. From the fields table, it is possible to:
+- Below the previous editing options, a table lists all the fields created and configured for the content-type or component. From the fields table, it is possible to:
- Click on the edit button ![Edit icon](/img/assets/icons/edit.svg) to edit a field
- Click on the delete button ![Delete icon](/img/assets/icons/delete.svg) to delete a field
@@ -67,7 +69,7 @@ The **Advanced Settings** tab allows to edit the following properties of the con
* **Draft & Publish**: Enable the draft and publish feature for the content-type or component. It is disabled by default.
* **Internationalization**: Enable the internationalization feature for the content-type or component. It is disabled by default.
-* **Review workflows**: Enable the [review workflows](/user-docs/settings/review-workflows) feature for the content-type. It is disabled by default.
+* **Review workflows**: Enable the [Review Workflows](/user-docs/settings/review-workflows) feature for the content-type. It is disabled by default.
## Deleting content-types
diff --git a/docusaurus/docs/user-docs/getting-started/setting-up-admin-panel.md b/docusaurus/docs/user-docs/getting-started/setting-up-admin-panel.md
new file mode 100644
index 0000000000..2d405069fa
--- /dev/null
+++ b/docusaurus/docs/user-docs/getting-started/setting-up-admin-panel.md
@@ -0,0 +1,110 @@
+---
+title: Setting up the admin panel
+displayed_sidebar: userDocsSidebar
+description: How to setup your Strapi Admin Panel
+---
+
+
+Before going over individual features, we recommend the following steps to set up and configure your Strapi admin panel correctly. Once you complete the setup, you can access the admin panel through the provided URL.
+
+
+## Accessing the admin panel
+
+
+The admin panel is the back office of your Strapi application. From the admin panel, you will be able to manage content-types, and write their actual content. It is also from the admin panel that you will manage users, both administrators and end users of your Strapi application.
+
+
+:::caution
+In order to access the admin panel, your Strapi application must be launched, and you must be aware of the URL to its admin panel (e.g. `api.example.com/admin`).
+:::
+
+
+
+
+
+To access the admin panel:
+
+
+1. Go to the URL of your Strapi application's admin panel.
+2. Enter your credentials to log in.
+3. Click on the **Login** button. You should be redirected to the homepage of the admin panel.
+
+
+### Using SSO for authentication
+
+
+If your Strapi application was configured to allow authentication through SSO (see [Configuring Single Sign-On](/user-docs/settings/single-sign-on)), you can access the admin panel using a specific provider instead of logging in with a regular Strapi administrator account.
+
+
+To do so, in the login page of your Strapi application, click on a chosen provider. If you cannot see your provider, click the ![icon more](/img/assets/getting-started/icon_more.png) button to access the full list of all available providers. You will be redirected to your provider's own login page where you will be able to authenticate.
+
+
+## Setting up your administrator profile
+
+
+If you are a new administrator, we recommend making sure your profile is all set, before diving into your Strapi application. From your administrator profile, you are able to modify your user information, such as name, username, email or password. You can also choose the language of the interface for your Strapi application.
+
+
+
+
+
+To modify your user information:
+
+
+1. Click on your account name or initials in the bottom left hand corner of the main navigation of your Strapi application.
+2. In the drop-down menu, click on **Profile**.
+3. Modify the information of your choice:
+
+
+| Profile & Experience | Instructions |
+| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| First name | Write your first name in the textbox. |
+| Last name | Write your last name in the textbox. |
+| Email | Write your complete email address in the textbox. |
+| Username | (optional) Write a username in the textbox. |
+| Interface language | Among the drop-down list, choose a language for your Strapi application interface. |
+| Interface mode | Among the drop-down list, choose a mode for your Strapi application interface: either "Light mode" or "Dark mode". Note that by default, the chosen mode for a Strapi application is based on the browser's mode. |
+
+
+4. Click on the **Save** button.
+
+
+### Changing your password
+
+
+To change the password of your account:
+
+
+1. Go to your administrator profile.
+2. Fill in the password-related options:
+
+
+| Password modification | |
+| --------------------- | ---------------------------------------------------------------------------------------------------------------- |
+| Current password | Write your current password in the textbox. π‘ You can click on the eye icon for the password to be shown. |
+| Password | Write the new password in the textbox. π‘ You can click on the eye icon for the password to be shown. |
+| Password confirmation | Write the same new password in the textbox. π‘ You can click on the eye icon for the password to be shown. |
+
+
+3. Click on the **Save** button.
+
+
+---
+
+
+Congratulations on being a new Strapi user! You're now ready to discover all the features and options that Strapi has to offer!
+
+
+
diff --git a/docusaurus/docs/user-docs/getting-started/user-guide-fundamentals.md b/docusaurus/docs/user-docs/getting-started/user-guide-fundamentals.md
new file mode 100644
index 0000000000..2301b88f79
--- /dev/null
+++ b/docusaurus/docs/user-docs/getting-started/user-guide-fundamentals.md
@@ -0,0 +1,19 @@
+---
+title: User Guide fundamentals
+displayed_sidebar: userDocsSidebar
+description: Here are some concepts to keep in mind
+---
+
+Before going any further into this user guide, we recommend you to acknowledge the main concepts below. They will help you to understand how Strapi works, and ensure a smooth Strapi experience.
+
+- **Development, Staging or Production Environment** When you start working on your application, it is in a development environment, which is the status for the content structure and application configuration. After deploying your application, it is in production or staging environment. This status change impacts how you can use your Strapi application, as some features are only available in development environment, such as the Content-type Builder. In this user guide the availability or not of a feature, depending on the application status, is always mentioned in the feature's introduction.
+
+- **Versions** Strapi is constantly evolving and growing. This implies that new releases are quite frequent, to improve what is already available but also to add new features to Strapi. For every new Strapi version, we communicate through our main channels and by sending notifications both on your terminal (when launching your Strapi application), and on your application's admin panel. We always recommend to use the latest version. However, we always keep live both the documentation of the current Strapi version, and the documentation of the previous major version β the latter being officially and actively maintained for up to 12 months after the release of the newest Strapi version.
+
+- **License and Pricing Plans** As a Strapi user you have the choice between using the Community Edition, which is entirely free, or the [Enterprise Edition](https://strapi.io/pricing-self-hosted). In this user guide, if a feature is only available for the Enterprise Edition, an badge is displayed beside the section's title. Strapi can also be hosted on Strapi Cloud by subscribing to a tier that meets the functionality, support, and customization options specified on [Strapi Cloud](https://strapi.io/pricing-cloud). In this user guide, the , , and badges can be displayed beside a section's title to indicate the feature is available on the tier.
+
+- **Future flags** Some incoming Strapi features are not yet ready to be shipped to all users, but Strapi still offers community users the opportunity to provide early feedback on these new features or changes. This feedback is invaluable in enhancing the feature before the final release. Such experimental features are indicated by a badge throughout the documentation and enabling these features requires enabling the corresponding future flags (see [Developer Docs](/dev-docs/configurations/features#enabling-a-future-flag)).
+
+- **Roles and Permissions** Some features of the admin panel, as well as the content managed with Strapi itself, are ruled by a system of permissions. From your Strapi admin panel, you have the possibility to define, at a detailed level, the roles and permissions of all administrators and end users. In this user guide, all features and possible options are documented. It is however possible, depending on your role and permissions, that you may not be able to access all these features and options. In that case, please refer to the main Super Admin of your Strapi application.
+
+With all this in mind, you should be ready to start your Strapi experience!
diff --git a/docusaurus/docs/user-docs/intro.md b/docusaurus/docs/user-docs/intro.md
index fa66fc309c..9b426bcbd1 100644
--- a/docusaurus/docs/user-docs/intro.md
+++ b/docusaurus/docs/user-docs/intro.md
@@ -5,107 +5,50 @@ sidebar_label: Welcome!
# Welcome to the Strapi User Guide!
-This user guide contains the functional documentation related to all features available in the admin panel of your Strapi application.
+
-
-
-Before going any further into this user guide, we recommend you to acknowledge the main concepts below. They will help you to understand how Strapi works, and ensure a smooth Strapi experience.
-
-- **Development, Staging or Production Environment** When you start working on your application, it is in a development environment, which is the status for the content structure and application configuration. After deploying your application, it is in production or staging environment. This status change impacts how you can use your Strapi application, as some features are only available in development environment, such as the Content-type Builder. In this user guide the availability or not of a feature, depending on the application status, is always mentioned in the feature's introduction.
-
-- **Versions** Strapi is constantly evolving and growing. This implies that new releases are quite frequent, to improve what is already available but also to add new features to Strapi. For every new Strapi version, we communicate through our main channels and by sending notifications both on your terminal (when launching your Strapi application), and on your application's admin panel. We always recommend to use the latest version. However, we always keep live both the documentation of the current Strapi version, and the documentation of the previous one - the latter being officially and actively maintained for 6 months after the release of the newest Strapi version.
+The documentation for Strapi contains 3 main sections, accessible from the top navigation bar:
-- **License and Pricing Plans** As a Strapi user you have the choice between using the Community Edition, which is entirely free, or the [Enterprise Edition](https://strapi.io/pricing-self-hosted). In this user guide, if a feature is only available for the Enterprise Edition, an badge is displayed beside the section's title.
+- π§βπ» The **[Developer Docs](/dev-docs/intro)** contain all the technical information related to the setup, advanced usage, customization, and update of your Strapi v4 application.
+- π§βπ« The **User Guide** that you're currently reading is all about using Strapi's admin panel.
+- βοΈ The **[Strapi Cloud](/cloud/intro)** documentation is about deploying your Strapi application to Strapi Cloud and managing your Strapi Cloud projects and settings.
-- **Roles and Permissions** Some features of the admin panel, as well as the content managed with Strapi itself, are ruled by a system of permissions. From your Strapi admin panel, you have the possibility to define, at a detailed level, the roles and permissions of all administrators and end users. In this user guide, all features and possible options are documented. It is however possible, depending on your role and permissions, that you may not be able to access all these features and options. In that case, please refer to the main Super Admin of your Strapi application.
+
-With all this in mind, you should be ready to start your Strapi experience!
+This documentation contains the functional documentation related to all features available in the main navigation of your Strapi application.
-## Accessing the admin panel
-
-The admin panel is the back office of your Strapi application. From the admin panel, you will be able to manage content-types, and write their actual content. It is also from the admin panel that you will manage users, both administrators and end users of your Strapi application.
-
-:::caution
-In order to access the admin panel, your Strapi application must be launched, and you must be aware of the URL to its admin panel (e.g. `api.example.com/admin`).
-:::
+Once you have logged in, you can access your Strapi admin panel:
-
-To access the admin panel:
-
-1. Go to the URL of your Strapi application's admin panel.
-2. Enter your credentials to log in.
-3. Click on the **Login** button. You should be redirected to the homepage of the admin panel.
-
-### Using SSO for authentication
-
-If your Strapi application was configured to allow authentication through SSO (see [Configuring Single Sign-On](/user-docs/settings/single-sign-on)), you can access the admin panel using a specific provider instead of logging in with a regular Strapi administrator account.
-
-To do so, in the login page of your Strapi application, click on a chosen provider. If you cannot see your provider, click the ![icon more](/img/assets/getting-started/icon_more.png) button to access the full list of all available providers. You will be redirected to your provider's own login page where you will be able to authenticate.
-
-## Setting up your administrator profile
-
-If you are a new administrator, we recommend making sure your profile is all set, before diving into your Strapi application. From your administrator profile, you are able to modify your user information, such as name, username, email or password. You can also choose the language of the interface for your Strapi application.
-
-
-To modify your user information:
-
-1. Click on your account name or initials in the bottom left hand corner of the main navigation of your Strapi application.
-2. In the drop-down menu, click on **Profile**.
-3. Modify the information of your choice:
+If you are not sure where to begin on this User Guide, we suggest you to:
-| Profile & Experience | Instructions |
-| --------------------- | ------------------------------------------------------------------------------------------------------- |
-| First name | Write your first name in the textbox. |
-| Last name | Write your last name in the textbox. |
-| Email | Write your complete email address in the textbox. |
-| Username | (optional) Write a username in the textbox. |
-| Interface language | Among the drop-down list, choose a language for your Strapi application interface. |
-| Interface mode | Among the drop-down list, choose a mode for your Strapi application interface: either "Light mode" or "Dark mode". Note that by default, the chosen mode for a Strapi application is based on the browser's mode. |
+1. Go through the [User Guide fundamentals](/user-docs/getting-started/user-guide-fundamentals).
+2. Follow the [Setting up the admin panel](/user-docs/getting-started/setting-up-admin-panel) guide.
+3. Continue your journey through the various User Guide sections. The ["What you will find here"](#what-you-will-find-here) section gives you an overview of the available topics.
-4. Click on the **Save** button.
+## What you will find here
-### Changing your password
+The table of content in this User Guide displays 7 main sections.
-To change the password of your account:
+Clicking on any of the following cards will direct you to the introductory page for that section, with additional details and concepts:
-1. Go to your administrator profile.
-2. Fill in the password-related options:
-
-| Password modification | |
-| --------------------- | ------------------------------------------------------------------------------------------------------- |
-| Current password | Write your current password in the textbox. π‘ You can click on the eye icon for the password to be shown. |
-| Password | Write the new password in the textbox. π‘ You can click on the eye icon for the password to be shown. |
-| Password confirmation | Write the same new password in the textbox. π‘ You can click on the eye icon for the password to be shown. |
-
-3. Click on the **Save** button.
-
-
----
-
-Congratulations on being a new Strapi user! You're now ready to discover all the features and options that Strapi has to offer!
+
+
+
+
+
+
+
+
+
:::strapi Welcome to the Strapi community!
If you have any trouble with your Strapi experience, you can reach us through [GitHub](https://github.com/strapi/) or our [forum](https://forum.strapi.io/)! The Strapi Community and Strapi team are always available to answer your questions or help you with anything!
-
-:::
diff --git a/docusaurus/docs/user-docs/media-library/adding-assets.md b/docusaurus/docs/user-docs/media-library/adding-assets.md
index b639cec83f..fd657e3bdb 100644
--- a/docusaurus/docs/user-docs/media-library/adding-assets.md
+++ b/docusaurus/docs/user-docs/media-library/adding-assets.md
@@ -35,3 +35,6 @@ A variety of media types and extensions are supported by the Media Library:
| Audio | - MP3 - WAV - OGG |
| File | - CSV - ZIP - PDF - XLS, XLSX - JSON |
+:::note
+Images added to the Media Library will be stored in the `/public/uploads` folder of your Strapi application.
+:::
diff --git a/docusaurus/docs/user-docs/media-library/introduction-to-the-media-library.md b/docusaurus/docs/user-docs/media-library/introduction-to-the-media-library.md
index 54f6718bc7..240cd9e8df 100644
--- a/docusaurus/docs/user-docs/media-library/introduction-to-the-media-library.md
+++ b/docusaurus/docs/user-docs/media-library/introduction-to-the-media-library.md
@@ -5,6 +5,7 @@ sidebar_position: 1
description: Introduction to the Media Library which allows to display and manage all assets uploaded in the application.
---
+import ScreenshotNumberReference from '/src/components/ScreenshotNumberReference.jsx';
# Introduction to the Media Library
@@ -24,10 +25,10 @@ The Media Library displays all assets uploaded in the application, either via th
From the Media Library, it is possible to:
-- upload a new asset (see [adding assets](/user-docs/media-library/adding-assets)) or create a new folder (see [organizing assets with folders](/user-docs/media-library/organizing-assets-with-folders)) (1),
-- sort the assets and folders or set filters (2) to find assets and folders more easily,
-- toggle between the list view ![List icon](/img/assets/icons/list_view.svg) and the grid view ![Grid icon](/img/assets/icons/grid_view.svg) to display assets, access settings ![Settings icon](/img/assets/icons/settings.svg) to [configure the view](#configuring-the-view), and make a textual search ![Search icon](/img/assets/icons/search.svg) (3) to find a specific asset or folder,
-- and view, navigate through, and manage folders (4).
+- upload a new asset (see [adding assets](/user-docs/media-library/adding-assets)) or create a new folder (see [organizing assets with folders](/user-docs/media-library/organizing-assets-with-folders)) ,
+- sort the assets and folders or set filters to find assets and folders more easily,
+- toggle between the list view ![List icon](/img/assets/icons/list_view.svg) and the grid view ![Grid icon](/img/assets/icons/grid_view.svg) to display assets, access settings ![Settings icon](/img/assets/icons/settings.svg) to [configure the view](#configuring-the-view), and make a textual search ![Search icon](/img/assets/icons/search.svg) to find a specific asset or folder,
+- and view, navigate through, and manage folders .
:::tip
Click the search icon ![Search icon](/img/assets/icons/search.svg) on the right side of the user interface to use a text search and find one of your assets or folders more quickly!
diff --git a/docusaurus/docs/user-docs/media-library/managing-assets.md b/docusaurus/docs/user-docs/media-library/managing-assets.md
index 55b6861f60..368805a85a 100644
--- a/docusaurus/docs/user-docs/media-library/managing-assets.md
+++ b/docusaurus/docs/user-docs/media-library/managing-assets.md
@@ -3,6 +3,7 @@ title: Managing individual assets
description: Instructions on how to manage assets uploaded to the Media Library, including editing, moving, and deleting assets, and cropping images.
---
+import ScreenshotNumberReference from '/src/components/ScreenshotNumberReference.jsx';
# Managing individual assets
@@ -20,13 +21,13 @@ Clicking on the edit ![Edit icon](/img/assets/icons/edit.svg) button of an asset
}}
/>
-- On the left, above the preview of the asset, control buttons (1) allow performing various actions:
+- On the left, above the preview of the asset, control buttons allow performing various actions:
- click on the delete button ![Delete icon](/img/assets/icons/delete.svg) to delete the asset,
- click on the download button ![Download icon](/img/assets/icons/download.svg) to download the asset,
- click on the copy link button ![Copy link icon](/img/assets/icons/link.svg) to copy the asset's link to the clipboard,
- optionally, click on the crop button ![Copy link icon](/img/assets/icons/crop.svg) to enter cropping mode for the image (see [cropping images](#cropping-images)).
-- On the right, meta data for the asset is displayed at the top of the window (2) and the fields below can be used to update the _File name_, _Alternative text_, _Caption_ and _Asset location_ (see [organizing assets with folders](/user-docs/media-library/organizing-assets-with-folders.md)) for the asset (3).
-- At the bottom, the **Replace Media** button (4) can be used to replace the asset file but keep the existing content of the other editable fields, and the **Finish** button is used to confirm any updates to the fields.
+- On the right, meta data for the asset is displayed at the top of the window and the fields below can be used to update the _File name_, _Alternative text_, _Caption_ and _Asset location_ (see [organizing assets with folders](/user-docs/media-library/organizing-assets-with-folders.md)) for the asset .
+- At the bottom, the **Replace Media** button can be used to replace the asset file but keep the existing content of the other editable fields, and the **Finish** button is used to confirm any updates to the fields.
## Moving assets
diff --git a/docusaurus/docs/user-docs/media-library/organizing-assets-with-folders.md b/docusaurus/docs/user-docs/media-library/organizing-assets-with-folders.md
index 4c9fa630e9..ecc8508d98 100644
--- a/docusaurus/docs/user-docs/media-library/organizing-assets-with-folders.md
+++ b/docusaurus/docs/user-docs/media-library/organizing-assets-with-folders.md
@@ -4,6 +4,7 @@ displayed_sidebar: userDocsSidebar
description: Instructions on how to use folders in the Media Library, including adding, editing, and deleting folders, and browsing their content.
---
+import ScreenshotNumberReference from '/src/components/ScreenshotNumberReference.jsx';
# Organizing assets with folders
@@ -19,9 +20,9 @@ Folders follow the permission system of assets (see [Users, Roles & Permissions]
By default, the Media Library displays folders and assets created at the root level. Clicking a folder navigates to this folder, and displays the following elements:
-- the folder title and breadcrumbs to navigate to a parent folder (1)
-- the subfolders (2) the current folder contains
-- all assets (3) from this folder
+- the folder title and breadcrumbs to navigate to a parent folder
+- the subfolders the current folder contains
+- all assets from this folder
+
+The ![Releases icon](/img/assets/icons/releases.svg) [Releases](/user-docs/releases/introduction) page allows creating new releases that will be used to organize entries.
+
+
+
+
+
+To create a new release:
+
+1. Click the **+ New Release** button in the upper right corner of the Releases page.
+2. Give the release a name.
+3. Define the date, time, and timezone to schedule the release (see [scheduling a release](/user-docs/releases/managing-a-release#scheduling-a-release)). Alternatively, uncheck the **Schedule release** checkbox if you do not want to automatically publish the release and release it manually instead.
+4. Click the **Continue** button.
+
+Adding entries to a release must be done from the Content Manager. You can add a single entry to a release while creating or editing the entry [in the edit view](/user-docs/content-manager/adding-content-to-releases).
+
+
+
diff --git a/docusaurus/docs/user-docs/releases/introduction.md b/docusaurus/docs/user-docs/releases/introduction.md
new file mode 100644
index 0000000000..81f03eb09f
--- /dev/null
+++ b/docusaurus/docs/user-docs/releases/introduction.md
@@ -0,0 +1,25 @@
+---
+title: Introduction to Releases
+description: Introduction to the Releases feature that enables content managers to organize entries to publish/unpublish simultaneously
+---
+
+# Releases
+
+Releases enables content managers to organize entries into containers that can perform publish and unpublish actions simultaneously. A release can contain entries from different content types and can mix locales.
+
+
+
+Administrators can access Releases from ![Releases icon](/img/assets/icons/releases.svg) _Releases_ in the main navigation of the admin panel.
+
+From the Releases view, it is possible to:
+
+- [create a new release](/user-docs/releases/creating-a-release),
+- view pending and done releases,
+- view which releases are [scheduled](/user-docs/releases/managing-a-release#scheduling-a-release), and when they will be published (in the Pending tab) or have been published (in the Done tab),
+- and click on a release to [manage its content](/user-docs/releases/managing-a-release).
diff --git a/docusaurus/docs/user-docs/releases/managing-a-release.md b/docusaurus/docs/user-docs/releases/managing-a-release.md
new file mode 100644
index 0000000000..738556ac04
--- /dev/null
+++ b/docusaurus/docs/user-docs/releases/managing-a-release.md
@@ -0,0 +1,104 @@
+---
+title: Managing a release
+description: Instructions on how to manage a Release from the admin panel
+---
+
+import ScreenshotNumberReference from '/src/components/ScreenshotNumberReference.jsx';
+
+# Managing a release
+
+Adding entries to a [release](/user-docs/releases/introduction) allow viewing them altogether on a single page.
+
+
+
+
+
+From a release page, you can:
+
+- edit the name of the release or delete the release
+- manually publish the release ,
+- adjust the view to display entries grouped either by locale, content-type, or action (publish/unpublish) ,
+- decide whether individual items included to the releases will be published or unpublished with the release .
+- edit a specific entry or remove it from the release .
+
+## Editing a release
+
+You can rename a release. To do so, while on a release page:
+
+1. Click on the ![More icon](/img/assets/icons/more.svg) button in the top right corner of the admin panel.
+2. Select ![Edit icon](/img/assets/icons/edit.svg) **Edit**.
+3. In the modal, change the name of the release in the _Name_ field.
+4. Click **Continue** to save the change.
+
+## Scheduling a release
+
+Releases can be [published manually](#publishing-a-release) or scheduled to be automatically published at a given date and time, with the timezone of your choice.
+
+You can schedule a release:
+- when [creating the release](/user-docs/releases/creating-a-release),
+- or once the release is already created, by editing it.
+
+To schedule an existing release, while on a release page:
+1. Click on the ![More icon](/img/assets/icons/more.svg) button in the top right corner of the admin panel.
+2. Select ![Edit icon](/img/assets/icons/edit.svg) **Edit**.
+3. In the modal, check the **Schedule release** checkbox.
+4. Select a date, time, and timezone for the release to be published.
+5. Click **Save**.
+
+
+
+## Choosing how entries are grouped
+
+A release page can display entries either grouped by locale, content-type, or action (publish or unpublish). To change how entries are grouped, click the **Group by β¦** dropdown and select an option from the list.
+
+## Publishing or unpublishing entries
+
+A release includes multiple entries. You can set the state of each entry with the **Publish** and **Unpublish** action buttons. When the release itself is βpublishedβ then the desired actions will be simultaneously performed on each entry.
+
+:::caution
+Since publishing an entry with a release means turning a draft entry into a published entry, Releases will not work if [Draft & Publish](/user-docs/content-manager/saving-and-publishing-content) is disabled for the content-type.
+:::
+
+## Editing an entry from a release
+
+By clicking on the ![More icon](/img/assets/icons/more.svg) at the end of the line of an entry and select the **Edit entry** button, you will be redirected to this entry in the Content Manager so you can edit it.
+
+## Removing entries from a release
+
+Entries can be removed from a release. To do so, click the ![More icon](/img/assets/icons/more.svg) at the end of the line of an entry and select the **Remove from release** button.
+
+## Publishing a release
+
+Publishing a release means that all the actions (publish or unpublish) defined for each entry included in the release will be performed simultaneously. To publish a release, click the **Publish** button in the top right corner of the admin panel.
+
+The _Status_ column displays the status of each entry:
+
+ - ![Success icon](/img/assets/icons/CheckCircle.svg) Already published: the entry is already published and publishing the release will not affect this entry
+ - ![Success icon](/img/assets/icons/CheckCircle.svg) Ready to publish: the entry is ready to be published with the release
+ - ![Fail icon](/img/assets/icons/CrossCircle.svg) "[field name] is required", "[field name] is too short" or "[field name] is too long": the entry cannot be published because of the issue stated in the red warning message.
+
+If some of your entries have a ![Fail icon](/img/assets/icons/CrossCircle.svg) status, click the ![More icon](/img/assets/icons/more.svg) and the **Edit the entry** button to fix the issues until all entries have the ![Success icon](/img/assets/icons/CheckCircle.svg) status. Note that you will have to click on the **Refresh** button to update the release page as you fix the various entries issues.
+
+:::caution
+Once a release is published, the release itself cannot be updated. You can not re-release that specific release with the same group of entries with some modifications; you must create another release.
+:::
+
+## Deleting a release
+
+You can delete a release. Deleting a release will only delete the release itself, but not the content-type entries included in the release. To delete a release, while on the release page:
+
+1. Click on the ![More icon](/img/assets/icons/more.svg) button in the top right corner of the admin panel.
+2. Select ![Delete icon](/img/assets/icons/delete.svg) **Delete**.
+3. In the confirmation dialog, click ![Delete icon](/img/assets/icons/delete.svg) **Confirm**.
diff --git a/docusaurus/docs/user-docs/settings/audit-logs.md b/docusaurus/docs/user-docs/settings/audit-logs.md
index a2ce3aaef3..77d13527ff 100644
--- a/docusaurus/docs/user-docs/settings/audit-logs.md
+++ b/docusaurus/docs/user-docs/settings/audit-logs.md
@@ -4,11 +4,7 @@ description: Reviewing the audit logs in Strapi.
sidebar_position: 2
---
-# Audit Logs
-
-:::info
-Audit Logs is an -only feature.
-:::
+# Audit Logs
The **Audit Logs** section provides a searchable and filterable display of all activities performed by users of the Strapi application.
diff --git a/docusaurus/docs/user-docs/settings/introduction.md b/docusaurus/docs/user-docs/settings/introduction.md
new file mode 100644
index 0000000000..fae131da32
--- /dev/null
+++ b/docusaurus/docs/user-docs/settings/introduction.md
@@ -0,0 +1,24 @@
+---
+title: Introduction to General Settings
+description: Reviewing the audit logs in Strapi.
+sidebar_position: 2
+---
+
+The ![Settings icon](/img/assets/icons/settings.svg) _Settings_ section in the main navigation of the admin panel includes all the required set-up information that determines how an administrator interacts with and manages their Strapi application.
+
+
+
+The following table lists the 4 Settings sub-sections, available settings in each sub-section, and corresponding links on this user guide to access detailed information:
+
+| Sub Section | Where to find information |
+| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Global Settings |
[Customize logo](/user-docs/settings/admin-panel)
[Manage and regenerate API tokens](/user-docs/settings/API-tokens)
[Manage content in different languages](/user-docs/settings/internationalization)
[Configure assets uploaded in Strapi application](/user-docs/settings/media-library-settings)
[Manage and adjust workflows for your team](/user-docs/settings/review-workflows)
[Configure Single Sign-on to authenticate administrators on your Strapi application](/user-docs/settings/single-sign-on)
[Manage Transfer Token](/user-docs/settings/transfer-tokens)
|
diff --git a/docusaurus/docs/user-docs/settings/review-workflows.md b/docusaurus/docs/user-docs/settings/review-workflows.md
index 7ecfeb0abc..fec9fe7018 100644
--- a/docusaurus/docs/user-docs/settings/review-workflows.md
+++ b/docusaurus/docs/user-docs/settings/review-workflows.md
@@ -3,17 +3,13 @@ title: Review Workflows
description: Managing your review workflows and stages in Strapi.
---
-# Managing Review Workflows
-
-:::info
-Review Workflows is an -only feature.
-:::
+# Managing Review Workflows
The Review Workflows feature allows you to create and manage workflows for your various content-types. Each workflow can consist of any review stages for your content, enabling your team to collaborate in the content creation flow from draft to publication.
Global settings > Review Workflows*. The Review workflows settings are only available to users with the Super Admin role by default. Other roles must be granted the **Review workflows** permissions. See [Users, Roles, & Permissions](/user-docs/users-roles-permissions) for more information.
-
-:::note
-Currently all roles using Review Workflows in the Content Manager need to have at least "Read" permission for workflow settings, defined in *Settings > Administration Panel > Roles > Edit a role > Settings > Review Workflows*.
-:::
-
+Before being available in the [Content Manager](/user-docs/content-manager/reviewing-content), review workflows must be configured from ![Settings icon](/img/assets/icons/settings.svg) _Settings > Global settings > Review Workflows_. The Review workflows settings are only available to users with the Super Admin role by default. Other roles must be granted the **Review workflows** permissions. See [Users, Roles, & Permissions](/user-docs/users-roles-permissions) for more information.
## Creating or editing a workflow
-
-:::info
-Single Sign-On is an -only feature.
-:::
-
Single Sign-On (SSO) can be made available on a Strapi application to allow administrators to authenticate through an identity provider (e.g. Microsoft Azure Active Directory). SSO configurations can be done from ![Settings icon](/img/assets/icons/settings.svg) *Settings > Global settings > Single Sign-On*.
*(Releases)* |
General
"Read" - gives access to the Releases feature
"Create" - allows to create releases
"Edit" - allows to edit releases
"Delete" - allows to delete releases
"Publish" - allows to publish releases
"Remove an entry from a release"
"Add an entry to a release"
|
| Content-Manager |
Single types
"Configure view" - allows to configure the edit view of a single type
Collection types
"Configure view" - allows to configure the edit view of a collection type
Components
"Configure Layout" - allows to configure the layout of a component
|
| Content-Type-Builder |
General
"Read" - gives access to the Content-type Builder plugin in read-only mode
|
| Upload *(Media Library)* |
General
"Access the Media Library" - gives access to the Media Library plugin
Assets
"Create (upload)" - allows to upload media files
"Update (crop, details, replace) + delete" - allows to edit uploaded media files
"Download" - allows to download uploaded media files
"Copy link" - allows to copy the link of an uploaded media file
);
}
-
-// TODO: make it responsive and create a wrapper component for flex or grid arrangements
diff --git a/docusaurus/src/components/CustomDocCardsWrapper.js b/docusaurus/src/components/CustomDocCardsWrapper.js
new file mode 100644
index 0000000000..29b5baf059
--- /dev/null
+++ b/docusaurus/src/components/CustomDocCardsWrapper.js
@@ -0,0 +1,9 @@
+import React from 'react';
+
+export default function CustomDocCardsWrapper({ children }) {
+ return (
+
{Icon && (
-
-
+
)}
+
+
+
+ The following diagram is interactive: you can click on any file or folder name highlighted in purple to go to the corresponding documentation section.
+
+
+
+ . # root of the plugin folder (e.g., /src/plugins/my-plugin)
+ βββ admin# Admin panel part of your plugin.
+ β βββ src
+ β βββ components # Contains your front-end components
+ β β βββ Initializer
+ β β β βββ index.js # Plugin initializer
+ β β βββ PluginIcon
+ β β βββ index.js # Contains the icon of your plugin in the main navigation
+ β βββ pages # Contains the pages of your plugin
+ β β βββ App
+ β β β βββ index.js # Skeleton around the actual pages
+ β β βββ HomePage
+ β β βββ index.js # Homepage of your plugin
+ β βββ translations # Translations files to make your plugin i18n-friendly
+ β β βββ en.json
+ β β βββ fr.json
+ β βββ utils
+ β β βββ getTrad.js # getTrad function to return the corresponding plugin translations
+ β βββ index.js # Main setup of your plugin, used to register elements in the admin panel
+ β βββ pluginId.js # pluginId variable computed from package.json name
+ βββ node_modules
+ βββ server# Back-end part of your plugin
+ β βββ config
+ β β βββ index.js # Contains the default server configuration
+ β βββ content-types# Content-types specific to your plugin
+ β β βββ index.js # Loads all the plugin's content-types
+ β βββ controllers# Controllers specific to your plugin
+ β β βββ index.js # Loads all the plugin's controllers
+ β β βββ my-controller.js # Custom controller example. You can rename it or delete it.
+ β βββ middlewares# Middlewares specific to your plugin
+ β β βββ index.js # Loads all the plugin's middlewares
+ β βββ policies# Policies specific to your plugin
+ β β βββ index.js # Loads all the plugin's policies
+ β βββ routes# Routes specific to your plugin
+ β β βββ index.js # Contains an example route for the my-controller custom controller example
+ β βββ services# Services specific to your plugin
+ β β βββ index.js # Loads all the plugin's services
+ β β βββ my-service.js # Custom service example. You can rename it or delete it.
+ β βββ bootstrap.js# Function that is called right after the plugin has registered
+ β βββ destroy.js# Function that is called to clean up the plugin after Strapi instance is destroyed
+ β βββ index.js # Loads the code for all the server elements
+ β βββ register.js# Function that is called to load the plugin, before bootstrap.
+ βββ package.json
+ βββ README.md
+ βββ strapi-admin.js# Entrypoint for the admin panel (front end)
+ βββ strapi-server.js# Entrypoint for the server (back end)
+
+
+
+
+
+
+
+
+ The following diagram is interactive: you can click on any file or folder name highlighted in purple to go to the corresponding documentation section.
+
+
+
+ . # root of the plugin folder (e.g., /src/plugins/my-plugin)
+ βββ admin# Admin panel part of your plugin.
+ β βββ src
+ β βββ components # Contains your front-end components
+ β β βββ Initializer
+ β β β βββ index.tsx # Plugin initializer
+ β β βββ PluginIcon
+ β β βββ index.tsx # Contains the icon of your plugin in the main navigation
+ β βββ pages # Contains the pages of your plugin
+ β β βββ App
+ β β β βββ index.tsx # Skeleton around the actual pages
+ β β βββ HomePage
+ β β βββ index.tsx # Homepage of your plugin
+ β βββ translations # Translations files to make your plugin i18n-friendly
+ β β βββ en.json
+ β β βββ fr.json
+ β βββ utils
+ β β βββ getTrad.ts # getTrad function to return the corresponding plugin translations
+ β βββ index.tsx # Main setup of your plugin, used to register elements in the admin panel
+ β βββ pluginId.tsx # pluginId variable computed from package.tsxon name
+ βββ dist # Build of the backend
+ βββ node_modules
+ βββ server# Back-end part of your plugin
+ β βββ config
+ β β βββ index.ts # Contains the default server configuration
+ β βββ content-types# Content-types specific to your plugin
+ β β βββ index.ts # Loads all the plugin's content-types
+ β βββ controllers# Controllers specific to your plugin
+ β β βββ index.ts # Loads all the plugin's controllers
+ β β βββ my-controller.ts # Custom controller example. You can rename it or delete it.
+ β βββ middlewares# Middlewares specific to your plugin
+ β β βββ index.ts # Loads all the plugin's middlewares
+ β βββ policies# Policies specific to your plugin
+ β β βββ index.ts # Loads all the plugin's policies
+ β βββ routes# Routes specific to your plugin
+ β β βββ index.ts # Contains an example route for the my-controller custom controller example
+ β βββ services# Services specific to your plugin
+ β β βββ index.ts # Loads all the plugin's services
+ β β βββ my-service.ts # Custom service example. You can rename it or delete it.
+ β βββ bootstrap.ts# Function that is called right after the plugin has registered
+ β βββ destroy.ts# Function that is called to clean up the plugin after Strapi instance is destroyed
+ β βββ index.ts # Loads the code for all the server elements
+ β βββ register.ts# Function that is called to load the plugin, before bootstrap.
+ βββ custom.d.ts # Generated types
+ βββ package.json
+ βββ README.md
+ βββ strapi-admin.js# Entrypoint for the admin panel (front end)
+ βββ strapi-server.js# Entrypoint for the server (back end)
+ βββ tsconfig.json # TypeScript compiler options for the admin panel part
+ βββ tsconfig.server.json # TypeScript compiler options for the server part
+
+
-
+
+ The following diagram is interactive: you can click on any file or folder name highlighted in purple to go to the corresponding documentation page.
. # root of the application
- βββββ .cache # files used to build the admin panel
+ βββββ .strapi # auto-generated folder β do not update manually
+ β βββββ client # files used by bundlers to render the application
+ β β index.html
+ β β app.js
βββββ .tmp
βββββ build# build of the admin panel
βββββ config # API configurations
@@ -58,7 +62,7 @@ export default function InteractiveProjectStructure() {
β β β strapi-server.js
β βββββ middlewares
β β βββββ (middleware-name).js
- β βββββ plugins# local plugins files
+ β βββββ plugins# local plugins files
β β βββββ (plugin-name)
β β βββββ admin
β β β βββββ src
@@ -69,7 +73,7 @@ export default function InteractiveProjectStructure() {
β β β βββββ policies
β β β package.json
β β β strapi-admin.js
- β β β strapi-server.js
+ β β β strapi-server.js
β ββββ policies
β β index.js# include register(), bootstrap() and destroy() functions
β .env
@@ -81,11 +85,16 @@ export default function InteractiveProjectStructure() {
+ The following diagram is interactive: you can click on any file or folder name highlighted in purple to go to the corresponding documentation page.
+ A headless CMS is a Content Management System that separates the presentation layer (i.e., the front end, where content is displayed) from the back end (where content is managed).
+
+
+ Strapi is a headless CMS that provides:
+
+
a back-end server exposing an API for your content,
+
and a graphical user interface, called the admin panel, to manage the content.
+
The presentation layer of your website or application powered by Strapi should be handled by another framework, not by Strapi.
+
+
+ )
+}
diff --git a/docusaurus/src/components/ScreenshotNumberReference.jsx b/docusaurus/src/components/ScreenshotNumberReference.jsx
new file mode 100644
index 0000000000..59146e5345
--- /dev/null
+++ b/docusaurus/src/components/ScreenshotNumberReference.jsx
@@ -0,0 +1,24 @@
+import React from 'react';
+
+const ScreenshotNumberReference = ({ number }) => {
+ return (
+
+ {number}
+
+ );
+};
+
+export default ScreenshotNumberReference;
\ No newline at end of file
diff --git a/docusaurus/src/components/SubtleCallout.js b/docusaurus/src/components/SubtleCallout.js
new file mode 100644
index 0000000000..7d31836546
--- /dev/null
+++ b/docusaurus/src/components/SubtleCallout.js
@@ -0,0 +1,16 @@
+import React from 'react';
+
+export default function SubtleCallout({
+ children,
+ title,
+ emoji = 'π€',
+}) {
+ return (
+
+
+ { emoji } { title }
+
+ { children }
+
+ );
+}
diff --git a/docusaurus/src/pages/home/_home.content.js b/docusaurus/src/pages/home/_home.content.js
index 395ae1f466..d779d56e95 100644
--- a/docusaurus/src/pages/home/_home.content.js
+++ b/docusaurus/src/pages/home/_home.content.js
@@ -58,10 +58,6 @@ export default {
label: 'Database configuration',
to: '/dev-docs/configurations/database',
},
- {
- label: 'Deployment guides',
- to: '/dev-docs/deployment',
- },
{
label: 'REST API',
to: '/dev-docs/api/rest',
@@ -70,6 +66,10 @@ export default {
label: 'GraphQL API',
to: '/dev-docs/api/graphql',
},
+ {
+ label: 'Back-end customization',
+ to: '/dev-docs/backend-customization',
+ },
],
},
{
diff --git a/docusaurus/src/scss/__index.scss b/docusaurus/src/scss/__index.scss
index 55b99ce150..4559b4ea2c 100644
--- a/docusaurus/src/scss/__index.scss
+++ b/docusaurus/src/scss/__index.scss
@@ -23,6 +23,7 @@
@import 'code-block.scss';
@import 'columns.scss';
@import 'container.scss';
+@import 'custom-doc-cards.scss';
@import 'details.scss';
@import 'footer.scss';
@import 'grid.scss';
diff --git a/docusaurus/src/scss/_base.scss b/docusaurus/src/scss/_base.scss
index e0843fb731..8e7d94d405 100644
--- a/docusaurus/src/scss/_base.scss
+++ b/docusaurus/src/scss/_base.scss
@@ -10,7 +10,7 @@
}
main {
- article:first-child:not(.col),
+ article:first-child:not(.col):not(.custom-doc-card),
article:first-child:not(.col) + nav {
--custom-main-px: var(--strapi-spacing-0);
--custom-main-width: 683px;
diff --git a/docusaurus/src/scss/admonition.scss b/docusaurus/src/scss/admonition.scss
index 627e8934fb..62612f845e 100644
--- a/docusaurus/src/scss/admonition.scss
+++ b/docusaurus/src/scss/admonition.scss
@@ -85,8 +85,7 @@
--custom-selection-color: var(--strapi-success-100);
}
- &--caution,
- &--warning {
+ &--caution {
--custom-admonition-background-color: var(--strapi-warning-100);
--custom-admonition-border-color: var(--strapi-warning-500);
--custom-admonition-code-background-color: var(--strapi-warning-200);
@@ -97,7 +96,8 @@
--custom-selection-color: var(--strapi-warning-100);
}
- &--danger {
+ &--danger,
+ &--warning {
--custom-admonition-background-color: var(--strapi-danger-100);
--custom-admonition-border-color: var(--strapi-danger-500);
--custom-admonition-code-background-color: var(--strapi-danger-200);
@@ -118,6 +118,20 @@
--custom-selection-background-color: var(--strapi-primary-700);
--custom-selection-color: var(--strapi-primary-100);
}
+
+ &--subtle {
+ border-radius: 8px;
+ border: none;
+ background-color: transparent;
+ border: solid 1px var(--strapi-neutral-500);
+ font-size: 90%;
+ }
+}
+
+@media screen and (min-width: 997px) {
+ [class*="sbs-column"]:nth-of-type(2) .theme-admonition--subtle {
+ margin-left: 80px;
+ }
}
/** Dark mode */
diff --git a/docusaurus/src/scss/badge.scss b/docusaurus/src/scss/badge.scss
index 6c3867deb3..fd3be44a94 100644
--- a/docusaurus/src/scss/badge.scss
+++ b/docusaurus/src/scss/badge.scss
@@ -1,4 +1,4 @@
-/** Component: Feature Badges (alpha, beta) */
+ /** Component: Feature Badges (alpha, beta) */
.badge {
--custom-badge-background-color: var(--strapi-neutral-200);
@@ -38,11 +38,30 @@
--custom-badge-color: rgb(160,25,240);
}
+ &--future {
+ --custom-badge-background-color: var(--strapi-danger-200);
+ --custom-badge-color: rgb(219, 0, 22);
+ }
+
&--enterprise {
--custom-badge-background-color: rgb(255, 241, 209);
--custom-badge-color: rgb(229, 136, 41);
}
+ &--strapicloudpro {
+ --custom-badge-background-color: rgb(55, 34, 254);
+ --custom-badge-color: #fff;
+ }
+
+ &--strapicloudteam {
+ --custom-badge-background-color: rgb(154, 53, 242);
+ --custom-badge-color: #fff;
+ }
+ &--strapiclouddev {
+ --custom-badge-background-color: rgb(44,170,73);
+ --custom-badge-color: #fff;
+ }
+
&--new {
--custom-badge-background-color: var(--strapi-warning-100);
--custom-badge-border-color: var(--strapi-warning-200);
diff --git a/docusaurus/src/scss/code-block.scss b/docusaurus/src/scss/code-block.scss
index 26f95e51a3..b29fe9f873 100644
--- a/docusaurus/src/scss/code-block.scss
+++ b/docusaurus/src/scss/code-block.scss
@@ -2,6 +2,7 @@
:root {
--custom-code-block-background-color: var(--strapi-neutral-1000);
+ --custom-code-block-background-color-highlighted: var(--strapi-neutral-800);
--custom-code-block-color: var(--strapi-neutral-0);
--custom-code-block-comment-color: var(--strapi-neutral-400);
--custom-code-block-key-color: var(--strapi-success-300);
@@ -81,6 +82,10 @@ pre.prism-code {
background: var(--custom-code-block-background-color);
}
+ [class*="highlighted-line"] {
+ background-color: var(--custom-code-block-background-color-highlighted);
+ }
+
[class*="codeBlockTitle"],
pre, pre code {
--ifm-code-font-size: 81.25%;
@@ -111,9 +116,11 @@ pre.prism-code {
}
}
+
/** Dark theme */
@include dark {
--custom-code-block-background-color: var(--strapi-neutral-100);
+ --custom-code-block-background-color-highlighted: var(--strapi-neutral-150);
--custom-code-block-color: var(--strapi-neutral-1000);
--custom-code-block-punctuation-color: var(--strapi-primary-200);
--custom-code-block-value-color: var(--strapi-neutral-900);
diff --git a/docusaurus/src/scss/custom-doc-cards.scss b/docusaurus/src/scss/custom-doc-cards.scss
new file mode 100644
index 0000000000..f6faffe46a
--- /dev/null
+++ b/docusaurus/src/scss/custom-doc-cards.scss
@@ -0,0 +1,46 @@
+/** Component: CustomDocCardWrapper */
+
+.custom-cards-wrapper {
+ display: flex;
+ flex-flow: row wrap;
+ max-width: 715px;
+ justify-content: flex-start;
+
+ .custom-doc-card {
+ max-width: 320px;
+ width: 320px;
+ margin-bottom: 10px;
+ margin-right: 20px;
+
+ a {
+ height: 170px;
+ }
+
+ }
+}
+
+/** Component: CustomDocCard */
+.custom-doc-card {
+
+ a {
+ text-decoration: none;
+ }
+
+ a:hover {
+ .cardTitle {
+ color: var(--strapi-primary-600);
+ }
+ }
+
+ .text--truncate.cardDescription {
+ // cancel --truncate styles since we can't remove the' class
+ overflow: auto;
+ white-space: normal;
+ }
+}
+
+.custom-doc-card--small {
+ .cardTitle {
+ margin-bottom: 0;
+ }
+}
diff --git a/docusaurus/src/scss/details.scss b/docusaurus/src/scss/details.scss
index d4d9f4da1f..da9249bc55 100644
--- a/docusaurus/src/scss/details.scss
+++ b/docusaurus/src/scss/details.scss
@@ -33,3 +33,10 @@ details.alert {
text-decoration: var(--ifm-link-decoration);
}
}
+
+@include dark {
+ details a {
+ color: var(--strapi-primary-500);
+ font-weight: 700;
+ }
+}
diff --git a/docusaurus/src/scss/table.scss b/docusaurus/src/scss/table.scss
index be78e041f7..e067b0011f 100644
--- a/docusaurus/src/scss/table.scss
+++ b/docusaurus/src/scss/table.scss
@@ -1,6 +1,7 @@
/** Component: Table */
table {
min-width: 100%;
+ overflow: auto;
thead {
--ifm-table-background: transparent;
diff --git a/docusaurus/src/theme/MDXComponents.js b/docusaurus/src/theme/MDXComponents.js
index 600c9a6afa..297075f1a3 100644
--- a/docusaurus/src/theme/MDXComponents.js
+++ b/docusaurus/src/theme/MDXComponents.js
@@ -16,16 +16,22 @@ import ColumnLeft from '../components/ColumnLeft';
import ColumnRight from '../components/ColumnRight';
import FeedbackPlaceholder from '../components/FeedbackPlaceholder';
import CustomDocCard from '../components/CustomDocCard';
+import CustomDocCardsWrapper from '../components/CustomDocCardsWrapper';
import { InteractiveQueryBuilder } from '../components/InteractiveQueryBuilder/InteractiveQueryBuilder';
-import { AlphaBadge, BetaBadge, EnterpriseBadge } from '../components/Badge';
+import { AlphaBadge, BetaBadge, EnterpriseBadge, FutureBadge, CloudProBadge, CloudTeamBadge, CloudDevBadge } from '../components/Badge';
import { SideBySideColumn, SideBySideContainer } from '../components';
import ThemedImage from '@theme/ThemedImage';
+import ScreenshotNumberReference from '../components/ScreenshotNumberReference';
import {
MultiLanguageSwitcher,
MultiLanguageSwitcherRequest,
MultiLanguageSwitcherResponse,
} from '../components/MultiLanguageSwitcher';
import { Annotation } from '../components/Annotation';
+import SubtleCallout from '../components/SubtleCallout';
+import { PluginsConfigurationFile, HeadlessCms } from '../components/ReusableAnnotationComponents/ReusableAnnotationComponents';
+import PostHogAdminPanelBasic from '../components/PostHog-AdminPanel-Basic';
+import PostHogAdminPanel from '../components/PostHog-AdminPanel';
export default {
// Re-use the default mapping
@@ -45,18 +51,35 @@ export default {
ApiCall,
AlphaBadge,
BetaBadge,
+ FutureBadge,
EnterpriseBadge,
+ CloudProBadge,
+ CloudTeamBadge,
+ CloudDevBadge,
Columns,
ColumnLeft,
ColumnRight,
FeedbackPlaceholder,
CustomDocCard,
+ CustomDocCardsWrapper,
InteractiveQueryBuilder,
+ SubtleCallout,
ThemedImage,
+ ScreenshotNumberReference,
SideBySideColumn,
SideBySideContainer,
MultiLanguageSwitcher,
MultiLanguageSwitcherRequest,
MultiLanguageSwitcherResponse,
Annotation,
+ /**
+ * Reusable annotation components go belowπ
+ */
+ PluginsConfigurationFile,
+ HeadlessCms,
+ /**
+ * PostHog-specific components go belowπ
+ */
+ PostHogAdminPanelBasic,
+ PostHogAdminPanel
};
diff --git a/docusaurus/static/img/assets/backend-customization/custom-global-middleware-in-action.gif b/docusaurus/static/img/assets/backend-customization/custom-global-middleware-in-action.gif
new file mode 100644
index 0000000000..e5eb869d3a
Binary files /dev/null and b/docusaurus/static/img/assets/backend-customization/custom-global-middleware-in-action.gif differ
diff --git a/docusaurus/static/img/assets/backend-customization/tutorial-auth-flow.png b/docusaurus/static/img/assets/backend-customization/tutorial-auth-flow.png
new file mode 100644
index 0000000000..44e63cc95a
Binary files /dev/null and b/docusaurus/static/img/assets/backend-customization/tutorial-auth-flow.png differ
diff --git a/docusaurus/static/img/assets/backend-customization/tutorial-owner-cantsubmit.png b/docusaurus/static/img/assets/backend-customization/tutorial-owner-cantsubmit.png
new file mode 100644
index 0000000000..eba4b3bc1a
Binary files /dev/null and b/docusaurus/static/img/assets/backend-customization/tutorial-owner-cantsubmit.png differ
diff --git a/docusaurus/static/img/assets/backend-customization/tutorial-single-type.png b/docusaurus/static/img/assets/backend-customization/tutorial-single-type.png
new file mode 100644
index 0000000000..f78d5f04cf
Binary files /dev/null and b/docusaurus/static/img/assets/backend-customization/tutorial-single-type.png differ
diff --git a/docusaurus/static/img/assets/backend-customization/tutorial-spreadsheet-data.png b/docusaurus/static/img/assets/backend-customization/tutorial-spreadsheet-data.png
new file mode 100644
index 0000000000..d90b00d333
Binary files /dev/null and b/docusaurus/static/img/assets/backend-customization/tutorial-spreadsheet-data.png differ
diff --git a/docusaurus/static/img/assets/backend-customization/tutorial-spreadsheet-url.png b/docusaurus/static/img/assets/backend-customization/tutorial-spreadsheet-url.png
new file mode 100644
index 0000000000..bb42af9ac3
Binary files /dev/null and b/docusaurus/static/img/assets/backend-customization/tutorial-spreadsheet-url.png differ
diff --git a/docusaurus/static/img/assets/backend-customization/tutorial-write-review-success.png b/docusaurus/static/img/assets/backend-customization/tutorial-write-review-success.png
new file mode 100644
index 0000000000..303f997d06
Binary files /dev/null and b/docusaurus/static/img/assets/backend-customization/tutorial-write-review-success.png differ
diff --git a/docusaurus/static/img/assets/backend-customization/tutorial-write-review.png b/docusaurus/static/img/assets/backend-customization/tutorial-write-review.png
new file mode 100644
index 0000000000..19f64360c8
Binary files /dev/null and b/docusaurus/static/img/assets/backend-customization/tutorial-write-review.png differ
diff --git a/docusaurus/static/img/assets/cloud/account-billing2.png b/docusaurus/static/img/assets/cloud/account-billing2.png
new file mode 100644
index 0000000000..97bc5f55cb
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/account-billing2.png differ
diff --git a/docusaurus/static/img/assets/cloud/account-billing2_DARK.png b/docusaurus/static/img/assets/cloud/account-billing2_DARK.png
new file mode 100644
index 0000000000..e637e41649
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/account-billing2_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/account-deletion.png b/docusaurus/static/img/assets/cloud/account-deletion.png
new file mode 100644
index 0000000000..5fe1300bb9
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/account-deletion.png differ
diff --git a/docusaurus/static/img/assets/cloud/account-deletion_DARK.png b/docusaurus/static/img/assets/cloud/account-deletion_DARK.png
new file mode 100644
index 0000000000..0e529ea360
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/account-deletion_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/account-invoices.png b/docusaurus/static/img/assets/cloud/account-invoices.png
new file mode 100644
index 0000000000..954850f390
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/account-invoices.png differ
diff --git a/docusaurus/static/img/assets/cloud/account-invoices_DARK.png b/docusaurus/static/img/assets/cloud/account-invoices_DARK.png
new file mode 100644
index 0000000000..cf6153b7bd
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/account-invoices_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/account-preferences.png b/docusaurus/static/img/assets/cloud/account-preferences.png
new file mode 100644
index 0000000000..f307acd302
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/account-preferences.png differ
diff --git a/docusaurus/static/img/assets/cloud/account-preferences_DARK.png b/docusaurus/static/img/assets/cloud/account-preferences_DARK.png
new file mode 100644
index 0000000000..3a3da0b180
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/account-preferences_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/account-repo-selection.png b/docusaurus/static/img/assets/cloud/account-repo-selection.png
new file mode 100644
index 0000000000..31057af0e6
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/account-repo-selection.png differ
diff --git a/docusaurus/static/img/assets/cloud/account-repo-selection_DARK.png b/docusaurus/static/img/assets/cloud/account-repo-selection_DARK.png
new file mode 100644
index 0000000000..1277d11de2
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/account-repo-selection_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/advanced-settings.png b/docusaurus/static/img/assets/cloud/advanced-settings.png
new file mode 100644
index 0000000000..dfc66562f7
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/advanced-settings.png differ
diff --git a/docusaurus/static/img/assets/cloud/advanced-settings_DARK.png b/docusaurus/static/img/assets/cloud/advanced-settings_DARK.png
new file mode 100644
index 0000000000..5e50a6f1aa
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/advanced-settings_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/cancel-deploy.png b/docusaurus/static/img/assets/cloud/cancel-deploy.png
new file mode 100644
index 0000000000..4b196232e3
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/cancel-deploy.png differ
diff --git a/docusaurus/static/img/assets/cloud/cancel-deploy_DARK.png b/docusaurus/static/img/assets/cloud/cancel-deploy_DARK.png
new file mode 100644
index 0000000000..136528303e
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/cancel-deploy_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/cancel-subscription-modal.png b/docusaurus/static/img/assets/cloud/cancel-subscription-modal.png
new file mode 100644
index 0000000000..b99cf1a690
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/cancel-subscription-modal.png differ
diff --git a/docusaurus/static/img/assets/cloud/cancel-subscription-modal_DARK.png b/docusaurus/static/img/assets/cloud/cancel-subscription-modal_DARK.png
new file mode 100644
index 0000000000..156cd470ca
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/cancel-subscription-modal_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/collaboration-dialog.png b/docusaurus/static/img/assets/cloud/collaboration-dialog.png
new file mode 100644
index 0000000000..f082e026d5
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/collaboration-dialog.png differ
diff --git a/docusaurus/static/img/assets/cloud/collaboration-dialog_DARK.png b/docusaurus/static/img/assets/cloud/collaboration-dialog_DARK.png
new file mode 100644
index 0000000000..471bfd0dc8
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/collaboration-dialog_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/collaboration-projectview.png b/docusaurus/static/img/assets/cloud/collaboration-projectview.png
new file mode 100644
index 0000000000..3cf0b86402
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/collaboration-projectview.png differ
diff --git a/docusaurus/static/img/assets/cloud/collaboration-projectview_DARK.png b/docusaurus/static/img/assets/cloud/collaboration-projectview_DARK.png
new file mode 100644
index 0000000000..7244492dbb
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/collaboration-projectview_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/deploy-logs.png b/docusaurus/static/img/assets/cloud/deploy-logs.png
new file mode 100644
index 0000000000..712b057340
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/deploy-logs.png differ
diff --git a/docusaurus/static/img/assets/cloud/deploy-logs_DARK.png b/docusaurus/static/img/assets/cloud/deploy-logs_DARK.png
new file mode 100644
index 0000000000..ee07fb849e
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/deploy-logs_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/deploy_logs.png b/docusaurus/static/img/assets/cloud/deploy_logs.png
index 3756039e17..8fdeba544b 100644
Binary files a/docusaurus/static/img/assets/cloud/deploy_logs.png and b/docusaurus/static/img/assets/cloud/deploy_logs.png differ
diff --git a/docusaurus/static/img/assets/cloud/deploy_logs_DARK.png b/docusaurus/static/img/assets/cloud/deploy_logs_DARK.png
new file mode 100644
index 0000000000..c13018b43d
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/deploy_logs_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/deploys.png b/docusaurus/static/img/assets/cloud/deploys.png
index 7ca7863d91..f2fc0be953 100644
Binary files a/docusaurus/static/img/assets/cloud/deploys.png and b/docusaurus/static/img/assets/cloud/deploys.png differ
diff --git a/docusaurus/static/img/assets/cloud/deploys_DARK.png b/docusaurus/static/img/assets/cloud/deploys_DARK.png
new file mode 100644
index 0000000000..c60745f48d
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/deploys_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/edit-subscription-modal.png b/docusaurus/static/img/assets/cloud/edit-subscription-modal.png
new file mode 100644
index 0000000000..0fcc738550
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/edit-subscription-modal.png differ
diff --git a/docusaurus/static/img/assets/cloud/edit-subscription-modal_DARK.png b/docusaurus/static/img/assets/cloud/edit-subscription-modal_DARK.png
new file mode 100644
index 0000000000..567393164b
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/edit-subscription-modal_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/login.png b/docusaurus/static/img/assets/cloud/login.png
index e1145ba9ef..4e0a8e2a92 100644
Binary files a/docusaurus/static/img/assets/cloud/login.png and b/docusaurus/static/img/assets/cloud/login.png differ
diff --git a/docusaurus/static/img/assets/cloud/login_DARK.png b/docusaurus/static/img/assets/cloud/login_DARK.png
new file mode 100644
index 0000000000..9cfaf20a7b
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/login_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/manage-subscriptions.png b/docusaurus/static/img/assets/cloud/manage-subscriptions.png
index 5cfb62bd5a..d76bf8c8dd 100644
Binary files a/docusaurus/static/img/assets/cloud/manage-subscriptions.png and b/docusaurus/static/img/assets/cloud/manage-subscriptions.png differ
diff --git a/docusaurus/static/img/assets/cloud/manage-subscriptions_DARK.png b/docusaurus/static/img/assets/cloud/manage-subscriptions_DARK.png
new file mode 100644
index 0000000000..f9d02a91d0
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/manage-subscriptions_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/notification-center.png b/docusaurus/static/img/assets/cloud/notification-center.png
index 90e36d6e3f..8304b02ecf 100644
Binary files a/docusaurus/static/img/assets/cloud/notification-center.png and b/docusaurus/static/img/assets/cloud/notification-center.png differ
diff --git a/docusaurus/static/img/assets/cloud/notification-center_DARK.png b/docusaurus/static/img/assets/cloud/notification-center_DARK.png
new file mode 100644
index 0000000000..cf70065dbf
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/notification-center_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/overview.png b/docusaurus/static/img/assets/cloud/overview.png
index 0fefcab961..32e8d62637 100644
Binary files a/docusaurus/static/img/assets/cloud/overview.png and b/docusaurus/static/img/assets/cloud/overview.png differ
diff --git a/docusaurus/static/img/assets/cloud/overview_DARK.png b/docusaurus/static/img/assets/cloud/overview_DARK.png
new file mode 100644
index 0000000000..4f0bad6b42
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/overview_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/plan-selection.png b/docusaurus/static/img/assets/cloud/plan-selection.png
index 20d2d54441..df5402c358 100644
Binary files a/docusaurus/static/img/assets/cloud/plan-selection.png and b/docusaurus/static/img/assets/cloud/plan-selection.png differ
diff --git a/docusaurus/static/img/assets/cloud/plan-selection_DARK.png b/docusaurus/static/img/assets/cloud/plan-selection_DARK.png
new file mode 100644
index 0000000000..c6d1198d33
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/plan-selection_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/project-creation-1.png b/docusaurus/static/img/assets/cloud/project-creation-1.png
new file mode 100644
index 0000000000..deafa9ec65
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/project-creation-1.png differ
diff --git a/docusaurus/static/img/assets/cloud/project-creation-1_DARK.png b/docusaurus/static/img/assets/cloud/project-creation-1_DARK.png
new file mode 100644
index 0000000000..fe1b2d4648
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/project-creation-1_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/project-creation-2.png b/docusaurus/static/img/assets/cloud/project-creation-2.png
new file mode 100644
index 0000000000..03ef262b0c
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/project-creation-2.png differ
diff --git a/docusaurus/static/img/assets/cloud/project-creation-2_DARK.png b/docusaurus/static/img/assets/cloud/project-creation-2_DARK.png
new file mode 100644
index 0000000000..eae2ae6db0
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/project-creation-2_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/project_list.png b/docusaurus/static/img/assets/cloud/project_list.png
index 502ddfd697..90566f2d62 100644
Binary files a/docusaurus/static/img/assets/cloud/project_list.png and b/docusaurus/static/img/assets/cloud/project_list.png differ
diff --git a/docusaurus/static/img/assets/cloud/project_list_DARK.png b/docusaurus/static/img/assets/cloud/project_list_DARK.png
new file mode 100644
index 0000000000..5521cebd8c
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/project_list_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/projects-empty.png b/docusaurus/static/img/assets/cloud/projects-empty.png
new file mode 100644
index 0000000000..4031874be1
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/projects-empty.png differ
diff --git a/docusaurus/static/img/assets/cloud/projects-empty_DARK.png b/docusaurus/static/img/assets/cloud/projects-empty_DARK.png
new file mode 100644
index 0000000000..090a859933
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/projects-empty_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/runtime-logs.png b/docusaurus/static/img/assets/cloud/runtime-logs.png
new file mode 100644
index 0000000000..81493a2f99
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/runtime-logs.png differ
diff --git a/docusaurus/static/img/assets/cloud/runtime-logs_DARK.png b/docusaurus/static/img/assets/cloud/runtime-logs_DARK.png
new file mode 100644
index 0000000000..f15cb2b273
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/runtime-logs_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings-general.png b/docusaurus/static/img/assets/cloud/settings-general.png
new file mode 100644
index 0000000000..9e10563822
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings-general.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings-general_DARK.png b/docusaurus/static/img/assets/cloud/settings-general_DARK.png
new file mode 100644
index 0000000000..65b582df93
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings-general_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings.png b/docusaurus/static/img/assets/cloud/settings.png
index 6d048a2abe..38070e1e68 100644
Binary files a/docusaurus/static/img/assets/cloud/settings.png and b/docusaurus/static/img/assets/cloud/settings.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_DARK.png b/docusaurus/static/img/assets/cloud/settings_DARK.png
new file mode 100644
index 0000000000..dc0798f7ad
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_backups.png b/docusaurus/static/img/assets/cloud/settings_backups.png
new file mode 100644
index 0000000000..2f8ff80a2a
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings_backups.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_backups_DARK.png b/docusaurus/static/img/assets/cloud/settings_backups_DARK.png
new file mode 100644
index 0000000000..d3fdbc9e82
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings_backups_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_billing.png b/docusaurus/static/img/assets/cloud/settings_billing.png
index 1ef40a8e24..ad18b60417 100644
Binary files a/docusaurus/static/img/assets/cloud/settings_billing.png and b/docusaurus/static/img/assets/cloud/settings_billing.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_billing_DARK.png b/docusaurus/static/img/assets/cloud/settings_billing_DARK.png
new file mode 100644
index 0000000000..aa46bb2da6
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings_billing_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_domains.png b/docusaurus/static/img/assets/cloud/settings_domains.png
index 0e7ccfa096..765f583f2a 100644
Binary files a/docusaurus/static/img/assets/cloud/settings_domains.png and b/docusaurus/static/img/assets/cloud/settings_domains.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_domains_DARK.png b/docusaurus/static/img/assets/cloud/settings_domains_DARK.png
new file mode 100644
index 0000000000..7ff835e012
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings_domains_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_invoices.png b/docusaurus/static/img/assets/cloud/settings_invoices.png
new file mode 100644
index 0000000000..2a66109438
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings_invoices.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_invoices_DARK.png b/docusaurus/static/img/assets/cloud/settings_invoices_DARK.png
new file mode 100644
index 0000000000..ee6c83f9db
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings_invoices_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_plans.png b/docusaurus/static/img/assets/cloud/settings_plans.png
new file mode 100644
index 0000000000..d3c6444c0c
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings_plans.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_plans_DARK.png b/docusaurus/static/img/assets/cloud/settings_plans_DARK.png
new file mode 100644
index 0000000000..6ed9b6c362
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings_plans_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_variables.png b/docusaurus/static/img/assets/cloud/settings_variables.png
index 8b72a9e64c..cdf0cd6dcf 100644
Binary files a/docusaurus/static/img/assets/cloud/settings_variables.png and b/docusaurus/static/img/assets/cloud/settings_variables.png differ
diff --git a/docusaurus/static/img/assets/cloud/settings_variables_DARK.png b/docusaurus/static/img/assets/cloud/settings_variables_DARK.png
new file mode 100644
index 0000000000..8e86b6b7d6
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/settings_variables_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/setup.png b/docusaurus/static/img/assets/cloud/setup.png
index 4a7b22f46a..a78d817d47 100644
Binary files a/docusaurus/static/img/assets/cloud/setup.png and b/docusaurus/static/img/assets/cloud/setup.png differ
diff --git a/docusaurus/static/img/assets/cloud/setup_DARK.png b/docusaurus/static/img/assets/cloud/setup_DARK.png
new file mode 100644
index 0000000000..dfe7fb229d
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/setup_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/template-modal.png b/docusaurus/static/img/assets/cloud/template-modal.png
new file mode 100644
index 0000000000..d2ea8caadf
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/template-modal.png differ
diff --git a/docusaurus/static/img/assets/cloud/template-modal_DARK.png b/docusaurus/static/img/assets/cloud/template-modal_DARK.png
new file mode 100644
index 0000000000..a827b58ba5
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/template-modal_DARK.png differ
diff --git a/docusaurus/static/img/assets/cloud/trigger-deploy.png b/docusaurus/static/img/assets/cloud/trigger-deploy.png
new file mode 100644
index 0000000000..a9e924b6c8
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/trigger-deploy.png differ
diff --git a/docusaurus/static/img/assets/cloud/trigger-deploy_DARK.png b/docusaurus/static/img/assets/cloud/trigger-deploy_DARK.png
new file mode 100644
index 0000000000..8f3465c358
Binary files /dev/null and b/docusaurus/static/img/assets/cloud/trigger-deploy_DARK.png differ
diff --git a/docusaurus/static/img/assets/content-manager/content-manager_displayed-fields.png b/docusaurus/static/img/assets/content-manager/content-manager_displayed-fields.png
index 3336af1c20..b45384f7f6 100644
Binary files a/docusaurus/static/img/assets/content-manager/content-manager_displayed-fields.png and b/docusaurus/static/img/assets/content-manager/content-manager_displayed-fields.png differ
diff --git a/docusaurus/static/img/assets/content-manager/content-manager_displayed-fields_DARK.png b/docusaurus/static/img/assets/content-manager/content-manager_displayed-fields_DARK.png
index 2bd91e6f07..afde5f7d85 100644
Binary files a/docusaurus/static/img/assets/content-manager/content-manager_displayed-fields_DARK.png and b/docusaurus/static/img/assets/content-manager/content-manager_displayed-fields_DARK.png differ
diff --git a/docusaurus/static/img/assets/content-manager/content-manager_filters.png b/docusaurus/static/img/assets/content-manager/content-manager_filters.png
index ac90a06ef3..859863c83c 100644
Binary files a/docusaurus/static/img/assets/content-manager/content-manager_filters.png and b/docusaurus/static/img/assets/content-manager/content-manager_filters.png differ
diff --git a/docusaurus/static/img/assets/content-manager/content-manager_filters_DARK.png b/docusaurus/static/img/assets/content-manager/content-manager_filters_DARK.png
index 98ae8e8a33..94628b74c4 100644
Binary files a/docusaurus/static/img/assets/content-manager/content-manager_filters_DARK.png and b/docusaurus/static/img/assets/content-manager/content-manager_filters_DARK.png differ
diff --git a/docusaurus/static/img/assets/content-manager/content-manager_list-view.png b/docusaurus/static/img/assets/content-manager/content-manager_list-view.png
index 66f0ac3082..290428bed1 100644
Binary files a/docusaurus/static/img/assets/content-manager/content-manager_list-view.png and b/docusaurus/static/img/assets/content-manager/content-manager_list-view.png differ
diff --git a/docusaurus/static/img/assets/content-manager/content-manager_list-view_DARK.png b/docusaurus/static/img/assets/content-manager/content-manager_list-view_DARK.png
index 24112b8b21..d0003f53b8 100644
Binary files a/docusaurus/static/img/assets/content-manager/content-manager_list-view_DARK.png and b/docusaurus/static/img/assets/content-manager/content-manager_list-view_DARK.png differ
diff --git a/docusaurus/static/img/assets/content-manager/content-source-map-visual-editing.png b/docusaurus/static/img/assets/content-manager/content-source-map-visual-editing.png
new file mode 100644
index 0000000000..717efbc654
Binary files /dev/null and b/docusaurus/static/img/assets/content-manager/content-source-map-visual-editing.png differ
diff --git a/docusaurus/static/img/assets/content-manager/review-assignee-dropdown.png b/docusaurus/static/img/assets/content-manager/review-assignee-dropdown.png
new file mode 100644
index 0000000000..6890613d6b
Binary files /dev/null and b/docusaurus/static/img/assets/content-manager/review-assignee-dropdown.png differ
diff --git a/docusaurus/static/img/assets/content-manager/review-stage-column.png b/docusaurus/static/img/assets/content-manager/review-stage-column.png
deleted file mode 100644
index c80ef48fff..0000000000
Binary files a/docusaurus/static/img/assets/content-manager/review-stage-column.png and /dev/null differ
diff --git a/docusaurus/static/img/assets/content-manager/review-stage-dropdown.png b/docusaurus/static/img/assets/content-manager/review-stage-dropdown.png
index 2da1b828e0..6ee55c0bcc 100644
Binary files a/docusaurus/static/img/assets/content-manager/review-stage-dropdown.png and b/docusaurus/static/img/assets/content-manager/review-stage-dropdown.png differ
diff --git a/docusaurus/static/img/assets/content-manager/review-workflow-list-view.png b/docusaurus/static/img/assets/content-manager/review-workflow-list-view.png
new file mode 100644
index 0000000000..399586b324
Binary files /dev/null and b/docusaurus/static/img/assets/content-manager/review-workflow-list-view.png differ
diff --git a/docusaurus/static/img/assets/content-type-builder/fields-selection.png b/docusaurus/static/img/assets/content-type-builder/fields-selection.png
index 6d8b4ca52f..634b1a3303 100644
Binary files a/docusaurus/static/img/assets/content-type-builder/fields-selection.png and b/docusaurus/static/img/assets/content-type-builder/fields-selection.png differ
diff --git a/docusaurus/static/img/assets/content-type-builder/fields-selection_DARK.png b/docusaurus/static/img/assets/content-type-builder/fields-selection_DARK.png
index 39f85867da..b2e31a7b4d 100644
Binary files a/docusaurus/static/img/assets/content-type-builder/fields-selection_DARK.png and b/docusaurus/static/img/assets/content-type-builder/fields-selection_DARK.png differ
diff --git a/docusaurus/static/img/assets/development/generate-plugin-content-type.png b/docusaurus/static/img/assets/development/generate-plugin-content-type.png
new file mode 100644
index 0000000000..1b9cd617a5
Binary files /dev/null and b/docusaurus/static/img/assets/development/generate-plugin-content-type.png differ
diff --git a/docusaurus/static/img/assets/icons/ArrowClockwise.svg b/docusaurus/static/img/assets/icons/ArrowClockwise.svg
new file mode 100644
index 0000000000..4a92e6a522
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/ArrowClockwise.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/icons/Browsers.svg b/docusaurus/static/img/assets/icons/Browsers.svg
new file mode 100644
index 0000000000..de61260875
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/Browsers.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/icons/CreditCard.svg b/docusaurus/static/img/assets/icons/CreditCard.svg
new file mode 100644
index 0000000000..36db6c2f77
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/CreditCard.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/icons/Duplicate copy.svg b/docusaurus/static/img/assets/icons/Duplicate copy.svg
new file mode 100644
index 0000000000..bb26e5f135
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/Duplicate copy.svg
@@ -0,0 +1,4 @@
+
diff --git a/docusaurus/static/img/assets/icons/Eye.svg b/docusaurus/static/img/assets/icons/Eye.svg
new file mode 100644
index 0000000000..4d9e6f8261
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/Eye.svg
@@ -0,0 +1,4 @@
+
diff --git a/docusaurus/static/img/assets/icons/Faders.svg b/docusaurus/static/img/assets/icons/Faders.svg
new file mode 100644
index 0000000000..de08c1a0ba
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/Faders.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/icons/Invoice.svg b/docusaurus/static/img/assets/icons/Invoice.svg
new file mode 100644
index 0000000000..f5b9188fed
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/Invoice.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/icons/MapTrifold.svg b/docusaurus/static/img/assets/icons/MapTrifold.svg
new file mode 100644
index 0000000000..ce6cc00215
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/MapTrifold.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/icons/ONHOLDCarretDown.svg b/docusaurus/static/img/assets/icons/ONHOLDCarretDown.svg
new file mode 100644
index 0000000000..939d208e69
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/ONHOLDCarretDown.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/icons/ONHOLDCarretUp.svg b/docusaurus/static/img/assets/icons/ONHOLDCarretUp.svg
new file mode 100644
index 0000000000..cf4cd7f6cd
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/ONHOLDCarretUp.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/icons/Palette.svg b/docusaurus/static/img/assets/icons/Palette.svg
new file mode 100644
index 0000000000..e98e082e9d
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/Palette.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/icons/code2.svg b/docusaurus/static/img/assets/icons/code2.svg
new file mode 100644
index 0000000000..8f9735d5dd
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/code2.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/icons/ctb_richtextblocks.svg b/docusaurus/static/img/assets/icons/ctb_richtextblocks.svg
new file mode 100644
index 0000000000..5e17a64576
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/ctb_richtextblocks.svg
@@ -0,0 +1,22 @@
+
diff --git a/docusaurus/static/img/assets/icons/more.svg b/docusaurus/static/img/assets/icons/more.svg
new file mode 100644
index 0000000000..ab774e46c0
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/more.svg
@@ -0,0 +1,5 @@
+
diff --git a/docusaurus/static/img/assets/icons/releases.svg b/docusaurus/static/img/assets/icons/releases.svg
new file mode 100644
index 0000000000..415a42dcaa
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/releases.svg
@@ -0,0 +1,10 @@
+
diff --git a/docusaurus/static/img/assets/icons/reorder.svg b/docusaurus/static/img/assets/icons/reorder.svg
new file mode 100644
index 0000000000..4057ed9af2
--- /dev/null
+++ b/docusaurus/static/img/assets/icons/reorder.svg
@@ -0,0 +1,3 @@
+
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-cloud-login.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-cloud-login.gif
new file mode 100644
index 0000000000..0c54c0887c
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-cloud-login.gif differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-first-login-cloud.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-first-login-cloud.gif
new file mode 100644
index 0000000000..53a402d076
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-first-login-cloud.gif differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-first-login-cloud_DARK.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-first-login-cloud_DARK.gif
new file mode 100644
index 0000000000..c6dca22513
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-first-login-cloud_DARK.gif differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-categories.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-categories.gif
index d803c0eb18..d3f9c1f16e 100644
Binary files a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-categories.gif and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-categories.gif differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-categories_DARK.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-categories_DARK.gif
new file mode 100644
index 0000000000..a74e2492bc
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-categories_DARK.gif differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part1-01-admin_panel.png b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part1-01-admin_panel.png
index 204bfb80a9..e8dd8e1757 100644
Binary files a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part1-01-admin_panel.png and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part1-01-admin_panel.png differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part1-01-admin_panel_DARK.png b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part1-01-admin_panel_DARK.png
new file mode 100644
index 0000000000..e89032f200
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part1-01-admin_panel_DARK.png differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-02-collection_ct.png b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-02-collection_ct.png
index e05786b074..3f0c79b7ba 100644
Binary files a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-02-collection_ct.png and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-02-collection_ct.png differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-02-collection_ct_DARK.png b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-02-collection_ct_DARK.png
new file mode 100644
index 0000000000..fe43ed25f4
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-02-collection_ct_DARK.png differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-03-restaurant.png b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-03-restaurant.png
index f1ec14175b..17afeb216f 100644
Binary files a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-03-restaurant.png and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-03-restaurant.png differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-03-restaurant_DARK.png b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-03-restaurant_DARK.png
new file mode 100644
index 0000000000..66f79a6c6a
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-03-restaurant_DARK.png differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-04-roles.png b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-04-roles.png
index 0d3ea626a1..4ee2b8ec81 100644
Binary files a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-04-roles.png and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-04-roles.png differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-04-roles_DARK.png b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-04-roles_DARK.png
new file mode 100644
index 0000000000..7011ad9108
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-part2-04-roles_DARK.png differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-publish.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-publish.gif
index 86eb457ebf..2f6b8a950e 100644
Binary files a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-publish.gif and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-publish.gif differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-publish_DARK.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-publish_DARK.gif
new file mode 100644
index 0000000000..d8e66f19d6
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-publish_DARK.gif differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-restaurant_2.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-restaurant_2.gif
index 3b04bc1364..b2e0641fe7 100644
Binary files a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-restaurant_2.gif and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-restaurant_2.gif differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-handson-restaurant_2_DARK.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-restaurant_2_DARK.gif
new file mode 100644
index 0000000000..30eb441ce4
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-handson-restaurant_2_DARK.gif differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-strapi-cloud-1.png b/docusaurus/static/img/assets/quick-start-guide/qsg-strapi-cloud-1.png
new file mode 100644
index 0000000000..6610fe06e4
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-strapi-cloud-1.png differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-strapi-cloud-1_DARK.png b/docusaurus/static/img/assets/quick-start-guide/qsg-strapi-cloud-1_DARK.png
new file mode 100644
index 0000000000..ca25b1f2ac
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-strapi-cloud-1_DARK.png differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-visit-cloud-app.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-visit-cloud-app.gif
new file mode 100644
index 0000000000..9181a6f34c
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-visit-cloud-app.gif differ
diff --git a/docusaurus/static/img/assets/quick-start-guide/qsg-visit-cloud-app_DARK.gif b/docusaurus/static/img/assets/quick-start-guide/qsg-visit-cloud-app_DARK.gif
new file mode 100644
index 0000000000..9e8da76a4d
Binary files /dev/null and b/docusaurus/static/img/assets/quick-start-guide/qsg-visit-cloud-app_DARK.gif differ
diff --git a/docusaurus/static/img/assets/releases/new-release.png b/docusaurus/static/img/assets/releases/new-release.png
new file mode 100644
index 0000000000..75436cde4c
Binary files /dev/null and b/docusaurus/static/img/assets/releases/new-release.png differ
diff --git a/docusaurus/static/img/assets/releases/new-release_DARK.png b/docusaurus/static/img/assets/releases/new-release_DARK.png
new file mode 100644
index 0000000000..3b05a4b583
Binary files /dev/null and b/docusaurus/static/img/assets/releases/new-release_DARK.png differ
diff --git a/docusaurus/static/img/assets/releases/release-cm-edit-view.png b/docusaurus/static/img/assets/releases/release-cm-edit-view.png
new file mode 100644
index 0000000000..a626862611
Binary files /dev/null and b/docusaurus/static/img/assets/releases/release-cm-edit-view.png differ
diff --git a/docusaurus/static/img/assets/releases/release-cm-edit-view_DARK.png b/docusaurus/static/img/assets/releases/release-cm-edit-view_DARK.png
new file mode 100644
index 0000000000..5bcc9123ee
Binary files /dev/null and b/docusaurus/static/img/assets/releases/release-cm-edit-view_DARK.png differ
diff --git a/docusaurus/static/img/assets/releases/release-details.png b/docusaurus/static/img/assets/releases/release-details.png
new file mode 100644
index 0000000000..9fe2898172
Binary files /dev/null and b/docusaurus/static/img/assets/releases/release-details.png differ
diff --git a/docusaurus/static/img/assets/releases/release-details_DARK.png b/docusaurus/static/img/assets/releases/release-details_DARK.png
new file mode 100644
index 0000000000..1a2f15b395
Binary files /dev/null and b/docusaurus/static/img/assets/releases/release-details_DARK.png differ
diff --git a/docusaurus/static/img/assets/releases/release-scheduling.png b/docusaurus/static/img/assets/releases/release-scheduling.png
new file mode 100644
index 0000000000..645431f283
Binary files /dev/null and b/docusaurus/static/img/assets/releases/release-scheduling.png differ
diff --git a/docusaurus/static/img/assets/releases/release-scheduling_DARK.png b/docusaurus/static/img/assets/releases/release-scheduling_DARK.png
new file mode 100644
index 0000000000..2c794aa24a
Binary files /dev/null and b/docusaurus/static/img/assets/releases/release-scheduling_DARK.png differ
diff --git a/docusaurus/static/img/assets/releases/releases-cm-list-view.png b/docusaurus/static/img/assets/releases/releases-cm-list-view.png
new file mode 100644
index 0000000000..cad3a14c15
Binary files /dev/null and b/docusaurus/static/img/assets/releases/releases-cm-list-view.png differ
diff --git a/docusaurus/static/img/assets/releases/releases-cm-list-view_DARK.png b/docusaurus/static/img/assets/releases/releases-cm-list-view_DARK.png
new file mode 100644
index 0000000000..c72cc93e4d
Binary files /dev/null and b/docusaurus/static/img/assets/releases/releases-cm-list-view_DARK.png differ
diff --git a/docusaurus/static/img/assets/releases/releases-overview.png b/docusaurus/static/img/assets/releases/releases-overview.png
new file mode 100644
index 0000000000..1bac02cacd
Binary files /dev/null and b/docusaurus/static/img/assets/releases/releases-overview.png differ
diff --git a/docusaurus/static/img/assets/releases/releases-overview_DARK.png b/docusaurus/static/img/assets/releases/releases-overview_DARK.png
new file mode 100644
index 0000000000..38f05757c5
Binary files /dev/null and b/docusaurus/static/img/assets/releases/releases-overview_DARK.png differ
diff --git a/docusaurus/static/img/assets/rest-api/ctb-article-components-structure.png b/docusaurus/static/img/assets/rest-api/ctb-article-components-structure.png
new file mode 100644
index 0000000000..50c8bf41bf
Binary files /dev/null and b/docusaurus/static/img/assets/rest-api/ctb-article-components-structure.png differ
diff --git a/docusaurus/static/img/assets/rest-api/ctb-blocks-dynamic-zone-structure-2.png b/docusaurus/static/img/assets/rest-api/ctb-blocks-dynamic-zone-structure-2.png
new file mode 100644
index 0000000000..6efc912ff7
Binary files /dev/null and b/docusaurus/static/img/assets/rest-api/ctb-blocks-dynamic-zone-structure-2.png differ
diff --git a/docusaurus/static/img/assets/rest-api/ctb-blocks-dynamic-zone-structure.png b/docusaurus/static/img/assets/rest-api/ctb-blocks-dynamic-zone-structure.png
new file mode 100644
index 0000000000..b9b1a737c5
Binary files /dev/null and b/docusaurus/static/img/assets/rest-api/ctb-blocks-dynamic-zone-structure.png differ
diff --git a/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram.png b/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram.png
new file mode 100644
index 0000000000..c2e392caa7
Binary files /dev/null and b/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram.png differ
diff --git a/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram1.png b/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram1.png
new file mode 100644
index 0000000000..ef863936ed
Binary files /dev/null and b/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram1.png differ
diff --git a/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram2.png b/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram2.png
new file mode 100644
index 0000000000..cebeccb34d
Binary files /dev/null and b/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram2.png differ
diff --git a/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram3.png b/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram3.png
new file mode 100644
index 0000000000..05de02bd3c
Binary files /dev/null and b/docusaurus/static/img/assets/rest-api/populate-foodadvisor-diagram3.png differ
diff --git a/docusaurus/static/img/assets/review-workflows/edit-view-dark.png b/docusaurus/static/img/assets/review-workflows/edit-view-dark.png
index 2f8306de4a..5d75a593b9 100644
Binary files a/docusaurus/static/img/assets/review-workflows/edit-view-dark.png and b/docusaurus/static/img/assets/review-workflows/edit-view-dark.png differ
diff --git a/docusaurus/static/img/assets/review-workflows/edit-view-light.png b/docusaurus/static/img/assets/review-workflows/edit-view-light.png
index 5b7f540703..43c0e3fcb3 100644
Binary files a/docusaurus/static/img/assets/review-workflows/edit-view-light.png and b/docusaurus/static/img/assets/review-workflows/edit-view-light.png differ
diff --git a/docusaurus/static/img/assets/settings/settings-i18n.png b/docusaurus/static/img/assets/settings/settings-i18n.png
index 185bbd22ea..d70fe843d1 100644
Binary files a/docusaurus/static/img/assets/settings/settings-i18n.png and b/docusaurus/static/img/assets/settings/settings-i18n.png differ
diff --git a/docusaurus/static/img/assets/settings/settings-i18n_DARK.png b/docusaurus/static/img/assets/settings/settings-i18n_DARK.png
index 99469ce787..0baf2ab620 100644
Binary files a/docusaurus/static/img/assets/settings/settings-i18n_DARK.png and b/docusaurus/static/img/assets/settings/settings-i18n_DARK.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_api-token-custom.png b/docusaurus/static/img/assets/settings/settings_api-token-custom.png
index cf6af9d060..962af3d234 100644
Binary files a/docusaurus/static/img/assets/settings/settings_api-token-custom.png and b/docusaurus/static/img/assets/settings/settings_api-token-custom.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_api-token-custom_DARK.png b/docusaurus/static/img/assets/settings/settings_api-token-custom_DARK.png
index a2646931cb..74d4fda325 100644
Binary files a/docusaurus/static/img/assets/settings/settings_api-token-custom_DARK.png and b/docusaurus/static/img/assets/settings/settings_api-token-custom_DARK.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_api-token.png b/docusaurus/static/img/assets/settings/settings_api-token.png
index a74991caa5..8d04139471 100644
Binary files a/docusaurus/static/img/assets/settings/settings_api-token.png and b/docusaurus/static/img/assets/settings/settings_api-token.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_api-token_DARK.png b/docusaurus/static/img/assets/settings/settings_api-token_DARK.png
index c3863ee434..37e133c60c 100644
Binary files a/docusaurus/static/img/assets/settings/settings_api-token_DARK.png and b/docusaurus/static/img/assets/settings/settings_api-token_DARK.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_create-transfer-token.png b/docusaurus/static/img/assets/settings/settings_create-transfer-token.png
index bb587df472..e735fe6a2f 100644
Binary files a/docusaurus/static/img/assets/settings/settings_create-transfer-token.png and b/docusaurus/static/img/assets/settings/settings_create-transfer-token.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_create-transfer-token_DARK.png b/docusaurus/static/img/assets/settings/settings_create-transfer-token_DARK.png
index cba49a941d..30f75bf668 100644
Binary files a/docusaurus/static/img/assets/settings/settings_create-transfer-token_DARK.png and b/docusaurus/static/img/assets/settings/settings_create-transfer-token_DARK.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_custom-logo.png b/docusaurus/static/img/assets/settings/settings_custom-logo.png
index cbe146fcc0..ce66c4e4f9 100644
Binary files a/docusaurus/static/img/assets/settings/settings_custom-logo.png and b/docusaurus/static/img/assets/settings/settings_custom-logo.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_custom-logo_DARK.png b/docusaurus/static/img/assets/settings/settings_custom-logo_DARK.png
index 4cbde06b40..2cafdc48f7 100644
Binary files a/docusaurus/static/img/assets/settings/settings_custom-logo_DARK.png and b/docusaurus/static/img/assets/settings/settings_custom-logo_DARK.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_media-library.png b/docusaurus/static/img/assets/settings/settings_media-library.png
index 4ce012157f..adae543fe5 100644
Binary files a/docusaurus/static/img/assets/settings/settings_media-library.png and b/docusaurus/static/img/assets/settings/settings_media-library.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_media-library_DARK.png b/docusaurus/static/img/assets/settings/settings_media-library_DARK.png
index 9545338010..49047968ab 100644
Binary files a/docusaurus/static/img/assets/settings/settings_media-library_DARK.png and b/docusaurus/static/img/assets/settings/settings_media-library_DARK.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_transfer-token.png b/docusaurus/static/img/assets/settings/settings_transfer-token.png
index 25b2f08c9a..fb21106c3f 100644
Binary files a/docusaurus/static/img/assets/settings/settings_transfer-token.png and b/docusaurus/static/img/assets/settings/settings_transfer-token.png differ
diff --git a/docusaurus/static/img/assets/settings/settings_transfer-token_DARK.png b/docusaurus/static/img/assets/settings/settings_transfer-token_DARK.png
index c6e9f8bb86..6a0cb80427 100644
Binary files a/docusaurus/static/img/assets/settings/settings_transfer-token_DARK.png and b/docusaurus/static/img/assets/settings/settings_transfer-token_DARK.png differ
diff --git a/docusaurus/static/img/assets/settings/up_email-templates.png b/docusaurus/static/img/assets/settings/up_email-templates.png
index 9d5306303a..f12289950e 100644
Binary files a/docusaurus/static/img/assets/settings/up_email-templates.png and b/docusaurus/static/img/assets/settings/up_email-templates.png differ
diff --git a/docusaurus/static/img/assets/settings/up_email-templates_DARK.png b/docusaurus/static/img/assets/settings/up_email-templates_DARK.png
index 7de310c3c6..880d9cec6f 100644
Binary files a/docusaurus/static/img/assets/settings/up_email-templates_DARK.png and b/docusaurus/static/img/assets/settings/up_email-templates_DARK.png differ
diff --git a/docusaurus/static/img/assets/settings/up_providers.png b/docusaurus/static/img/assets/settings/up_providers.png
index 25a756bdb7..dfda1233c7 100644
Binary files a/docusaurus/static/img/assets/settings/up_providers.png and b/docusaurus/static/img/assets/settings/up_providers.png differ
diff --git a/docusaurus/static/img/assets/settings/up_providers_DARK.png b/docusaurus/static/img/assets/settings/up_providers_DARK.png
index 2da0aad7eb..42cce2b4c1 100644
Binary files a/docusaurus/static/img/assets/settings/up_providers_DARK.png and b/docusaurus/static/img/assets/settings/up_providers_DARK.png differ
diff --git a/docusaurus/static/img/assets/settings/up_settings.png b/docusaurus/static/img/assets/settings/up_settings.png
index c225b96d6a..491da0bc51 100644
Binary files a/docusaurus/static/img/assets/settings/up_settings.png and b/docusaurus/static/img/assets/settings/up_settings.png differ
diff --git a/docusaurus/static/img/assets/settings/up_settings_DARK.png b/docusaurus/static/img/assets/settings/up_settings_DARK.png
index 9030fd8685..25e54337d8 100644
Binary files a/docusaurus/static/img/assets/settings/up_settings_DARK.png and b/docusaurus/static/img/assets/settings/up_settings_DARK.png differ
diff --git a/docusaurus/static/img/assets/users-permissions/sso-config-custom-validator.png b/docusaurus/static/img/assets/users-permissions/sso-config-custom-validator.png
new file mode 100644
index 0000000000..585896dc6e
Binary files /dev/null and b/docusaurus/static/img/assets/users-permissions/sso-config-custom-validator.png differ
diff --git a/docusaurus/static/img/assets/users-permissions/sso-config-custom-validator_DARK.png b/docusaurus/static/img/assets/users-permissions/sso-config-custom-validator_DARK.png
new file mode 100644
index 0000000000..ac27a0acb7
Binary files /dev/null and b/docusaurus/static/img/assets/users-permissions/sso-config-custom-validator_DARK.png differ
diff --git a/docusaurus/vercel.json b/docusaurus/vercel.json
index 6eea27f7e8..b099865de0 100644
--- a/docusaurus/vercel.json
+++ b/docusaurus/vercel.json
@@ -7,10 +7,30 @@
"source": "/cloud/",
"destination": "/cloud/intro"
},
+ {
+ "source": "/developer-docs/latest/getting-started/quick-start.html%23_1-install-strapi-and-create-a-new-project",
+ "destination": "/dev-docs/quick-start"
+ },
+ {
+ "source": "/user-docs/intro%23using-sso-for-authentication-",
+ "destination": "/user-docs/getting-started/setting-up-admin-panel#using-sso-for-authentication-"
+ },
{
"source": "/dev-docs/deployment/strapi-cloud",
"destination": "/cloud/intro"
},
+ {
+ "source": "/developer-docs/latest/getting-started/quick-start.html",
+ "destination": "/dev-docs/quick-start"
+ },
+ {
+ "source": "/developer-docs/latest/setup-deployment-guides/installation/digitalocean-one-click.html",
+ "destination": "/dev-docs/deployment/digitalocean-app-platform"
+ },
+ {
+ "source": "/developer-docs/latest/setup-deployment-guides/installation/digitalocean-one-click",
+ "destination": "/dev-docs/deployment/digitalocean-app-platform"
+ },
{
"source": "/developer-docs",
"destination": "/dev-docs"
@@ -487,6 +507,10 @@
"source": "/developer-docs/latest/developer-resources/database-apis-reference/rest-api.html",
"destination": "/dev-docs/api/rest"
},
+ {
+ "source": "/dev-docs/api/rest/guides",
+ "destination": "/dev-docs/api/rest/guides/intro"
+ },
{
"source": "/developer-docs/latest/developer-resources/database-apis-reference/rest/api-parameters.html",
"destination": "/dev-docs/api/rest/parameters"
@@ -561,7 +585,11 @@
},
{
"source": "/user-docs/latest/plugins/introduction-to-plugins.html",
- "destination": "/user-docs/plugins/introduction-to-plugins"
+ "destination": "/user-docs/plugins"
+ },
+ {
+ "source": "/user-docs/plugins/introduction-to-plugins",
+ "destination": "/user-docs/plugins"
},
{
"source": "/user-docs/latest/plugins/installing-plugins-via-marketplace.html",
diff --git a/docusaurus/yarn.lock b/docusaurus/yarn.lock
index d6e57bc9ad..16683b0f39 100644
--- a/docusaurus/yarn.lock
+++ b/docusaurus/yarn.lock
@@ -4236,6 +4236,11 @@ dns-packet@^5.2.2:
dependencies:
"@leichtgewicht/ip-codec" "^2.0.1"
+docusaurus-plugin-hubspot@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/docusaurus-plugin-hubspot/-/docusaurus-plugin-hubspot-1.0.1.tgz#a68d09e048dd686ddeda97a53eb3d48cd9beaac8"
+ integrity sha512-sX/6dMQ1nkh5sneNLIdOqrZdzFDlxBGcFwGjiQBS3z3JH87UinlSSVWpzCHU/PFKCAX3cqQSTjZwA/JP5dLPzQ==
+
docusaurus-plugin-image-zoom@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/docusaurus-plugin-image-zoom/-/docusaurus-plugin-image-zoom-0.1.1.tgz#f5e16ae568f7b74e8a357ee67ea7922521f64539"
@@ -4718,6 +4723,11 @@ feed@^4.2.2:
dependencies:
xml-js "^1.6.11"
+fflate@^0.4.8:
+ version "0.4.8"
+ resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
+ integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
+
file-loader@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
@@ -7039,6 +7049,25 @@ postcss@^8.4.13, postcss@^8.4.7:
picocolors "^1.0.0"
source-map-js "^1.0.2"
+posthog-docusaurus@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/posthog-docusaurus/-/posthog-docusaurus-2.0.0.tgz#8b8ac890a2d780c8097a1a9766a3d24d2a1f1177"
+ integrity sha512-nDSTIhmH/Fexv347Gx6wBCE97Z+fZTj0p/gqVYAaolMwSdVuzwyFWcFA+aW9uzA5Y5hjzRwwKJJOrIv8smkYkA==
+
+posthog-js@^1.141.4:
+ version "1.141.4"
+ resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.141.4.tgz#c3fcc8bf312c8baaed2b9032cd5fb527a712283b"
+ integrity sha512-e8rUFEnAR1MB+YqrjWLEmvm0d1X90cebCPNyby6oNX1cp36s/PpxeTx+Up7bArJmRv2N+rT1Kd5sJ7jpXWAonA==
+ dependencies:
+ fflate "^0.4.8"
+ preact "^10.19.3"
+ web-vitals "^4.0.1"
+
+preact@^10.19.3:
+ version "10.22.0"
+ resolved "https://registry.yarnpkg.com/preact/-/preact-10.22.0.tgz#a50f38006ae438d255e2631cbdaf7488e6dd4e16"
+ integrity sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==
+
prepend-http@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
@@ -8866,6 +8895,11 @@ web-namespaces@^1.0.0:
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec"
integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==
+web-vitals@^4.0.1:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.0.tgz#008949ab79717a68ccaaa3c4371cbc7bbbd78a92"
+ integrity sha512-ohj72kbtVWCpKYMxcbJ+xaOBV3En76hW47j52dG+tEGG36LZQgfFw5yHl9xyjmosy3XUMn8d/GBUAy4YPM839w==
+
web-worker@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da"