diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml index 8c69e14ca9..c9c95e1110 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml @@ -1,7 +1,5 @@ -name: Documentation Bug Report +name: Documentation Issue description: Create a report to help us improve the Strapi documentation. -title: "[Bug]: " -labels: "type: bug" body: - type: input attributes: diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 0000000000..9b548fe200 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,7 @@ +{ + "default": true, + "no-hard-tabs": true, + "MD025": false, + "MD013": false, + "MD033": false +} diff --git a/.sclt-changelog.json b/.sclt-changelog.json index 1d150c26f3..87ba59f23d 100644 --- a/.sclt-changelog.json +++ b/.sclt-changelog.json @@ -3,7 +3,7 @@ "labelsToHeadline": { "new content": "✨ New content", "updated content": "πŸ–Œ Updated content", - "chore": "🧹 Chore, fixes, typos, and other improvements" + "fix": "🧹 Fixes, typos, chore, and other improvements" }, "footer": "
Thanks a lot for your contributions! πŸ₯°πŸš€" } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 142bb03ccb..d65d7b18b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,10 @@ Contributing to the Strapi documentation implies 2 steps: 2. [Submit a pull request](#-pull-requests) for review. +*** +*Please note*: The Strapi Documentation team is currently not accepting new deployment or integration guides. These guides will soon be moved to another resource and probably only maintained by the Strapi Community while the Strapi Documentation team will focus on documenting only core Strapi products and features. Thank you for your understanding πŸ™ +*** + ## πŸ¦– Docusaurus Strapi’s documentation is built with the React- and Markdown-based [Docusaurus](https://docusaurus.io) framework. @@ -45,6 +49,8 @@ The Strapi Documentation team has created a complete style guide for you to make πŸ’ While writing, please consider the [12 Rules of Technical Writing](https://handbook.strapi.io/user-success-manual/12-rules-of-technical-writing) that the Strapi Documentation team will use to assess the quality and consistency of the contribution. 😊 +**Important:** If you use an automatic formatter in your code editor (e.g., Prettier), **please disable it** as it could add unnecessary changes to your pull request or cause rendering issues because some Markdown formatting is based on whitespace. + ### Working locally: Set up the project To set up the Docusaurus project on your machine, perform the following steps from a terminal instance: diff --git a/README.md b/README.md index 2ab9c9967d..ad59a15912 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,27 @@ --- -If you are looking for the Strapi code, please see the [Strapi Monorepo](https://github.com/strapi/strapi). This Repo is only for our [documentation](https://docs.strapi.io). +If you are looking for the Strapi code, please see the [Strapi Monorepo](https://github.com/strapi/strapi). The present repository is only for the official Strapi documentation hosted at [docs.strapi.io](https://docs.strapi.io). Blog articles hosted at [strapi.io/blog](https://strapi.io/blog) and [community content](https://github.com/strapi/community-content) are not covered by the present repository either. --- -Strapi is a free and open-source headless CMS delivering your content anywhere you need. +Strapi is a free and open-source headless CMS that delivers your content anywhere you need. - **Keep control over your data**. With Strapi, you know where your data is stored, and you keep full control at all times. - **Self-hosted**. You can host and scale Strapi projects the way you want. You can choose any hosting platform you want: AWS, Netlify, Heroku, a VPS, or a dedicated server. You can scale as you grow, 100% independent. - **Database agnostic**. You can choose the database you prefer. Strapi works with SQL databases: PostgreSQL, MySQL, MariaDB, and SQLite. - **Customizable**. You can quickly build your logic by fully customizing APIs, routes, or plugins to fit your needs perfectly. +## Documentation websites + +The main official Strapi documentation website is hosted at [docs.strapi.io](https://docs.strapi.io). + +Additional official Strapi documentation websites cover different content: + +- Documentation for the older, unsupported version 3 of Strapi is hosted at [docs-v3.strapi.io](https://docs-v3.strapi.io). +- Upcoming and/or experimental documentation is hosted at [docs-next.strapi.io](https://docs-next.strapi.io). Starting autumn/winter 2023, this website will include in-progress documentation for Strapi v5. +- Documentation for contributors, which include more in-depth and experimental content for developers who actively maintain and contribute to Strapi, is hosted at [contributor.strapi.io](https://contributor.strapi.io). This experimental documentation is maintained by Strapi engineers and any issue should be reported to the [`strapi/strapi`](https://github.com/strapi/strapi/issues/new/choose) repository. + ## Contribution requirements The following are required if you are submitting pull requests to the documentation: @@ -30,15 +40,11 @@ The following are required if you are submitting pull requests to the documentat πŸ‘‰ For more information on how to contribute, please read our [contribution guide](./CONTRIBUTING.md). -## 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. - -## Bugs +## Issues -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: +Submitting issues help to improve the documentation. If you find incorrect screenshot(s), instructions, or code examples, or if you discover UX/UI bugs on the website, please use our [documentation issue](https://github.com/strapi/documentation/issues/new?template=BUG_REPORT.yml) template to report them. To submit an issue: -1. 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. +1. Verify the issue is only with one of the Strapi documentation websites. Product-related issues, missing Strapi features, and any issues not directly related to the documentation should be directed at the main [strapi/strapi](https://github.com/strapi/strapi) repository. Updates to blog posts found at [strapi.io/blog](https://strapi.io/blog) are also outside the scope of the present documentation repository; you can either reach the author of the blog post directly or use the [contact form](https://strapi.io/contact) on Strapi's corporate website if you have questions about blog posts. 2. Follow the issue template and fill out as much information as you can. Technical questions should be asked using the following resources: @@ -46,6 +52,10 @@ Technical questions should be asked using the following resources: - Open a Q&A thread on our [official forum](https://forum.strapi.io). - Engage with other community members on our [community Discord server](https://discord.strapi.io). +## 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. These requests will be checked by the Strapi Documentation team then transferred to another internal tool outside GitHub for priorization and subsequent actions. + ## Releases Thanks to a continuous integration/continuous delivery workflow, the Strapi documentation can be updated up to several times a day, depending on the number and frequency of contributions. Weekly snapshot [releases](https://github.com/strapi/documentation/releases) happen on Wednesdays and include a list of changes since the latest release. diff --git a/docusaurus/docs/cloud/account/account-billing.md b/docusaurus/docs/cloud/account/account-billing.md new file mode 100644 index 0000000000..ae45d1ec7b --- /dev/null +++ b/docusaurus/docs/cloud/account/account-billing.md @@ -0,0 +1,72 @@ +--- +title: Account billing & invoices +displayed_sidebar: cloudSidebar +description: Manage billing details and invoices for Strapi Cloud account. +canonicalUrl: https://docs.strapi.io/cloud/account/account-billing.html +--- + +import InvoiceStatus from '/docs/snippets/invoices-statuses.md' + +# Account billing & invoices + +Through the *Profile* page, accessible by clicking on your profile picture on the top right hand corner of the interface then clicking on **Profile**, you can access the [![Billing icon](/img/assets/icons/CreditCard.svg) *Billing*](#account-billing) and [![Invoices icon](/img/assets/icons/Invoice.svg) *Invoices*](#account-invoices) tabs. + +## Account billing + +The ![Billing icon](/img/assets/icons/CreditCard.svg) *Billing* tab displays and enables you to modify the billing details and payment method set for the account. + + + +The *Payment method* section of the ![Billing icon](/img/assets/icons/CreditCard.svg) *Billing* tab allows you to manage the credit cards that can be used for the Strapi Cloud projects. The *Billing details* section requires to be filled in, at least for the mandatory fields, as this information will be the default billing details for all Strapi Cloud projects related to your account. + +### Adding a new credit card + +1. In the *Payment method* section of the ![Billing icon](/img/assets/icons/CreditCard.svg) *Billing* tab, click on the **Add card** button. +2. Fill in the following fields: + +| Field name | Description | +| --- | --- | +| Card Number | Write the number of the credit card to add as payment method. | +| Expires | Write the expiration date of the credit card. | +| CVC | Write the 3-numbers code displayed at the back of the credit card. | + +3. Click on the **Save** button. + +:::tip +The first credit card to be added as payment method for the account will by default be the primary one. It is however possible to define another credit card as primary by clicking on the ![Menu icon](/img/assets/icons/more.svg) icon, then **Switch as primary**. +::: + +### Deleting a credit card + +To remove a credit card from the list of payment methods for the account: + +1. Click on the ![Menu icon](/img/assets/icons/more.svg) icon of the credit card you wish to delete. +2. Click **Remove card**. The card is immediately deleted. + +:::note +You cannot delete the primary card as at least one credit card must be available as payment method, and the primary card is by default that one. If the credit card you wish to delete is currently the primary card, you must first define another credit card as primary, then delete it. +::: + +## Account invoices + +The ![Invoices icon](/img/assets/icons/Invoice.svg) *Invoices* tab displays the complete list of invoices for all your Strapi Cloud projects. + + + + + +:::strapi Invoices are also available per project. +In the *Settings > Invoices* tab of any project, you will find the invoices for that project only. Feel free to check the [dedicated documentation](/cloud/projects/settings#invoices). +::: \ No newline at end of file diff --git a/docusaurus/docs/cloud/account/account-settings.md b/docusaurus/docs/cloud/account/account-settings.md new file mode 100644 index 0000000000..11c95bd67d --- /dev/null +++ b/docusaurus/docs/cloud/account/account-settings.md @@ -0,0 +1,56 @@ +--- +title: Profile settings +displayed_sidebar: cloudSidebar +description: Manage Strapi Cloud account settings. +canonicalUrl: https://docs.strapi.io/cloud/account/account-settings.html +--- + +# Profile settings + +The *Profile* page enables you to manage your account details and preferences. It is accessible by clicking on your profile picture, on the top right hand corner of the interface, and **Profile**. + +There are 3 tabs available in the *Profile* interface: ![General icon](/img/assets/icons/Faders.svg) [*General*](#general), ![Preferences icon](/img/assets/icons/Palette.svg) [*Preferences*](#preferences), ![Billing icon](/img/assets/icons/CreditCard.svg) *Billing* and ![Invoices icon](/img/assets/icons/Invoice.svg) Invoices (the last 2 are documented in the [Account billing details](/cloud/account/account-billing) section of this documentation). + +## General + +The ![General icon](/img/assets/icons/Faders.svg) *General* tab enables you to edit the following details for your account profile: + +- Details: to see the name associated with your account. +- Connected accounts: to manage Google, GitHub and GitLab accounts connected with your Strapi Cloud account (see [Managing connected accounts](#managing-connected-accounts)). +- Delete account: to permanently delete your Strapi Cloud account (see [Deleting Strapi Cloud account](#deleting-strapi-cloud-account)). + + + +### Managing connected accounts + +You can connect a Google, GitLab and GitHub account to your Strapi Cloud account. The _Connected accounts_ section lists accounts that are currently connected to your Strapi Cloud account. From there you can also connect a new Google, GitLab and GitHub account if one is not already connected. + +To connect a new Google, GitLab or GitHub account to your Strapi Cloud account, click on the **Connect account** button and follow the next steps on the corresponding website. + +You can also click on the three dots button of a connected account and click on the "Manage on" button to manage your GitHub, GitLab or Google account directly on the corresponding website. + +### Deleting Strapi Cloud account + +You can delete your Strapi Cloud account, but it will be permanent and irreversible. All associated projects and their data will be deleted as well and the subscriptions for the projects will automatically be canceled. + +1. In the *Delete account* section of the ![General icon](/img/assets/icons/Faders.svg) *General* tab, click on the **Delete account** button. +2. In the dialog, type `DELETE` in the textbox. +3. Confirm the deletion of your account by clicking on the **Delete** button. + +## Preferences + +The ![Preferences icon](/img/assets/icons/Palette.svg) *Preferences* tab enables you to choose the appearance of your Strapi Cloud dashboard: either the Light or Dark theme. + + diff --git a/docusaurus/docs/cloud/account/settings.md b/docusaurus/docs/cloud/account/settings.md deleted file mode 100644 index 048e3277ea..0000000000 --- a/docusaurus/docs/cloud/account/settings.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Account Settings -displayed_sidebar: cloudSidebar -description: View and manage your projects on Strapi Cloud. -canonicalUrl: https://docs.strapi.io/cloud/account/settings.html ---- - -# Account Settings - -The **Account Settings** page enables you to manage your account details and preferences. There are three sections available: - -## General - -The **General** tab enables you to edit the following details for your account: - -* **Name**: The name associated with your account. - -## Preferences - -The **Preferences** tab enables you to edit the following details for your account: - -* **Appearance**: Choose between the **Light** and **Dark** themes. - -## Billing - -The **Billing** tab displays your billing information and enables you to update your payment method. - -![Account billing settings](/img/assets/cloud/account-billing.png) - -### Managing subscriptions - -Using the **Manage subscriptions** button, you can view and manage your subscriptions, account and billing information, and payment method. - -1. Click the **Manage subscriptions** button. A login modal will appear. - Manage subscriptions login - - -2. Enter the billing email address associated with your account and click **Continue**. A one-time password will be sent to the email address. - -3. Enter the one-time password and click **Continue**. The **Manage Subscriptions** page will appear. - Manage subscriptions - -4. From here you can view and edit your: - * Active Subscription - * Account Information - * Billing & Shipping Addresses - * Payment Methods - * Billing History - -#### Active subscription - -Click on the active subscription tile to view the details of your subscription: - -Active subscription - -Here you can view your current subscription plan, and any additional add-ons, as well as the monthly billed amount. - -You can also: - -* Edit your **Project Name** -* Update your **Shipping Details**, the mailing/billing address associated with your subscription -* [Edit your subscription](#edit-subscription) -* **Cancel Subscription** - -##### Edit subscription - -Click the **Edit Subscription** button to change your subscription plan or add-ons: -Edit subscription - -Use the **Change** button to select a different subscription plan: either **Pro** or **Team**. - -Use the **Add Addons** buttons to add additional seats to your plan: -Adding seats - -Click **Update Subscription** to save your changes. diff --git a/docusaurus/docs/cloud/advanced/database.md b/docusaurus/docs/cloud/advanced/database.md index 6bb89542f8..9e497eaf71 100644 --- a/docusaurus/docs/cloud/advanced/database.md +++ b/docusaurus/docs/cloud/advanced/database.md @@ -9,19 +9,20 @@ canonicalUrl: https://docs.strapi.io/cloud/advanced/database.html Strapi Cloud provides a pre-configured PostgreSQL database by default. However, you can also configure it to utilize an external SQL database, if needed. -:::caution -While possible, it is not recommended to use an external database with Strapi Cloud, unless there is an explicit need to do so. This is because Strapi Cloud provides a managed database that is optimized for Strapi. Using an external database may result in unexpected behavior and/or performance issues (e.g., network latency may impact performance). Strapi cannot provide security or support with configuring external databases. - -::: - :::prerequisites - -- A local Strapi project running on `v4.8.1+`. +- A local Strapi project running on `v4.8.2+`. - Credentials for external database. - If using an existing database, the schema must match the Strapi project schema. +::: +:::caution +While it's possible to use an external database with Strapi Cloud, you should do it while keeping in mind the following considerations: +- Strapi Cloud already provides a managed database that is optimized for Strapi. +- Using an external database may result in unexpected behavior and/or performance issues (e.g., network latency may impact performance). For performance reasons, it's recommended to host your external database close to the region where your Strapi Cloud project is hosted. You can find where your Strapi Cloud project is hosted in your Project Settings (see [Project Settings > General > Selected Region](/cloud/projects/settings#general)). +- Strapi can't provide security or support with external databases used with Strapi Cloud. ::: + ## Configuration The project `./config/database.js` or `./config/database.ts` file must match the configuration found in the [environment variables in database configurations](https://docs.strapi.io/dev-docs/configurations/database#environment-variables-in-database-configurations) section. @@ -47,14 +48,19 @@ Before pushing changes, add environment variables to the Strapi Cloud project: :::caution To ensure a smooth deployment, it is recommended to not change the names of the environment variables. - ::: ## Deployment To deploy the project and utilize the external database, push the changes from earlier. This will trigger a rebuild and new deployment of the Strapi Cloud project. -![deployment](https://res.cloudinary.com/dz7knyfbp/image/upload/v1681936928/deploy_h49uly.png) + Once the application finishes building, the project will use the external database. diff --git a/docusaurus/docs/cloud/advanced/email.md b/docusaurus/docs/cloud/advanced/email.md new file mode 100644 index 0000000000..2c5ccd38be --- /dev/null +++ b/docusaurus/docs/cloud/advanced/email.md @@ -0,0 +1,287 @@ +--- +title: Email Provider +displayed_sidebar: cloudSidebar +description: Configure Strapi Cloud to use a third-party email provider. +canonicalUrl: https://docs.strapi.io/cloud/advanced/email.html +--- + +# Email Provider + +Strapi Cloud comes with a basic email provider out of the box. However, it can also be configured to utilize another email provider, if needed. + +:::caution +Please be advised that Strapi are unable to provide support for third-party email providers. + +::: + +:::prerequisites + +- A local Strapi project running on `v4.8.2+`. +- Credentials for another email provider (see [Strapi Market](https://market.strapi.io/providers)). + +::: + +## Configuration + +Configuring another email provider for use with Strapi Cloud requires 3 steps: + +1. Install the provider plugin in your local Strapi project. +2. Configure the provider in your local Strapi project. +3. Add environment variables to the Strapi Cloud project. + +### Install the Provider Plugin + +Using either `npm` or `yarn`, install the provider plugin in your local Strapi project as a package dependency by following the instructions in the respective entry for that provider in the [Marketplace](https://market.strapi.io/providers). + +### Configure the Provider + +In your Strapi project, create a `./config/env/production/plugins.js` or `./config/env/production/plugins.ts` file with the following content: + + + + +```js title=./config/env/production/plugins.js + +module.exports = ({ env }) => ({ + // … some unrelated plugins configuration options + // highlight-start + email: { + config: { + // … provider-specific upload configuration options go here + } + // highlight-end + // … some other unrelated plugins configuration options + } +}); +``` + + + + +```ts title=./config/env/production/plugins.ts + +export default ({ env }) => ({ + // … some unrelated plugins configuration options + // highlight-start + email: { + config: { + // … provider-specific upload configuration options go here + } + // highlight-end + // … some other unrelated plugins configuration options + } +}); +``` + + + + + +:::caution +The file structure must match the above path exactly, or the configuration will not be applied to Strapi Cloud. +::: + +Each provider will have different configuration settings available. Review the respective entry for that provider in the [Marketplace](https://market.strapi.io/providers). + +**Example:** + + + + + +```js title=./config/env/production/plugins.js +module.exports = ({ env }) => ({ + // ... + email: { + config: { + provider: 'sendgrid', + providerOptions: { + apiKey: env('SENDGRID_API_KEY'), + }, + settings: { + defaultFrom: 'myemail@protonmail.com', + defaultReplyTo: 'myemail@protonmail.com', + }, + }, + }, + // ... +}); +``` + + + + +```js title=./config/env/production/plugins.js +module.exports = ({ env }) => ({ + // ... + email: { + config: { + provider: 'amazon-ses', + providerOptions: { + key: env('AWS_SES_KEY'), + secret: env('AWS_SES_SECRET'), + amazon: 'https://email.us-east-1.amazonaws.com', + }, + settings: { + defaultFrom: 'myemail@protonmail.com', + defaultReplyTo: 'myemail@protonmail.com', + }, + }, + }, + // ... +}); +``` + + + + +```js title=./config/env/production/plugins.js +module.exports = ({ env }) => ({ + // ... + email: { + config: { + provider: 'mailgun', + providerOptions: { + key: env('MAILGUN_API_KEY'), // Required + domain: env('MAILGUN_DOMAIN'), // Required + url: env('MAILGUN_URL', 'https://api.mailgun.net'), //Optional. If domain region is Europe use 'https://api.eu.mailgun.net' + }, + settings: { + defaultFrom: 'myemail@protonmail.com', + defaultReplyTo: 'myemail@protonmail.com', + }, + }, + }, + // ... +}); +``` + + + + + + + + +```ts title=./config/env/production/plugins.ts +export default ({ env }) => ({ + // ... + email: { + config: { + provider: 'sendgrid', + providerOptions: { + apiKey: env('SENDGRID_API_KEY'), + }, + settings: { + defaultFrom: 'myemail@protonmail.com', + defaultReplyTo: 'myemail@protonmail.com', + }, + }, + }, + // ... +}); +``` + + + + +```ts title=./config/env/production/plugins.ts +export default ({ env }) => ({ + // ... + email: { + config: { + provider: 'amazon-ses', + providerOptions: { + key: env('AWS_SES_KEY'), + secret: env('AWS_SES_SECRET'), + amazon: 'https://email.us-east-1.amazonaws.com', + }, + settings: { + defaultFrom: 'myemail@protonmail.com', + defaultReplyTo: 'myemail@protonmail.com', + }, + }, + }, + // ... +}); +``` + + + + +```ts title=./config/env/production/plugins.ts +export default ({ env }) => ({ + // ... + email: { + config: { + provider: 'mailgun', + providerOptions: { + key: env('MAILGUN_API_KEY'), // Required + domain: env('MAILGUN_DOMAIN'), // Required + url: env('MAILGUN_URL', 'https://api.mailgun.net'), //Optional. If domain region is Europe use 'https://api.eu.mailgun.net' + }, + settings: { + defaultFrom: 'myemail@protonmail.com', + defaultReplyTo: 'myemail@protonmail.com', + }, + }, + }, + // ... +}); +``` + + + + + + +:::tip +Before pushing the above changes to GitHub, add environment variables to the Strapi Cloud project to prevent triggering a rebuild and new deployment of the project before the changes are complete. +::: + +### Strapi Cloud Configuration + +1. Log into Strapi Cloud and click on the corresponding project on the Projects page. +2. Click on the **Settings** tab and choose **Variables** in the left menu. +3. Add the required environment variables specific to the email provider. +4. Click **Save**. + +**Example:** + + + + +| Variable | Value | +|--------------------|-----------------------| +| `SENDGRID_API_KEY` | your_sendgrid_api_key | + + + + +| Variable | Value | +|------------------|---------------------| +| `AWS_SES_KEY` | your_aws_ses_key | +| `AWS_SES_SECRET` | your_aws_ses_secret | + + + + +| Variable | Value | +|-------------------|----------------------| +| `MAILGUN_API_KEY` | your_mailgun_api_key | +| `MAILGUN_DOMAIN` | your_mailgun_domain | +| `MAILGUN_URL` | your_mailgun_url | + + + + + +## Deployment + +To deploy the project and utilize another party email provider, push the changes from earlier. This will trigger a rebuild and new deployment of the Strapi Cloud project. + +Once the application finishes building, the project will use the new email provider. + +:::strapi Custom Provider +If you want to create a custom email provider, please refer to the [Providers](/dev-docs/providers#creating-providers) documentation in the Developer Documentation. +::: diff --git a/docusaurus/docs/cloud/advanced/upload.md b/docusaurus/docs/cloud/advanced/upload.md new file mode 100644 index 0000000000..150c715d22 --- /dev/null +++ b/docusaurus/docs/cloud/advanced/upload.md @@ -0,0 +1,425 @@ +--- +title: Upload Provider +displayed_sidebar: cloudSidebar +description: Configure Strapi Cloud to use a third-party upload provider. +canonicalUrl: https://docs.strapi.io/cloud/advanced/upload.html +--- + +# Upload Provider + +Strapi Cloud comes with a local upload provider out of the box. However, it can also be configured to utilize a third-party upload provider, if needed. + +:::caution +Please be advised that Strapi are unable to provide support for third-party upload providers. +::: + +:::prerequisites + +- A local Strapi project running on `v4.8.2+`. +- Credentials for a third-party upload provider (see [Strapi Market](https://market.strapi.io/providers)). + +::: + +## Configuration + +Configuring a third-party upload provider for use with Strapi Cloud requires 4 steps: + +1. Install the provider plugin in your local Strapi project. +2. Configure the provider in your local Strapi project. +3. Configure the Security Middleware in your local Strapi project. +4. Add environment variables to the Strapi Cloud project. + +### Install the Provider Plugin + +Using either `npm` or `yarn`, install the provider plugin in your local Strapi project as a package dependency by following the instructions in the respective entry for that provider in the [Marketplace](https://market.strapi.io/providers). + +### Configure the Provider + +To configure a 3rd-party upload provider in your Strapi project, create or edit the plugins configuration file for your production environment `./config/env/production/plugins.js|ts` by adding upload configuration options as follows: + + + + +```js title=./config/env/production/plugins.js + +module.exports = ({ env }) => ({ +// … some unrelated plugins configuration options +// highlight-start +upload: { + config: { + // … provider-specific upload configuration options go here + } +// highlight-end +// … some other unrelated plugins configuration options +} +}); +``` + + + + +```ts title=./config/env/production/plugins.ts + +export default ({ env }) => ({ +// … some unrelated plugins configuration options +// highlight-start +upload: { + config: { + // … provider-specific upload configuration options go here + } +// highlight-end +// … some other unrelated plugins configuration options +} +}); +``` + + + + +:::caution +The file structure must match the above path exactly, or the configuration will not be applied to Strapi Cloud. +::: + +Each provider will have different configuration settings available. Review the respective entry for that provider in the [Marketplace](https://market.strapi.io/providers). + +**Example:** + + + + + +```js title=./config/env/production/plugins.js +module.exports = ({ env }) => ({ + // ... + upload: { + config: { + provider: 'cloudinary', + providerOptions: { + cloud_name: env('CLOUDINARY_NAME'), + api_key: env('CLOUDINARY_KEY'), + api_secret: env('CLOUDINARY_SECRET'), + }, + actionOptions: { + upload: {}, + uploadStream: {}, + delete: {}, + }, + }, + }, + // ... +}); +``` + + + + +```js title=./config/env/production/plugins.js +module.exports = ({ env }) => ({ + // ... + upload: { + config: { + provider: 'aws-s3', + providerOptions: { + baseUrl: env('CDN_URL'), + rootPath: env('CDN_ROOT_PATH'), + s3Options: { + accessKeyId: env('AWS_ACCESS_KEY_ID'), + secretAccessKey: env('AWS_ACCESS_SECRET'), + region: env('AWS_REGION'), + params: { + ACL: env('AWS_ACL', 'public-read'), + signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60), + Bucket: env('AWS_BUCKET'), + }, + }, + }, + actionOptions: { + upload: {}, + uploadStream: {}, + delete: {}, + }, + }, + }, + // ... +}); +``` + + + + + + + + +```ts title=./config/env/production/plugins.ts +export default ({ env }) => ({ + // ... + upload: { + config: { + provider: 'cloudinary', + providerOptions: { + cloud_name: env('CLOUDINARY_NAME'), + api_key: env('CLOUDINARY_KEY'), + api_secret: env('CLOUDINARY_SECRET'), + }, + actionOptions: { + upload: {}, + uploadStream: {}, + delete: {}, + }, + }, + }, + // ... +}); +``` + + + + +```ts title=./config/env/production/plugins.ts +export default ({ env }) => ({ + // ... + upload: { + config: { + provider: 'aws-s3', + providerOptions: { + baseUrl: env('CDN_URL'), + rootPath: env('CDN_ROOT_PATH'), + s3Options: { + accessKeyId: env('AWS_ACCESS_KEY_ID'), + secretAccessKey: env('AWS_ACCESS_SECRET'), + region: env('AWS_REGION'), + params: { + ACL: env('AWS_ACL', 'public-read'), + signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60), + Bucket: env('AWS_BUCKET'), + }, + }, + }, + actionOptions: { + upload: {}, + uploadStream: {}, + delete: {}, + }, + }, + }, + // ... +}); +``` + + + + + + +### Configure the Security Middleware + +Due to the default settings in the Strapi Security Middleware you will need to modify the `contentSecurityPolicy` settings to properly see thumbnail previews in the Media Library. + +To do this in your Strapi project: + +1. Navigate to `./config/middleware.js` or `./config/middleware.ts` in your Strapi project. +2. Replace the default `strapi::security` string with the object provided by the upload provider. + +**Example:** + + + + + +```js title=./config/middleware.js +module.exports = [ + // ... + { + name: 'strapi::security', + config: { + contentSecurityPolicy: { + useDefaults: true, + directives: { + 'connect-src': ["'self'", 'https:'], + 'img-src': [ + "'self'", + 'data:', + 'blob:', + 'market-assets.strapi.io', + 'res.cloudinary.com' + ], + 'media-src': [ + "'self'", + 'data:', + 'blob:', + 'market-assets.strapi.io', + 'res.cloudinary.com', + ], + upgradeInsecureRequests: null, + }, + }, + }, + }, + // ... +]; +``` + + + + +```js title=./config/middleware.js +module.exports = [ + // ... + { + name: 'strapi::security', + config: { + contentSecurityPolicy: { + useDefaults: true, + directives: { + 'connect-src': ["'self'", 'https:'], + 'img-src': [ + "'self'", + 'data:', + 'blob:', + 'market-assets.strapi.io', + 'yourBucketName.s3.yourRegion.amazonaws.com', + ], + 'media-src': [ + "'self'", + 'data:', + 'blob:', + 'market-assets.strapi.io', + 'yourBucketName.s3.yourRegion.amazonaws.com', + ], + upgradeInsecureRequests: null, + }, + }, + }, + }, + // ... +]; +``` + + + + + + + + +```ts title=./config/middleware.ts +export default [ + // ... + { + name: 'strapi::security', + config: { + contentSecurityPolicy: { + useDefaults: true, + directives: { + 'connect-src': ["'self'", 'https:'], + 'img-src': [ + "'self'", + 'data:', + 'blob:', + 'market-assets.strapi.io', + 'res.cloudinary.com' + ], + 'media-src': [ + "'self'", + 'data:', + 'blob:', + 'market-assets.strapi.io', + 'res.cloudinary.com', + ], + upgradeInsecureRequests: null, + }, + }, + }, + }, + // ... +]; +``` + + + + +```ts title=./config/middleware.ts +export default [ + // ... + { + name: 'strapi::security', + config: { + contentSecurityPolicy: { + useDefaults: true, + directives: { + 'connect-src': ["'self'", 'https:'], + 'img-src': [ + "'self'", + 'data:', + 'blob:', + 'market-assets.strapi.io', + 'yourBucketName.s3.yourRegion.amazonaws.com', + ], + 'media-src': [ + "'self'", + 'data:', + 'blob:', + 'market-assets.strapi.io', + 'yourBucketName.s3.yourRegion.amazonaws.com', + ], + upgradeInsecureRequests: null, + }, + }, + }, + }, + // ... +]; +``` + + + + + + +:::tip +Before pushing the above changes to GitHub, add environment variables to the Strapi Cloud project to prevent triggering a rebuild and new deployment of the project before the changes are complete. +::: + +### Strapi Cloud Configuration + +1. Log into Strapi Cloud and click on the corresponding project on the Projects page. +2. Click on the **Settings** tab and choose **Variables** in the left menu. +3. Add the required environment variables specific to the upload provider. +4. Click **Save**. + +**Example:** + + + + +| Variable | Value | +|---------------------|-------------------------| +| `CLOUDINARY_NAME` | your_cloudinary_name | +| `CLOUDINARY_KEY` | your_cloudinary_api_key | +| `CLOUDINARY_SECRET` | your_cloudinary_secret | + + + + +| Variable | Value | +|---------------------|------------------------| +| `AWS_ACCESS_KEY_ID` | your_aws_access_key_id | +| `AWS_ACCESS_SECRET` | your_aws_access_secret | +| `AWS_REGION` | your_aws_region | +| `AWS_BUCKET` | your_aws_bucket | +| `CDN_URL` | your_cdn_url | +| `CDN_ROOT_PATH` | your_cdn_root_path | + + + + +## Deployment + +To deploy the project and utilize the third-party upload provider, push the changes from earlier. This will trigger a rebuild and new deployment of the Strapi Cloud project. + +Once the application finishes building, the project will use the new upload provider. + +:::strapi Custom Provider +If you want to create a custom upload provider, please refer to the [Providers](/dev-docs/providers#creating-providers) documentation in the Developer Documentation. +::: diff --git a/docusaurus/docs/cloud/cli/cloud-cli.md b/docusaurus/docs/cloud/cli/cloud-cli.md new file mode 100644 index 0000000000..e46cb3c036 --- /dev/null +++ b/docusaurus/docs/cloud/cli/cloud-cli.md @@ -0,0 +1,77 @@ +--- +sidebar_label: 'Cloud CLI' +displayed_sidebar: cloudSidebar +sidebar_position: 3 +--- + +# Command Line Interface (CLI) + +Strapi Cloud comes with a Command Line Interface (CLI) which allows you to log in and out, and to deploy a local project without it having to be hosted on a remote git repository. The CLI works with both the `yarn` and `npm` package managers. + +:::note +It is recommended to install Strapi locally only, which requires prefixing all of the following `strapi` commands with the package manager used for the project setup (e.g `npm run strapi help` or `yarn strapi help`) or a dedicated node package executor (e.g. `npx strapi help`). +::: + +## strapi login + +**Alias:** `strapi cloud:login` + +Log in Strapi Cloud. + +```bash +strapi login +``` + +This command automatically opens a browser window to first ask you to confirm that the codes displayed in both the browser window and the terminal are the same. Then you will be able to log into Strapi Cloud via Google, GitHub or GitLab. Once the browser window confirms successful login, it can be safely closed. + +If the browser window doesn't automatically open, the terminal will display a clickable link as well as the code to enter manually. + +## strapi deploy + +**Alias:** `strapi cloud:deploy` + +Deploy a new local project (< 100MB) in Strapi Cloud. + +```bash +strapi deploy +``` + +This command must be used after the `login` one. It deploys a local Strapi project on Strapi Cloud, without having to host it on a remote git repository beforehand. The terminal will inform you when the project is successfully deployed on Strapi Cloud. + +Once the project is first deployed on Strapi Cloud with the CLI, the `deploy` command can be reused to trigger a new deployment of the same project. + +:::caution +The `deploy` command can only be used by new users who have never created a Strapi Cloud project, and for which the free trial is still available. Once a project is deployed with the CLI, it isn't possible to deploy another project on the same Strapi Cloud account with the CLI. +::: + +:::note +Once you deployed your project, if you visit the Strapi Cloud dashboard, you may see some limitations as well as impacts due to creating a Strapi Cloud project that is not in a remote repository and which was deployed with the CLI. + +- Some areas in the dashboard that are usually reserved to display information about the git provider will be blank. +- Some buttons, such as the **Trigger deploy** button, will be greyed out and unclickable since, unless you have [connected a git repository to your Strapi Cloud project](/cloud/getting-started/deployment-cli#automatically-deploying-subsequent-changes). +::: + + +## strapi projects + +**Alias:** `strapi cloud:projects` + +Lists all Strapi Cloud projects associated with your account. + +```bash +strapi projects +``` + +This command retrieves and displays a list of all projects hosted on your Strapi Cloud account. + +## strapi logout + +**Alias:** `strapi cloud:logout` + +Log out of Strapi Cloud. + +```bash +strapi logout +``` + +This command logs you out of Strapi Cloud. Once the `logout` command is run, a browser page will open and the terminal will display a confirmation message that you were successfully logged out. You will not be able to use the `deploy` command anymore. diff --git a/docusaurus/docs/cloud/getting-started/caching.md b/docusaurus/docs/cloud/getting-started/caching.md index c2410d74fc..1e615b3afc 100644 --- a/docusaurus/docs/cloud/getting-started/caching.md +++ b/docusaurus/docs/cloud/getting-started/caching.md @@ -1,5 +1,5 @@ --- -sidebar_label: 'Caching for App Performance' +sidebar_label: 'Caching for app performance' displayed_sidebar: cloudSidebar sidebar_position: 4 --- diff --git a/docusaurus/docs/cloud/getting-started/cloud-fundamentals.md b/docusaurus/docs/cloud/getting-started/cloud-fundamentals.md new file mode 100644 index 0000000000..24ba6b8cd4 --- /dev/null +++ b/docusaurus/docs/cloud/getting-started/cloud-fundamentals.md @@ -0,0 +1,20 @@ +--- +sidebar_label: 'Cloud fundamentals' +displayed_sidebar: cloudSidebar +slug: /cloud/cloud-fundamentals +sidebar_position: 1 +--- + +# Strapi Cloud fundamentals + +Before going any further into this Strapi Cloud documentation, we recommend you to acknowledge the main concepts below. They will help you to understand how Strapi Cloud works, and ensure a smooth Strapi Cloud experience. + +- **Hosting Platform**
Strapi Cloud is a hosting platform that allows to deploy already existing Strapi projects created with Strapi CMS (Content Management System). Strapi Cloud is *not* the SaaS (Software as a Service) version of Strapi CMS. Feel free to refer to the [Developer Documentation](https://docs.strapi.io/dev-docs/intro) and [User Guide](https://docs.strapi.io/user-docs/intro) to learn more about Strapi CMS. + +- **Strapi Cloud Pricing Plans**
As a Strapi Cloud user you have the choice between 3 tiers: Developer, Pro and Team. Depending on the tier, you have access to different functionalities, support and customization options (see [Pricing page](https://strapi.io/pricing-cloud) for more details). In this Strapi Cloud documentation, the , , and badges can be displayed beside a section's title to indicate for which tier the feature is available. + +- **Strapi CMS Enterprise features**
Some of Strapi features, usually accessible via the Enterprise Edition of Strapi CMS, are included in some Strapi Cloud tiers (see [Pricing page](https://strapi.io/pricing-cloud) and [Information on billing & usage](/cloud/getting-started/usage-billing) for more details). These features, highlighted with an badge, are documented in the [User Guide](https://docs.strapi.io/user-docs/intro) and the [Developer Documentation](https://docs.strapi.io/dev-docs/intro). + +- **Types of Strapi Cloud users**
There can be 2 types of users on a Strapi Cloud project: owners and maintainers. The owner is the one who has created the project and has therefore access to all features and options for the project. Maintainers are users who have been invited to contribute to an already created project by its owner. Maintainers, as documented in the [Collaboration](/cloud/projects/collaboration) page, cannot view and access all features and options from the Strapi Cloud dashboard. + +- **Support**
The level of support provided by the Strapi Support team depends on the Strapi Cloud tier you subscribed for. The Developer and Pro tiers include Basic support while the Team tier includes Standard support. Please refer to the [dedicated support article](https://support.strapi.io/support/solutions/articles/67000680833-what-is-supported-by-the-strapi-team#Not-Supported) for all details regarding support levels. \ No newline at end of file diff --git a/docusaurus/docs/cloud/getting-started/deployment-cli.md b/docusaurus/docs/cloud/getting-started/deployment-cli.md new file mode 100644 index 0000000000..49c5efe6a9 --- /dev/null +++ b/docusaurus/docs/cloud/getting-started/deployment-cli.md @@ -0,0 +1,94 @@ +--- +title: with Cloud CLI +displayed_sidebar: cloudSidebar +description: Learn how to deploy your Strapi application via the CLI. +canonicalUrl: https://docs.strapi.io/cloud/getting-started/deployment-cli.html +sidebar_position: 2 +--- + +# Project deployment with the Command Line Interface (CLI) + +This is a step-by-step guide for deploying your project on Strapi Cloud for the first time, using the Command Line Interface. + +:::prerequisites +Before you can deploy your Strapi application on Strapi Cloud using the Command Line Interface, you need to have the following prerequisites: + +- Be a first-time Strapi Cloud user: you must never have deployed a project with Strapi Cloud before, and your free trial must still be available. +- Have a Google, GitHub or GitLab account. +- Have an already created Strapi project (see [Installing from CLI in the Developer Documentation](/dev-docs/installation/cli)), stored locally. The project must be less than 100MB. +- Have available storage in your hard drive where the temporary folder of your operating system is stored. +::: + +## Logging in to Strapi Cloud + +1. Open your terminal. + +2. Navigate to the folder of your Strapi project, stored locally on your computer. + +3. Enter the following command to log into Strapi Cloud: + + + + + ```bash + yarn strapi login + ``` + + + + + ```bash + npx run strapi login + ``` + + + + +4. In the browser window that opens automatically, confirm that the code displayed is the same as the one written in the terminal message. + +5. Still in the browser window, choose whether to login via Google, GitHub or GitLab. The window should confirm the successful login soon after. + +## Deploying your project + +1. From your terminal, still from the folder of your Strapi project, enter the following command to deploy the project: + + + + + ```bash + yarn strapi deploy + ``` + + + + + ```bash + npx run strapi deploy + ``` + + + + +2. Follow the progression bar in the terminal until confirmation that the project was successfully deployed with Strapi Cloud. + +### Automatically deploying subsequent changes + +By default, when creating and deploying a project with the Cloud CLI, you need to manually deploy again all subsequent changes by running the corresponding `deploy` command everytime you make a change. + +Another option is to enable automatic deployment through a git repository. To do so: + +1. Host your code on a git repository, such as [GitHub](https://www.github.com) or [GitLab](https://www.gitlab.com). +2. Connect your Strapi Cloud project to the repository (see the _Connected repository_ setting in [Projects Settings > General](/cloud/projects/settings#general)). +3. Still in _Projects Settings > General_ tab, tick the box for the "Deploy the project on every commit pushed to this branch" setting. From now on, a new deployment to Strapi Cloud will be triggered any time a commit is pushed to the connected git repository. + +:::note +Automatic deployment is compatible with all other deployment methods, so once a git repository is connected, you can trigger a new deployment to Strapi Cloud [from the Cloud dashboard](/cloud/projects/deploys), [from the CLI](/cloud/cli/cloud-cli#strapi-deploy), or by pushing new commits to your connected repository. +::: + +## ⏩ What to do next? + +Now that you have deployed your project via the Command Line Interface, we encourage you to explore the following ideas to have an even more complete Strapi Cloud experience: + +- Fill in your [billing information](/cloud/account/account-billing) to prevent your project from being suspended at the end of the trial period. +- Visit the Cloud dashboard to follow [insightful metrics and information](/cloud/projects/overview) on your Strapi project. +- Check out the full [Command Line Interface documentation](/cloud/cli/cloud-cli) to learn about the other commands available. diff --git a/docusaurus/docs/cloud/getting-started/deployment-options.md b/docusaurus/docs/cloud/getting-started/deployment-options.md new file mode 100644 index 0000000000..c5b401211d --- /dev/null +++ b/docusaurus/docs/cloud/getting-started/deployment-options.md @@ -0,0 +1,21 @@ +--- +title: Project deployment +displayed_sidebar: cloudSidebar +description: Learn how to deploy your Strapi application on Strapi Cloud, via the Cloud dashboard or the CLI. +canonicalUrl: https://docs.strapi.io/cloud/getting-started/deployment-options.html +sidebar_position: 2 +--- + +# Project deployment with Strapi Cloud + +You have 2 options to deploy your project with Strapi Cloud: + +- either with the user interface (UI), meaning that you will perform all the actions directly on the Strapi Cloud dashboard, +- or using the Cloud Comment Line Interface (CLI), meaning that you will only interact with a terminal. + +The guides below will guide you through all the steps for each of the deployment options. + + + + + diff --git a/docusaurus/docs/cloud/getting-started/deployment.md b/docusaurus/docs/cloud/getting-started/deployment.md index d622732d00..df428d8e27 100644 --- a/docusaurus/docs/cloud/getting-started/deployment.md +++ b/docusaurus/docs/cloud/getting-started/deployment.md @@ -1,93 +1,208 @@ --- -title: Deployment +title: with Cloud dashboard displayed_sidebar: cloudSidebar description: Learn how to deploy your Strapi application on Strapi Cloud. canonicalUrl: https://docs.strapi.io/cloud/getting-started/deployment.html sidebar_position: 2 --- -# Strapi Cloud +# Project deployment with the Cloud dashboard -This is a step-by-step guide for deploying your Strapi application on Strapi Cloud. +This is a step-by-step guide for deploying your project on Strapi Cloud for the first time, using the Cloud dashboard. -## Prerequisites +:::prerequisites +Before you can deploy your Strapi application on Strapi Cloud using the Cloud dashboard, you need to have the following prerequisites: -Before you can deploy your Strapi application on Strapi Cloud, you need to have the following prerequisites: - -* Strapi version `4.8.1` or higher -* Database: Project must be compatible with PostgreSQL. Strapi does not support and does not recommend using any external databases, though it's possible to configure one (see [advanced database configuration](/cloud/advanced/database)). -* Project(s) source code hosted on [GitHub](https://github.com) - * The connected repository can contain multiple Strapi applications. Each Strapi app must be in a separate directory. +* Strapi version `4.8.2` or higher +* Project database must be compatible with PostgreSQL. Strapi does not support and does not recommend using any external databases, though it's possible to configure one (see [advanced database configuration](/cloud/advanced/database)). +* Project(s) source code hosted on [GitHub](https://github.com) or [GitLab](https://about.gitlab.com/). The connected repository can contain multiple Strapi applications. Each Strapi app must be in a separate directory. +* Specifically for GitLab: at least have "[Maintainer](https://docs.gitlab.com/ee/user/permissions.html)" permissions for the project to import on Strapi Cloud. +::: -## Getting started +## Logging in to Strapi Cloud 1. Navigate to the [Strapi Cloud](https://cloud.strapi.io) login page. - ![Strapi Cloud login page](/img/assets/cloud/login.png) +2. You have the options to **Log in with GitHub**, **Log in with Google** or **Log in with GitLab**. Choose your provider and log in. This initial login will create your Strapi Cloud account. Once logged in, you will be redirected to the Strapi Cloud *Projects* page where you can create your first Strapi Cloud project. -2. You are prompted to **Log In with GitHub**. Your Strapi Cloud account is created during this initial login. + -3. Once logged in, you will be redirected to the Strapi Cloud **Projects** page. From here you can create your first Strapi Cloud project. +## Deploying a project - ![Strapi Cloud Projects page](/img/assets/cloud/projects_empty.png) +1. From the *Projects* page, click the **Create project** button. -## Creating a project + -1. From the **Projects** page, click the **Create Project** button. - You will be shown a **plan selection** dialog. - This will allow you to select the most relevant plan for your project. Refer to [Pricing](https://strapi.io/pricing-cloud) for more information. +2. You will be redirected to the first project deployment interface. This interface contains 3 steps: choosing a plan, connecting a remote git repository, and setting up the project. - ![Plan selection page](/img/assets/cloud/plan-selection.png) + - :::note - Strapi Cloud offers a free trial for only one project. +3. Choose a plan for your Strapi Cloud project: either Developer, Pro, Team, or the 14-days free trial. Feel free to refer to [Pricing](https://strapi.io/pricing-cloud) for more information. - If you have already used a free trial for a previous project, the option will no longer appear in the plan selection. + :::note Notes + - Strapi Cloud offers a free trial for only one project and you will not need to share your credit card details to deploy your first project. Once the free trial has already been used for a previous project, the option will no longer appear in the plan selection. + - You can't upload HTML files to Strapi Cloud during your free trial. ::: -2. If you selected the free trial option, you are presented with additional information about the trial. +4. Connect a git repository to your new Strapi Cloud project. - ![Trial confirmation page](/img/assets/cloud/trial-confirmation.png) + :::strapi Choose your path for your new Strapi Cloud project! + Select one of the tabs below depending on how you wish to proceed: + - by using a prebuilt template and creating a new repository on GitHub to discover Strapi Cloud easily and quickly *(recommended for new users and beginners β€” not available on another provider than GitHub)*, + - or by using your own, already existing GitHub or GitLab repository and Strapi project. + ::: -3. You are prompted to **Connect with GitHub**. + - ![Connect with Github page](/img/assets/cloud/connect-with-github.png) + - :::tip - Connect the GitHub account and/or Organizations that own the repository or repositories you want to deploy. This can be different from the account that owns the Strapi Cloud account. + 4.a. Click on the **Use template** button. If you are deploying a project for the first time, you may first have to select GitHub as git provider and then you will see the option to use a template. + + 4.b. In the *Create repository with template* modal, choose: + + - the GitHub account where the repository will be created + - the template to use for the new project (e.g. Blog) + + + + 4.c. Click on the **Create repository** button. A modal will confirm the creation of the repository. + + 4.d. If you have already given Strapi Cloud access to all repositories of your GitHub account, go directly to the next step. If not, you will be redirected to a GitHub modal where you will have to allow Strapi Cloud access to the newly created repository (more information in the [GitHub documentation](https://docs.github.com/en/apps/overview)). - You will be redirected to GitHub to authorize Strapi Cloud to access your repository. + 4.e. Back in the project deployment interface, select your *Account* and the *Repository* you just created. + + + + + + + + 4.a. (optional) If you are deploying a project for the first time, you may first have to select a git provider: either GitHub or GitLab. If you already deployed a project with one git provider, you can afterward deploy another project using another provider by clicking on the **Switch git provider** button and selecting either GitHub or GitLab. + + :::tip + Connect the GitHub or GitLab account and/or organizations that own the repository or repositories you want to deploy. This can be different from the account that owns the Strapi Cloud account. ::: -4. After granting the required access from GitHub, select the desired repository to install Strapi Cloud. + 4.b. If you have already given Strapi Cloud access to all repositories of your GitHub or GitLab account, go directly to the next step. If not, you will be redirected to a modal where you will have to allow Strapi Cloud permission to access some or all your repositories on GitHub/GitLab (more information in the [GitHub](https://docs.github.com/en/apps/overview) and [GitLab](https://docs.gitlab.com/ee/integration/oauth_provider.html#view-all-authorized-applications) documentations). + + 4.c. Back in the project deployment interface, select your *Account* and a *Repository*. + + - ![Project Import - Select Repository](/img/assets/cloud/import.png) + -5. Click **Next** to proceed to the Project Set up page and enter the following information: - * **Project name**: The name of your Strapi app, this is fetched from the repository name but can be edited. It is automatically converted to slug format (`my-strapi-app`). - * **GitHub branch**: The default branch to use for this deployment. This uses the [default branch](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-branches-in-your-repository/changing-the-default-branch) of the repository but can be changed via the drop-down. - * **Deploy on push**: When enabled, this will automatically deploy the latest changes from the selected branch. When disabled, you will need to manually deploy the latest changes. + - ![Project Setup](/img/assets/cloud/setup.png) +5. Set up your Strapi Cloud project. -6. (**Optional**) Select **Show Advanced Settings** to configure the following options: - * **Base directory**: The directory where your Strapi app is located in the repository. This is useful if you have multiple Strapi apps in the same repository or if you have a monorepo. - * [**Environment variables**](/dev-docs/configurations/environment/): Environment variables are used to configure your Strapi app. You can add environment variables to your Strapi Cloud project by clicking **Add Environment Variable**. You can also add environment variables to your Strapi app by adding a `.env` file to the root of your Strapi app directory. The environment variables defined in the `.env` file will be used by Strapi Cloud. + 5.a. Fill in the following information: - ![Advanced Setup](/img/assets/cloud/advanced.png) + | Setting name | Instructions | + |--------------|---------------------------------------------------------------------------------------------------------| + | Display name | Write the name of your Strapi app, this is fetched from the repository name but can be edited. It is automatically converted to slug format (`my-strapi-app`). | + | Git branch | Choose from the drop-down the default branch to use for this deployment. This uses the default branch of the repository. | + | Deploy on push | Check the box to automatically deploy the latest changes from the selected branch. When disabled, you will need to manually deploy the latest changes. | + | Region | Choose the geographic location of the servers where your Strapi application is hosted. Selected region can either be New York in North America (default) or Amsterdam in Europe. | + + :::note + The Git branch and "Deploy on push" settings can be modified afterwards through the project's setting, however the project name and hosting region setting can only be chosen during the creation of the project (see [Project Settings](/cloud/projects/settings)). + ::: + + 5.b. (optional) Click on **Show advanced settings** to fill in the following options: + + + + | Setting name | Instructions | + |--------------|---------------------------------------------------------------------------------------------------------| + | Base directory | Write the name of the directory where your Strapi app is located in the repository. This is useful if you have multiple Strapi apps in the same repository or if you have a monorepo. | + | Environment variables | Click on **Add variable** to add environment variables used to configure your Strapi app (see [Environment variables](/dev-docs/configurations/environment/) for more information). You can also add environment variables to your Strapi application by adding a `.env` file to the root of your Strapi app directory. The environment variables defined in the `.env` file will be used by Strapi Cloud. | + | Node version | Choose a Node version from the drop-down. Default Node version will automatically be chosen to best match the version of your Strapi project. If you manually choose a version that doesn't match with your Strapi project, the build will fail but the explanation will be displayed in the build logs. | :::strapi Using Environment Variables - You can use environment variable to connect your project to an external database rather than the default one used by Strapi Cloud. See the [Database Configuration documentation](/dev-docs/configurations/database#environment-variables-in-database-configurations) for details. If you'd like to revert and use our default database again, you have to remove your `DATABASE_` environment variables (no automatic migration implied). + You can use environment variable to connect your project to an external database rather than the default one used by Strapi Cloud (see [database configuration](/dev-docs/configurations/database#environment-variables-in-database-configurations) for more details). If you would like to revert and use Strapi's default database again, you have to remove your `DATABASE_` environment variables (no automatic migration implied). - You can also set up here a custom email provider (Sendgrid is set as the default one for the Strapi apps hosted on Cloud). See the [Providers Configuration](/dev-docs/providers#configuring-providers) for details. + You can also set up here a custom email provider. Sendgrid is set as the default one for the Strapi applications hosted on Strapi Cloud (see [providers configuration](/dev-docs/providers#configuring-providers) for more details). ::: -7. Click **Next** to proceed to the **Payment and billing** page. Enter the corresponding billing details. +## Setting up billing details + +:::strapi No billing step for free trials +If you chose the free trial, this billing step will be skipped as you will not be asked to share your credit card details at the creation of the project. - ![Payment and billing](/img/assets/cloud/billing-info.png) +During the free trial, will be kept informed of the number of remaining free days. You will then be notified by email and via the Strapi Cloud dashboard whenever it is time to fill in your billing information to move to a paid plan. + +πŸ‘‰ Skip to step 5 of the section below to finalise the creation of your project. +::: -8. Click **Create** to finalize the project creation. An initial deployment is triggered and you are redirected to the **Projects** page. +1. Click on the **Continue to billing** button. You will directly be redirected to the second and final project deployment interface. There you can review all your new project setup information, enter payment & billing details and receive your invoice. -:::warning + + +2. Review your project: make sure the plan and setup information are correct. If needed, click the ![Edit icon](/img/assets/icons/edit.svg) **Edit** button to be redirected to the first interface of the project creation and fix any mistake. + +3. In the Payment section, fill in at least all mandatory elements for *Payment method* and *Billing information*. + +4. Check your invoice which informs you of what should be paid now and the following month. Optionally, you can enter a *Discount code* if you have one. + +5. Click on the **Create project** button to finalize the deployment of your new Strapi Cloud project. An initial deployment will automatically be triggered and you will be redirected to the *Projects* page. + +:::caution Create your Admin user after the initial deployment is complete. Do not share your application URL with anyone until you have created your Admin user. ::: + +## ⏩ What to do next? + +Now that you have deployed your project via the Cloud dashboard, we encourage you to explore the following ideas to have an even more complete Strapi Cloud experience: + +- If you chose the free trial during your first project deployment, make sure to fill in your [billing information](/cloud/account/account-billing) afterward to prevent your project from being suspended at the end of the trial period. +- Invite other users to [collaborate on your project](/cloud/projects/collaboration). +- Check out the [deployments management documentation](/cloud/projects/deploys) to learn how to trigger new deployments for your project. diff --git a/docusaurus/docs/cloud/getting-started/intro.md b/docusaurus/docs/cloud/getting-started/intro.md index 66eb3a16ca..abc4287764 100644 --- a/docusaurus/docs/cloud/getting-started/intro.md +++ b/docusaurus/docs/cloud/getting-started/intro.md @@ -7,28 +7,48 @@ sidebar_position: 1 # Welcome to the Strapi Cloud Docs! -This documentation contains all technical documentation related to the setup, deployment, update and customization of your Strapi Cloud account and applications. + -## What is Strapi Cloud +The documentation for Strapi contains 3 main sections, accessible from the top navigation bar: -Strapi Cloud is a managed content platform built on top of Strapi, the leading open-source headless CMS. It is a fully managed hosting platform that allows you to deploy your Strapi applications in a matter of minutes. +- πŸ§‘β€πŸ’» The **[Developer Docs](/dev-docs/intro)** contain all the technical information related to the setup, advanced usage, customization, and update of your Strapi v5 application. +- πŸ§‘β€πŸ« The **[User Guide](/user-docs/intro)** is all about using Strapi's admin panel. +- ☁️ The **Strapi Cloud** documentation that you're currently reading is about deploying your Strapi application to Strapi Cloud and managing your Strapi Cloud projects and settings. -Strapi Cloud enables you to increase your content velocity without having to compromise on customization needs and requirements. Development teams can rely on Strapi Cloud to abstract away the complexity of infrastructure management while keeping your development workflow and extending the core capabilities of Strapi. Content managers can use Strapi Cloud to autonomously manage all types of content and benefit from a complete set of content collaboration, security, and compliance features. + -The typical workflow is to create your Strapi (v4.8.1 or later) application locally, extend the application with plugins or custom code, version the application's codebase through GitHub, and deploy the application in seconds with Strapi Cloud (see [deployment and prerequisites](/cloud/getting-started/deployment#prerequisites)). +This documentation contains all technical documentation related to the setup, deployment, update and customization of your Strapi Cloud account and applications. -## Strapi Community +## What is Strapi Cloud -Strapi Cloud is built on top of open-source Strapi, a community-oriented project with an emphasis on transparency, and Strapi Cloud users can benefit from the years of experience, knowledge, and contributions by the Strapi community as a whole. +[Strapi Cloud](https://cloud.strapi.io) is a hosting platform that allows you to deploy your Strapi applications in a matter of minutes. It is a fully managed content platform built on top of Strapi, the leading open-source headless CMS. -The Strapi team has at heart to share their vision and build the future of Strapi with the Strapi community. This is why the [roadmap](https://feedback.strapi.io) is open: as all insights are very important and will help steer the project in the right direction, any community member is most welcome to share ideas and opinions there. +Strapi Cloud enables you to increase your content velocity without having to compromise on customization needs and requirements. Development teams can rely on Strapi Cloud to abstract away the complexity of infrastructure management while keeping your development workflow and extending the core capabilities of Strapi. Content managers can use Strapi Cloud to autonomously manage all types of content and benefit from a complete set of content collaboration, security, and compliance features. + +## What you will find here -:::strapi Want to join the community? -You can join [GitHub](https://github.com/strapi/strapi), the [Forum](https://forum.strapi.io/), and the [Discord](https://discord.strapi.io) to share your ideas and opinions with other community members and members of the Strapi team. If you're looking for news and updates about Strapi, [Twitter](https://twitter.com/strapijs) and the [blog](https://strapi.io/blog) are pretty good places to start! +:::prerequisites +The typical workflow, which is recommended by the Strapi team, is: +1. Create your Strapi application locally (v4.8.2 or later). +2. Optionally, extend the application with plugins or custom code. +3. Version the application's codebase through your git provider (GitHub or GitLab). +4. Deploy the application with Strapi Cloud. ::: -## Support +The Strapi Cloud documentation that you are currently reading is organised in topics in a order that should correspond to your journey with the product. The following cards, on which you can click, will redirect you to the main topics and steps of your Strapi Cloud journey. -When having an issue or a question, the [forum](https://forum.strapi.io) is great first place to reach out for help. Both the Strapi community and core developers often check this platform and answer posts. + + + + + + + + + -For enterprise support, please see our [Enterprise Support platform](https://support.strapi.io/support/home), please note that you will need to have an active enterprise license to place tickets. +:::strapi Welcome to the Strapi community! +Strapi Cloud is built on top of Strapi, an open-source, community-oriented project. The Strapi team has at heart to share their vision and build the future of Strapi with the Strapi community. This is why the [roadmap](https://feedback.strapi.io) is open: as all insights are very important and will help steer the project in the right direction. Any community member is most welcome to share ideas and opinions there. + +You can also join [GitHub](https://github.com/strapi/strapi), the [Forum](https://forum.strapi.io/), and the [Discord](https://discord.strapi.io) and benefit from the years of experience, knowledge, and contributions by the Strapi community as a whole. +::: diff --git a/docusaurus/docs/cloud/getting-started/usage-billing.md b/docusaurus/docs/cloud/getting-started/usage-billing.md index b67d972f4a..f11189c611 100644 --- a/docusaurus/docs/cloud/getting-started/usage-billing.md +++ b/docusaurus/docs/cloud/getting-started/usage-billing.md @@ -1,72 +1,97 @@ --- -sidebar_label: 'Usage & Billing' +sidebar_label: 'Information on billing & usage' displayed_sidebar: cloudSidebar sidebar_position: 3 --- -# Usage & Billing - -This page contains general information related to the usage and billing of your Strapi Cloud account and applications. Strapi Cloud offers a free 7-day trial for all new accounts and two paid plans: **Pro** and **Team**. - -The usage-based pricing for Strapi Cloud is based on the following criteria and limits: - -| Feature | Free Trial | Pro | Team | -| --- | --- | --- | --- | -| **Seats** | 10 | 10 | 20 | -| **Database Entries** | 100,000 | 100,000 | 1,000,000 | -| **Assets Storage** | 5GB | 150GB | 500GB | -| **Assets Bandwidth** | 100GB | 500GB | 1,000GB | -| **API Requests** | 10,000 | 1,000,000 | 2,000,000 | -| **Audit Logs** | 7 days retention | N/A | 7 days retention | - -Where: - -- **Seats** are the maximum number of users that can access the Strapi Admin Panel. -- **Database Entries** are the number of entries in your database. -- **Assets Storage** is the amount of storage used by your assets. -- **Assets Bandwidth** is the amount of bandwidth used by your assets. -- **API Requests** are the number of requests made to your APIs. This includes requests made to the GraphQL and REST APIs. +# Information on billing & usage + +This page contains general information related to the usage and billing of your Strapi Cloud account and projects. + +Strapi Cloud offers a free 14 days trial for all new accounts, and 3 paid plans: Developer, Pro and Team (see [Pricing page](https://strapi.io/pricing-cloud)). The table below summarises Strapi Cloud usage-based pricing tiers, for general features & usage, CMS features and Cloud specific features: + +| Feature | Free Trial | Developer | Pro | Team | +| --- | --- | --- | --- | --- | +| **Seats** | 10 | 1 | 5 | 10 | +| **Database Entries** | 1,000 | 1,000 | 100,000 | 1,000,000 | +| **Assets Storage** | 5GB | 15GB | 150GB | 500GB | +| **Assets Bandwidth** | 50GB | 50GB per month | 500GB per month | 1,000GB per month | +| **API Requests** | 10,000 | 100,000 | 1,000,000 | 10,000,000 | +| | | | | | +| **Audit Logs** | 7 days retention | N/A | N/A | 7 days retention | +| **Releases** | 3 pending releases | N/A | N/A | 3 pending releases | +| **Review Workflows** | up to 2 | N/A | N/A | up to 2 | +| | | | | | +| **Backups** | N/A | N/A | Weekly | Weekly | + +:::strapi Additional information on usage and features +- General features & usage: + - Seats are the maximum number of users that can access the Strapi admin panel. + - Database entries are the number of entries in your database. + - Assets storage is the amount of storage used by your assets. + - Assets bandwidth is the amount of bandwidth used by your assets. + - API requests are the number of requests made to your APIs. This includes requests made to the GraphQL and REST APIs. +- CMS features: + - Audit Logs refers to the maximum number of days for which the feature retains the activities that happened (see [Audit Logs in User Guide](/user-docs/settings/audit-logs) for more information). + - Releases refers to the maximum number of pending releases that can be created (see [Releases in User Guide](/user-docs/releases/introduction) for more information). + - Review Workflows refers to the maximum number of workflows that can be created and used (see [Review Workflows in User Guide](/user-docs/settings/review-workflows) for more information). +- Cloud specific feature: Backups refers to the automatic backups of Strapi Cloud projects (see [dedicated page in Cloud documentation](/cloud/projects/settings#backups) for more information). +::: ## Seats management -Seats represent the maximum number of users that can access the Strapi Admin Panel. Each plan comes with a default number of seats. - -You can add more seats either by upgrading to a higher plan, or manually adding individual seats as desired. Seat can be added from the **Account Settings** -> **Manage Subscription** menu as detailed [here](../account/settings#managing-subscriptions). +Seats represent the maximum number of users that can access the Strapi admin panel. Each plan comes with a default number of seats. -### Maximum seats +You can add more seats either by upgrading to a higher plan, or manually adding individual seats as desired. Seats can be added from the **Billing & Usage** tab of a project's settings (see [Managing project's number of seats](/cloud/projects/settings#managing-projects-number-of-seats)). -Each plan has a maximum number of seats that can be added. The maximum number of seats for each plan is as follows: +There is however a maximum number of seats that can be added per plan: | Plan | Maximum Seats | | --- | --- | | **Free Trial** | 10 | -| **Pro** | 30 | +| **Developer** | 3 | +| **Pro** | 20 | | **Team** | 50 | + ## Billing -Billing is based on the usage of your Strapi Cloud account and applications. You will be billed monthly for the usage of your account and applications. You can view your usage and billing information in the [Billing](https://cloud.strapi.io/profile/billing) section of your Strapi Cloud account. +Billing is based on the usage of your Strapi Cloud account and projects. You will be billed monthly for the usage of your account and applications. You can view your usage and billing information in the [Billing](https://cloud.strapi.io/profile/billing) section of your Strapi Cloud account. ### Overages If you exceed the limits of your plan for API Requests, Asset Bandwidth, or Asset Storage, you will be charged for the corresponding overages. -For example, if you exceed the 500GB limit in asset bandwidth of the Pro plan, you will be charged for the excess bandwidth at the end of the current billing period. Overages are not prorated and are charged in full. +For example, if you exceed the 500GB limit in asset bandwidth of the Pro plan, you will be charged for the excess bandwidth at the end of the current billing period or on project deletion. Overages are not prorated and are charged in full. Overages are charged according to the following rates: | Feature | Rate | -| :--- | ---: | +| --- | --- | | **API Requests** | $1.50 / 25k requests | -| **Asset Bandwidth** | $25.00 / 100GB | -| **Asset Storage** | $2.00/GB per month | +| **Asset Bandwidth** | $30.00 / 100GB | +| **Asset Storage** | $0.60 / GB per month | ### Project suspension -Projects may end up in a **Suspended** state for various reasons, including violating the [terms of service](https://strapi.io/cloud-legal) or exceeding the limits of your free trial plan. +Projects may end up in a **Suspended** state for various reasons, including: not paying the invoice, exceeding the limits of your free trial plan, or violating the [terms of service](https://strapi.io/cloud-legal). + +If your project is suspended, you will no longer be able to access the application or trigger new deployments. You will also be unable to access the Strapi admin panel. + +You can view the status of your project in the [Projects](https://cloud.strapi.io/projects) section of your Strapi Cloud account and you will be notified by email. + +:::warning +If you do not resolve the issue within 30 days, your suspended project will be deleted and all data will be permanently lost. To avoid this situation, you will be sent a first email when your project becomes suspended, then another email every 5 days until one week left, to remind you to solve the issue. The last week before the deletion of the project, you will be sent 3 more emails: 6 days, 3 days and 1 day before your project is finally deleted. +::: + +#### Project suspension after subscription cancellation + +If you don't pay the invoice, the subscription of your project will automatically be cancelled and the project will be suspended. You can reactivate the subscription through the billing modal (see [Edit subscription](/cloud/account/account-billing#edit-subscription)). -If your project is suspended, you will no longer be able to access the application or trigger new deployments. You will also be unable to access the Strapi Admin Panel. You can view the status of your project in the [Projects](https://cloud.strapi.io/projects) section of your Strapi Cloud account. +1. Log into the billing modal and go to the *Subscription details* of the subscription associated with the suspended project. You should see a warning message confirming that the subscription was canceled for the following reason: "Not Paid". +2. Go back to the homepage of the billing modal, listing subscriptions and billing options. +3. Go to *Payment methods* and add a new, working card to pay the invoice. As soon as the invoice is paid, your project will automatically be reactivated. -Owners of suspended projects will receive an email notification with instructions on how to resolve the issue. If you do not receive an email notification, please contact [Strapi Support](mailto:support@strapi.io). +#### Project suspension for other reasons -If you do not resolve the issue within 10 days, your project will be deleted and all data will be permanently lost. \ No newline at end of file +If your project was suspended for reasons other than unpaid invoice leading to subscription cancellation, you may not have the possibility to reactivate your project yourself. You should receive an email with instructions on how to resolve the issue. If you do not receive the email notification, please contact [Strapi Support](mailto:support@strapi.io). diff --git a/docusaurus/docs/cloud/projects/collaboration.md b/docusaurus/docs/cloud/projects/collaboration.md new file mode 100644 index 0000000000..c4dcf6d49c --- /dev/null +++ b/docusaurus/docs/cloud/projects/collaboration.md @@ -0,0 +1,76 @@ +--- +title: Collaboration +displayed_sidebar: cloudSidebar +description: Share your projects on Strapi Cloud to collaborate with others. +canonicalUrl: https://docs.strapi.io/cloud/projects/collaboration.md +sidebar_position: 1 +--- + +# Collaboration on projects + +Projects are created by a user via their Strapi Cloud account. Strapi Cloud users can share their projects to anyone else, so these new users can have access to the project dashboard and collaborate on that project, without the project owner to ever have to share their credentials. + +Users invited to collaborate on a project, called maintainers, do not have the same permissions as the project owner. Contrary to the project owner, maintainers: + +- Cannot share the project themselves to someone else +- Cannot delete the project from the project settings +- Cannot access the *Billing* section of project settings + +## Sharing a project + + + +To invite a new maintainer to collaborate on a project: + +1. From the *Projects* page, click on the project of your choice to be redirected to its dashboard. +2. Click on the **Share** button located in the dashboard's header. +3. In the *Share [project name]* dialog, type the email address of the person to invite in the textbox. A dropdown indicating "Invite [email address]" should appear. +4. Click on the dropdown: the email address should be displayed in a purple box right below the textbox. +5. (optional) Repeat steps 3 and 4 to invite more people. Email addresses can only entered one by one but invites can be sent to several email addresses at the same time. +6. Click on the **Send** button. + +New maintainers will be sent an email containing a link to click on to join the project. Once a project is shared, avatars representing the maintainers will be displayed in the project dashboard's header, next to the **Share** button, to see how many maintainers collaborate on that project and who they are. + +:::tip +Avatars use GitHub, Google or GitLab profile pictures, but for pending users only initials will be displayed until the activation of the maintainer account. You can hover over an avatar to display the full name of the maintainer. +::: + +## Managing maintainers + +From the *Share [project name]* dialog accessible by clicking on the **Share** button of a project dashboard, projects owners can view the full list of maintainers who have been invited to collaborate on the project. From there, it is possible to see the current status of each maintainer and to manage them. + + + +Maintainers whose full name is displayed are users who did activate their account following the invitation email. If however there are maintainers in the list whose email address is displayed, it means they haven't activated their accounts and can't access the project dashboard yet. In that case, a status should be indicated right next to the email address to explain the issue: + +- Pending: the invitation email has been sent but the maintainer hasn't acted on it yet. +- Expired: the email has been sent over 72 hours ago and the invitation expired. + +For Expired statuses, it is possible to send another invitation email by clicking on the **Manage** button, then **Resend invite**. + +### Revoking maintainers + +To revoke a maintainer's access to the project dashboard: + +1. Click on the **Share** button in the project dashboard's header. +2. In the list of *People with access*, find the maintainer whose access to revoke and click on the **Manage** button. +3. Click on the **Revoke** button. +4. In the confirmation dialog, click again on the **Revoke** button. + +The revoked maintainer will completely stop having access to the project dashboard. + +:::note +Maintainers whose access to the project has been revoked do not receive any email or notification. +::: diff --git a/docusaurus/docs/cloud/projects/deploys-history.md b/docusaurus/docs/cloud/projects/deploys-history.md new file mode 100644 index 0000000000..e00d61cffe --- /dev/null +++ b/docusaurus/docs/cloud/projects/deploys-history.md @@ -0,0 +1,59 @@ +--- +title: Deploy history & logs +displayed_sidebar: cloudSidebar +description: View projects' deploy history and logs. +canonicalUrl: https://docs.strapi.io/cloud/deploys-history.html +sidebar_position: 1 +--- + +# Deploy history and logs + +For each Strapi Cloud project, you can access the history of all deployments that occured and their details including build and deploy logs. This information is available in the *Deploys* tab, located in the header of any chosen project. + +## Viewing deploy history + +In the *Deploys* tab is displayed a chronological list of cards with the details of all historical deployments for your project. + + + +Each card displays the following information: +- Commit SHA πŸ’‘ The commit SHA (or hash) is the unique ID of your commit, which refers to a specific change that was made at a specific time., with a direct link to your git provider, and commit message +- Deployment status: + - *Deploying* + - *Done* + - *Cancelled* + - *Build failed* + - *Deploy failed* +- Last deployment time (when the deployment was triggered and the duration) +- Production branch + +## Accessing deployment details & logs + +From the *Deploys* tab, you can click on the ![See logs button](/img/assets/icons/Eye.svg) **See logs** button of any chosen deployment card to be redirected to the *Log details*. It contains the deployment's details logs. + + + +In the *Deploy details* section of the *Log details* page is displayed the following information: +- *Status*, which can be *Building*, *Deploying*, *Done*, *Cancelled*, *Build failed*, or *Deploy failed* +- *Maintenance*: when the deployment occured +- *Deployed in*: the amount of time the deployment took +- *Commit*: the commit SHA πŸ’‘ The commit SHA (or hash) is the unique ID of your commit, which refers to a specific change that was made at a specific time., with a direct link to your git provider, and commit message used for this deployment +- *Branch*: the branch used for this deployment + +In the *Logs* section of the *Log details* page you can click on the arrow buttons ![Down arrow](/img/assets/icons/ONHOLDCarretDown.svg) ![Up arrow](/img/assets/icons/ONHOLDCarretUp.svg) to show or hide the build and deploy logs of the deployment. + +:::tip +Click the ![Copy button](/img/assets/icons/duplicate.svg) **Copy to clipboard** button to copy the log contents. +::: diff --git a/docusaurus/docs/cloud/projects/deploys.md b/docusaurus/docs/cloud/projects/deploys.md new file mode 100644 index 0000000000..eef1d7b73a --- /dev/null +++ b/docusaurus/docs/cloud/projects/deploys.md @@ -0,0 +1,47 @@ +--- +title: Deployments management +displayed_sidebar: cloudSidebar +description: Manage your projects's deploys. +canonicalUrl: https://docs.strapi.io/cloud/projects/deploys.html +sidebar_position: 1 +--- + +# Deployments management + +The creation of a new Strapi Cloud project automatically trigger the deployment of that project. After that, deployments can be: + +- manually triggered whenever needed, [from the Cloud dashboard](#triggering-a-new-deployment) or [from the CLI](/cloud/cli/cloud-cli#strapi-deploy), +- or automatically triggered everytime a new commit is pushed to the branch, if the Strapi Cloud project is connected to a git repository and the "deploy on push" option is enabled (see [Project settings](/cloud/projects/settings#modifying-git-repository--branch)). + +Ongoing deployments can also be [manually cancelled](#cancelling-a-deployment) if needed. + +## Triggering a new deployment + +To manually trigger a new deployment for your project, click on the **Trigger deploy** button always displayed in the right corner of a project dashboard's header. This action will add a new card in the *Deploys* tab, where you can monitor the status and view the deployment logs live (see [Deploy history and logs](/cloud/projects/deploys-history)). + + + +## Cancelling a deployment + +If for any reason you want to cancel an ongoing and unfinished deployment: + +1. Go to the *Log details* page of the deployment (see [Accessing log details](/cloud/projects/deploys-history#accessing-deployment-details--logs)). +2. Click on the **Cancel deploy** button in the top right corner. The status of the deployment will automatically change to *Cancelled*. + +:::tip +You can also cancel a deployment from the *Deploys* tab which lists the deployments history. The card of ongoing deployment with the *Building* status will display a **Cancel deploy** button. +::: + + diff --git a/docusaurus/docs/cloud/projects/notifications.md b/docusaurus/docs/cloud/projects/notifications.md index c6ada239ad..725699dab1 100644 --- a/docusaurus/docs/cloud/projects/notifications.md +++ b/docusaurus/docs/cloud/projects/notifications.md @@ -8,19 +8,25 @@ sidebar_position: 3 # Notifications -The **Notification center** can be opened by clicking the bell icon ![Notification icon](/img/assets/icons/notifications.svg) in the top navigation of the Cloud dashboard. +The Notification center can be opened by clicking the bell icon ![Notification icon](/img/assets/icons/notifications.svg) in the top navigation of the Cloud dashboard. -It displays a list of the latest notifications for all existing projects. Clicking on a notification card from the list will redirect you to the **Log details** of the corresponding deployment. +It displays a list of the latest notifications for all your existing projects. Clicking on a notification card from the list will redirect you to the *Log details* page of the corresponding deployment (more information in [Deploy history & logs](/cloud/projects/deploys-history#accessing-deployment-details--logs)). -![Projects page - List](/img/assets/cloud/notification-center.png) + The following notifications can be listed in the Notifications center: -* **Deploy completed**: When a deployment is successfully done. -* **Build failed**: When deployment fails during the build stage. -* **Deploy failed** When deployment fails during the deployment stage. -* **Deploy triggered** When deployment is triggered by a new push to the connected repository. This notification is however not sent when the deployment is triggered manually. +- *Deploy completed*: when a deployment is successfully done. +- *Build failed*: when a deployment fails during the build stage. +- *Deploy failed*: when a deployment fails during the deployment stage. +- *Deploy triggered*: when a deployment is triggered by a new push to the connected repository. This notification is however not sent when the deployment is triggered manually. :::note -All notifications older than 30 days are automatically removed from the **Notification center**. +All notifications older than 30 days are automatically removed from the Notification center. ::: diff --git a/docusaurus/docs/cloud/projects/overview.md b/docusaurus/docs/cloud/projects/overview.md index e3fa0f1465..883ef78e67 100644 --- a/docusaurus/docs/cloud/projects/overview.md +++ b/docusaurus/docs/cloud/projects/overview.md @@ -1,125 +1,64 @@ --- -title: Projects +title: Projects overview displayed_sidebar: cloudSidebar description: View and manage your projects on Strapi Cloud. canonicalUrl: https://docs.strapi.io/cloud/projects/overview.html sidebar_position: 1 --- -# Projects +# Projects overview -The **Projects** page displays a list of all your Strapi Cloud projects. From here you can manage your projects and access the corresponding applications. +The *Projects* page displays a list of all your Strapi Cloud projects. From here you can manage your projects and access the corresponding applications. -![Projects page - List](/img/assets/cloud/project_list.png) + Each project card displays the following information: -* **Project name** -* **Status**: Displays the current status of the project. One of the following: - * **Disconnected** if the project repository is not connected to Strapi Cloud. - * **Suspended** if the project has been suspended. Contact Strapi support to reactivate the project. - * **Incompatible version** if the project is using a Strapi version that is not compatible with Strapi Cloud. -* **Last deployment date**: Timestamp of the last deployment. -* **Options** menu: Open the menu to select from the available options: - * **Visit App**: Go to the application. - * **Go to Deploys**: Go to the [**Deploys**](#deploys) page. - * **Go to Settings**: Go to the [**Settings**](./settings/) page. +* the project name +* the current status of the project: + * *Disconnected*, if the project repository is not connected to Strapi Cloud + * *Suspended*, if the project has been suspended (refer to [Project suspension](/cloud/getting-started/usage-billing#project-suspension) to reactivate the project) + * *Incompatible version*, if the project is using a Strapi version that is not compatible with Strapi Cloud +* the last deployment date + +Each project card also displays a ![Menu icon](/img/assets/icons/more.svg) menu icon to access the following options: +* **Visit App**: to be redirected to the application +* **Go to Deploys**: to be redirected to the [*Deploys*](/cloud/projects/deploys) page +* **Go to Settings**: to be redirected to the [*Settings*](/cloud/projects/settings) page + +## Accessing a project's overview + +From the *Projects* page, click on any project card to access the *Overview* of your project. It displays all details such as usage and status information and gives access to deployment history and available settings. + +:::strapi Navigating Strapi Cloud projects dashboards +Once you click on a project page, you access the dedicated dashboard for your chosen project. It is by default that you land on the *Overview* tab, however the header of the project's dashboard doesn't change and always offers the following options: + +- links to the other available tabs for the project: *Overview*, [*Deploys*](/cloud/projects/deploys), [*Runtime Logs*](/cloud/projects/runtime-logs) and [*Settings*](/cloud/projects/settings) +- the **Share** button to invite a new maintainer to collaborate on your project β€” and if the project is already shared: avatars of the maintainers (see [Collaboration](/cloud/projects/collaboration)) +- the **Trigger deploy** button to trigger a new deployment of your project +- the **Visit app** button to access your application +::: + + + +From the *Overview* tab, you can: +- view a recap of the main settings of your project, such as: + - the link to the source repository + - the name of the branch + - the name of the base directory + - the URL and link to the application +- view your project's usage (see [Usage](/cloud/getting-started/usage-billing) for more information) +- view your project's latest deploys (see [Deploys](/cloud/projects/deploys) for more information) -## Project details - -From the **Projects** page, click on any project card to access that project's details page. - -The project details page displays the following tabs: **Deploys** and [**Settings**](./settings/). - -### Overview - -The **Overview** tab displays a high detail view of the project, including its configuration, usage, and deployment history. - -![Project overview](/img/assets/cloud/overview.png) - -From this tab you can: - -* **Trigger deploy**: Click the **Trigger deploy** button to trigger a new deployment -* **Visit App**: Click the **Visit App** button to access the application -* Quickly view project [**Usage**](/cloud/getting-started/usage-billing) -* Quickly view project [**Deploys**](#deploys) - -### Deploys - -The **Deploys** tab displays a chronological list of cards with the details of all historical deployments for the project. - -![Project deploys](/img/assets/cloud/deploys.png) - -Each card displays the following information: - -* **Commit SHA** -* **Commit message** -* **Deployment status**: Whether the project is: - * **Deploying** - * **Done** - * **Cancelled** - * **Build failed** - * **Deploy failed** -* **Last deployment time**: When the deployment was triggered and the duration. -* **Production branch** -* **Options** menu (`...`): Use to access the [**Deployment details**](#deployment-details) page. For in-progress deployments, you can also cancel the deployment. - -From this page you can also trigger a new deployment, cancel a pending deployment, and click on any deployment to view additional details. - -#### Deployment details - -From the **Deploys** tab, view the deployment history for this project. Use the **Options** menu (`...`) to access the **Deployment details** page for any deployment. - -![Deployment details](/img/assets/cloud/deploy_logs.png) - -The deployment details page displays the following information: - -* **Status**: Whether the project is **Building**, **Deploying**, **Done**, **Cancelled**, **Build failed**, or **Deploy failed**. -* **Triggered**: When the deployment was triggered. -* **Deployed In**: The time it took to deploy the project. -* **Commit**: The commit used for this deployment. Click the commit SHA to view the commit on GitHub. -* **Branch**: The branch used for this deployment. -* **Logs**: The Build, Deploy, and Runtime logs of the deployment. Click the **copy logs** button to copy the log contents. - - \ No newline at end of file diff --git a/docusaurus/docs/cloud/projects/runtime-logs.md b/docusaurus/docs/cloud/projects/runtime-logs.md new file mode 100644 index 0000000000..8041d1f682 --- /dev/null +++ b/docusaurus/docs/cloud/projects/runtime-logs.md @@ -0,0 +1,23 @@ +--- +title: Runtime logs +displayed_sidebar: cloudSidebar +description: View runtime logs of Strapi Cloud project. +canonicalUrl: https://docs.strapi.io/cloud/projects/runtime-logs.html +sidebar_position: 1 +--- + +# Runtime logs + +From a chosen project's dashboard, the *Runtime logs* tab, located in the header, displays the live logs of the project. + +:::note +The *Runtime logs* are only accessible once the project is successfully deployed. +::: + + \ No newline at end of file diff --git a/docusaurus/docs/cloud/projects/settings.md b/docusaurus/docs/cloud/projects/settings.md index bdd6be677a..43984894ea 100644 --- a/docusaurus/docs/cloud/projects/settings.md +++ b/docusaurus/docs/cloud/projects/settings.md @@ -1,57 +1,146 @@ --- -title: Project Settings +title: Project settings displayed_sidebar: cloudSidebar description: View and manage your projects on Strapi Cloud. canonicalUrl: https://docs.strapi.io/cloud/projects/settings.html sidebar_position: 2 --- -# Project Settings +import InvoiceStatus from '/docs/snippets/invoices-statuses.md' -The *Project Settings* page enables you to manage the configuration and settings for of Strapi Cloud project. There are 4 tabs available: *General*, *Domains*, *Variables* and *Billing*. +# Project settings + +From a chosen project's dashboard, the *Settings* tab, located in the header, enables you to manage the configurations and settings for your Strapi Cloud project. + +There are 7 tabs available: + +- ![General icon](/img/assets/icons/Faders.svg) [*General*](#general), +- ![Backups icon](/img/assets/icons/ArrowClockwise.svg) [*Backups*](#backups), +- ![Domains icon](/img/assets/icons/Browsers.svg) [*Domains*](#domains), +- ![Variables icon](/img/assets/icons/code2.svg) [*Variables*](#variables), +- ![Billing & Usage icon](/img/assets/icons/CreditCard.svg) [*Billing & Usage*](#billing--usage), +- ![Plans icon](/img/assets/icons/MapTrifold.svg) [Plans](#plans), +- and ![Invoices icon](/img/assets/icons/Invoice.svg) [Invoices](#invoices). ## General -The *General* tab enables you to check and update the following options for the project: +The ![General icon](/img/assets/icons/Faders.svg) *General* tab enables you to check and update the following options for the project: + +- *Details*: to see the name of your Strapi Cloud project, used to identify the project on the Cloud Dashboard, Strapi CLI, and deployment URLs. The project name is set at project creation (see [Project creation](/cloud/getting-started/deployment)) and cannot be modified afterwards. +- *Connected Git repository*: to change the branch of the GitHub repository used for your project (see [Modifying GitHub repository branch](#modifying-git-repository--branch)). Also allows to enable/disable the "deploy on push" option. +- *Selected region*: to see the hosting region of the project, meaning the geographical location of the servers where the project and its data and resources are stored. The hosting region is set at project creation (see [Project creation](/cloud/getting-started/deployment)) and cannot be modified afterwards. +- *Debug info*: to see the internal project name for the project. This is useful for support purposes. +- *Node version*: to change the Node version of the project (see [Modifying Node version](#modifying-node-version)). +- *Delete project*: to permanently delete your Strapi Cloud project (see [Deleting Strapi Cloud project](#deleting-strapi-cloud-project)). + + + +### Modifying git repository & branch + +The GitHub or Gitlab repository, branch and base directory for a Strapi Cloud project are by default chosen at the creation of the project (see [Creating a project](/cloud/getting-started/deployment)). After the project's creation, via the project's settings, it is possible to: -- Details: to modify the name of your Strapi Cloud project, used to identify the project on the Cloud Dashboard, Strapi CLI, and deployment URLs. The project name cannot be edited after creation. -- Connected Git repository: to change the branch of the GitHub repository used for your project (see [Modifying GitHub repository branch](#modifying-github-repository-branch)). -- Selected region: to see the hosting region of the project. -- Debug info: to see the internal project name for the project. This is useful for support purposes. -- Delete project: to permanently delete your Strapi Cloud project (see [Deleting Strapi Cloud project](#deleting-strapi-cloud-project)). +- update the project's repository or switch to another git provider (see [Updating repository](#updating-repository)), +- edit the project's branch, base directory and deploy on push setting (see [Editing branch](#editing-branch)). -![Project settings](/img/assets/cloud/settings.png) +:::caution +Updating the git repository could result in the loss of the project and its data, for instance if the wrong repository is selected or if the data schema between the old and new repository doesn't match. +::: -### Modifying GitHub repository branch +#### Updating repository -The GitHub repository branch and base directory for a Strapi Cloud project are by default chosen at the creation of the project (see [Creating a project](/cloud/getting-started/deployment)). Both can afterwards be edited via the account settings. +1. In the *Connected git repository* section of the ![General icon](/img/assets/icons/Faders.svg) *General* tab, click on the **Update repository** button. +2. (optional) If you wish to not only update the repository but switch to another git provider, click on the **Switch to GitHub/GitLab** button at the bottom of the *Update repository* dialog. You will be redirected to the chosen git provider's authorization settings before getting back to the *Update repository dialog*. +3. In the *Update repository* dialog, fill in the 3 available settings: -1. In the *Connected Git repository* section of the *General* tab, click on the **Edit** button. -2. In the *Edit Git settings* dialog, edit the available options of your choice: + | Setting name | Instructions | + | --------------- | ------------------------------------------------------------------------ | + | Account | Choose an account from the drop-down list. | + | Repository | Choose a repository from the drop-down list. | + | Git branch | Choose a branch from the drop-down list. | + | Deploy the project on every commit pushed to this branch | Tick the box to automatically trigger a new deployment whenever a new commit is pushed to the selected branch. Untick it to disable the option. | + +4. Click on the **Save** button. +5. In the confirmation dialog, confirm your changes by clicking on the **Relink repository** button. + +#### Editing branch + +1. In the *Connected git repository* section of the ![General icon](/img/assets/icons/Faders.svg) *General* tab, click on the **Edit branch** button. +2. In the *Edit branch* dialog, edit the settings below: | Setting name | Instructions | | --------------- | ------------------------------------------------------------------------ | - | Selected branch | Choose a branch from the drop-down list. | + | Git branch | Choose a branch from the drop-down list. | | Base directory | Write the path of the base directory in the textbox. | - | Deploy the project on every commit pushed to this branch | Check the box to automatically trigger a new deployment whenever a new commit is pushed to the selected branch. | + | Deploy the project on every commit pushed to this branch | Tick the box to automatically trigger a new deployment whenever a new commit is pushed to the selected branch. Untick it to disable the option. | 3. Click on the **Save** button. +### Modifying Node version + +The project's Node version is first chosen at the creation of the project (see [Creating a project](/cloud/getting-started/deployment)), through the advanced settings. It is possible to switch to another Node version afterwards. + +1. In the *Node version* section of the ![General icon](/img/assets/icons/Faders.svg) *General* tab, click on the **Edit** button. +2. Using the *Node version* drop-down in the dialog, click on the version of your choice. +3. Click on the **Save** button. +4. Click on the **Trigger deploy** button in the right corner of the project's header. If the deployment fails, it is because the Node version doesn't match the version of your Strapi project. You will have to switch to the other Node version and re-deploy your project again. + ### Deleting Strapi Cloud project You can delete any Strapi Cloud project, but it will be permanent and irreversible. Associated domains, deployments and data will be deleted as well and the subscription for the project will automatically be cancelled. -1. In the *Delete project* section of the *General* tab, click on the **Delete project** button. -2. In the dialog, type `DELETE` in the textbox. -3. Confirm the deletion of your project by clicking on the **Delete** button. +1. In the *Delete project* section of the ![General icon](/img/assets/icons/Faders.svg) *General* tab, click on the **Delete project** button. +2. In the dialog, select the reason why you are deleting your project. If selecting "Other" or "Missing feature", a textbox will appear to let you write additional information. +3. Confirm the deletion of your project by clicking on the **Delete project** button at the bottom of the dialog. + +## Backups {#backups} + +The ![Backups icon](/img/assets/icons/ArrowClockwise.svg) *Backups* tab informs you of the status and date of the latest backup of your Strapi Cloud projects. The databases associated with all existing Strapi Cloud projects are indeed automatically backed up weekly and those backups are retained for a one-month period. + +:::note +The backup feature is not available for Strapi Cloud projects using the free trial or the Developer plan. You will need to upgrade to either the Pro or Team plan to have your project automatically backed up. + +Note also that only project owners can restore a backup. Maintainers have access to the ![Backups icon](/img/assets/icons/ArrowClockwise.svg) *Backups* tab but the **Restore backup** button won't be displayed for them. Refer to [Collaboration](/cloud/projects/collaboration) for more information. +::: + +:::tip +For projects created before the release of the Backup feature in October 2023, the first backup will automatically be triggered with the next deployment of the project. +::: + + + +### Restoring a backup + +If you need to restore a backup of your project: + +1. In the ![Backups icon](/img/assets/icons/ArrowClockwise.svg) *Backups* section, click on the **Restore backup** button. +2. In the dialog, choose one of the available backups of your project in the *Choose backup* drop-down. +3. Click on the **Restore** button of the dialog. Once the restoration finished, your project will be back to the state it was at the time of the chosen backup. ## Domains -The *Domains* tab enables you to manage domains and connect new ones. +The ![Domains icon](/img/assets/icons/Browsers.svg) *Domains* tab enables you to manage domains and connect new ones. -![Domains](/img/assets/cloud/settings_domains.png) + -All existing domains for your Strapi Cloud project are listed in the *Domains* tab. For each domain, you can: +All existing domains for your Strapi Cloud project are listed in the ![Domains icon](/img/assets/icons/Browsers.svg) *Domains* tab. For each domain, you can: - see its current status: - ![Edit icon](/img/assets/icons/CheckCircle.svg) Active: the domain is currently confirmed and active @@ -78,16 +167,119 @@ Default domain names are made of 2 randomly generated words followed by a hash. ## Variables -[Environment variables](../../dev-docs/configurations/environment) are used to configure the environment of your Strapi app, such as the database connection. +Environment variables (more information in the [Developer Documentation](../../dev-docs/configurations/environment)) are used to configure the environment of your Strapi application, such as the database connection. + + + +In the ![Variables icon](/img/assets/icons/code2.svg) *Variables* tab, you can: +- click the **Add variable** button to create a new variable +- edit any variable, each being composed of a *Name* and a *Value* +- click the ![Delete icon](/img/assets/icons/delete.svg) delete button associated with any variable to delete it +- click the **Save** button to save any change made on the page + +## Billing & Usage + +The ![Billing & Usage icon](/img/assets/icons/CreditCard.svg) *Billing & Usage* tab displays all information on the current subscription plan and included usage for the project, and allows to have a detailed look at the usage. Through this tab, you can also [manage the number of seats for your project](#managing-projects-number-of-seats). + +:::tip +In the Usage section of the ![Billing & Usage icon](/img/assets/icons/CreditCard.svg) *Billing & Usage* tab, you can see the current monthly usage of your project compared to the maximum usage allowed by your project's subscription. Use the *Time range* filters to see the project's usage for any chosen month. + +Note also that if your usage indicates that another subscription plan would fit better for your project, a message will be displayed in the ![Billing & Usage icon](/img/assets/icons/CreditCard.svg) *Billing & Usage* tab to advise which plan you could switch to. +::: + + + +### Managing project's number of seats + +You can manually add more seats or lower the number of seats for your project without necessarily upgrading or downgrading to another plan (see [full documentation on seats management](/cloud/getting-started/usage-billing#seats-management)). + +#### Adding more seats for the project + +1. In the ![Billing & Usage icon](/img/assets/icons/CreditCard.svg) *Billing & Usage* tab of your project's settings, click on the **Manage** button next to the displayed number of seats. +2. In the window that opens, select with the drop-down the number of *Additional seats* of your choice. The cost of the additional seats is automatically calculated and displayed in the window. +3. (optional) Click **I have a discount code**, enter your discount code in the field, and click on the **Apply** button. +4. Click the **Save** button to confirm. You will automatically be billed with the payment method defined in your profile. + +#### Removing seats from the project + +1. In the ![Billing & Usage icon](/img/assets/icons/CreditCard.svg) *Billing & Usage* tab of your project's settings, click on the **Manage** button next to the displayed number of seats. +2. In the window that opens, select with the drop-down the number of *Billed seats* you want to keep. +3. Click the **Save button** to confirm. The new, lower number of seats will not be effective until the next month. + +## Plans + +The ![Plans icon](/img/assets/icons/MapTrifold.svg) *Plans* tab displays an overview of the available Strapi Cloud plans and allows you to upgrade or downgrade from your current plan to another. + +:::note +If you are using the free trial, the *Plan* tab shows a countdown of how many days you have left, as well as indications of the next steps. For more information about the free trial and project suspension, please refer to [Information on billing & usage](/cloud/getting-started/usage-billing). +::: + + + +### Upgrading to another plan + +Strapi Cloud plan upgrades to another, higher plan are immediate and can be managed for each project via the project settings. + +:::note +When using the free trial, the buttons to upgrade to another plan are greyed out and unusable until you have filled in your billing information. Please refer to [Account billing details](/cloud/account/account-billing) for more information. +::: + +To upgrade your current plan to a higher one: + +1. In the ![Plans icon](/img/assets/icons/MapTrifold.svg) *Plans* tab of your project's settings, click on the **Upgrade** button of the plan you want to upgrade to. +2. In the window that opens, check the payment details that indicate how much you will have to pay immediately after confirming the upgrade, and the available options. + + a. (optional) Click the **Edit** button to select another payment method. + b. (optional) Click **I have a discount code**, enter your discount code in the field, and click on the **Apply** button. + +3. Click on the **Upgrade to [plan name]** button to confirm the upgrade of your Strapi project to another plan. + +### Downgrading to another plan + +Strapi Cloud plan downgrades can be managed for each project via the project settings. Downgrades are however not immediately effective: the higher plan will still remain active until the end of the current month (e.g. if you downgrade from the Team plan to the Pro plan on June 18th, your project will remain on the Team plan until the end of the month: on July 1st, the Pro plan will be effective for the project). + +:::caution +Make sure to check the usage of your Strapi Cloud project before downgrading: if your current usage exceeds the limits of the lower plan, you are taking the risk of getting charged for the overages. Note also that you may lose access to some features: for example, downgrading to the Developer plan which doesn't include the Backups feature, would make you lose all your project's backups. Please refer to [Information on billing & usage](/cloud/getting-started/usage-billing) for more information. +::: + +To downgrade your current plan to a lower one: -You can view default values, and create/edit/delete environment variables for your project in the **Variables** tab: +1. In the ![Plans icon](/img/assets/icons/MapTrifold.svg) *Plans* tab of your project's settings, click on the **Downgrade** button of the plan you want to downgrade to. +2. In the window that opens, check the information related to downgrading. +3. Click on the **Downgrade** button to confirm the downgrade of your Strapi project's plan. -![Project variables](/img/assets/cloud/settings_variables.png) +## Invoices -## Billing +The ![Invoices icon](/img/assets/icons/Invoice.svg) *Invoices* tab displays the full list of invoices for your Strapi Cloud project as well as their status. -The *Billing* section displays the current subscription plan and included usage for the project. + -Use the **Manage subscription** button to change the subscription plan. + -![Project billing](/img/assets/cloud/settings_billing.png) +:::strapi Invoices are also available in your profile settings. +In the *Profile > Invoices* tab, you will find the complete list of invoices for all your projects. Feel free to check the [dedicated documentation](/cloud/account/account-billing#account-invoices). +::: \ No newline at end of file diff --git a/docusaurus/docs/dev-docs/admin-panel-customization.md b/docusaurus/docs/dev-docs/admin-panel-customization.md index 50145560ed..3c748fae5a 100644 --- a/docusaurus/docs/dev-docs/admin-panel-customization.md +++ b/docusaurus/docs/dev-docs/admin-panel-customization.md @@ -5,13 +5,14 @@ toc_max_heading_level: 4 --- + import FeedbackCallout from '/docs/snippets/backend-customization-feedback-cta.md' const captionStyle = {fontSize: '12px'} const imgStyle = {width: '100%', margin: '0' } -The admin panel is a `node_module` that is similar to a plugin, except that it encapsulates all the installed plugins of a Strapi application. Some of its aspects can be [customized](#customization-options), and plugins can also [extend](#extension) it. +The admin panel is a React-based single-page application. It encapsulates all the installed plugins of a Strapi application. Some of its aspects can be [customized](#customization-options), and plugins can also [extend](#extension) it. -To toggle hot reloading and get errors in the console while developing, start Strapi in front-end development mode by running the application with the `--watch-admin` flag: +To start your strapi instance with hot reloading while developing, run the following command: ```bash cd my-app # cd into the root directory of the Strapi application project @@ -26,7 +27,6 @@ Customizing the admin panel is helpful to better reflect your brand identity or - 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 [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 @@ -40,19 +40,16 @@ To make the admin panel accessible from `http://localhost:1337/dashboard`, use t ```js title="./config/server.js" - module.exports = ({ env }) => ({ - host: env('HOST', '0.0.0.0'), - port: env.int('PORT', 1337), - + host: env("HOST", "0.0.0.0"), + port: env.int("PORT", 1337), }); ``` ```js title="./config/admin.js" - module.exports = ({ env }) => ({ - url: '/dashboard', -}) + url: "/dashboard", +}); ``` @@ -60,19 +57,16 @@ module.exports = ({ env }) => ({ ```js title="./config/server.ts" - export default ({ env }) => ({ - host: env('HOST', '0.0.0.0'), - port: env.int('PORT', 1337), - + host: env("HOST", "0.0.0.0"), + port: env.int("PORT", 1337), }); ``` ```js title="./config/admin.ts" - export default ({ env }) => ({ - url: '/dashboard', -}) + url: "/dashboard", +}); ``` @@ -84,26 +78,27 @@ For more advanced settings please see the [admin panel configuration](/dev-docs/ #### Host and port +:::note +From 4.15.1 this is now deprecated. The strapi server now supports the live updating of the admin panel in development mode. +::: + By default, the front end development server runs on `localhost:8000` but this can be modified: ```js title="./config/server.js" - module.exports = ({ env }) => ({ - host: env('HOST', '0.0.0.0'), - port: env.int('PORT', 1337), + host: env("HOST", "0.0.0.0"), + port: env.int("PORT", 1337), }); ``` ```js title="./config/admin.js" - module.exports = ({ env }) => ({ - host: 'my-host', // only used along with `strapi develop --watch-admin` command + host: "my-host", // only used along with `strapi develop --watch-admin` command port: 3000, // only used along with `strapi develop --watch-admin` command }); - ``` @@ -111,20 +106,17 @@ module.exports = ({ env }) => ({ ```js title="./config/server.ts" - export default ({ env }) => ({ - host: env('HOST', '0.0.0.0'), - port: env.int('PORT', 1337), + host: env("HOST", "0.0.0.0"), + port: env.int("PORT", 1337), }); ``` ```js title="./config/admin.ts" - export default ({ env }) => ({ - host: 'my-host', // only used along with `strapi develop --watch-admin` command + host: "my-host", // only used along with `strapi develop --watch-admin` command port: 3000, // only used along with `strapi develop --watch-admin` command }); - ``` @@ -145,16 +137,16 @@ Any file used by the `config` object (e.g. a custom logo) should be placed in a The `config` object accepts the following parameters: -| Parameter | Type | Description | -| --------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| `auth` | Object | Accepts a `logo` key to replace the default Strapi [logo](#logos) on login screen | -| `head` | Object | Accepts a `favicon` key to replace the default Strapi [favicon](#favicon) | -| `locales` | Array of Strings | Defines availables locales (see [updating locales](#locales)) | -| `translations` | Object | [Extends the translations](#extending-translations) | -| `menu` | Object | Accepts the `logo` key to change the [logo](#logos) in the main navigation | -| `theme.light` and `theme.dark` | Object | [Overwrite theme properties](#theme-extension) for Light and Dark modes | -| `tutorials` | Boolean | Toggles [displaying the video tutorials](#tutorial-videos) | -| `notifications` | Object | Accepts the `releases` key (Boolean) to toggle [displaying notifications about new releases](#releases-notifications) | +| Parameter | Type | Description | +| ------------------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------- | +| `auth` | Object | Accepts a `logo` key to replace the default Strapi [logo](#logos) on login screen | +| `head` | Object | Accepts a `favicon` key to replace the default Strapi [favicon](#favicon) | +| `locales` | Array of Strings | Defines availables locales (see [updating locales](#locales)) | +| `translations` | Object | [Extends the translations](#extending-translations) | +| `menu` | Object | Accepts the `logo` key to change the [logo](#logos) in the main navigation | +| `theme.light` and `theme.dark` | Object | [Overwrite theme properties](#theme-extension) for Light and Dark modes | +| `tutorials` | Boolean | Toggles [displaying the video tutorials](#tutorial-videos) | +| `notifications` | Object | Accepts the `releases` key (Boolean) to toggle [displaying notifications about new releases](#releases-notifications) |
Example of a custom configuration for the admin panel @@ -163,10 +155,9 @@ The `config` object accepts the following parameters: ```jsx title="./my-app/src/admin/app.js" - -import AuthLogo from './extensions/my-logo.png'; -import MenuLogo from './extensions/logo.png'; -import favicon from './extensions/favicon.ico'; +import AuthLogo from "./extensions/my-logo.png"; +import MenuLogo from "./extensions/logo.png"; +import favicon from "./extensions/favicon.png"; export default { config: { @@ -174,12 +165,12 @@ export default { auth: { logo: AuthLogo, }, - // Replace the favicon + // Replace the favicon head: { favicon: favicon, }, // Add a new locale, other than 'en' - locales: ['fr', 'de'], + locales: ["fr", "de"], // Replace the Strapi logo in the main navigation menu: { logo: MenuLogo, @@ -189,39 +180,38 @@ export default { // overwrite light theme properties light: { colors: { - primary100: '#f6ecfc', - primary200: '#e0c1f4', - primary500: '#ac73e6', - primary600: '#9736e8', - primary700: '#8312d1', - danger700: '#b72b1a' + primary100: "#f6ecfc", + primary200: "#e0c1f4", + primary500: "#ac73e6", + primary600: "#9736e8", + primary700: "#8312d1", + danger700: "#b72b1a", }, }, // overwrite dark theme properties dark: { - // ... - } + // ... + }, }, // Extend the translations translations: { fr: { - 'Auth.form.email.label': 'test', - Users: 'Utilisateurs', - City: 'CITY (FRENCH)', + "Auth.form.email.label": "test", + Users: "Utilisateurs", + City: "CITY (FRENCH)", // Customize the label of the Content Manager table. - Id: 'ID french', + Id: "ID french", }, }, - // Disable video tutorials + // Disable video tutorials tutorials: false, - // Disable notifications about new Strapi releases + // Disable notifications about new Strapi releases notifications: { releases: false }, }, bootstrap() {}, }; - ``` @@ -229,10 +219,9 @@ export default { ```jsx title="./my-app/src/admin/app.ts" - -import AuthLogo from './extensions/my-logo.png'; -import MenuLogo from './extensions/logo.png'; -import favicon from './extensions/favicon.ico'; +import AuthLogo from "./extensions/my-logo.png"; +import MenuLogo from "./extensions/logo.png"; +import favicon from "./extensions/favicon.png"; export default { config: { @@ -240,12 +229,14 @@ export default { auth: { logo: AuthLogo, }, - // Replace the favicon + // Replace the favicon head: { - favicon: favicon, + // Try to change the origin favicon.png file in the + // root of strapi project if this config don't work. + favicon: favicon, }, // Add a new locale, other than 'en' - locales: ['fr', 'de'], + locales: ["fr", "de"], // Replace the Strapi logo in the main navigation menu: { logo: MenuLogo, @@ -253,33 +244,32 @@ export default { // Override or extend the theme theme: { colors: { - primary100: '#f6ecfc', - primary200: '#e0c1f4', - primary500: '#ac73e6', - primary600: '#9736e8', - primary700: '#8312d1', - danger700: '#b72b1a' + primary100: "#f6ecfc", + primary200: "#e0c1f4", + primary500: "#ac73e6", + primary600: "#9736e8", + primary700: "#8312d1", + danger700: "#b72b1a", }, }, // Extend the translations translations: { fr: { - 'Auth.form.email.label': 'test', - Users: 'Utilisateurs', - City: 'CITY (FRENCH)', + "Auth.form.email.label": "test", + Users: "Utilisateurs", + City: "CITY (FRENCH)", // Customize the label of the Content Manager table. - Id: 'ID french', + Id: "ID french", }, }, - // Disable video tutorials + // Disable video tutorials tutorials: false, - // Disable notifications about new Strapi releases + // Disable notifications about new Strapi releases notifications: { releases: false }, }, bootstrap() {}, }; - ``` @@ -295,13 +285,12 @@ To update the list of available locales in the admin panel, use the `config.loca ```jsx title="./my-app/src/admin/app.js" - export default { config: { - locales: ['ru', 'zh'] + locales: ["ru", "zh"], }, bootstrap() {}, -} +}; ``` @@ -309,13 +298,12 @@ export default { ```jsx title="./my-app/src/admin/app.ts" - export default { config: { - locales: ['ru', 'zh'] + locales: ["ru", "zh"], }, bootstrap() {}, -} +}; ``` @@ -336,17 +324,16 @@ Translation key/value pairs are declared in `@strapi/admin/admin/src/translation ```js title="./my-app/src/admin/app.js" - export default { config: { - locales: ['fr'], + locales: ["fr"], translations: { fr: { - 'Auth.form.email.label': 'test', - Users: 'Utilisateurs', - City: 'CITY (FRENCH)', + "Auth.form.email.label": "test", + Users: "Utilisateurs", + City: "CITY (FRENCH)", // Customize the label of the Content Manager table. - Id: 'ID french', + Id: "ID french", }, }, }, @@ -359,17 +346,16 @@ export default { ```js title="./my-app/src/admin/app.ts" - export default { config: { - locales: ['fr'], + locales: ["fr"], translations: { fr: { - 'Auth.form.email.label': 'test', - Users: 'Utilisateurs', - City: 'CITY (FRENCH)', + "Auth.form.email.label": "test", + Users: "Utilisateurs", + City: "CITY (FRENCH)", // Customize the label of the Content Manager table. - Id: 'ID french', + Id: "ID french", }, }, }, @@ -386,13 +372,12 @@ A plugin's key/value pairs are declared independently in the plugin's files at ` ```js title="./my-app/src/admin/app.js" - export default { config: { - locales: ['fr'], + locales: ["fr"], translations: { fr: { - 'Auth.form.email.label': 'test', + "Auth.form.email.label": "test", // Translate a plugin's key/value pair by adding the plugin's name as a prefix // In this case, we translate the "plugin.name" key of plugin "content-type-builder" "content-type-builder.plugin.name": "Constructeur de Type-Contenu", @@ -408,13 +393,12 @@ export default { ```js title="./my-app/src/admin/app.ts" - export default { config: { - locales: ['fr'], + locales: ["fr"], translations: { fr: { - 'Auth.form.email.label': 'test', + "Auth.form.email.label": "test", // Translate a plugin's key/value pair by adding the plugin's name as a prefix // In this case, we translate the "plugin.name" key of plugin "content-type-builder" "content-type-builder.plugin.name": "Constructeur de Type-Contenu", @@ -465,22 +449,21 @@ To replace the favicon, use the following procedure: 1. (_optional_) Create a `./src/admin/extensions/` folder if the folder does not already exist. 2. Upload your favicon into `./src/admin/extensions/`. -3. Replace the existing **favicon.ico** file at the Strapi application root with a custom `favicon.ico` file. +3. Replace the existing **favicon.png|ico** file at the Strapi application root with a custom `favicon.png|ico` file. 4. Update `./src/admin/app.js` with the following: - ```js title="./src/admin/app.js" - - import favicon from './extensions/favicon.png'; + ```js title="./src/admin/app.js" + import favicon from "./extensions/favicon.png"; - export default { - config: { - // replace favicon with a custom icon - head: { - favicon: favicon, - }, - } - } - ``` + export default { + config: { + // replace favicon with a custom icon + head: { + favicon: favicon, + }, + }, + }; + ``` 5. Rebuild, launch and revisit your Strapi app by running `yarn build && yarn develop` in the terminal. @@ -525,12 +508,11 @@ To change the current WYSIWYG, you can install a [third-party plugin](https://ma ```js title="./src/admin/app.js" - -import MyNewWYSIGWYG from './extensions/components/MyNewWYSIGWYG' // this file contains the logic for your new WYSIWYG +import MyNewWYSIGWYG from "./extensions/components/MyNewWYSIGWYG"; // this file contains the logic for your new WYSIWYG export default { bootstrap(app) { - app.addFields({ type: 'wysiwyg', Component: MyNewWYSIGWYG }); + app.addFields({ type: "wysiwyg", Component: MyNewWYSIGWYG }); }, }; ``` @@ -540,12 +522,11 @@ export default { ```js title="./src/admin/app.ts" - -import MyNewWYSIGWYG from './extensions/components/MyNewWYSIGWYG' // this file contains the logic for your new WYSIWYG +import MyNewWYSIGWYG from "./extensions/components/MyNewWYSIGWYG"; // this file contains the logic for your new WYSIWYG export default { bootstrap(app) { - app.addFields({ type: 'wysiwyg', Component: MyNewWYSIGWYG }); + app.addFields({ type: "wysiwyg", Component: MyNewWYSIGWYG }); }, }; ``` @@ -557,15 +538,24 @@ export default { Email templates should be edited through the admin panel, using the [Users and Permissions plugin settings](/user-docs/settings/configuring-users-permissions-plugin-settings#configuring-email-templates). -### Webpack configuration +## Bundlers (experimental) + +2 different bundlers can be used with your Strapi application, [webpack](#webpack) and [vite](#vite). + +### Webpack + +In v4 this is the defacto bundler that Strapi uses to build the admin panel. :::prerequisites -Make sure to rename the default `webpack.config.example.js` file into `webpack.config.js` before customizing webpack. +Make sure to rename the default `webpack.config.example.js` file into `webpack.config.[js|ts]` before customizing webpack. ::: -In order to extend the usage of webpack v5, define a function that extends its configuration inside `./my-app/src/admin/webpack.config.js`: +In order to extend the usage of webpack v5, define a function that extends its configuration inside `./my-app/src/admin/webpack.config.[js|ts]`: + + + -```js +```js title="./my-app/src/admin/webpack.config.js" module.exports = (config, webpack) => { // Note: we provide webpack above so you should not `require` it @@ -577,10 +567,79 @@ module.exports = (config, webpack) => { }; ``` -:::note -Only `./src/admin/app.js` and the files under the `./src/admin/extensions` folder are being watched by the webpack dev server. + + + + +```ts title="./my-app/src/admin/webpack.config.ts" +export default (config, webpack) => { + // Note: we provide webpack above so you should not `require` it + + // Perform customizations to webpack config + config.plugins.push(new webpack.IgnorePlugin(/\/__tests__\//)); + + // Important: return the modified config + return config; +}; +``` + + + + +### Vite + +:::caution +This is considered experimental. Please report any issues you encounter. ::: +To use `vite` as a bundler you will need to pass it as an option to the `strapi develop` command: + +```bash +strapi develop --watch-admin --bundler=vite +``` + +To extend the usage of `vite`, define a function that extends its configuration inside `./my-app/src/admin/vite.config.[js|ts]`: + + + + +```js title="./my-app/src/admin/vite.config.js" +const { mergeConfig } = require("vite"); + +module.exports = (config) => { + // Important: always return the modified config + return mergeConfig(config, { + resolve: { + alias: { + "@": "/src", + }, + }, + }); +}; +``` + + + + + +```ts title="./my-app/src/admin/vite.config.ts" +import { mergeConfig } from "vite"; + +export default (config) => { + // Important: always return the modified config + return mergeConfig(config, { + resolve: { + alias: { + "@": "/src", + }, + }, + }); +}; +``` + + + + ## Extension There are 2 use cases to extend the admin panel: @@ -593,8 +652,8 @@ There are 2 use cases to extend the admin panel: The administration is a React front-end application calling an API. The front end and the back end are independent and can be deployed on different servers, which brings us to different scenarios: -* Deploy the entire project on the same server. -* Deploy the administration panel on a server (AWS S3, Azure, etc) different from the API server. +- Deploy the entire project on the same server. +- Deploy the administration panel on a server (AWS S3, Azure, etc) different from the API server. Build configurations differ for each case. @@ -646,18 +705,16 @@ To deploy the front end and the back end on different servers, use the following ```js title="./config/server.js" - module.exports = ({ env }) => ({ - host: env('HOST', '0.0.0.0'), - port: env.int('PORT', 1337), - url: 'http://yourbackend.com', + host: env("HOST", "0.0.0.0"), + port: env.int("PORT", 1337), + url: "http://yourbackend.com", }); ``` ```js title="./config/admin.js" - module.exports = ({ env }) => ({ - url: '/', // Note: The administration will be accessible from the root of the domain (ex: http://yourfrontend.com/) + url: "/", // Note: The administration will be accessible from the root of the domain (ex: http://yourfrontend.com/) serveAdminPanel: false, // http://yourbackend.com will not serve any static admin files }); ``` @@ -667,18 +724,16 @@ module.exports = ({ env }) => ({ ```js title="./config/server.ts" - export default ({ env }) => ({ - host: env('HOST', '0.0.0.0'), - port: env.int('PORT', 1337), - url: 'http://yourbackend.com', + host: env("HOST", "0.0.0.0"), + port: env.int("PORT", 1337), + url: "http://yourbackend.com", }); ``` ```js title="./config/admin.ts" - export default ({ env }) => ({ - url: '/', // Note: The administration will be accessible from the root of the domain (ex: http://yourfrontend.com/) + url: "/", // Note: The administration will be accessible from the root of the domain (ex: http://yourfrontend.com/) serveAdminPanel: false, // http://yourbackend.com will not serve any static admin files }); ``` diff --git a/docusaurus/docs/dev-docs/api/content-api.md b/docusaurus/docs/dev-docs/api/content-api.md new file mode 100644 index 0000000000..195f663ceb --- /dev/null +++ b/docusaurus/docs/dev-docs/api/content-api.md @@ -0,0 +1,59 @@ +--- +title: Strapi Content API +# description: todo +displayed_sidebar: devDocsSidebar +pagination_prev: dev-docs/setup-deployment +--- + +# Strapi APIs to access your content + +Once you've created and configured a Strapi project, created a data structure with the [Content-Type Builder](/user-docs/content-type-builder) and started adding data through the [Content Manager](/user-docs/content-manager), you likely would like to access your content. + +From a front-end application, your content can be accessed through Strapi's Content API, which is exposed: +- by default through the [REST API](/dev-docs/api/rest) +- and also through the [GraphQL API](/dev-docs/api/graphql) if you installed the Strapi built-in [GraphQL plugin](/dev-docs/plugins/graphql). + +REST and GraphQL APIs represent the top-level layers of the Content API exposed to external applications. Strapi also provides 2 lower-level APIs: + +- The [Entity Service API](/dev-docs/api/entity-service) is the recommended API to interact with your application's database within the [backend server](/dev-docs/customization) or through [plugins](/dev-docs/plugins). The Entity Service is the layer that handles Strapi's complex data structures like components and dynamic zones, which the lower-level layers are not aware of. +- The [Query Engine API](/dev-docs/api/query-engine) interacts with the database layer at a lower level and is used under the hood to execute database queries. It gives unrestricted internal access to the database layer, but should be used only if the Entity Service API does not cover your use case. + +```mermaid + flowchart BT + database[(Database)] <--> queryEngine[Query Engine API] + subgraph Strapi backend + direction BT + queryEngine <--> entityService[Entity Service API] + entityService <--> content([Your content]) + content <--> rest[REST API] + content <--> graphql[GraphQL API] + end + rest <==> frontend{{Your frontend application}} + graphql <==> frontend + click rest "/dev-docs/api/rest" + click graphql "/dev-docs/api/graphql" + click entityService "/dev-docs/api/entity-service" + click queryEngine "/dev-docs/api/query-engine" +``` + +
+ +This documentation section includes reference information about the following Strapi APIs and some integration guides with 3rd party technologies: + + + + + + + + + + + + + + + +:::note Plugin APIs +[Plugins](/dev-docs/plugins) also have their dedicated APIs: the Server API and the Admin Panel API. These plugin-related APIs are offered to develop plugins and allow a plugin to interact either with the back-end server of Strapi ([Server API](/dev-docs/api/plugins/server-api)) or with the admin panel of Strapi ([Admin Panel API](/dev-docs/api/plugins/admin-panel-api)). +::: diff --git a/docusaurus/docs/dev-docs/api/entity-service.md b/docusaurus/docs/dev-docs/api/entity-service.md index 3fb85a844e..ccad73387e 100644 --- a/docusaurus/docs/dev-docs/api/entity-service.md +++ b/docusaurus/docs/dev-docs/api/entity-service.md @@ -9,9 +9,13 @@ import BackendIntroCrosslink from '/docs/snippets/backend-custom-intro-crosslink # Entity Service API -The Strapi backend provides an Entity Service API, built on top of the [Query Engine API](/dev-docs/api/query-engine/). The Entity Service is the layer that handles Strapi's complex data structures like [components](/dev-docs/backend-customization/models#components) and [dynamic zones](/dev-docs/backend-customization/models#dynamic-zones), and uses the Query Engine API under the hood to execute database queries. +:::prerequisites +Before diving deeper into the Entity Service 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 an Entity Service API, built on top of the [Query Engine API](/dev-docs/api/query-engine/). The Entity Service is the layer that handles Strapi's complex data structures like [components](/dev-docs/backend-customization/models#components) and [dynamic zones](/dev-docs/backend-customization/models#dynamic-zones), and uses the Query Engine API under the hood to execute database queries. :::strapi Entity Service API vs. Query Engine API @@ -33,7 +37,12 @@ const entry = await strapi.entityService.findOne('api::article.article', 1, { ## Available operations -The Entity Service API allows: +The Entity Service API allows the following operations on entities: -- [CRUD operations on entities](/dev-docs/api/entity-service/crud) (e.g. [`findOne`](/dev-docs/api/entity-service/crud#findone), [`findMany`](/dev-docs/api/entity-service/crud#findmany), [`create`](/dev-docs/api/entity-service/crud#create), [`update`](/dev-docs/api/entity-service/crud#update), [`delete`](/dev-docs/api/entity-service/crud#delete)) with the ability to [filter](/dev-docs/api/entity-service/filter), [order and paginate results](/dev-docs/api/entity-service/order-pagination), and [populate relations, components and dynamic zones](/dev-docs/api/entity-service/populate) -- the [creation and update of components and dynamic zones](/dev-docs/api/entity-service/components-dynamic-zones) + + + + + + + diff --git a/docusaurus/docs/dev-docs/api/entity-service/crud.md b/docusaurus/docs/dev-docs/api/entity-service/crud.md index a85363d232..06bf1cf546 100644 --- a/docusaurus/docs/dev-docs/api/entity-service/crud.md +++ b/docusaurus/docs/dev-docs/api/entity-service/crud.md @@ -61,6 +61,10 @@ Syntax: `findMany(uid: string, parameters: Params)` β‡’ `Entry[]` | `populate` | Relations, components and dynamic zones to [populate](/dev-docs/api/entity-service/populate) | [`PopulateParameter`](/dev-docs/api/entity-service/populate) | | `publicationState` | Publication state, can be:
  • `live` to return only published entries
  • `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

\"Paella

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

\"Nasi

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

\"Pintxos

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

\"Pastel

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

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

\"Sichuan

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

\"Tagine

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. + + + + + +
+ Example login page +
A possible example of a login form on the front-end website of FoodAdvisor
+
+ + +
+
+ + + + +**🎯 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 ( + +
+
+

Login

+ + + +
+
+
+ ); +}; + +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. + + + + + +
+ Visiting a restaurant page updates the Google Sheets spreadsheet +
Every GET request to a restaurant's page executes the code of a custom middleware, updating a Google Sheets spreadsheet in real-time.
+
+ +
+ +
+ + + + + +**🎯 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. + + + + + +
+ Restaurant owner can't submit reviews +
When the restaurant's owner tries to submit a new review, a custom error is returned with the REST API response and a toast notification is displayed on the front-end website.
+
+ +
+
+ +**🎯 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

+
+ +