diff --git a/.algolia/docsearch.config.json b/.algolia/docsearch.config.json index 1c0f6dfb85..4c70d2654d 100644 --- a/.algolia/docsearch.config.json +++ b/.algolia/docsearch.config.json @@ -26,7 +26,7 @@ "type": "xpath", "global": true }, - "text": ".content__default p, .content__default ul>li a, .content__default ul>li code, .content__default table > tr, th, td, p, code", + "text": ".content__default p, .content__default p>code, .content__default ol, .content__default ol, .content__default ol code, .content__default ul>li, .content__default ul>li code, .content__default table>tbody code, .content__default table>tbody td, .content__default table > p, .content__default custom-block.tip p, .content__default custom-block.tip code", "lang": { "selector": "/html/@lang", "type": "xpath", @@ -43,3 +43,4 @@ ] } } + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ec0d7f595..3159f181c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,13 +2,13 @@ Strapi is an open-source project administered by [the Strapi team](https://strapi.io/company). We appreciate your interest and efforts to contribute to Strapi. -All efforts to contribute are highly appreciated, we recommend you talk to a maintainer prior to spending a lot of time making a pull request that may not align with the project roadmap. Please note that contributions, pull requests, and issues should be written in English. +All contributions are highly appreciated, we recommend you talk to a maintainer prior to investing a lot of time in a pull request that may not align with the project roadmap. Please note that contributions, pull requests, and issues should be written in English. ## Open Development & Community Driven Strapi is an open-source project. See the [LICENSE](https://github.com/strapi/documentation/blob/main/LICENSE) file for licensing information. All of the work is available on GitHub. -The core team and the contributors send pull requests which go through the same validation process. +The core team and contributors submit pull requests that go through the same validation process. ## Code of Conduct @@ -16,14 +16,12 @@ This project and everyone participating in it are governed by the [Strapi Code o ## Documentation Requests -Requests for new documentation are highly encouraged, this is not limited to new additions but also changes or more information requested on existing documentation. Please use our [request documentation](https://github.com/strapi/documentation/issues/new?template=DOC_REQUEST.md&title%5B%5D=REQUEST) issue template. If you are requesting documentation, please feel free to open a pull request. +Requests for new documentation are highly encouraged, this is not limited to new additions but also changes or more information requested on existing documentation. Please use our [request documentation](https://github.com/strapi/documentation/issues/new?template=DOC_REQUEST.md&title%5B%5D=REQUEST) issue template. ## Bugs -Bug reports help to improve the documentation. Please use our [Documentation Bug Report](https://github.com/strapi/documentation/issues/new?template=BUG_REPORT.yml) template to report documentation bugs. Before submitting an issue: +Bug reports help to improve the documentation. Please use our [Documentation Bug Report](https://github.com/strapi/documentation/issues/new?template=BUG_REPORT.yml) template to report documentation bugs. To submit an issue: -- Check for existing pull requests that may address the same issue. -- Check for related open issues, if so, please provide context on the existing issue. - Follow the issue template and fill out as much information as you can. - Verify the issue is only with the Strapi documentation, code issues should be directed at the main [strapi/strapi](https://github.com/strapi/strapi) repository. @@ -119,10 +117,9 @@ You are now ready to contribute to the Strapi documentation! 🚀 ### Write technical documentation -For lengthier contributions, we provide general guidelines that can help you write clear and concise documentation: +The Strapi documentation follows the [Google Style Guide](https://developers.google.com/style). The [Highlights](https://developers.google.com/style/highlights) section provides information on tone, structure, and formatting. -- The [12 Rules of Technical Writing](https://handbook.strapi.io/user-success-manual/12-rules-of-technical-writing) gives an overview of how to structure and write clear documentation. -- The [Strapi Documentation Style Guide](https://handbook.strapi.io/user-success-manual/strapi-documentation-style-guide) has formatting guidelines and how to implement formatting in markdown files. +The Strapi [Formatting Style Guide](https://github.com/strapi/documentation/blob/main/formatting_style_guide.pdf) has formatting guidelines and how to implement formatting in markdown files. When you are finished writing, create a pull request from your forked repository to the original `documentation` repository (see [the GitHub docs](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) for more information) or use the _Create a new branch for this commit and start a pull request_ option if you are using the GitHub web browser interface (see [the GitHub docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request)). diff --git a/docs/.vuepress/config/sidebar-developer.js b/docs/.vuepress/config/sidebar-developer.js index db1ca21d4c..574c86fe6a 100644 --- a/docs/.vuepress/config/sidebar-developer.js +++ b/docs/.vuepress/config/sidebar-developer.js @@ -488,28 +488,4 @@ const developer = [ }, ], }, - { - title: '📚 Guides', - collapsable: true, - children: [ - ['/developer-docs/latest/guides/auth-request', 'Authenticated request'], - // ['/developer-docs/latest/guides/slug', 'Create a slug system'], - // ['/developer-docs/latest/guides/is-owner', 'Create is owner policy'], - // ['/developer-docs/latest/guides/custom-admin', 'Custom admin'], - // ['/developer-docs/latest/guides/custom-data-response', 'Custom data response'], - // ['/developer-docs/latest/guides/error-catching', 'Error catching'], - // ['/developer-docs/latest/guides/external-data', 'Fetching external data'], - ['/developer-docs/latest/guides/jwt-validation', 'JWT validation'], - ['/developer-docs/latest/guides/process-manager', 'Process manager'], - ['/developer-docs/latest/guides/scheduled-publication', 'Scheduled publication'], - // ['/developer-docs/latest/guides/secure-your-app', 'Secure your application'], - // ['/developer-docs/latest/guides/send-email', 'Send email programmatically'], - [ - '/developer-docs/latest/guides/registering-a-field-in-admin', - 'New WYSIWYG field in admin panel', - ], - // ['/developer-docs/latest/guides/client', 'Setup a third party client'], - - ], - }, ]; diff --git a/docs/.vuepress/redirects b/docs/.vuepress/redirects index 073a8289ee..44022ea205 100644 --- a/docs/.vuepress/redirects +++ b/docs/.vuepress/redirects @@ -5,3 +5,10 @@ /developer-docs/latest/setup-deployment-guides/installation/platformsh.html /developer-docs/latest/setup-deployment-guides/installation/installation.html /developer-docs/latest/setup-deployment-guides/installation/digitalocean-customization.html /developer-docs/latest/setup-deployment-guides/installation/installation.html /developer-docs/latest/concepts/draft-and-publish.html /user-docs/latest/content-manager/saving-and-publishing-content.html +/developer-docs/latest/guides/process-manager.html /developer-docs/latest/setup-deployment-guides/deployment/optional-software/process-manager.html +/developer-docs/latest/guides/draft.html /user-docs/latest/content-manager/saving-and-publishing-content.html +/developer-docs/latest/guides/unit-testing.html /developer-docs/latest/developer-resources/unit-testing.html +/developer-docs/latest/guides/auth-request.html /developer-docs/latest/plugins/users-permissions.html +/developer-docs/latest/guides/registering-a-field-in-admin.html /developer-docs/latest/development/custom-fields.html +/developer-docs/latest/guides/scheduled-publication.html /user-docs/latest/content-manager/saving-and-publishing-content.html +/developer-docs/latest/guides/jwt-validation.html /developer-docs/latest/plugins/users-permissions.html \ No newline at end of file diff --git a/docs/developer-docs/latest/development/admin-customization.md b/docs/developer-docs/latest/development/admin-customization.md index 13f9c3affe..e388b58180 100644 --- a/docs/developer-docs/latest/development/admin-customization.md +++ b/docs/developer-docs/latest/development/admin-customization.md @@ -23,7 +23,7 @@ Customizing the admin panel is helpful to better reflect your brand identity or - The [access URL, host and port](#access-url) can be modified through the server configuration. - The [configuration object](#configuration-options) allows replacing the logos and favicon, defining locales and extending translations, extending the theme, and disabling some Strapi default behaviors like displaying video tutorials or notifications about new Strapi releases. - The [WYSIWYG editor](#wysiwyg-editor) can be replaced or customized. -- The [forgotten password email](#forgotten-password-email) can be customized with a template and variables. +- The [email templates](#email-templates) should be customized using the Users and Permissions plugin. - The [webpack configuration](#webpack-configuration) based on webpack 5 can also be extended for advanced customization ### Access URL @@ -567,92 +567,9 @@ export default { -### 'Forgotten password' email - -To customize the 'Forgotten password' email, provide your own template (formatted as a [lodash template](https://lodash.com/docs/4.17.15#template)). - -The template will be compiled with the following variables: `url`, `user.email`, `user.username`, `user.firstname`, `user.lastname`. - -**Example**: - - - - -```js -// path: ./config/admin.js - -const forgotPasswordTemplate = require('./email-templates/forgot-password'); - -module.exports = ({ env }) => ({ - // ... - forgotPassword: { - from: 'support@mywebsite.fr', - replyTo: 'support@mywebsite.fr', - emailTemplate: forgotPasswordTemplate, - }, - // ... -}); -``` - -```js -// path: ./config/email-templates/forgot-password.js - -const subject = `Reset password`; - -const html = `

Hi <%= user.firstname %>

-

Sorry you lost your password. You can click here to reset it: <%= url %>

`; - -const text = `Hi <%= user.firstname %> -Sorry you lost your password. You can click here to reset it: <%= url %>`; - -module.exports = { - subject, - text, - html, -}; -``` - -
- - - -```js -// path: ./config/admin.ts - -import forgotPasswordTemplate from './email-templates/forgot-password'; - -export default ({ env }) => ({ - // ... - forgotPassword: { - from: 'support@mywebsite.fr', - replyTo: 'support@mywebsite.fr', - emailTemplate: forgotPasswordTemplate, - }, - // ... -}); -``` - -```js -// path: ./config/email-templates/forgot-password.ts - -const subject = `Reset password`; - -const html = `

Hi <%= user.firstname %>

-

Sorry you lost your password. You can click here to reset it: <%= url %>

`; - -const text = `Hi <%= user.firstname %> -Sorry you lost your password. You can click here to reset it: <%= url %>`; - -export default { - subject, - text, - html, -}; -``` - -
-
+### Email templates +Email templates should be edited through the admin panel, using the [Users and Permissions plugin settings](/user-docs/latest/settings/configuring-users-permissions-plugin-settings.md#configuring-email-templates). ### Webpack configuration diff --git a/docs/developer-docs/latest/development/backend-customization/routes.md b/docs/developer-docs/latest/development/backend-customization/routes.md index 95e7af00c2..3a229f3a16 100644 --- a/docs/developer-docs/latest/development/backend-customization/routes.md +++ b/docs/developer-docs/latest/development/backend-customization/routes.md @@ -430,7 +430,7 @@ import { factories } from '@strapi/strapi'; export default factories.createCoreRouter('api::restaurant.restaurant', { config: { find: { - middlwares: [ + middlewares: [ // point to a registered middleware 'middleware-name', diff --git a/docs/developer-docs/latest/development/custom-fields.md b/docs/developer-docs/latest/development/custom-fields.md index 14a2d3f2ce..0e207a8dab 100644 --- a/docs/developer-docs/latest/development/custom-fields.md +++ b/docs/developer-docs/latest/development/custom-fields.md @@ -44,6 +44,24 @@ Currently, custom fields cannot add new data types to Strapi and must use existi ::: details Example: Registering an example "color" custom field on the server: +In the following example, the `color-picker` plugin was created using the CLI generator (see [plugins development](/developer-docs/latest/plugins-development.md)): + +```js +// path: ./src/plugins/color-picker/server/register.js + +'use strict'; + +module.exports = ({ strapi }) => { + strapi.customFields.register({ + name: 'color', + plugin: 'color-picker', + type: 'string', + }); +}; +``` + +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 // path: ./src/plugins/color-picker/strapi-server.js @@ -85,93 +103,42 @@ The `app.customFields` object exposes a `register()` method on the `StrapiApp` i ::: details Example: Registering an example "color" custom field in the admin panel: +In the following example, the `color-picker` plugin was created using the CLI generator (see [plugins development](/developer-docs/latest/plugins-development.md)): + ```jsx -// path: ./src/plugins/color-picker/strapi-admin.js - -register(app) { - app.customFields.register({ - name: "color", - pluginId: "color-picker", // the custom field is created by a color-picker plugin - type: "string", // the color will be stored as a string - intlLabel: { - id: "color-picker.color.label", - defaultMessage: "Color", - }, - intlDescription: { - id: "color-picker.color.description", - defaultMessage: "Select any color", - }, - icon: ColorIcon, - components: { - Input: async () => import(/* webpackChunkName: "input-component" */ "./Input"), - }, - options: { - base: [ - /* - Declare settings to be added to the "Base settings" section - of the field in the Content-Type Builder - */ - { - sectionTitle: { // Add a "Format" settings section - id: 'color-picker.color.section.format', - defaultMessage: 'Format', - }, - items: [ // Add settings items to the section - { - /* - Add a "Color format" dropdown - to choose between 2 different format options - for the color value: hexadecimal or RGBA - */ - intlLabel: { - id: 'color-picker.color.format.label', - defaultMessage: 'Color format', - }, - name: 'options.format', - type: 'select', - value: 'hex', // option selected by default - options: [ // List all available "Color format" options - { - key: 'hex', - value: 'hex', - metadatas: { - intlLabel: { - id: 'color-picker.color.format.hex', - defaultMessage: 'Hexadecimal', - }, - }, - }, - { - key: 'rgba', - value: 'rgba', - metadatas: { - intlLabel: { - id: 'color-picker.color.format.rgba', - defaultMessage: 'RGBA', - }, - }, - }, - ], - }, - ], - }, - ], - advanced: [ - /* - Declare settings to be added to the "Advanced settings" section - of the field in the Content-Type Builder - */ - ], - validator: args => ({ - format: yup.string().required({ - id: 'options.color-picker.format.error', - defaultMessage: 'The color format is required', - }), - }) - }), - }, - }); -} +// path: ./src/plugins/color-picker/admin/src/index.js + +import ColorPickerIcon from './components/ColorPicker/ColorPickerIcon'; + +export default { + register(app) { + // ... app.addMenuLink() goes here + // ... app.registerPlugin() goes here + + app.customFields.register({ + name: "color", + pluginId: "color-picker", // the custom field is created by a color-picker plugin + type: "string", // the color will be stored as a string + intlLabel: { + id: "color-picker.color.label", + defaultMessage: "Color", + }, + intlDescription: { + id: "color-picker.color.description", + defaultMessage: "Select any color", + }, + icon: ColorPickerIcon, // don't forget to create/import your icon component + components: { + Input: async () => import(/* webpackChunkName: "input-component" */ "./admin/src/components/Input"), + }, + options: { + // declare options here + }, + }); + } + + // ... bootstrap() goes here +}; ``` ::: @@ -182,17 +149,21 @@ register(app) { ::: details Example: Registering an Input component -```js -// path: ./src/plugins/my-custom-field-plugin/strapi-admin.js +In the following example, the `color-picker` plugin was created using the CLI generator (see [plugins development](/developer-docs/latest/plugins-development.md)): -register(app) { - app.customFields.register({ - // … - components: { - Input: async () => import(/* webpackChunkName: "input-component" */ "./Input"), - } - // … - }); +```jsx +// path: ./src/plugins/color-picker/admin/src/index.js + +export default { + register(app) { + app.customFields.register({ + // … + components: { + Input: async () => import(/* webpackChunkName: "input-component" */ "./Input"), + } + // … + }); + } } ``` @@ -227,103 +198,87 @@ Each object in the `items` array can contain the following parameters: | `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` | - - ::: details Example: Declaring options for an example "color" custom field: +In the following example, the `color-picker` plugin was created using the CLI generator (see [plugins development](/developer-docs/latest/plugins-development.md)): + ```jsx -// path: ./src/plugins/my-custom-field-plugin/strapi-admin.js +// path: ./src/plugins/color-picker/admin/src/index.js + +// imports go here (ColorPickerIcon, pluginId, yup package…) -register(app) { - app.customFields.register({ +export default { + register(app) { + // ... app.addMenuLink() goes here + // ... app.registerPlugin() goes here + app.customFields.register({ // … - options: { - base: [ - { - intlLabel: { - id: 'color-picker.color.format.label', - defaultMessage: 'Color format', - }, - name: 'options.format', - type: 'select', - value: 'hex', - options: [ - { - key: '__null_reset_value__', - value: '', - metadatas: { - intlLabel: { - id: 'color-picker.color.format.placeholder', - defaultMessage: 'Select a format', - }, - hidden: true, - }, - }, - { - key: 'hex', - value: 'hex', - metadatas: { - intlLabel: { - id: 'color-picker.color.format.hex', - defaultMessage: 'Hexadecimal', - }, - }, + options: { + base: [ + /* + Declare settings to be added to the "Base settings" section + of the field in the Content-Type Builder + */ + { + sectionTitle: { // Add a "Format" settings section + id: 'color-picker.color.section.format', + defaultMessage: 'Format', }, - { - key: 'rgba', - value: 'rgba', - metadatas: { + items: [ // Add settings items to the section + { + /* + Add a "Color format" dropdown + to choose between 2 different format options + for the color value: hexadecimal or RGBA + */ intlLabel: { - id: 'color-picker.color.format.rgba', - defaultMessage: 'RGBA', + id: 'color-picker.color.format.label', + defaultMessage: 'Color format', }, + name: 'options.format', + type: 'select', + value: 'hex', // option selected by default + options: [ // List all available "Color format" options + { + key: 'hex', + value: 'hex', + metadatas: { + intlLabel: { + id: 'color-picker.color.format.hex', + defaultMessage: 'Hexadecimal', + }, + }, + }, + { + key: 'rgba', + value: 'rgba', + metadatas: { + intlLabel: { + id: 'color-picker.color.format.rgba', + defaultMessage: 'RGBA', + }, + }, + }, + ], }, - }, - ], - }, - ], - advanced: [ - { - sectionTitle: { - id: 'global.settings', - defaultMessage: 'Settings', + ], }, - items: [ - { - name: 'required', - type: 'checkbox', - intlLabel: { - id: 'form.attribute.item.requiredField', - defaultMessage: 'Required field', - }, - description: { - id: 'form.attribute.item.requiredField.description', - defaultMessage: "You won't be able to create an entry if this field is empty", - }, - }, - { - name: 'private', - type: 'checkbox', - intlLabel: { - id: 'form.attribute.item.privateField', - defaultMessage: 'Private field', - }, - description: { - id: 'form.attribute.item.privateField.description', - defaultMessage: 'This field will not show up in the API response', - }, - }, - ], - }, - ], - validator: args => ({ - format: yup.string().required({ - id: 'options.color-picker.format.error', - defaultMessage: 'The color format is required', - }), - }), - }, - }); + ], + advanced: [ + /* + Declare settings to be added to the "Advanced settings" section + of the field in the Content-Type Builder + */ + ], + validator: args => ({ + format: yup.string().required({ + id: 'options.color-picker.format.error', + defaultMessage: 'The color format is required', + }), + }) + }, + }); + } } ``` diff --git a/docs/developer-docs/latest/development/plugins-development.md b/docs/developer-docs/latest/development/plugins-development.md index a9c565f8c7..8c90960646 100644 --- a/docs/developer-docs/latest/development/plugins-development.md +++ b/docs/developer-docs/latest/development/plugins-development.md @@ -18,7 +18,7 @@ Strapi provides a [command line interface (CLI)](/developer-docs/latest/develope 1. Navigate to the root of a Strapi project. 2. Run `yarn strapi generate` or `npm run strapi generate` in a terminal window to start the interactive CLI. -4. Choose "plugin" from the list, press Enter, and give the plugin a name in kebab-case (e.g. my-plugin-name) +4. Choose "plugin" from the list, press Enter, and give the plugin a name in kebab-case (e.g. `my-plugin`) 5. Choose either `JavaScript` or `TypeScript` for the plugin language. 6. Enable the plugin by adding it to the [plugins configurations](/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md) file: diff --git a/docs/developer-docs/latest/development/plugins-extension.md b/docs/developer-docs/latest/development/plugins-extension.md index f415ad31b5..fc291bd889 100644 --- a/docs/developer-docs/latest/development/plugins-extension.md +++ b/docs/developer-docs/latest/development/plugins-extension.md @@ -33,12 +33,9 @@ Plugins can be extended in 2 ways: - [extending the plugin's content-types](#extending-a-plugin-s-content-types) - [extending the plugin's interface](#extending-a-plugin-s-interface) (e.g. to add controllers, services, policies, middlewares and more) -::: note -Currently it's not possible to extend the admin panel part of a plugin. Consider using [patch-package](https://www.npmjs.com/package/patch-package) if admin panel customizations are required. -::: - :::warning -New versions of Strapi are released with [migration guides](/developer-docs/latest/update-migration-guides/migration-guides.md), but these guides might not cover unexpected breaking changes in your plugin extensions. Consider forking a plugin if extensive customizations are required. +* New versions of Strapi are released with [migration guides](/developer-docs/latest/update-migration-guides/migration-guides.md), but these guides might not cover unexpected breaking changes in your plugin extensions. Consider forking a plugin if extensive customizations are required. +* Currently, the admin panel part of a plugin can only be extended using [patch-package](https://www.npmjs.com/package/patch-package), but please consider that doing so might break your plugin in future major versions of Strapi. ::: ## Extending a plugin's content-types diff --git a/docs/developer-docs/latest/development/typescript.md b/docs/developer-docs/latest/development/typescript.md index e7c38468f9..84041365f9 100644 --- a/docs/developer-docs/latest/development/typescript.md +++ b/docs/developer-docs/latest/development/typescript.md @@ -225,4 +225,4 @@ yarn develop -After completing the preceding procedure a `dist` directory will be added at the project route and the project has access to the same TypeScript features as a new TypeScript-supported Strapi project. +After completing the preceding procedure a `dist` directory will be added at the project root and the project has access to the same TypeScript features as a new TypeScript-supported Strapi project. diff --git a/docs/developer-docs/latest/guides/auth-request.md b/docs/developer-docs/latest/guides/auth-request.md deleted file mode 100644 index a1d2919a80..0000000000 --- a/docs/developer-docs/latest/guides/auth-request.md +++ /dev/null @@ -1,253 +0,0 @@ ---- -title: Authenticated request - Strapi Developer Docs -description: Learn how you can request the API of your Strapi project as an authenticated user. -canonicalUrl: https://docs.strapi.io/developer-docs/latest/guides/auth-request.html ---- - -# Authenticated request - -Learn how to make API requests as an authenticated user. - -## Introduction - -This guide shows you how to assign [roles and permissions](/developer-docs/latest/plugins/users-permissions.md) to multiple users and [authenticate API requests](/developer-docs/latest/plugins/users-permissions.md#authentication) with JSON Web Tokens (JWT). - -To demonstrate how roles work, you will create two different roles and grant each role certain permissions. - -**Authors** can fetch, create, and update Articles; **Readers** can only fetch Articles. - -## Project Setup - -To follow along, you must have a Strapi project. If you don’t have a Strapi project, run the following command: - - - - -```sh -npx create-strapi-app@latest my-project --quickstart -``` - - - -```sh -yarn create strapi-app my-project --quickstart -``` - - - - -After creating your Strapi project, you will be redirected to your project’s [admin panel](http://localhost:1337/admin). - -### Create a new Collection Type - -Create an **Articles** collection type. - -To create a new collection: -1. In the left sidebar, select **Content-Type Builder**. -2. Select **+ Create new collection type**. -3. In the *Display Name* field, enter “Articles”. - a. In the *API ID (Singular)* field, enter “article”. - b. In the *API ID (Plural)* field, enter “articles”. -4. Select **Continue**. -5. Select *Text*. -6. In the *Name* field, enter “title”, select *Short text*, and select **Finish**. -7. Select **Add another field to this collection type** and select *Rich text*. -8. In the *Name* field, enter “content” and select **Finish**. -9. Select **Save**. - -With your Articles content type ready, create some sample articles: - -1. Go to *Content Manager*. -2. Under *COLLECTION TYPES*, select *Articles*. -3. Select **+ Create new entry**. -4. Enter a title and some sample text in the content textbox. -5. Select **Save** and then **Publish**. - -### Create Roles and Permissions - -Create an Author role and manage its permissions: -1. From the left sidebar, select *Settings*. -2. Under *Users & Permissions Plugin*, select *Roles*. -3. Select **+ Add new role**. -4. In the *Name* field, enter “Author” and enter a **Description** (for example, “User with author permissions”). -5. Select the *Article* content type and **Select All**. -6. Select **Save**. - -Create another role called Reader by repeating the steps above, but only select **find** and **findOne** from the Article content type permissions. - -::: note -Roles are authenticated by default. -::: - -### Create users - -Create **two users** with the following data. - -| **User 1** | **User Data** | -|--------------|---------------------| -| **username** | author | -| **email** | author@strapi.io | -| **password** | strapi | -| **role** | Author | - -| **User 2** | **User Data** | -|--------------|---------------------| -| **username** | reader | -| **email** | reader@strapi.io | -| **password** | strapi | -| **role** | Reader | - -## Log in as a Reader - -To log in as a user with the role of Reader, send a **POST** request to the `/api/auth/local` API route. - -:::: tabs card - -::: tab axios - -```js -import axios from 'axios'; - -const { data } = await axios.post('http://localhost:1337/api/auth/local', { - identifier: 'reader@strapi.io', - password: 'strapi', -}); - -console.log(data); -``` - -::: - -::: tab Postman - -If you use **Postman**, set the **body** to **raw** and select **JSON** as your data format: - -```json -{ - "identifier": "reader@strapi.io", - "password": "strapi" -} -``` - -::: - -:::: -If your request is successful, you will receive the **user's JWT** in the `jwt` key: - -```json -{ - "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTc2OTM4MTUwLCJleHAiOjE1Nzk1MzAxNTB9.UgsjjXkAZ-anD257BF7y1hbjuY3ogNceKfTAQtzDEsU", - "user": { - "id": 1, - "username": "reader", - ... - } -} -``` - -Save the `JWT` in your application or copy it to your clipboard. You will use it to make future requests. - -::: note -See the [login documentation](/developer-docs/latest/plugins/users-permissions.md#login) for more information. -::: - -### Fetch articles - -Fetch the Articles you created earlier by sending a **GET** request to the `/articles` route: - -:::: tabs card - -::: tab axios - -```js -import axios from 'axios'; - -const { data } = await axios.get('http://localhost:1337/api/articles'); - -console.log(data); -``` - -::: - -::: tab Postman - -```http -GET http://localhost:1337/api/articles -``` - -::: - -:::: -Your response will return a `403 Forbidden` error. - -When a user sends an unauthorized request (a request that omits an `Authorization` header), Strapi assigns that user a [Public role](https://docs.strapi.io/developer-docs/latest/plugins/users-permissions.html#public-role) by default. - -To authenticate a user’s request, use the bearer authentication scheme by including an `Authorization` header signed with the user’s JWT ( `Bearer [JWT Token]`): - -```js -import axios from 'axios'; - -const { data } = await axios.get('http://localhost:1337/api/articles', { - headers: { - Authorization: - 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTc2OTM4MTUwLCJleHAiOjE1Nzk1MzAxNTB9.UgsjjXkAZ-anD257BF7y1hbjuY3ogNceKfTAQtzDEsU', - }, -}); - -console.log(data); -``` - -With your bearer token included in the `Authorization` header, you will receive a `Status: 200 OK` response and a payload containing your articles. - -### Create an Article - -Now, create an Article by sending a **POST** request to the `/api/articles` route: - -:::: tabs card - -::: tab axios - -```js -import axios from 'axios'; - -const { data } = await axios.post( - 'http://localhost:1337/api/articles', - { - data: { - title: 'my article', - content: 'my super article content', - } - }, - { - headers: { - Authorization: - 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTc2OTM4MTUwLCJleHAiOjE1Nzk1MzAxNTB9.UgsjjXkAZ-anD257BF7y1hbjuY3ogNceKfTAQtzDEsU', - }, - } - ); - - console.log(data); -``` - -::: - -::: tab Postman - -```json -{ - "data": { - "title": "my article", - "content": "my super article content" - } -} -``` - -::: - -:::: - -You will receive a `403 Forbidden` response because you made this request as a user with the role Reader. - -Only users with the role Author can create Articles. Sign in with the Author user credentials to receive your JWT. Then, send the **POST** request to the `/articles` endpoint by including the JWT in the `Authorization` header. - -You will receive a `200 OK` response and see your new article in the payload. diff --git a/docs/developer-docs/latest/guides/jwt-validation.md b/docs/developer-docs/latest/guides/jwt-validation.md deleted file mode 100644 index 689929afed..0000000000 --- a/docs/developer-docs/latest/guides/jwt-validation.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: JWT validation - Strapi Developer Docs -description: Learn in this guide how to validate a JWT (JSON Web Token) with a third party service. -canonicalUrl: https://docs.strapi.io/developer-docs/latest/guides/jwt-validation.html ---- - -# JWT validation - -!!!include(developer-docs/latest/guides/snippets/guide-not-updated.md)!!! - -In this guide we will see how to validate a `JWT` (JSON Web Token) with a third party service. - -When you sign in with the authentication route `POST /auth/local`, Strapi generates a `JWT` which lets your users request your API as an authenticated one. - -```json -{ - "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNTcxODIyMDAzLCJleHAiOjE1NzQ0MTQwMDN9.T5XQGSDZ6TjgM5NYaVDbYJt84qHZTrtBqWu1Q3ShINw", - "user": { - "email": "admin@strapi.io", - "id": 1, - "username": "admin" - } -} -``` - -These users are managed in the application's database and can be managed via the admin dashboard. - -We can now imagine you have a `JWT` that comes from [Auth0](https://auth0.com) and you want to make sure the `JWT` is correct before allowing the user to use the Strapi API endpoints. - -## Customize the JWT validation function - -We will update the function that validates the `JWT`. This feature is powered by the **Users & Permissions** [plugin](/developer-docs/latest/plugins/upload.md). - -Here is the file we will have to customize: [permission.js](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/config/policies/permissions.js) - -- We have to create a file that follows this path `./extensions/users-permissions/config/policies/permissions.js`. -- You will have to add in this new file, the same content of the original one. - -Now we are ready to create our custom validation code. - -## Write our own logic - -First we have to define where we write our code. - -```js -const _ = require('lodash'); - -module.exports = async (ctx, next) => { - let role; - - if (ctx.request && ctx.request.header && ctx.request.header.authorization) { - try { - const { id, isAdmin = false } = await strapi.plugins[ - 'users-permissions' - ].services.jwt.getToken(ctx); - - ... - - } catch (err) { - // It will be there! - - return handleErrors(ctx, err, 'unauthorized'); - } -``` - -The `jwt.getToken` will throw an error if the token doesn't come from Strapi. So if it's not a Strapi `JWT` token, let's test if it's an Auth0 one. - -We will have to write our validation code before throwing an error. - -By using the [Auth0 get user profile](https://auth0.com/docs/api/authentication?http#get-user-info) documentation, you will verify a valid user matches with the current `JWT` - -```js -const _ = require('lodash'); -const axios = require('axios'); - -module.exports = async (ctx, next) => { - let role; - - if (ctx.request && ctx.request.header && ctx.request.header.authorization) { - try { - const { id, isAdmin = false } = await strapi.plugins[ - 'users-permissions' - ].services.jwt.getToken(ctx); - - ... - - } catch (err) { - try { - const data = await axios({ - method: 'post', - url: 'http://YOUR_DOMAIN/userinfo', - headers: { - Authorization: ctx.request.header.authorization - } - }); - - // if you want do more validation test - // feel free to add your code here. - - return await next(); - } catch (error) { - return handleErrors(ctx, new Error('Invalid token: Token did not match with Strapi and Auth0'), 'unauthorized'); - } - } -``` - -:::caution -In the code example we use `axios`, so you will have to install the dependency to make it work. You can choose another library if you prefer. -::: diff --git a/docs/developer-docs/latest/guides/process-manager.md b/docs/developer-docs/latest/guides/process-manager.md deleted file mode 100644 index 5ffeac8180..0000000000 --- a/docs/developer-docs/latest/guides/process-manager.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -title: Process Manager - Strapi Developer Docs -description: Learn in this guide how you can start a Strapi application using a process manager. -sidebarDepth: 2 -canonicalUrl: https://docs.strapi.io/developer-docs/latest/guides/process-manager.html ---- - -# Process manager - -!!!include(developer-docs/latest/guides/snippets/guide-not-updated.md)!!! - -In this guide we will see how you can start a Strapi application using a process manager. We will use [PM2](https://pm2.keymetrics.io/) in this example. - -## Install PM2 - -PM2 allows you to keep your Strapi project alive and to reload it without downtime. - -You will install PM2 globally - - - - -```sh -yarn global add pm2 -``` - - - -```sh -npm install pm2 -g -``` - - - - -## Basic usage - -### Starting with server.js file - -The basic usage to start an application with PM2 will be to run a command like this `pm2 start server.js`. - -But here we are facing an issue. In your project you don't have a `.js` file to run your Strapi application. - -So first let's create a `server.js` file that will let you run the `pm2` command. -::: note -TypeScript projects require additional code in the `server.js` file to identify the correct directory. See the following TypeScript code example or the [TypeScript documentation](/developer-docs/latest/development/typescript.md#start-strapi-programmatically) for additional details. -::: - - - - -```js -// path: `./server.js` - -const strapi = require('@strapi/strapi'); -strapi().start(); -``` - - - - -```js -// path: `./server.js` - -const strapi = require('@strapi/strapi'); -const app = strapi({ distDir: '' }); -app.start(); -``` - - - - -Now you will be able to start your server by running `pm2 start server.js`. - -### Starting with strapi command - -By default there are 2 important commands. - -- `yarn develop` to start your project in development mode. -- `yarn start` to start your app for production. - -You can also start your process manager using the `yarn start` command. - -`pm2 start npm --name app -- run start` - -## Configuration file - -PM2 lets you create a config file to save all information to start your server properly at anytime. - -By running `pm2 init` it will init an `ecosystem.config.js` in your application. - -Then replace the content of this file by the following code. - -```js -module.exports = { - apps: [ - { - name: 'app', - script: 'npm', - args: 'start', - }, - ], -}; -``` - -And then run `pm2 start ecosystem.config.js` to start the pm2 process. - -You can see the full documentation of available configuration in the [PM2 ecosystem file documentation](https://pm2.keymetrics.io/docs/usage/application-declaration/). diff --git a/docs/developer-docs/latest/guides/registering-a-field-in-admin.md b/docs/developer-docs/latest/guides/registering-a-field-in-admin.md deleted file mode 100644 index af9430786b..0000000000 --- a/docs/developer-docs/latest/guides/registering-a-field-in-admin.md +++ /dev/null @@ -1,406 +0,0 @@ ---- -title: Field Registering - Strapi Developer Docs -description: Learn in this guide how you can create a new Field for your admin panel. -canonicalUrl: https://docs.strapi.io/developer-docs/latest/guides/registering-a-field-in-admin.html ---- - -# Creating a new WYSIWYG field in the admin panel - -In this guide we will see how you can create a new field for the admin panel. - -For this example, we will replace the default WYSIWYG with [CKEditor](https://ckeditor.com/ckeditor-5/) in the Content Manager by creating a new plugin that will add a new field in your application. - -## Setting up the plugin - -1. Create a new project: - - :::: tabs card - - ::: tab yarn - - Create an application and prevent the server from starting automatically with the following command: - - ``` - yarn create strapi-app my-app --quickstart --no-run - ``` - - The `--no-run` flag was added as we will run additional commands to create a plugin right after the project generation. - - ::: - - ::: tab npx - - Create an application and prevent the server from starting automatically with the following command: - - ``` - npx create-strapi-app@latest my-app --quickstart --no-run - ``` - - The `--no-run` flag was added as we will run additional commands to create a plugin right after the project generation. - - ::: - - :::: - -2. Generate a plugin: - - :::: tabs card - - ::: tab yarn - - ``` - cd my-app - yarn strapi generate - ``` - - Choose "plugin" from the list, press Enter and name the plugin `wysiwyg`. - - ::: - - ::: tab npm - - ``` - cd my-app - npm run strapi generate - ``` - - Choose "plugin" from the list, press Enter and name the plugin `wysiwyg`. - - ::: - - :::: - -3. Enable the plugin by adding it to the [plugins configurations](/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md) file: - - ```js - // path: ./config/plugins.js - - module.exports = { - // ... - 'wysiwyg': { - enabled: true, - resolve: './src/plugins/wysiwyg' // path to plugin folder - }, - // ... - } - ``` - -4. Install the required dependencies: - - - - ```bash - cd src/plugins/wysiwyg - yarn add @ckeditor/ckeditor5-react @ckeditor/ckeditor5-build-classic - ``` - - - - ```bash - cd src/plugins/wysiwyg - npm install @ckeditor/ckeditor5-react @ckeditor/ckeditor5-build-classic - ``` - - - -5. Start the application with the front-end development mode: - - - - ```bash - # Go back to the application root folder - cd ../../.. - yarn develop --watch-admin - ``` - - - - ```bash - # Go back to the application root folder - cd ../../.. - npm run develop -- --watch-admin - ``` - - - -::: note NOTE -Launching the Strapi server in watch mode without creating a user account first will open `localhost:1337` with a JSON format error. Creating a user on `localhost:8081` prevents this alert. -::: - -We now need to create our new WYSIWYG, which will replace the default one in the Content Manager. - -## Creating the WYSIWYG - -In this part we will create 3 components: - -- a `MediaLib` component used to insert media in the editor -- an `Editor` component that uses [CKEditor](https://ckeditor.com/) as the WYSIWYG editor -- a `Wysiwyg` component to wrap the CKEditor - -The following code examples can be used to implement the logic for the 3 components: - -::: details Example of a MediaLib component used to insert media in the editor: - -```js -// path: ./src/plugins/wysiwyg/admin/src/components/MediaLib/index.js - -import React from 'react'; -import { prefixFileUrlWithBackendUrl, useLibrary } from '@strapi/helper-plugin'; -import PropTypes from 'prop-types'; - -const MediaLib = ({ isOpen, onChange, onToggle }) => { - const { components } = useLibrary(); - const MediaLibraryDialog = components['media-library']; - - const handleSelectAssets = files => { - const formattedFiles = files.map(f => ({ - alt: f.alternativeText || f.name, - url: prefixFileUrlWithBackendUrl(f.url), - mime: f.mime, - })); - - onChange(formattedFiles); - }; - - if(!isOpen) { - return null - }; - - return( - - ); -}; - -MediaLib.defaultProps = { - isOpen: false, - onChange: () => {}, - onToggle: () => {}, -}; - -MediaLib.propTypes = { - isOpen: PropTypes.bool, - onChange: PropTypes.func, - onToggle: PropTypes.func, -}; - -export default MediaLib; -``` - -::: - -::: details Example of an Editor component using CKEditor as the WYSIWYG editor: - -```js -// path: ./src/plugins/wysiwyg/admin/src/components/Editor/index.js - -import React from 'react'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import { CKEditor } from '@ckeditor/ckeditor5-react'; -import ClassicEditor from '@ckeditor/ckeditor5-build-classic'; -import { Box } from '@strapi/design-system/Box'; - -const Wrapper = styled(Box)` - .ck-editor__main { - min-height: ${200 / 16}em; - > div { - min-height: ${200 / 16}em; - } - // Since Strapi resets css styles, it can be configured here (h2, h3, strong, i, ...) - } -`; - -const configuration = { - toolbar: [ - 'heading', - '|', - 'bold', - 'italic', - 'link', - 'bulletedList', - 'numberedList', - '|', - 'indent', - 'outdent', - '|', - 'blockQuote', - 'insertTable', - 'mediaEmbed', - 'undo', - 'redo', - ], -}; - -const Editor = ({ onChange, name, value, disabled }) => { - return ( - - editor.setData(value || '')} - onChange={(event, editor) => { - const data = editor.getData(); - onChange({ target: { name, value: data } }); - }} - /> - - ); -}; - -Editor.defaultProps = { - value: '', - disabled: false -}; - -Editor.propTypes = { - onChange: PropTypes.func.isRequired, - name: PropTypes.string.isRequired, - value: PropTypes.string, - disabled: PropTypes.bool -}; - -export default Editor; -``` - -::: - -::: details Example of a Wysiwyg component wrapping CKEditor: - -```js -// path: ./src/plugins/wysiwyg/admin/src/components/Wysiwyg/index.js - -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { Stack } from '@strapi/design-system/Stack'; -import { Box } from '@strapi/design-system/Box'; -import { Button } from '@strapi/design-system/Button'; -import { Typography } from '@strapi/design-system/Typography'; -import Landscape from '@strapi/icons/Landscape'; -import MediaLib from '../MediaLib'; -import Editor from '../Editor'; -import { useIntl } from 'react-intl'; - -const Wysiwyg = ({ name, onChange, value, intlLabel, disabled, error, description, required }) => { - const { formatMessage } = useIntl(); - const [mediaLibVisible, setMediaLibVisible] = useState(false); - - const handleToggleMediaLib = () => setMediaLibVisible(prev => !prev); - - const handleChangeAssets = assets => { - let newValue = value ? value : ''; - - assets.map(asset => { - if (asset.mime.includes('image')) { - const imgTag = `

${asset.alt}

`; - - newValue = `${newValue}${imgTag}` - } - - // Handle videos and other type of files by adding some code - }); - - onChange({ target: { name, value: newValue } }); - handleToggleMediaLib(); - }; - - return ( - <> - - - - {formatMessage(intlLabel)} - - {required && - * - } - - - - {error && - - {formatMessage({ id: error, defaultMessage: error })} - - } - {description && - - {formatMessage(description)} - - } - - - - ); -}; - -Wysiwyg.defaultProps = { - description: '', - disabled: false, - error: undefined, - intlLabel: '', - required: false, - value: '', -}; - -Wysiwyg.propTypes = { - description: PropTypes.shape({ - id: PropTypes.string, - defaultMessage: PropTypes.string, - }), - disabled: PropTypes.bool, - error: PropTypes.string, - intlLabel: PropTypes.shape({ - id: PropTypes.string, - defaultMessage: PropTypes.string, - }), - name: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - required: PropTypes.bool, - value: PropTypes.string, -}; - -export default Wysiwyg; -``` - -::: - -## Registering the field - -The last step is to register the `wysiwyg` field with the `Wysiwyg` component using `addFields()`. Replace the content of the `admin/src/index.js` field of the plugin with the following code: - -```js -// path: ./src/plugins/wysiwyg/admin/src/index.js - -import pluginPkg from "../../package.json"; -import Wysiwyg from "./components/Wysiwyg"; -import pluginId from "./pluginId"; - -const name = pluginPkg.strapi.name; - -export default { - register(app) { - app.addFields({ type: 'wysiwyg', Component: Wysiwyg }); - - app.registerPlugin({ - id: pluginId, - isReady: true, - name, - }); - }, - bootstrap() {}, -}; -``` - -And _voilà_, if you [create a new collection type or single type](/user-docs/latest/content-types-builder/creating-new-content-type.md) with a [rich text field](/user-docs/latest/content-types-builder/configuring-fields-content-type.md#rich-text) you will see the implementation of [CKEditor](https://ckeditor.com/ckeditor-5/) instead of the default WYSIWYG: - -![Screenshot of Content Manager using CKEditor for rich text fields](../assets/guides/register-field-admin/ckeditor-rich-text.png) diff --git a/docs/developer-docs/latest/guides/scheduled-publication.md b/docs/developer-docs/latest/guides/scheduled-publication.md deleted file mode 100644 index 5676d3f2f2..0000000000 --- a/docs/developer-docs/latest/guides/scheduled-publication.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: Scheduled Publication - Strapi Developer Docs -description: Learn in this guide how to create an article schedule system. -canonicalUrl: https://docs.strapi.io/developer-docs/latest/guides/scheduled-publication.html ---- - -# Scheduled publication - -!!!include(developer-docs/latest/guides/snippets/guide-not-updated.md)!!! - -This guide will explain how to create an article schedule system. - -## Introduction - -What we want here is to be able to set a publication date for an article, and at this date, switch the `draft` state to `published`. - -## Example - -For this example, we will have to add a `publish_at` attribute to the **Article** Content Type. - -- Click on the Content-type Builder link in the left menu -- Select the **Article** Content Type -- Add another field - - `date` attribute named `publish_at` with `datetime` type - -And add some data with different dates and state to be able to see the publication happen. -Make sure to create some entries with a draft state and a `publish_at` that is before the current date. - -The goal will be to check every minute if there are draft articles that have a `publish_at` lower that the current date. - -## Create a CRON task - -To execute a function every minutes, we will use a CRON task. - -Here is the [full documentation](/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md) of this feature. If your CRON task requires to run based on a specific timezone then do look into the full documentation. - -**Path —** `./config/functions/cron.js` - -```js -module.exports = { - '*/1 * * * *': () => { - console.log('1 minute later'); - }, -}; -``` - -Make sure the enabled cron config is set to true in `./config/server.js` file. - -::: tip -Please note that Strapi's built in CRON feature will not work if you plan to use `pm2` or node based clustering. You will need to execute these CRON tasks outside of Strapi. -::: - -## Business logic - -Now we can start writing the publishing logic. The code that will fetch all `draft` **Articles** with a `publish_at` that is before the current date. - -Then we will update the `published_at` of all these articles. - -**Path —** `./config/functions/cron.js` - -```js -module.exports = { - '*/1 * * * *': async () => { - // fetch articles to publish - const draftArticleToPublish = await strapi.api.article.services.article.find({ - _publicationState: 'preview', // preview returns both draft and published entries - published_at_null: true, // so we add another condition here to filter entries that have not been published - publish_at_lt: new Date(), - }); - - // update published_at of articles - await Promise.all(draftArticleToPublish.map(article => { - return strapi.api.article.services.article.update( - { id: article.id }, - { published_at: new Date() } - ); - })); - }, -}; -``` - -And tada! diff --git a/docs/developer-docs/latest/guides/slug.md b/docs/developer-docs/latest/guides/slug.md deleted file mode 100644 index 611ad18e6c..0000000000 --- a/docs/developer-docs/latest/guides/slug.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -title: Slug System - Strapi Developer Docs -description: Learn in this guide how to create a slug system for a Post, Article or any Content Type you want in your Strapi project. -canonicalUrl: https://docs.strapi.io/developer-docs/latest/guides/slug.html ---- - -# Create a slug system - -!!!include(developer-docs/latest/guides/snippets/guide-not-updated.md)!!! - -This guide will explain how to create a slug system for a Post, Article or any Content Type you want. - -## Create attributes - -To start building your slug system you need a `string` field as a **base** for your slug, in this example we will use `title`. - -You will also need another `string` field that contains the slugified value of your `title`, in this case we will use `slug`. - -![Slug fields](../assets/guides/slug/fields.png) - -## Configure the layout for the content editor - -Let's configure the layout of the **edit page** to make it more user friendly for the content editor. - -- Click on the **Article** link in the left menu. -- Then on the `+ Add New Article` button. -- And finally on the `Configure the layout` button. - -Here we will be able to setup the `slug` field. - -- Click on the `slug` field. -- At the bottom of the page, edit the **placeholder** value to `Generated automatically based on the title`. -- And click **OFF** for **Editable field** option. -- Don't forget to save your updates. - -:::: tabs card - -::: tab View before - -![View before](../assets/guides/slug/layout-before.png) - -::: - -::: tab View after - -![View after](../assets/guides/slug/layout-after.png) - -::: - -::: tab View configuration - -![Edit View config](../assets/guides/slug/layout-config.png) - -::: - -:::: - -## Auto create/update the `slug` attribute - -For that you will have to install `slugify` node module in your application. - -When it's done, you have to update the lifecycle of the **Article** Content Type to auto complete the `slug` field. - -**Path —** `./src/api/[api-name]/content-types/[content-type-name]/lifecycles.js` - -:::: tabs card - -::: tab Bookshelf - -```js -const slugify = require('slugify'); - -module.exports = { - - async beforeCreate(event) { - if (event.params.data.title) { - event.params.data.slug = slugify(event.params.data.title, { lower: true }); - } - }, - async beforeUpdate(event) { - if (event.params.data.title) { - event.params.data.slug = slugify(event.params.data.title, { lower: true }); - } - }, -}; -``` - -::: - -:::: - -## Fetch article by `slug` - -Then you will be able to fetch your **Articles** by this slug. - -You will be able to find your articles by slug with this request `GET /articles?filters[slug]=my-article-slug` diff --git a/docs/developer-docs/latest/plugins/upload.md b/docs/developer-docs/latest/plugins/upload.md index fa6b5cb2ad..0a60c53843 100644 --- a/docs/developer-docs/latest/plugins/upload.md +++ b/docs/developer-docs/latest/plugins/upload.md @@ -90,7 +90,7 @@ You can pass configuration to the middleware directly by setting it in the [`bod ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: "strapi::body", @@ -104,7 +104,7 @@ module.exports = { }, }, // ... -}; +]; ``` @@ -114,7 +114,7 @@ module.exports = { ```js // path: ./config/middlewares.ts -export default { +export default [ // ... { name: "strapi::body", @@ -128,7 +128,7 @@ export default { }, }, // ... -}; +]; ``` diff --git a/docs/developer-docs/latest/plugins/users-permissions.md b/docs/developer-docs/latest/plugins/users-permissions.md index 8a57c95d11..93ead4641e 100644 --- a/docs/developer-docs/latest/plugins/users-permissions.md +++ b/docs/developer-docs/latest/plugins/users-permissions.md @@ -1,59 +1,123 @@ --- -title: Roles & Permissions - Strapi Developer Docs +title: Users & Permissions - Strapi Developer Docs description: Protect your API with a full authentication process based on JWT and manage the permissions between the groups of users. sidebarDepth: 2 canonicalUrl: https://docs.strapi.io/developer-docs/latest/plugins/users-permissions.html --- -# Roles & Permissions +# Users & Permissions -This plugin provides a way to protect your API with a full authentication process based on JWT. This plugin comes also with an ACL strategy that allows you to manage the permissions between the groups of users. +This plugin provides a full authentication process based on [JSON Web Tokens (JWT)](https://en.wikipedia.org/wiki/JSON_Web_Token) to protect your API. It also provides an access-control list (ACL) strategy that enables you to manage permissions between groups of users. -To access the plugin admin panel, click on the **Settings** link in the left menu and then everything will be under the **USERS & PERMISSIONS PLUGIN** section. +To access the plugin admin panel, click on the **Settings** link in the left menu of your Strapi application dashboard and under the **USERS & PERMISSIONS PLUGIN** section you will find sections for managing **Roles**, **Providers**, **Email Templates**, and **Advanced Settings**. ## Concept When this plugin is installed, it adds an access layer on your application. -The plugin uses [`jwt token`](https://en.wikipedia.org/wiki/JSON_Web_Token) to authenticate users. +The plugin uses `JWTs` to authenticate users. Your JWT contains your user ID, which is matched to the group your user is in and used to determine whether to allow access to the route. -Each time an API request is sent, the server checks if an `Authorization` header is present and verifies if the user making the request has access to the resource. - -To do so, your JWT contains your user ID and we are able to match the group your user is in and at the end to know if the group allows access to the route. +Each time an API request is sent the server checks if an `Authorization` header is present and verifies if the user making the request has access to the resource. ## Manage role permissions ### Public role -This role is used when you receive a request that doesn't have an `Authorization` header. -If you allow some permissions in this role, everybody will be able to access the endpoints you selected. -This is common practice to select `find` / `findOne` endpoints when you want your front-end application to access all the content without developing user authentication and authorization. +This is the default role used when the server receives a request without an `Authorization` header. Any permissions (i.e. accessible endpoints) granted to this role will be accessible by anyone. + +It is common practice to select `find` / `findOne` endpoints when you want your front-end application to access all the content without requiring user authentication and authorization. ### Authenticated role -This is the default role that is given to every **new user** if no role is provided at creation. In this role you will be able to define routes that a user can access. +This is the default role that is given to every **new user** at creation if no role is provided. In this role you define routes that a user can access. ### Permissions management -By clicking on the **Role** name, you will be able to see all functions available in your application (and these functions are related to a specific route) +By clicking on the **Role** name, you can see all functions available in your application (with these functions related to the specific route displayed). -If you check a function name, it makes this route accessible by the current role you are editing. -On the right sidebar you will be able to see the URL related to this function. +If you check a function name, it makes this route accessible by the current role you are editing. On the right sidebar you can see the URL related to this function. ### Update the default role -When you create a user without a role or if you use the `/api/auth/local/register` route, the `authenticated` role is given to the user. +When you create a user without a role, or if you use the `/api/auth/local/register` route, the `authenticated` role is given to the user. To change the default role, go to the `Advanced settings` tab and update the `Default role for authenticated users` option. ## Authentication +### Login + +Submit the user's identifier and password credentials for authentication. On successful authentication the response data will have the user's information along with an authentication token. + +#### Local + +The `identifier` param can be an **email** or **username**. + +:::: tabs card + +::: tab axios + +```js +import axios from 'axios'; + +// Request API. +axios + .post('http://localhost:1337/api/auth/local', { + identifier: 'user@strapi.io', + password: 'strapiPassword', + }) + .then(response => { + // Handle success. + console.log('Well done!'); + console.log('User profile', response.data.user); + console.log('User token', response.data.jwt); + }) + .catch(error => { + // Handle error. + console.log('An error occurred:', error.response); + }); +``` + +::: + +::: tab Postman + +If you use **Postman**, set the **body** to **raw** and select **JSON** as your data format: + +```json +{ + "identifier": "user@strapi.io", + "password": "strapiPassword" +} +``` + +If the request is successful you will receive the **user's JWT** in the `jwt` key: + +```json +{ + "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTc2OTM4MTUwLCJleHAiOjE1Nzk1MzAxNTB9.UgsjjXkAZ-anD257BF7y1hbjuY3ogNceKfTAQtzDEsU", + "user": { + "id": 1, + "username": "user", + ... + } +} +``` + +::: + +:::: + ### Token usage -A jwt token may be used for making permission-restricted API requests. To make an API request as a user, place the jwt token into an `Authorization` header of the GET request. A request without a token, will assume the `public` role permissions by default. Modify the permissions of each user's role in admin dashboard. Authentication failures return a 401 (unauthorized) error. +The `jwt` may then be used for making permission-restricted API requests. To make an API request as a user place the JWT into an `Authorization` header of the `GET` request. + +Any request without a token will assume the `public` role permissions by default. Modify the permissions of each user's role in the admin dashboard. + +Authentication failures return a `401 (unauthorized)` error. #### Usage -- The `token` variable is the `data.jwt` received when logging in or registering. +The `token` variable is the `data.jwt` received when logging in or registering. ```js import axios from 'axios'; @@ -80,12 +144,13 @@ axios ### JWT configuration You can configure the JWT generation by using the [plugins configuration file](/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md). -We are using [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) to generate the JWT. + +Strapi uses [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) to generate the JWT. Available options: - `jwtSecret`: random string used to create new JWTs, typically set using the `JWT_SECRET` [environment variable](/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md#strapi-s-environment-variables). -- `jwt.expiresIn`: expressed in seconds or a string describing a time span zeit/ms.
+- `jwt.expiresIn`: expressed in seconds or a string describing a time span.
Eg: 60, "45m", "10h", "2 days", "7d", "2y". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (minutes, hours, days, years, etc), otherwise milliseconds unit is used by default ("120" is equal to "120ms"). @@ -133,7 +198,7 @@ export default ({ env }) => ({ :::warning -Setting JWT expiry for more than 30 days is **absolutely not recommended** due to massive security concerns. +Setting JWT expiry for more than 30 days is **not recommended** due to security concerns. ::: ### Registration @@ -165,60 +230,32 @@ axios }); ``` -### Login - -Submit the user's identifier and password credentials for authentication. When the authentication is successful, the response data returned will have the user's information along with a jwt authentication token. - -#### Local - -- The `identifier` param can either be an **email** or a **username**. - -```js -import axios from 'axios'; - -// Request API. -axios - .post('http://localhost:1337/api/auth/local', { - identifier: 'user@strapi.io', - password: 'strapiPassword', - }) - .then(response => { - // Handle success. - console.log('Well done!'); - console.log('User profile', response.data.user); - console.log('User token', response.data.jwt); - }) - .catch(error => { - // Handle error. - console.log('An error occurred:', error.response); - }); -``` - ### Providers -Thanks to [Grant](https://github.com/simov/grant) and [Purest](https://github.com/simov/purest), you can easily use OAuth and OAuth2 providers to enable authentication in your application. +Thanks to [Grant](https://github.com/simov/grant) and [Purest](https://github.com/simov/purest), you can use OAuth and OAuth2 providers to enable authentication in your application. -For better understanding, you may find as follows the description of the login flow. To simplify the explanation, we used `github` as the provider but it works the same for the other providers. +For better understanding, review the following description of the login flow. We use `github` as the provider but it works the same for other providers. #### Understanding the login flow -Let's say that strapi's backend is located at: strapi.website.com. -Let's say that your app frontend is located at: website.com. +Let's say that: +* Strapi's backend is located at: `strapi.website.com`, and +* Your app frontend is located at: `website.com` -1. The user goes on your frontend app (`https://website.com`) and click on your button `connect with Github`. -2. The frontend redirect the tab to the backend URL: `https://strapi.website.com/api/connect/github`. +1. The user goes on your frontend app (`https://website.com`) and clicks on your button `connect with Github`. +2. The frontend redirects the tab to the backend URL: `https://strapi.website.com/api/connect/github`. 3. The backend redirects the tab to the GitHub login page where the user logs in. 4. Once done, Github redirects the tab to the backend URL:`https://strapi.website.com/api/connect/github/callback?code=abcdef`. -5. The backend uses the given `code` to get from Github an `access_token` that can be used for a period of time to make authorized requests to Github to get the user info (the email of the user of example). -6. Then, the backend redirects the tab to the url of your choice with the param `access_token` (example: `http://website.com/connect/github/redirect?access_token=eyfvg`) -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) +5. The backend uses the given `code` to get an `access_token` from Github that can be used for a period of time to make authorized requests to Github to get the user info. +6. Then, the backend redirects the tab to the url of your choice with the param `access_token` (example: `http://website.com/connect/github/redirect?access_token=eyfvg`). +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). #### Setting up the server url -Before setting up a provider, you need to specify the absolute url of your backend in `server.js`. +Before setting up a provider you must specify the absolute url of your backend in `server.js`. **example -** `config/server.js` @@ -255,12 +292,12 @@ export default ({ env }) => ({ :::tip -Later on you will give this url to your provider.
For development, some providers accept the use of localhost urls but many don't. In this case we recommend to use [ngrok](https://ngrok.com/docs) (`ngrok http 1337`) that will make a proxy tunnel from a url it created to your localhost url (ex: `url: env('', 'https://5299e8514242.ngrok.io'),`). +Later you will give this url to your provider.
For development, some providers accept the use of localhost urls but many don't. In this case we recommend to use [ngrok](https://ngrok.com/docs) (`ngrok http 1337`) that will make a proxy tunnel from a url it created to your localhost url (ex: `url: env('', 'https://5299e8514242.ngrok.io'),`). ::: #### Setting up the provider - examples -Instead of a generic explanation, for better understanding, we decided to show an example for each provider. +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).
It (the frontend app) will be running on `http://localhost:3000`.
@@ -782,28 +819,30 @@ Now you can make authenticated requests 🎉 More info here: [token usage](#toke ::: tab Forgot & Reset flow -The flow was thought this way: +The assumed general flow: -1. The user goes to your **forgotten password page** -2. The user enters his/her email address -3. Your forgotten password page sends a request to the backend to send an email with the reset password link to the user -4. The user receives the email, and clicks on the special link -5. The link redirects the user to your **reset password page** -6. The user enters his/her new password -7. The **reset password page** sends a request to the backend with the new password -8. If the request contains the code contained in the link at step 3., the password is updated -9. The user can log in with the new password +1. The user goes to your **forgotten password page**. +2. The user enters their email address. +3. Your forgotten password page sends a request to the backend to send an email with the reset password link to the user. +4. The user receives the email and clicks on the special link. +5. The link redirects the user to your **reset password page**. +6. The user enters their new password. +7. The **reset password page** sends a request to the backend with the new password. +8. If the request contains the code contained in the link at step 3, the password is updated. +9. The user can log in with the new password. In the following section we will detail steps 3. and 7.. #### Forgotten password: ask for the reset password link -This action sends an email to a user with the link to your own reset password page. -The link will be enriched with the url param `code` that is needed for the [reset password](#reset-password) at step 7.. +This action sends an email to a user with the link to your reset password page. The link will be enriched with the url param `code` that is needed for the [reset password](#reset-password) at step 7. + +First, you must specify the following: -First, you must specify the url to your reset password page in the admin panel: **Settings > USERS & PERMISSIONS PLUGIN > Advanced Settings > Reset Password Page**. +* In the admin panel: **Settings > USERS & PERMISSIONS PLUGIN > Advanced Settings > Reset Password** page, the `url` to your reset password page. +* In the admin panel: **Settings > USERS & PERMISSIONS PLUGIN > Email Template** page, the **Shipper email**. -Then, your **forgotten password page** has to make the following request to your backend. +Then, your **forgotten password page** has to make the following request to your backend: ```js import axios from 'axios'; @@ -824,9 +863,9 @@ axios #### Reset Password: send the new password This action will update the user password. -Also works with the [GraphQL Plugin](./graphql.md), with the `resetPassword` mutation. +This also works with the [GraphQL Plugin](./graphql.md), with the `resetPassword` mutation. -Your **reset password page** has to make the following request to your backend. +Your **reset password page** has to make the following request to your backend: ```js import axios from 'axios'; @@ -866,7 +905,7 @@ axios.post( }, { headers: { - Authorization: 'Bearer ', + Authorization: 'Bearer ', }, } ); @@ -882,11 +921,11 @@ axios.post( In production, make sure the `url` config property is set. Otherwise the validation link will redirect to `localhost`. More info on the config [here](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md). ::: -After having registered, if you have set **Enable email confirmation** to **ON**, the user will receive a confirmation link by email. The user has to click on it to validate his/her registration. +After registering, if you have set **Enable email confirmation** to **ON**, the user will receive a confirmation link by email. The user has to click on it to validate their registration. _Example of the confirmation link:_ `https://yourwebsite.com/api/auth/email-confirmation?confirmation=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaWF0IjoxNTk0OTgxMTE3LCJleHAiOjE1OTc1NzMxMTd9.0WeB-mvuguMyr4eY8CypTZDkunR--vZYzZH6h6sChFg` -If needed, you can re-send the confirmation email by making the following request. +If needed you can re-send the confirmation email by making the following request: ```js import axios from 'axios'; @@ -908,9 +947,7 @@ axios The `user` object is available to successfully authenticated requests. -#### Usage - -- The authenticated `user` object is a property of `ctx.state`. +The authenticated `user` object is a property of `ctx.state`. ```js create: async ctx => { @@ -928,6 +965,8 @@ create: async ctx => { }; ``` + +--> ## Templating emails -By default, this plugin comes with only two templates (reset password and email address confirmation) at the moment. More templates will come later. The templates use Lodash's template() method to populate the variables. +By default this plugin comes with two templates: reset password and email address confirmation. The templates use Lodash's `template()` method to populate the variables. You can update these templates under **Plugins** > **Roles & Permissions** > **Email Templates** tab in the admin panel. @@ -1075,7 +1113,9 @@ You can update these templates under **Plugins** > **Roles & Permissions** > **E ## Security configuration -JWT tokens can be verified and trusted because the information is digitally signed. To sign a token a _secret_ is required. By default Strapi generates one that is stored in `./extensions/users-permissions/config/jwt.js`. This is useful during development but for security reasons it is **recommended** to set a custom token via an environment variable `JWT_SECRET` when deploying to production. +JWTs can be verified and trusted because the information is digitally signed. To sign a token a _secret_ is required. By default Strapi generates and stores it in `./extensions/users-permissions/config/jwt.js`. + +This is useful during development but for security reasons it is **recommended** to set a custom token via an environment variable `JWT_SECRET` when deploying to production. By default you can set a `JWT_SECRET` environment variable and it will be used as secret. If you want to use another variable you can update the configuration file. @@ -1108,5 +1148,5 @@ export default { ::: tip -You can learn more on configuration in the documentation [here](/developer-docs/latest/setup-deployment-guides/configurations.md). +You can learn more about configuration [here](/developer-docs/latest/setup-deployment-guides/configurations.md). ::: diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md index cde00eaaf6..95d5f7be50 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md @@ -177,7 +177,7 @@ For a full list of available options for `koa-body`, check the [koa-body documen ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: 'strapi::body', @@ -189,7 +189,7 @@ module.exports = { }, }, // ... -} +] ``` ::: @@ -211,7 +211,7 @@ The `compression` middleware is based on [koa-compress](https://github.com/koajs ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: 'strapi::compression', @@ -220,7 +220,7 @@ module.exports = { }, }, // ... -} +] ``` ::: @@ -243,7 +243,7 @@ This security middleware is about cross-origin resource sharing (CORS) and is ba ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: 'strapi::cors', @@ -255,7 +255,7 @@ module.exports = { }, }, // ... -} +] ``` ::: @@ -280,7 +280,7 @@ The `favicon` middleware serves the favicon and is based on [koa-favicon](https: ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: 'strapi::favicon', @@ -289,7 +289,7 @@ module.exports = { }, }, // ... -} +] ``` ::: @@ -312,7 +312,7 @@ The `whitelist` and `blacklist` options support wildcards (e.g. `whitelist: ['19 ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: 'strapi::ip', @@ -322,7 +322,7 @@ module.exports = { }, }, // ... -} +] ``` ::: @@ -345,7 +345,7 @@ const { formats: { prettyPrint, levelFilter }, } = require('@strapi/logger'); -module.exports = { +module.exports = [ transports: [ new winston.transports.Console({ level: 'http', @@ -355,7 +355,7 @@ module.exports = { ), }), ], -}; +]; ``` ::: @@ -373,7 +373,7 @@ The `poweredBy` middleware adds a `X-Powered-By` parameter to the response heade ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: 'strapi::poweredBy', @@ -382,7 +382,7 @@ module.exports = { }, }, // ... -} +] ``` ::: @@ -402,7 +402,7 @@ The `query` middleware is a query parser based on [qs](https://github.com/ljharb ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: 'strapi::query', @@ -412,7 +412,7 @@ module.exports = { }, }, // ... -} +] ``` ::: @@ -444,7 +444,7 @@ You can customize the path of the public folder by editing the [server configura ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: 'strapi::public', @@ -454,7 +454,7 @@ module.exports = { }, }, // ... -} +] ``` ::: @@ -490,7 +490,7 @@ The default directives include a `dl.airtable.com` value. This value is set for ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: 'strapi::security', @@ -519,7 +519,7 @@ module.exports = { }, }, // ... -} +] ``` ::: @@ -546,7 +546,7 @@ The `session` middleware allows the use of cookie-based sessions, based on [koa- ```js // path: ./config/middlewares.js -module.exports = { +module.exports = [ // ... { name: 'strapi::session', @@ -556,7 +556,7 @@ module.exports = { }, }, // ... -} +] ``` ::: diff --git a/docs/developer-docs/latest/setup-deployment-guides/deployment.md b/docs/developer-docs/latest/setup-deployment-guides/deployment.md index 376bb82761..290571b1f2 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/deployment.md +++ b/docs/developer-docs/latest/setup-deployment-guides/deployment.md @@ -304,3 +304,16 @@ Additional guides for optional software additions that compliment or improve the + +
+ + + + + +
diff --git a/docs/developer-docs/latest/setup-deployment-guides/deployment/optional-software/process-manager.md b/docs/developer-docs/latest/setup-deployment-guides/deployment/optional-software/process-manager.md new file mode 100644 index 0000000000..bef2c9ebe3 --- /dev/null +++ b/docs/developer-docs/latest/setup-deployment-guides/deployment/optional-software/process-manager.md @@ -0,0 +1,150 @@ +--- +title: Process Manager - Strapi Developer Docs +description: How to install and start a Strapi application using a process manager. +sidebarDepth: 2 +canonicalUrl: https://docs.strapi.io/developer-docs/latest/guides/process-manager.html +--- + +# Process manager + +Process managers allow you to keep your Strapi application running and to reload it without downtime. The following documentation uses the [PM2](https://pm2.keymetrics.io/) process manager and describes: + +- installing PM2, +- starting Strapi using a `server.js` file, +- starting Strapi using the `strapi` command, +- starting and managing Strapi using an `ecosystem.config.js` file. + +The appropriate procedure for starting PM2 depends on the hosting provider and your application configuration. + +## Install PM2 + +Install PM2 globally: + + + + +```sh +yarn global add pm2 +``` + + + +```sh +npm install pm2 -g +``` + + + + +## Start PM2 with a `server.js` file + +The basic usage to start an application with PM2 is to run a command such as `pm2 start server.js`. To configure and run your application: + +1. Create a `server.js` file at the application root. +2. Add the following code snippet to the `server.js` file: + + + + + ```js + // path: `./server.js` + + const strapi = require('@strapi/strapi'); + strapi().start(); + ``` + + + + + ```js + // path: `./server.js` + + const strapi = require('@strapi/strapi'); + const app = strapi({ distDir: '' }); + app.start(); + ``` + + + + +3. Start the server by running `pm2 start server.js` in the project root directory. + +::: note +TypeScript projects require additional code in the `server.js` file to identify the correct directory. See the previous TypeScript code example or the [TypeScript documentation](/developer-docs/latest/development/typescript.md#start-strapi-programmatically) for additional details. +::: + +## Start PM2 with the `strapi` command + +To start PM2 and your application from a terminal you should start PM2 and pass the application name and start command as arguments: + + + + +```sh +pm2 start yarn --name app --start + +``` + + + + + +```sh +pm2 start npm --name app -- run start + +``` + + + + +## Start and configure PM2 with a `config.js` file + +A PM2 configuration file allows you to save the information necessary to start your server properly at any time. This is commonly used for cloud hosting providers, where you might not have access to a terminal window to start the server. To use a configuration file: + +1. Run `pm2 init` at the application root to create an `ecosystem.config.js` file. +2. Replace the `ecosystem.config.js` file content with the following code example: + + + + + + ```js + // path: ./ecosystem.config.js + + module.exports = { + apps: [ + { + name: 'app', + script: 'yarn', + args: 'start', + }, + ], + }; + ``` + + + + + + ```js + // path: ./ecosystem.config.js + + module.exports = { + apps: [ + { + name: 'app', + script: 'npm', + args: 'start', + }, + ], + }; + ``` + + + + +3. Run `pm2 start ecosystem.config.js` to start the PM2 process. + +::: note +The `ecosystem.config.js` code example is the minimum configuration. The [PM2 ecosystem file documentation](https://pm2.keymetrics.io/docs/usage/application-declaration/) provides all of the configuration options. +::: diff --git a/docs/developer-docs/latest/update-migration-guides/migration-guides.md b/docs/developer-docs/latest/update-migration-guides/migration-guides.md index 7696bf65de..d430e206a1 100644 --- a/docs/developer-docs/latest/update-migration-guides/migration-guides.md +++ b/docs/developer-docs/latest/update-migration-guides/migration-guides.md @@ -20,6 +20,7 @@ Migrations are necessary when upgrades to Strapi include breaking changes. The m - [Migration guide from 4.1.8+ to 4.1.10](migration-guides/v4/migration-guide-4.1.8-to-4.1.10.md) - [Migration guide from 4.2.x to 4.3.x](migration-guides/v4/migration-guide-4.2.x-to-4.3.x.md) - [Migration guide from 4.3.6 to 4.3.8](migration-guides/v4/migration-guide-4.3.6-to-4.3.8.md) +- [Migration guide from 4.4.3 to 4.4.5](migration-guides/v4/migration-guide-4.4.3-to-4.4.5.md) ## v3 to v4 migration guides diff --git a/docs/developer-docs/latest/update-migration-guides/migration-guides/v4/code/frontend/theming.md b/docs/developer-docs/latest/update-migration-guides/migration-guides/v4/code/frontend/theming.md index 3abbe22c02..1e30c900b8 100644 --- a/docs/developer-docs/latest/update-migration-guides/migration-guides/v4/code/frontend/theming.md +++ b/docs/developer-docs/latest/update-migration-guides/migration-guides/v4/code/frontend/theming.md @@ -18,7 +18,7 @@ Strapi v4 introduces the [Strapi Design System](https://design-system.strapi.io/ To customize the theme in Strapi v4: 1. Rename the `./src/admin/app.example.js` file to `./src/admin/app.js`. -2. In `./src/admin/app.js`, declare new key/value pairs in the `config.theme` object, updating the design elements (e.g. colors, shadows, sizes) of the [default theme](https://github.com/strapi/design-system/blob/main/packages/strapi-design-system/src/themes/light-theme.js). +2. In `./src/admin/app.js`, declare new key/value pairs in the `config.theme` object, updating the design elements (e.g. colors, shadows, sizes) of the [default theme](https://github.com/strapi/design-system/tree/main/packages/strapi-design-system/src/themes/lightTheme). ::: details Example of theme customization in Strapi v4: @@ -37,6 +37,7 @@ export default { ::: -::: tip Customization tip -The [Strapi Design System](https://design-system.strapi.io/) is fully customizable. +::: tip Customization tips +* The [Strapi Design System](https://design-system.strapi.io/) is fully customizable. +* Strapi v4 supports light and dark modes. See [admin customization](/developer-docs/latest/development/admin-customization.md#theme-extension) documentation for more details. ::: diff --git a/docs/developer-docs/latest/update-migration-guides/migration-guides/v4/migration-guide-4.4.3-to-4.4.5.md b/docs/developer-docs/latest/update-migration-guides/migration-guides/v4/migration-guide-4.4.3-to-4.4.5.md new file mode 100644 index 0000000000..161c92c1e8 --- /dev/null +++ b/docs/developer-docs/latest/update-migration-guides/migration-guides/v4/migration-guide-4.4.3-to-4.4.5.md @@ -0,0 +1,47 @@ +--- +title: Migrate from 4.4.3 to 4.3.5 - Strapi Developer Docs +description: Learn how you can migrate your Strapi application from 4.4.3 to 4.4.5. +canonicalUrl: https://docs.strapi.io/developer-docs/latest/update-migration-guides/migration-guides/v4/migration-guide-4.4.3-to-4.4.5.html +--- + +# v4.4.3 to v4.4.5 migration guide + +The Strapi v4.4.3 to v4.4.5 migration guide upgrades v4.4.3 to v4.4.5. The migration changes the name of the favicon from `favicon.ico` to `favicon.png`. + +:::caution +This migration guide skips v4.4.4, which introduced a problem in `koa/cors` due to a missing dependency update. +::: + +## Upgrading the application dependencies + +:::prerequisites +Stop the server before starting the upgrade. +::: + +1. Upgrade all of the Strapi packages in `package.json` to `4.4.5`: + + ```json + // path: package.json + + { + // ... + "dependencies": { + "@strapi/strapi": "4.4.5", + "@strapi/plugin-users-permissions": "4.4.5", + "@strapi/plugin-i18n": "4.4.5", + // ... + } + } + ``` + +2. Save the edited `package.json` file. + +3. Replace the existing `favicon.ico` with [`favicon.png`](https://user-images.githubusercontent.com/8593673/198366643-7261700d-c8c4-4ebb-83c8-792a330ab4a5.png): + +4. Run either `yarn` or `npm install` to install the new version. + +::: tip +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. +::: + +!!!include(developer-docs/latest/update-migration-guides/migration-guides/v4/snippets/Rebuild-and-start-snippet.md)!!! diff --git a/docs/package.json b/docs/package.json index 2c8302ff75..2e8b792a34 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "strapi-docs", - "version": "4.4.4", + "version": "4.4.6", "main": "index.js", "scripts": { "dev": "yarn create:config-file && vuepress dev", diff --git a/docs/user-docs/latest/content-manager/saving-and-publishing-content.md b/docs/user-docs/latest/content-manager/saving-and-publishing-content.md index 50041a724d..a072286219 100644 --- a/docs/user-docs/latest/content-manager/saving-and-publishing-content.md +++ b/docs/user-docs/latest/content-manager/saving-and-publishing-content.md @@ -32,6 +32,10 @@ When a content is not a draft anymore, but has been published, it is indicated o ![Editing published version](../assets/content-manager/editing_published_version.png) +::: tip +To schedule publication, i.e. convert a draft to a published entry at a given date and time, you can follow [this technical guide](https://forum.strapi.io/t/schedule-publications/23184) which requires adding custom code to the Strapi application. +::: + ### Unpublishing content Published contents can be unpublished, switching back to being drafts again. @@ -51,4 +55,4 @@ You can delete entries from the list view of a collection type, by clicking on t ::: caution If the [Internationalization plugin](/user-docs/latest/plugins/strapi-plugins.md#internationalization-plugin) is installed, entries can only be deleted one locale at the time. -::: \ No newline at end of file +::: diff --git a/docs/user-docs/latest/content-types-builder/configuring-fields-content-type.md b/docs/user-docs/latest/content-types-builder/configuring-fields-content-type.md index ff327ec9c6..0357edde79 100644 --- a/docs/user-docs/latest/content-types-builder/configuring-fields-content-type.md +++ b/docs/user-docs/latest/content-types-builder/configuring-fields-content-type.md @@ -395,12 +395,16 @@ The UID field displays a field that sets a unique identifier, optionally based o :::: +::: tip +The UID field can be used to create a slug based on the Attached field. +::: + ## Custom fields Custom fields are a way to extend Strapi’s capabilities by adding new types of fields to content-types or components. Once installed (see [Marketplace](/user-docs/latest/plugins/installing-plugins-via-marketplace.md) documentation), custom fields are listed in the _Custom_ tab when selecting a field for a content-type. -Each custom field type can have basic and advanced settings. The [Marketplace](https://market.strapi.io/) lists available custom fields, and hosts dedicated documentation for each custom field, including specific settings. +Each custom field type can have basic and advanced settings. The [Marketplace](https://market.strapi.io/plugins?categories=Custom+fields) lists available custom fields, and hosts dedicated documentation for each custom field, including specific settings. ## Components diff --git a/formatting_style_guide.pdf b/formatting_style_guide.pdf new file mode 100644 index 0000000000..a60576ce84 Binary files /dev/null and b/formatting_style_guide.pdf differ