diff --git a/docs/.vuepress/config/sidebar-developer.js b/docs/.vuepress/config/sidebar-developer.js index 2546cdb018..caa2ed7d24 100644 --- a/docs/.vuepress/config/sidebar-developer.js +++ b/docs/.vuepress/config/sidebar-developer.js @@ -105,7 +105,11 @@ const developer = [ '/developer-docs/latest/setup-deployment-guides/configurations/optional/rbac.md', 'Role-Based Access Control (RBAC)', ], + [ + '/developer-docs/latest/setup-deployment-guides/configurations/optional/typescript.md', + 'TypeScript', ], + ], }, ], }, @@ -222,6 +226,7 @@ const developer = [ ['/developer-docs/latest/development/admin-customization', 'Admin panel customization'], ['/developer-docs/latest/development/plugins-extension.md', 'Plugins extension'], ['/developer-docs/latest/development/plugins-development.md', 'Plugins development'], + ['/developer-docs/latest/development/typescript.md', 'TypeScript'], ['/developer-docs/latest/development/providers.md', 'Providers'], ], }, diff --git a/docs/.vuepress/config/sidebar-user.js b/docs/.vuepress/config/sidebar-user.js index 9451846bd9..ce90cb2e6b 100644 --- a/docs/.vuepress/config/sidebar-user.js +++ b/docs/.vuepress/config/sidebar-user.js @@ -52,6 +52,28 @@ const user = [ ], ], }, + { + collapsable: false, + title: 'Media Library', + children: [ + [ + '/user-docs/latest/media-library/introduction-to-media-library.md', + 'Introduction to the Media Library' + ], + [ + '/user-docs/latest/media-library/adding-assets.md', + 'Adding assets' + ], + [ + '/user-docs/latest/media-library/managing-assets.md', + 'Managing individual assets' + ], + [ + '/user-docs/latest/media-library/organizing-assets-with-folders.md', + 'Organizing assets with folders' + ], + ] + }, { collapsable: false, title: 'Users, Roles & Permissions', @@ -101,4 +123,4 @@ const user = [ ], ], }, -]; \ No newline at end of file +]; diff --git a/docs/.vuepress/config/theme-config.js b/docs/.vuepress/config/theme-config.js index 9f1351dd97..a992ec0190 100644 --- a/docs/.vuepress/config/theme-config.js +++ b/docs/.vuepress/config/theme-config.js @@ -58,6 +58,11 @@ const themeConfig = { link: '/user-docs/latest/users-roles-permissions/introduction-to-users-roles-permissions.html', }, + { + text: 'Media Library', + link: + '/user-docs/latest/media-library/introduction-to-media-library.html', + }, { text: 'Plugins', link: '/user-docs/latest/plugins/introduction-to-plugins.html', diff --git a/docs/developer-docs/latest/developer-resources/cli/CLI.md b/docs/developer-docs/latest/developer-resources/cli/CLI.md index c4df7b6094..9b9b846ecf 100644 --- a/docs/developer-docs/latest/developer-resources/cli/CLI.md +++ b/docs/developer-docs/latest/developer-resources/cli/CLI.md @@ -6,7 +6,7 @@ canonicalUrl: https://docs.strapi.io/developer-docs/latest/developer-resources/c # Command Line Interface (CLI) -Strapi comes with a full featured Command Line Interface (CLI) which lets you scaffold and manage your project in seconds. +Strapi comes with a full featured Command Line Interface (CLI) which lets you scaffold and manage your project in seconds. ::: 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`). @@ -222,7 +222,7 @@ strapi admin:reset-user-password --email=chef@strapi.io --password=Gourmet1234 ## strapi generate -Run a fully interactive CLI to generate APIs, [controllers](/developer-docs/latest/development/backend-customization/controllers.md), [content-types](/developer-docs/latest/development/backend-customization/models.md), [plugins](/developer-docs/latest/development/plugins-development.md#creating-a-plugin), [policies](/developer-docs/latest/development/backend-customization/policies.md), [middlewares](/developer-docs/latest/development/backend-customization/middlewares.md) and [services](/developer-docs/latest/development/backend-customization/services.md). +Run a fully interactive CLI to generate APIs, [controllers](/developer-docs/latest/development/backend-customization/controllers.md), [content-types](/developer-docs/latest/development/backend-customization/models.md), [plugins](/developer-docs/latest/development/plugins-development.md#create-a-plugin), [policies](/developer-docs/latest/development/backend-customization/policies.md), [middlewares](/developer-docs/latest/development/backend-customization/middlewares.md) and [services](/developer-docs/latest/development/backend-customization/services.md). ```sh strapi generate @@ -230,7 +230,7 @@ strapi generate ## strapi templates:generate -Create a template from the current strapi project +Create a template from the current Strapi project. ```bash strapi templates:generate @@ -241,6 +241,23 @@ strapi templates:generate Example: `strapi templates:generate ../strapi-template-name` will copy the required files and folders to a `template` directory inside `../strapi-template-name` +## strapi ts:generate-types + +Generate [TypeScript](/developer-docs/latest/development/typescript.md) typings for the project schemas. + +```sh +strapi ts:generate-types +``` + +* **strapi ts:generate-types --verbose**
+ Generate typings with the verbose mode enabled, displaying a detailed table of the generated schemas. +* **strapi ts:generate-types --silent** or **strapi ts:generate-types -s**
+ Generate typings with the silent mode enabled, completely removing all the logs in the terminal. +* **strapi ts:generate-types --out-dir <path>** or **strapi ts:generate-types -o <path>**
+ Generate typings specifying the output directory in which the file will be created. +* **strapi ts:generate-types --file <filename>** or **strapi ts:generate-types -f <filename>**
+ Generate typings specifiying the name of the file to contain the types declarations. + ## strapi routes:list Display a list of all the available [routes](/developer-docs/latest/development/backend-customization/routes.md). diff --git a/docs/developer-docs/latest/developer-resources/error-handling.md b/docs/developer-docs/latest/developer-resources/error-handling.md index 239e01de46..9a69f8d372 100644 --- a/docs/developer-docs/latest/developer-resources/error-handling.md +++ b/docs/developer-docs/latest/developer-resources/error-handling.md @@ -70,6 +70,9 @@ Error functions accept 2 parameters that correspond to the `error.message` and ` - the first parameter of the function is the error `message` - and the second one is the object that will be set as `details` in the response received + + + ```js // path: ./src/api/[api-name]/controllers/my-controller.js @@ -86,6 +89,32 @@ module.exports = { ``` + + + + + + +```js + +// path: ./src/api/[api-name]/controllers/my-controller.ts + +export default { + renameDog: async (ctx, next) => { + const newName = ctx.request.body.name; + if (!newName) { + return ctx.badRequest('name is missing', { foo: 'bar' }) + } + ctx.body = strapi.service('api::dog.dog').rename(newName); + } +} + +``` + + + + + :::note [Services](/developer-docs/latest/development/backend-customization/services.md) don't have access to the controller's `ctx` object. If services need to throw errors, these need to be caught by the controller, that in turn is in charge of calling the proper error function. ::: diff --git a/docs/developer-docs/latest/development/admin-customization.md b/docs/developer-docs/latest/development/admin-customization.md index fbe783ef8f..cb13f903a1 100644 --- a/docs/developer-docs/latest/development/admin-customization.md +++ b/docs/developer-docs/latest/development/admin-customization.md @@ -34,6 +34,9 @@ By default, the administration panel is exposed via [http://localhost:1337/admin To make the admin panel accessible from `http://localhost:1337/dashboard`, use this in the [server configuration](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md) file: + + + ```js //path: ./config/server.js @@ -51,6 +54,33 @@ module.exports = ({ env }) => ({ }) ``` + + + + + +```js +//path: ./config/server.ts + +export default ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), + +}); + + +// path: ./config/admin.ts + +export default ({ env }) => ({ + url: '/dashboard', +}) +``` + + + + + + :::strapi Advanced settings For more advanced settings please see the [admin panel configuration](/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md) documentation. ::: @@ -59,6 +89,9 @@ For more advanced settings please see the [admin panel configuration](/developer By default, the front end development server runs on `localhost:8000` but this can be modified: + + + ```js // path: ./config/server.js @@ -77,6 +110,33 @@ module.exports = ({ env }) => ({ ``` + + + + +```js +// path: ./config/server.ts + +export default ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), +}); + + +// path: ./config/admin.ts + +export default ({ env }) => ({ + host: 'my-host', // only used along with `strapi develop --watch-admin` command + port: 3000, // only used along with `strapi develop --watch-admin` command +}); + +``` + + + + + + ### Configuration options ::: prerequisites @@ -103,6 +163,10 @@ The `config` object accepts the following parameters: | `notifications` | Object | Accepts the `releases` key (Boolean) to toggle [displaying notifications about new releases](#releases-notifications) | ::: details Example of a custom configuration for the admin panel: +
+ + + ```jsx // path: ./my-app/src/admin/app.js @@ -159,12 +223,79 @@ export default { ``` + + + + +```jsx +// path: ./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'; + +export default { + config: { + // Replace the Strapi logo in auth (login) views + auth: { + logo: AuthLogo, + }, + // Replace the favicon + head: { + favicon: favicon, + }, + // Add a new locale, other than 'en' + locales: ['fr', 'de'], + // Replace the Strapi logo in the main navigation + menu: { + logo: MenuLogo, + }, + // Override or extend the theme + theme: { + colors: { + 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)', + // Customize the label of the Content Manager table. + Id: 'ID french', + }, + }, + // Disable video tutorials + tutorials: false, + // Disable notifications about new Strapi releases + notifications: { release: false }, + }, + + bootstrap() {}, +}; + +``` + + + + + + ::: #### Locales To update the list of available locales in the admin panel, use the `config.locales` array: + + + ```jsx // path: ./my-app/src/admin/app.js @@ -176,6 +307,27 @@ export default { } ``` + + + + +```jsx +// path: ./my-app/src/admin/app.ts + +export default { + config: { + locales: ['ru', 'zh'] + }, + bootstrap() {}, +} +``` + + + + + + + ::: note NOTES * The `en` locale cannot be removed from the build as it is both the fallback (i.e. if a translation is not found in a locale, the `en` will be used) and the default locale (i.e. used when a user opens the administration panel for the first time). @@ -186,6 +338,9 @@ export default { Translation key/value pairs are declared in `@strapi/admin/admin/src/translations/[language-name].json` files. These keys can be extended through the `config.translations` key: + + + ```js // path: ./my-app/src/admin/app.js @@ -206,6 +361,36 @@ export default { }; ``` + + + + + +```js +// path: ./my-app/src/admin/app.ts + +export default { + config: { + locales: ['fr'], + translations: { + fr: { + 'Auth.form.email.label': 'test', + Users: 'Utilisateurs', + City: 'CITY (FRENCH)', + // Customize the label of the Content Manager table. + Id: 'ID french', + }, + }, + }, + bootstrap() {}, +}; +``` + + + + + + If more translations files should be added, place them in `./src/admin/extensions/translations` folder. #### Logos @@ -253,6 +438,10 @@ Strapi applications can be displayed either in Light or Dark mode (see [administ To change the current WYSIWYG, you can install a [third-party plugin](https://market.strapi.io/), create your own plugin (see [creating a new field in the admin panel](/developer-docs/latest/guides/registering-a-field-in-admin.md)) or take advantage of the [bootstrap lifecycle](/developer-docs/latest/developer-resources/plugin-api-reference/admin-panel.md#bootstrap) and the [extensions](#extension) system: + + + + ```js // path: ./src/admin/app.js @@ -265,6 +454,27 @@ export default { }; ``` + + + + + +```js +// path: ./src/admin/app.ts + +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 }); + }, +}; +``` + + + + + ### 'Forgotten password' email To customize the 'Forgotten password' email, provide your own template (formatted as a [lodash template](https://lodash.com/docs/4.17.15#template)). @@ -273,6 +483,9 @@ The template will be compiled with the following variables: `url`, `user.email`, **Example**: + + + ```js // path: ./config/admin.js @@ -307,6 +520,49 @@ module.exports = { }; ``` + + + + +```js +// path: ./config/admin.ts + +import forgotPasswordTemplate from './email-templates/forgot-password'; + +export default ({ env }) => ({ + // ... + forgotPassword: { + from: 'support@mywebsite.fr', + replyTo: 'support@mywebsite.fr', + emailTemplate: forgotPasswordTemplate, + }, + // ... +}); +``` + +```js +// path: ./config/email-templates/forgot-password.ts + +const subject = `Reset password`; + +const html = `

Hi <%= user.firstname %>

+

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

`; + +const text = `Hi <%= user.firstname %> +Sorry you lost your password. You can click here to reset it: <%= url %>`; + +export default { + subject, + text, + html, +}; +``` + +
+
+ + + ### Webpack configuration ::: prerequisites @@ -389,6 +645,9 @@ You might want to [change the path to access the administration panel](#access-u To deploy the front end and the back end on different servers, use the following configuration: + + + ```js // path: ./config/server.js @@ -407,6 +666,34 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/server.ts + +export default ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), + url: 'http://yourbackend.com', +}); + + +// path: ./config/admin.ts + +export default ({ env }) => ({ + 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 +}); +``` + + + + + + + After running `yarn build` with this configuration, the `build` folder will be created/overwritten. Use this folder to serve it from another server with the domain of your choice (e.g. `http://yourfrontend.com`). The administration URL will then be `http://yourfrontend.com` and every request from the panel will hit the backend at `http://yourbackend.com`. diff --git a/docs/developer-docs/latest/development/backend-customization/controllers.md b/docs/developer-docs/latest/development/backend-customization/controllers.md index afbba800f9..bb169dc17c 100644 --- a/docs/developer-docs/latest/development/backend-customization/controllers.md +++ b/docs/developer-docs/latest/development/backend-customization/controllers.md @@ -24,6 +24,9 @@ A new controller can be implemented: - in `./src/api/[api-name]/controllers/` for API controllers (this location matters as controllers are auto-loaded by Strapi from there) - or in a folder like `./src/plugins/[plugin-name]/server/controllers/` for plugin controllers, though they can be created elsewhere as long as the plugin interface is properly exported in the `strapi-server.js` file (see [Server API for Plugins documentation](/developer-docs/latest/developer-resources/plugin-api-reference/server.md)) + + + ```js // path: ./src/api/restaurant/controllers/restaurant.js @@ -66,6 +69,59 @@ module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) })); ``` + + + + +```js +// path: ./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 + async findOne(ctx) { + const { id } = ctx.params; + const { query } = ctx; + + const entity = await strapi.service('api::restaurant.restaurant').findOne(id, query); + const sanitizedEntity = await this.sanitizeOutput(entity, ctx); + + return this.transformResponse(sanitizedEntity); + } +})); +``` + + + + + + + + Each controller action can be an `async` or `sync` function. Every action receives a context object (`ctx`) as a parameter. `ctx` contains the [request context](/developer-docs/latest/development/backend-customization/requests-responses.md#requests) and the [response context](/developer-docs/latest/development/backend-customization/requests-responses.md#responses). @@ -73,6 +129,9 @@ Every action receives a context object (`ctx`) as a parameter. `ctx` contains th A specific `GET /hello` [route](/developer-docs/latest/development/backend-customization/routes.md) is defined, the name of the router file (i.e. `index`) is used to call the controller handler (i.e. `index`). Every time a `GET /hello` request is sent to the server, Strapi calls the `index` action in the `hello.js` controller, which returns `Hello World!`: + + + ```js // path: ./src/api/hello/routes/hello.js @@ -97,6 +156,37 @@ module.exports = { }; ``` + + + + +```js +// path: ./src/api/hello/routes/hello.ts + +export default { + routes: [ + { + method: 'GET', + path: '/hello', + handler: 'hello.index', + } + ] +} +``` + +```js +// path: ./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 + }, +}; +``` + + + + ::: ::: note diff --git a/docs/developer-docs/latest/development/backend-customization/middlewares.md b/docs/developer-docs/latest/development/backend-customization/middlewares.md index 63953834de..50480b44d2 100644 --- a/docs/developer-docs/latest/development/backend-customization/middlewares.md +++ b/docs/developer-docs/latest/development/backend-customization/middlewares.md @@ -27,18 +27,41 @@ A new application-level or API-level middleware can be implemented: Middlewares working with the REST API are functions like the following: + + + ```js // path: ./src/middlewares/my-middleware.js or ./src/api/[api-name]/middlewares/my-middleware.js + module.exports = (config, { strapi })=> { return (context, next) => {}; }; ``` + + + + +```js +// path: ./src/middlewares/my-middleware.js or ./src/api/[api-name]/middlewares/my-middleware.ts + +export default (config, { strapi })=> { + return (context, next) => {}; +}; +``` + + + + Once created, custom middlewares should be added to the [middlewares configuration file](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md#loading-order) or Strapi won't load them. ::: details Example of a custom timer middleware + + + ```js +// path: /config/middlewares.js module.exports = () => { return async (ctx, next) => { const start = Date.now(); @@ -51,6 +74,28 @@ module.exports = () => { }; ``` + + + + +```js +// path: /config/middlewares.ts + +export default () => { + return async (ctx, next) => { + const start = Date.now(); + + await next(); + + const delta = Math.ceil(Date.now() - start); + ctx.set('X-Response-Time', delta + 'ms'); + }; +}; +``` + + + + ::: The GraphQL plugin also allows [implementing custom middlewares](/developer-docs/latest/plugins/graphql.md#middlewares), with a different syntax. diff --git a/docs/developer-docs/latest/development/backend-customization/models.md b/docs/developer-docs/latest/development/backend-customization/models.md index 8a044858ee..db9423c27d 100644 --- a/docs/developer-docs/latest/development/backend-customization/models.md +++ b/docs/developer-docs/latest/development/backend-customization/models.md @@ -34,6 +34,10 @@ The content-types has the following models files: These models files are stored in `./src/api/[api-name]/content-types/[content-type-name]/`, and any JavaScript or JSON file found in these folders will be loaded as a content-type's model (see [project structure](/developer-docs/latest/setup-deployment-guides/file-structure.md)). +:::note +In [TypeScript](/developer-docs/latest/development/typescript.md)-enabled projects, schema typings can be generated using the `ts:generate-types` command. +::: + ### Components Component models can't be created with CLI tools. Use the [Content-type Builder](/user-docs/latest/content-types-builder/introduction-to-content-types-builder.md) or create them manually. @@ -606,6 +610,9 @@ To configure a content-type lifecycle hook, create a `lifecycles.js` file in the Each event listener is called sequentially. They can be synchronous or asynchronous. + + + ```js // ./src/api/[api-name]/content-types/restaurant/lifecycles.js @@ -625,6 +632,34 @@ module.exports = { }; ``` + + + + + + +```js +// ./src/api/[api-name]/content-types/restaurant/lifecycles.ts + +export default { + beforeCreate(event) { + const { data, where, select, populate } = event.params; + + // let's do a 20% discount everytime + event.params.data.price = event.params.data.price * 0.8; + }, + + afterCreate(event) { + const { result, params } = event; + + // do something to the result; + }, +}; +``` + + + + Using the database layer API, it's also possible to register a subscriber and listen to events programmatically: ```js diff --git a/docs/developer-docs/latest/development/backend-customization/policies.md b/docs/developer-docs/latest/development/backend-customization/policies.md index ef7e244947..aa667d67c7 100644 --- a/docs/developer-docs/latest/development/backend-customization/policies.md +++ b/docs/developer-docs/latest/development/backend-customization/policies.md @@ -26,6 +26,10 @@ A new policy can be implemented:
Global policy implementation example: + + + + ```js // path: ./src/policies/is-authenticated.js @@ -39,12 +43,39 @@ module.exports = (policyContext, config, { strapi }) => { }; ``` + + + + + + +```js +// path: ./src/policies/is-authenticated.ts + +export default (policyContext, config, { strapi }) => { + if (policyContext.state.user) { // if a session is open + // go to next policy or reach the controller's action + return true; + } + + return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass +}; +``` + + + + + `policyContext` is a wrapper arround the [controller](/developer-docs/latest/development/backend-customization/controllers.md) context. It adds some logic that can be useful to implement a policy for both REST and GraphQL.
Policies can be configured using a `config` object: + + + + ```js // path: .src/api/[api-name]/policies/my-policy.js @@ -57,6 +88,28 @@ module.exports = (policyContext, config, { strapi }) => { }; ``` + + + + + +```js +// path: .src/api/[api-name]/policies/my-policy.ts + +export default (policyContext, config, { strapi }) => { + if (policyContext.state.user.role.code === config.role) { // if user's role is the same as the one described in configuration + return true; + } + + return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass + } +}; +``` + + + + + ## Usage To apply policies to a route, add them to its configuration object (see [routes documentation](/developer-docs/latest/development/backend-customization/routes.md#policies)). @@ -75,6 +128,9 @@ To list all the available policies, run `yarn strapi policies:list`. Global policies can be associated to any route in a project. + + + ```js // path: ./src/api/restaurant/routes/router.js @@ -97,10 +153,45 @@ module.exports = { } ``` + + + + +```js +// path: ./src/api/restaurant/routes/router.ts + +export default { + routes: [ + { + method: 'GET', + path: '/restaurants', + handler: 'Restaurant.find', + config: { + /** + Before executing the find action in the Restaurant.js controller, + we call the global 'is-authenticated' policy, + found at ./src/policies/is-authenticated.js. + */ + policies: ['global::is-authenticated'] + } + } + ] +} +``` + + + + + + ### Plugin policies [Plugins](/developer-docs/latest/plugins/plugins-intro.md) can add and expose policies to an application. For example, the [Users & Permissions plugin](/user-docs/latest/users-roles-permissions/introduction-to-users-roles-permissions.md) comes with policies to ensure that the user is authenticated or has the rights to perform an action: + + + + ```js // path: ./src/api/restaurant/routes/router.js @@ -122,14 +213,86 @@ module.exports = { } ``` + + + + + +```js +// path: ./src/api/restaurant/routes/router.ts + +export default { + routes: [ + { + method: 'GET', + path: '/restaurants', + handler: 'Restaurant.find', + config: { + /** + The `isAuthenticated` policy prodived with the `users-permissions` plugin + is executed before the `find` action in the `Restaurant.js` controller. + */ + policies: ['plugins::users-permissions.isAuthenticated'] + } + } + ] +} +``` + + + + + ### API policies API policies are associated to the routes defined in the API where they have been declared. + + + ```js // path: ./src/api/restaurant/policies/is-admin.js. +export default (policyContext, config, { strapi }) => { + if (policyContext.state.user.role.name === 'Administrator') { + // Go to next policy or will reach the controller's action. + return true; + } + + return false; +}; + + +// path: ./src/api/restaurant/routes/router.ts + +export default { + routes: [ + { + method: 'GET', + path: '/restaurants', + handler: 'Restaurant.find', + config: { + /** + The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.js` + is executed before the `find` action in the `Restaurant.js` controller. + */ + policies: ['is-admin'] + } + } + ] +} + +``` + + + + + +```js + +// path: ./src/api/restaurant/policies/is-admin.ts. + module.exports = async (policyContext, config, { strapi }) => { if (policyContext.state.user.role.name === 'Administrator') { // Go to next policy or will reach the controller's action. @@ -140,7 +303,7 @@ module.exports = async (policyContext, config, { strapi }) => { }; -// path: ./src/api/restaurant/routes/router.js +// path: ./src/api/restaurant/routes/router.ts module.exports = { routes: [ @@ -150,7 +313,7 @@ module.exports = { handler: 'Restaurant.find', config: { /** - The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.js` + The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.ts` is executed before the `find` action in the `Restaurant.js` controller. */ policies: ['is-admin'] @@ -161,8 +324,16 @@ module.exports = { ``` + + + + + To use a policy in another API, reference it with the following syntax: `api::[apiName].[policyName]`: + + + ```js // path: ./src/api/category/routes/router.js @@ -183,3 +354,33 @@ module.exports = { ] } ``` + + + + + +```js +// path: ./src/api/category/routes/router.ts + +export default { + routes: [ + { + method: 'GET', + path: '/categories', + handler: 'Category.find', + config: { + /** + The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.ts` + is executed before the `find` action in the `Restaurant.js` controller. + */ + policies: ['api::restaurant.is-admin'] + } + } + ] +} +``` + + + + + diff --git a/docs/developer-docs/latest/development/backend-customization/routes.md b/docs/developer-docs/latest/development/backend-customization/routes.md index 70f10881cd..95e7af00c2 100644 --- a/docs/developer-docs/latest/development/backend-customization/routes.md +++ b/docs/developer-docs/latest/development/backend-customization/routes.md @@ -43,6 +43,9 @@ A core router file is a JavaScript file exporting the result of a call to `creat
+ + + ```js // path: ./src/api/[apiName]/routes/[routerName].js (e.g './src/api/restaurant/routes/restaurant.js') @@ -66,10 +69,46 @@ module.exports = createCoreRouter('api::restaurant.restaurant', { }); ``` + + + + +```js +// path: ./src/api/[apiName]/routes/[routerName].ts (e.g './src/api/restaurant/routes/restaurant.ts') + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::restaurant.restaurant', { + prefix: '', + only: ['find', 'findOne'], + except: [], + config: { + find: { + auth: false, + policies: [], + middlewares: [], + }, + findOne: {}, + create: {}, + update: {}, + delete: {}, + }, +}); +``` + + + + + + +
Generic implementation example: + + + ```js // path: ./src/api/restaurant/routes/restaurant.js @@ -87,6 +126,33 @@ module.exports = createCoreRouter('api::restaurant.restaurant', { }); ``` + + + + + +```js +// path: ./src/api/restaurant/routes/restaurant.ts + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::restaurant.restaurant', { + only: ['find'], + config: { + find: { + auth: false + policies: [], + middlewares: [], + } + } +}); +``` + + + + + + This only allows a `GET` request on the `/restaurants` path from the core `find` [controller](/developer-docs/latest/development/backend-customization/controllers.md) without authentication. ### Creating custom routers @@ -131,6 +197,32 @@ module.exports = { } ``` + + + + +```js +// path: ./src/api/restaurant/routes/custom-restaurant.ts + +export default { + routes: [ + { // Path defined with a URL parameter + method: 'GET', + path: '/restaurants/:category/:id', + handler: 'Restaurant.findOneByCategory', + }, + { // Path defined with a regular expression + method: 'GET', + path: '/restaurants/:region(\\d{2}|\\d{3})/:id', // Only match when the first parameter contains 2 or 3 digits. + handler: 'Restaurant.findOneByRegion', + } + ] +} +``` + + + + ::: ## Configuration @@ -148,6 +240,9 @@ Both [core routers](#configuring-core-routers) and [custom routers](#creating-cu ::: tab Core router policy + + + ```js // path: ./src/api/restaurant/routes/restaurant.js @@ -173,13 +268,50 @@ module.exports = createCoreRouter('api::restaurant.restaurant', { }); ``` + + + + +```js +// path: ./src/api/restaurant/routes/restaurant.ts + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::restaurant.restaurant', { + config: { + find: { + policies: [ + // point to a registered policy + 'policy-name', + + // point to a registered policy with some custom configuration + { name: 'policy-name', config: {} }, + + // pass a policy implementation directly + (policyContext, config, { strapi }) => { + return true; + }, + ] + } + } +}); +``` + + + + + ::: ::: tab Custom router policy + + + ```js // path: ./src/api/restaurant/routes/custom-restaurant.js + module.exports = { routes: [ { @@ -205,6 +337,44 @@ module.exports = { }; ``` + + + + +```js +// path: ./src/api/restaurant/routes/custom-restaurant.ts + + +export default { + routes: [ + { + method: 'GET', + path: '/articles/customRoute', + handler: 'controllerName.actionName', + config: { + policies: [ + // point to a registered policy + 'policy-name', + + // point to a registered policy with some custom configuration + { name: 'policy-name', config: {} }, + + // pass a policy implementation directly + (policyContext, config, { strapi }) => { + return true; + }, + ] + }, + }, + ], +}; +``` + + + + + + ::: :::: @@ -220,6 +390,9 @@ module.exports = { ::: tab Core router middleware + + + ```js // path: ./src/api/restaurant/routes/restaurant.js @@ -245,10 +418,45 @@ module.exports = createCoreRouter('api::restaurant.restaurant', { }); ``` + + + + +```js +// path: ./src/api/restaurant/routes/restaurant.ts + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::restaurant.restaurant', { + config: { + find: { + middlwares: [ + // point to a registered middleware + 'middleware-name', + + // point to a registered middleware with some custom configuration + { name: 'middleware-name', config: {} }, + + // pass a middleware implementation directly + (ctx, next) => { + return next(); + }, + ] + } + } +}); +``` + + + + ::: ::: tab Custom router middleware + + + ```js // path: ./src/api/restaurant/routes/custom-restaurant.js @@ -277,6 +485,41 @@ module.exports = { }; ``` + + + + +```js +// path: ./src/api/restaurant/routes/custom-restaurant.ts + +export default { + routes: [ + { + method: 'GET', + path: '/articles/customRoute', + handler: 'controllerName.actionName', + config: { + middlewares: [ + // point to a registered middleware + 'middleware-name', + + // point to a registered middleware with some custom configuration + { name: 'middleware-name', config: {} }, + + // pass a middleware implementation directly + (ctx, next) => { + return next(); + }, + ], + }, + }, + ], +}; +``` + + + + ::: :::: @@ -291,6 +534,9 @@ In some scenarios, it can be useful to have a route publicly available and contr ::: tab Core router with a public route + + + ```js // path: ./src/api/restaurant/routes/restaurant.js @@ -305,10 +551,34 @@ module.exports = createCoreRouter('api::restaurant.restaurant', { }); ``` + + + + +```js +// path: ./src/api/restaurant/routes/restaurant.ts + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::restaurant.restaurant', { + config: { + find: { + auth: false + } + } +}); +``` + + + + ::: ::: tab Custom router with a public route + + + ```js // path: ./src/api/restaurant/routes/custom-restaurant.js @@ -326,6 +596,30 @@ module.exports = { }; ``` + + + + +```js +// path: ./src/api/restaurant/routes/custom-restaurant.ts + +export default { + routes: [ + { + method: 'GET', + path: '/articles/customRoute', + handler: 'controllerName.actionName', + config: { + auth: false, + }, + }, + ], +}; +``` + + + + ::: :::: diff --git a/docs/developer-docs/latest/development/backend-customization/services.md b/docs/developer-docs/latest/development/backend-customization/services.md index d2816b9f90..2fd4091734 100644 --- a/docs/developer-docs/latest/development/backend-customization/services.md +++ b/docs/developer-docs/latest/development/backend-customization/services.md @@ -24,6 +24,9 @@ A new service can be implemented: To manually create a service, export a factory function that returns the service implementation (i.e. an object with methods). This factory function receives the `strapi` instance: + + + ```js // path: ./src/api/restaurant/services/restaurant.js @@ -61,6 +64,50 @@ module.exports = createCoreService('api::restaurant.restaurant', ({ strapi }) => })); ``` + + + + +```js +// path: ./src/api/restaurant/services/restaurant.ts + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreService('api::restaurant.restaurant', ({ strapi }) => ({ + // Method 1: Creating an entirely custom service + async exampleService(...args) { + let response = { okay: true } + + if (response.okay === false) { + return { response, error: true } + } + + return response + }, + + // Method 2: Wrapping a core service (leaves core logic in place) + async find(...args) { + // Calling the default core controller + const { results, pagination } = await super.find(...args); + + // some custom logic + results.forEach(result => { + result.counter = 1; + }); + + return { results, pagination }; + }, + + // Method 3: Replacing a core service + async findOne(entityId, params = {}) { + return strapi.entityService.findOne('api::restaurant.restaurant', entityId, this.getFetchParams(params)); + } +})); +``` + + + + ::: strapi Entity Service API To get started creating your own services, see Strapi's built-in functions in the [Entity Service API](/developer-docs/latest/developer-resources/database-apis-reference/entity-service-api.md) documentation. ::: @@ -69,9 +116,13 @@ To get started creating your own services, see Strapi's built-in functions in th The goal of a service is to store reusable functions. An `email` service could be useful to send emails from different functions in our codebase: + + + ```js // path: ./src/api/email/services/email.js + const { createCoreService } = require('@strapi/strapi').factories; const nodemailer = require('nodemailer'); // Requires nodemailer to be installed (npm install nodemailer) @@ -100,8 +151,49 @@ module.exports = createCoreService('api::email.email', ({ strapi }) => ({ })); ``` + + + +```js +// path: ./src/api/email/services/email.ts + + +import { factories } from '@strapi/strapi'; //check with soup +const nodemailer = require('nodemailer'); // Requires nodemailer to be installed (npm install nodemailer) + +// Create reusable transporter object using SMTP transport. +const transporter = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'user@gmail.com', + pass: 'password', + }, +}); + +export default factories.createCoreService('api::restaurant.restaurant', ({ strapi }) => ({ + send(from, to, subject, text) { + // Setup e-mail data. + const options = { + from, + to, + subject, + text, + }; + + // Return a promise of the function that sends the email. + return transporter.sendMail(options); + }, +})); +``` + + + + The service is now available through the `strapi.service('api::email.email').send(...args)` global variable. It can be used in another part of the codebase, like in the following controller: + + + ```js // path: ./src/api/user/controllers/user.js @@ -124,6 +216,35 @@ module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) })); ``` + + + + +```js +// path: ./src/api/user/controllers/user.ts + +export default factories.createCoreController('api::restaurant.restaurant', ({ strapi }) => ({ + // GET /hello + async signup(ctx) { + const { userData } = ctx.body; + + // Store the new user in database. + const user = await strapi.service('plugin::users-permissions.user').add(userData); + + // Send an email to validate his subscriptions. + strapi.service('api::email.email').send('welcome@mysite.com', user.email, 'Welcome', '...'); + + // Send response to the server. + ctx.send({ + ok: true, + }); + }, +})); +``` + + + + :::: ::: note @@ -275,3 +396,5 @@ strapi.service('plugin::pluginName.serviceName'); ::: tip To list all the available services, run `yarn strapi services:list`. ::: + +::: diff --git a/docs/developer-docs/latest/development/backend-customization/webhooks.md b/docs/developer-docs/latest/development/backend-customization/webhooks.md index 7de8c36ce8..9db55d6fe0 100644 --- a/docs/developer-docs/latest/development/backend-customization/webhooks.md +++ b/docs/developer-docs/latest/development/backend-customization/webhooks.md @@ -17,14 +17,19 @@ If you need to notify other applications about changes in the Users collection, ## Available configurations -You can set webhook configurations inside the file `./config/server.js`. +You can set webhook configurations inside the file `./config/server`. - `webhooks` - `defaultHeaders`: You can set default headers to use for your webhook requests. This option is overwritten by the headers set in the webhook itself. **Example configuration** + + + ```js +//path: ./config/server.js + module.exports = { webhooks: { defaultHeaders: { @@ -34,6 +39,25 @@ module.exports = { }; ``` + + + + +```js +//path: ./config/server.ts + +export default { + webhooks: { + defaultHeaders: { + 'Custom-Header': 'my-custom-header', + }, + }, +}; +``` + + + + ## Securing your webhooks Most of the time, webhooks make requests to public URLs, therefore it is possible that someone may find that URL and send it wrong information. @@ -41,13 +65,18 @@ Most of the time, webhooks make requests to public URLs, therefore it is possibl To prevent this from happening you can send a header with an authentication token. Using the Admin panel you would have to do it for every webhook. Another way is to define `defaultHeaders` to add to every webhook requests. -You can configure these global headers by updating the file at `./config/server.js`: +You can configure these global headers by updating the file at `./config/server`: :::: tabs card ::: tab Simple token + + + ```js +//path: ./config.server.js + module.exports = { webhooks: { defaultHeaders: { @@ -57,11 +86,35 @@ module.exports = { }; ``` + + + + +```js +// path: ./config.server.ts + + export default { + webhooks: { + defaultHeaders: { + Authorization: 'Bearer my-very-secured-token', + }, + }, +}; +``` + + + + ::: ::: tab Environment variable + + + ```js +//path: ./config.server.js + module.exports = { webhooks: { defaultHeaders: { @@ -71,6 +124,25 @@ module.exports = { }; ``` + + + + +```js +//path: ./config.server.ts + +export default { + webhooks: { + defaultHeaders: { + Authorization: `Bearer ${process.env.WEBHOOK_TOKEN}`, + }, + }, +}; +``` + + + + :::: If you are developing the webhook handler yourself you can now verify the token by reading the headers. diff --git a/docs/developer-docs/latest/development/plugins-development.md b/docs/developer-docs/latest/development/plugins-development.md index fbc12c28d0..09587101ed 100644 --- a/docs/developer-docs/latest/development/plugins-development.md +++ b/docs/developer-docs/latest/development/plugins-development.md @@ -6,23 +6,28 @@ canonicalUrl: https://docs.strapi.io/developer-docs/latest/development/plugins-d # Plugins development -Strapi allows you to create local plugins that work exactly like the external ones installed with npm or through the Marketplace. +Strapi allows the development of local plugins that work exactly like the external plugins available from the [Marketplace](https://market.strapi.io). :::strapi Extending plugins If you would rather extend an existing plugin than create a new one, see the [Plugins extension](/developer-docs/latest/development/plugins-extension.md) documentation. ::: -## Creating a plugin +## Create a plugin -To create a plugin, use Strapi CLI tools: +Strapi provides a [command line interface (CLI)](/developer-docs/latest/developer-resources/cli/CLI.md) for creating plugins. To create a plugin: -1. (_Optional_) If you don't already have an existing project, create a new development project with `strapi new myDevelopmentProject`. -2. Start the project with `cd myDevelopmentProject && strapi develop`. -3. In a new terminal window, run `cd /path/to/myDevelopmentProject && strapi generate` to launch the interactive `strapi generate` CLI. -4. Choose "plugin" from the list, press Enter, and give the plugin a name. -5. Enable the plugin by adding it to the [plugins configurations](/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md) file: - ```js - // path: /path/to/myDevelopmentProject/config/plugins.js +1. Navigate to the root of a Strapi project. +2. Run `yarn strapi generate` or `npm run strapi generate` in a terminal window to start the interactive CLI. +4. Choose "plugin" from the list, press Enter, and give the plugin a name in kebab-case (e.g. my-plugin-name) +5. Choose either `JavaScript` or `TypeScript` for the plugin language. +6. Enable the plugin by adding it to the [plugins configurations](/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md) file: + + + + + +```js +// path: ./config/plugins.js module.exports = { // ... @@ -32,18 +37,37 @@ To create a plugin, use Strapi CLI tools: }, // ... } - ``` - ::: caution - The plugin name must be kebab-case. - Example: an-example-of-kebab-case - ::: -5. Run `strapi build` to build the plugin. +``` + + + + +```js + // path: ./config/plugins.ts + + export default { + // ... + 'my-plugin': { + enabled: true, + resolve: './src/plugins/my-plugin' // path to plugin folder + }, + // ... + } + + +``` + + + + +7. (*TypeScript-specific*) Run `npm run install` or `yarn install` in the newly-created plugin directory. +8. Run `yarn build` or `npm run build` to build the plugin. -Plugins created this way are located in the `plugins` folder of the application (see [project structure](/developer-docs/latest/setup-deployment-guides/file-structure.md)). +Plugins created using the preceding directions are located in the `plugins` directory of the application (see [project structure](/developer-docs/latest/setup-deployment-guides/file-structure.md)). -## Adding features to a plugin +## Add features to a plugin -Strapi provides programmatic APIs for your plugin to hook into some of Strapi's features. +Strapi provides programmatic APIs for plugins to hook into some of Strapi's features. Plugins can register with the server and/or the admin panel, by looking for entry point files at the root of the package: - `strapi-server.js` for the Server (see [Server API](/developer-docs/latest/developer-resources/plugin-api-reference/server.md)), diff --git a/docs/developer-docs/latest/development/typescript.md b/docs/developer-docs/latest/development/typescript.md new file mode 100644 index 0000000000..0a85da0c55 --- /dev/null +++ b/docs/developer-docs/latest/development/typescript.md @@ -0,0 +1,134 @@ +--- +title: Typescript - Strapi Developer Docs +description: Learn how you can use Typescript for your Strapi application. +canonicalUrl: https://docs.strapi.io/developer-docs/latest/development/typescript.html +--- + +# TypeScript development + +::: callout 🚧 JavaScript to TypeScript migration +Migrating existing Strapi applications written in JavaScript is not currently recommended. In the meantime, feel free to ask for help on the [forum](https://forum.strapi.io/) or the community [Discord](https://discord.strapi.io). +::: + +TypeScript adds an additional type system layer above JavaScript, which means that existing JavaScript code is also TypeScript code. Strapi supports TypeScript in new projects on v4.2.0 and above. TypeScript-enabled projects allow developing plugins with TypeScript as well as using TypeScript typings. + +::: strapi Getting started with TypeScript +To start developing in TypeScript, use the [CLI documentation](/developer-docs/latest/setup-deployment-guides/installation/cli.md) to create a new TypeScript project. Additionally, the [project structure](/developer-docs/latest/setup-deployment-guides/file-structure.md) and [TypeScript configuration](/developer-docs/latest/setup-deployment-guides/configurations/optional/typescript.md) sections have TypeScript-specific resources for understanding and configuring an application. +::: + +## Start developing in TypeScript + +Starting the development environment for a TypeScript-enabled project requires building the admin panel prior to starting the server. In development mode, the application source code is compiled to the `./dist/build` directory and recompiled with each change in the Content-type Builder. To start the application, run the following commands in the root directory: + + + + + +```sh +npm run build +npm run develop +``` + + + + + +```sh +yarn build +yarn develop +``` + + + + + +## Use TypeScript typings + +Strapi provides typings on the `Strapi` class to improve the TypeScript developing experience. These typings come with an autocomplete feature that automatically offers suggestions while developing. + +To experience TypeScript-based autocomplete while developing Strapi applications, you could try the following: + +1. From your code editor, open the `./src/index.ts` file. +2. In the `register` method, declare the `strapi` argument as of type `Strapi`: + + ```js + // path: ./src/index.ts + + import '@strapi/strapi'; + + export default { + register( { strapi }: { strapi: Strapi }) { + // ... + }, + }; + ``` + +2. Within the body of the `register` method, start typing `strapi.` and use keyboard arrows to browse the available properties. +3. Choose `runLifecyclesfunction` from the list. +4. When the `strapi.runLifecyclesFunctions` method is added, a list of available lifecycle types (i.e. `register`, `bootstrap` and `destroy`) are returned by the code editor. Use keyboard arrows to choose one of the lifecycles and the code will autocomplete. + +## Generate typings for project schemas + +To generate typings for your project schemas use the [`ts:generate-types` CLI command](/developer-docs/latest/developer-resources/cli/CLI.md#strapi-ts-generate-types). The `ts:generate-types` command creates the file `schemas.d.ts`, at the project root, which stores the schema typings. The optional `--verbose` flag returns a detailed table of the generated schemas. + +To use `ts:generate-types`run the following code in a terminal at the project root: + + + + +```sh +npm run strapi ts:generate-types --verbose #optional flag + +``` + + + + + +```sh +yarn strapi ts:generate-types --verbose #optional flag + +``` + + + + +## Develop a plugin using TypeScript + +New plugins can be generated following the [plugins development documentation](/developer-docs/latest/development/plugins-development.md). There are 2 important distinctions for TypeScript applications: + +- After creating the plugin, run `yarn` or `npm install` in the plugin directory `src/admin/plugins/[my-plugin-name]` to install the dependencies for the plugin. +- Run `yarn build` or `npm run build` in the plugin directory `src/admin/plugins/[my-plugin-name]` to build the admin panel including the plugin. + +::: note +It is not necessary to repeat the `yarn` or `npm install` command after the initial installation. The `yarn build` or `npm run build` command is necessary to implement any plugin development that affects the admin panel. +::: + +## Start Strapi programmatically + +To start Strapi programmatically in a TypeScript project the Strapi instance requires the compiled code location. This section describes how to set and indicate the compiled code directory. + +### Use the `strapi()` factory + +Strapi can be run programmatically by using the `strapi()` factory. Since the code of TypeScript projects is compiled in a specific directory, the parameter `distDir` should be passed to the factory to indicate where the compiled code should be read: + +```js +// path: ./src/plugins//server/index.js + +const strapi = require('@strapi/strapi'); + +const app = await strapi({ distDir: './dist' }); +``` + +### Use the `strapi.compile()` function + +The `strapi.compile()` function should be mostly used for developing tools that need to start a Strapi instance and detect whether the project includes TypeScript code. `strapi.compile()` automatically detects the project language. If the project code contains any TypeScript code, `strapi.compile()` compiles the code and returns a context with specific values for the directories that Strapi requires: + +```js + +const strapi = require('@strapi/strapi'); + +const appContext = await strapi.compile(); +const app = await strapi(appContext); + +``` diff --git a/docs/developer-docs/latest/getting-started/troubleshooting.md b/docs/developer-docs/latest/getting-started/troubleshooting.md index 2f721601a6..749693a763 100644 --- a/docs/developer-docs/latest/getting-started/troubleshooting.md +++ b/docs/developer-docs/latest/getting-started/troubleshooting.md @@ -101,8 +101,12 @@ Likewise since Strapi is Node.js based, in order for changes with the SSL certif Due to these two issues, it is recommended you use a proxy application such as [Nginx](/developer-docs/latest/setup-deployment-guides/deployment/optional-software/nginx-proxy.md), [Caddy](/developer-docs/latest/setup-deployment-guides/deployment/optional-software/caddy-proxy.md), [HAProxy](/developer-docs/latest/setup-deployment-guides/deployment/optional-software/haproxy-proxy.md), Apache, Traefik, or many others to handle your edge routing to Strapi. There are settings in the environment [server.json](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md) to handle upstream proxies. The proxy block requires all settings to be filled out and will modify any backend plugins such as authentication providers and the upload plugin to replace your standard `localhost:1337` with the proxy URL. +## Can I use TypeScript in a Strapi project? + +TypeScript is supported in Strapi projects from v4.2.0-beta.1 TypeScript code examples are available throughout the core Developer Documentation and a [dedicated TypeScript support page](/developer-docs/latest/development/typescript.md). + ## Is X feature available yet? -You can see the [Public roadmap](https://feedback.strapi.io/) to see which feature requests are currently being worked on and which have not been started yet. +You can see the [ProductBoard roadmap](https://feedback.strapi.io/) to see which feature requests are currently being worked on and which have not been started yet and create new feature requests. diff --git a/docs/developer-docs/latest/plugins/email.md b/docs/developer-docs/latest/plugins/email.md index 3c4fc8d572..4d7f0aee11 100644 --- a/docs/developer-docs/latest/plugins/email.md +++ b/docs/developer-docs/latest/plugins/email.md @@ -94,30 +94,188 @@ await strapi.plugins['email'].services.email.sendTemplatedEmail( ## Sending emails with a lifecycle hook - To trigger an email based on administrator actions in the admin panel use [lifecycle hooks](/developer-docs/latest/development/backend-customization/models.md#lifecycle-hooks) and the [`send()` function](#using-the-send-function). For example, to send an email each time a new content entry is added in the Content Manager use the `afterCreate` lifecycle hook: +To install a new provider run: + + + + +```sh +npm install @strapi/provider-email-sendgrid --save +``` + + + +```sh +yarn add @strapi/provider-email-sendgrid --save +``` + + + + +### Configure your provider + +After installing your provider you will need to add some settings in `./config/plugins.js`. If this file doesn't exists, you'll need to create it. Check the README of each provider to know what configuration settings the provider needs. + +::: note +When using community providers, pass the full package name to the `provider` key (e.g. `provider: 'strapi-provider-email-mandrill'`). Only Strapi-maintained providers can use the shortcode format (e.g. `provider: 'sendmail'`). +::: + +Here is an example of a configuration made for the provider [@strapi/provider-email-sendgrid](https://www.npmjs.com/package/@strapi/provider-email-sendgrid). + +**Path —** ``. + + + + ```js +//path: ./config/plugins.js + +module.exports = ({ env }) => ({ + // ... + email: { + config: { + provider: 'sendgrid', + providerOptions: { + apiKey: env('SENDGRID_API_KEY'), + }, + settings: { + defaultFrom: 'juliasedefdjian@strapi.io', + defaultReplyTo: 'juliasedefdjian@strapi.io', + testAddress: 'juliasedefdjian@strapi.io', + }, + }, + }, + // ... +}); +``` + + + + + +```js +//path: ./config/plugins.ts + +export default = ({ env }) => ({ + // ... + email: { + config: { + provider: 'sendgrid', + providerOptions: { + apiKey: env('SENDGRID_API_KEY'), + }, + settings: { + defaultFrom: 'juliasedefdjian@strapi.io', + defaultReplyTo: 'juliasedefdjian@strapi.io', + testAddress: 'juliasedefdjian@strapi.io', + }, + }, + }, + // ... +}); +``` + + + + +::: tip +If you're using a different provider depending on your environment, you can specify the correct configuration in `./config/env/${yourEnvironment}/plugins.js`. More info here: [Environments](/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md) +::: + +::: tip +Only one email provider will be active at all time. If the email provider setting isn't picked up by strapi, verify you have put the file `plugins.js` in the correct folder, and with correct filename. The selection of email provider is done via configuration file only. +::: + +::: tip +When testing the new email provider with those two email templates created during strapi setup, the _shipper email_ on the template, with default no-reply@strapi.io need to be updated in according to your email provider, otherwise it will fail the test. +More info here: [Configure templates Locally](/user-docs/latest/settings/configuring-users-permissions-plugin-settings.md#configuring-email-templates) +::: + +## Create new provider -// path: ./src/api/{api-name}/content-types/{content-type-name}/lifecycles.js +Default template + + + + +```js +//path: ./config/plugins.js module.exports = { - async afterCreate(event) { // Connected to "Save" button in admin panel - const { result } = event; - - try{ - await strapi.plugins['email'].services.email.send({ - to: 'valid email address', - from: 'your verified email address', // e.g. single sender verification in SendGrid - cc: 'valid email address', - bcc: 'valid email address', - replyTo: 'valid email address', - subject: 'The Strapi Email plugin worked successfully', - text: '${fieldName}', // Replace with a valid field ID - html: 'Hello world!', - - }) - } catch(err) { - console.log(err); + init: (providerOptions = {}, settings = {}) => { + return { + send: async options => {}, + }; + }, +}; +``` + + + + + +```js +//path: ./config/plugins.ts +export default { + init: (providerOptions = {}, settings = {}) => { + return { + send: async options => {}, + }; + }, +}; +``` + + + + + + + +In the `send` function you will have access to: + +- `providerOptions` that contains configurations written in `plugins.js` +- `settings` that contains configurations written in `plugins.js` +- `options` that contains options you send when you call the `send` function from the email plugin service + +To use it you will have to publish it on **npm**. + +### Create a local provider + +If you want to create your own provider without publishing it on **npm** you can follow these steps: + +- Create a `providers` folder in your application. +- Create your provider (e.g. `./providers/strapi-provider-email-[...]/...`) +- Then update your `package.json` to link your `strapi-provider-email-[...]` dependency to the [local path](https://docs.npmjs.com/files/package.json#local-paths) of your new provider. + +```json +{ + ... + "dependencies": { + ... + "strapi-provider-email-[...]": "file:providers/strapi-provider-email-[...]", + ... + } +} +``` + +- Finally, run `yarn install` or `npm install` to install your new custom provider. + +## Troubleshooting + +You received an `Auth.form.error.email.invalid` error even though the email is valid and exists in the database. + +Here is the error response you get from the API. + +```json +{ + "statusCode": 400, + "error": "Bad Request", + "message": [ + { + "messages": [ + { + "id": "Auth.form.error.email.invalid" } } } diff --git a/docs/developer-docs/latest/plugins/graphql.md b/docs/developer-docs/latest/plugins/graphql.md index 3aa899a9f6..198f243983 100644 --- a/docs/developer-docs/latest/plugins/graphql.md +++ b/docs/developer-docs/latest/plugins/graphql.md @@ -48,6 +48,59 @@ Plugins configuration are defined in the `config/plugins.js` file. This configur ::: caution The maximum number of items returned by the response is limited to 100 by default. This value can be changed using the `amountLimit` configuration option, but should only be changed after careful consideration: a large query can cause a DDoS (Distributed Denial of Service) and may cause abnormal load on your Strapi server, as well as your database server. ::: + + + + + +```js +// path: ./config/plugins.js + +module.exports = { + // + graphql: { + config: { + endpoint: '/graphql', + shadowCRUD: true, + playgroundAlways: false, + depthLimit: 7, + amountLimit: 100, + apolloServer: { + tracing: false, + }, + }, + }, +}; +``` + + + + + +```js +// path: ./config/plugins.ts + +export default { + // + graphql: { + config: { + endpoint: '/graphql', + shadowCRUD: true, + playgroundAlways: false, + depthLimit: 7, + amountLimit: 100, + apolloServer: { + tracing: false, + }, + }, + }, +}; +``` + + + + + ## Shadow CRUD To simplify and automate the build of the GraphQL schema, we introduced the Shadow CRUD feature. It automatically generates the type definitions, queries, mutations and resolvers based on your models. @@ -169,6 +222,10 @@ Strapi provides a programmatic API to customize GraphQL, which allows: ::: details Example of GraphQL customizations + + + + ```js // path: ./src/index.js @@ -232,6 +289,77 @@ module.exports = { }; ``` + + + + +```js +// path: ./src/index.ts + +export default { + /** + * An asynchronous register function that runs before + * your application is initialized. + * + * This gives you an opportunity to extend code. + */ + register({ strapi }) { + const extensionService = strapi.plugin('graphql').service('extension'); + + extensionService.shadowCRUD('api::restaurant.restaurant').disable(); + extensionService.shadowCRUD('api::category.category').disableQueries(); + extensionService.shadowCRUD('api::address.address').disableMutations(); + extensionService.shadowCRUD('api::document.document').field('locked').disable(); + extensionService.shadowCRUD('api::like.like').disableActions(['create', 'update', 'delete']); + + const extension = ({ nexus }) => ({ + // Nexus + types: [ + nexus.objectType({ + name: 'Book', + definition(t) { + t.string('title'); + }, + }), + ], + plugins: [ + nexus.plugin({ + name: 'MyPlugin', + onAfterBuild(schema) { + console.log(schema); + }, + }), + ], + // GraphQL SDL + typeDefs: ` + type Article { + name: String + } + `, + resolvers: { + Query: { + address: { + resolve() { + return { value: { city: 'Montpellier' } }; + }, + }, + }, + }, + resolversConfig: { + 'Query.address': { + auth: false, + }, + }, + }); + extensionService.use(extension); + }, +}; +``` + + + + + ::: ### Disabling operations in the Shadow CRUD @@ -320,6 +448,10 @@ The `types` and `plugins` parameters are based on [Nexus](https://nexusjs.org/). ::: details Example: + + + + ```js // path: ./src/index.js @@ -344,6 +476,38 @@ module.exports = { } ``` + + + + +```js + +// path: ./src/index.ts + +export default { + register({ strapi }) { + const extension = ({ nexus }) => ({ + types: [ + nexus.objectType({ + … + }), + ], + plugins: [ + nexus.plugin({ + … + }) + ] + }) + + strapi.plugin('graphql').service('extension').use(extension) + } +} +``` + + + + + ::: :::: @@ -378,6 +542,10 @@ To change how the authorization is configured, use the resolver configuration de ::: details Examples of authorization configuration + + + + ```js // path: ./src/index.js @@ -413,6 +581,49 @@ module.exports = { ``` + + + + +```js + +// path: ./src/index.ts + +export default { + register({ strapi }) { + const extensionService = strapi.plugin('graphql').service('extension'); + + extensionService.use({ + resolversConfig: { + 'Query.categories': { + /** + * Querying the Categories content-type + * bypasses the authorization system. + */ + auth: false + }, + 'Query.restaurants': { + /** + * Querying the Restaurants content-type + * requires the find permission + * on the 'Address' content-type + * of the 'Address' API + */ + auth: { + scope: ['api::address.address.find'] + } + }, + } + }) + } +} + +``` + + + + + ::: ##### Policies @@ -429,6 +640,10 @@ The `context` object gives access to: ::: details Example of a custom GraphQL policy applied to a resolver + + + + ```js // path: ./src/index.js @@ -458,6 +673,43 @@ module.exports = { } ``` + + + + +```js + +// path: ./src/index.ts + +export default { + register({ strapi }) { + const extensionService = strapi.plugin('graphql').service('extension'); + + extensionService.use({ + resolversConfig: { + 'Query.categories': { + policies: [ + (context, { strapi }) => { + console.log('hello', context.parent) + /** + * If 'categories' have a parent, the function returns true, + * so the request won't be blocked by the policy. + */ + return context.parent !== undefined; + } + ], + auth: false, + }, + } + }) + } +} +``` + + + + + ::: #### Middlewares @@ -474,6 +726,10 @@ Middlewares with GraphQL can even act on nested resolvers, which offer a more gr :::details Examples of custom GraphQL middlewares applied to a resolver + + + + ```js // path: ./src/index.js @@ -528,6 +784,68 @@ module.exports = { } ``` + + + + +```js + +// path: ./src/index.ts + +export default { + register({ strapi }) { + const extensionService = strapi.plugin('graphql').service('extension'); + + extensionService.use({ + resolversConfig: { + 'Query.categories': { + middlewares: [ + /** + * Basic middleware example #1 + * Log resolving time in console + */ + async (next, parent, args, context, info) => { + console.time('Resolving categories'); + + // call the next resolver + const res = await next(parent, args, context, info); + + console.timeEnd('Resolving categories'); + + return res; + }, + /** + * Basic middleware example #2 + * Enable server-side shared caching + */ + async (next, parent, args, context, info) => { + info.cacheControl.setCacheHint({ maxAge: 60, scope: "PUBLIC" }); + return next(parent, args, context, info); + }, + /** + * Basic middleware example #3 + * change the 'name' attribute of parent with id 1 to 'foobar' + */ + (resolve, parent, ...rest) => { + if (parent.id === 1) { + return resolve({...parent, name: 'foobar' }, ...rest); + } + + return resolve(parent, ...rest); + } + ], + auth: false, + }, + } + }) + } +} +``` + + + + + ::: ## Usage with the Users & Permissions plugin @@ -539,6 +857,7 @@ The [Users & Permissions plugin](/developer-docs/latest/plugins/users-permission Usually you need to sign up or register before being recognized as a user then perform authorized requests. :::request Mutation + ```graphql mutation { register(input: { username: "username", email: "email", password: "password" }) { @@ -550,6 +869,7 @@ mutation { } } ``` + ::: You should see a new user is created in the `Users` collection type in your Strapi admin panel. diff --git a/docs/developer-docs/latest/plugins/upload.md b/docs/developer-docs/latest/plugins/upload.md index 73c2909661..e8681ea4bf 100644 --- a/docs/developer-docs/latest/plugins/upload.md +++ b/docs/developer-docs/latest/plugins/upload.md @@ -56,6 +56,10 @@ The library we use is [`koa-body`](https://github.com/dlau/koa-body), and it use You can pass configuration to the middleware directly by setting it in the [`body` middleware](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md#body) configuration in `./config/middlewares.js`: + + + + ```js // path: ./config/middlewares.js @@ -76,24 +80,36 @@ module.exports = { }; ``` -In addition to the middleware configuration, you can pass the `sizeLimit`, which is an integer in bytes, in the `providerOptions` of the [plugin configuration](/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md) in `./config/plugins.js`: + + + ```js -// path: ./config/plugins.js +// path: ./config/middlewares.js -module.exports = { +export default { // ... - upload: { + { + name: "strapi::body", config: { - providerOptions: { - sizeLimit: 250 * 1024 * 1024 // 256mb in bytes - } - } - } -} + formLimit: "256mb", // modify form body + jsonLimit: "256mb", // modify JSON body + textLimit: "256mb", // modify text body + formidable: { + maxFileSize: 200 * 1024 * 1024, // multipart data, modify here limit of uploaded file size + }, + }, + }, + // ... +}; ``` -### Responsive images + + + + + +### Responsive Images When the `Enable responsive friendly upload` setting is enabled in the settings panel the plugin will generate the following responsive image sizes: | Name | Largest Dimension | @@ -104,6 +120,10 @@ When the `Enable responsive friendly upload` setting is enabled in the settings These sizes can be overridden in `./config/plugins.js`: + + + + ```js // path: ./config/plugins.js @@ -122,6 +142,33 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/plugins.ts + +export default ({ env }) => ({ + upload: { + config: { + breakpoints: { + xlarge: 1920, + large: 1000, + medium: 750, + small: 500, + xsmall: 64 + }, + }, + }, +}); +``` + + + + + + :::caution Breakpoint changes will only apply to new images, existing images will not be resized or have new sizes generated. ::: @@ -419,3 +466,297 @@ In our second example, you can upload and attach multiple pictures to the restau // ... } ``` + +## Using a provider + +By default Strapi provides a provider that uploads files to a local directory. You might want to upload your files to another provider like AWS S3. + +Below are the providers maintained by the Strapi team: + +- [Amazon S3](https://www.npmjs.com/package/@strapi/provider-upload-aws-s3) +- [Cloudinary](https://www.npmjs.com/package/@strapi/provider-upload-cloudinary) +- [Local](https://www.npmjs.com/package/@strapi/provider-upload-local) +- [Rackspace](https://www.npmjs.com/package/@strapi/provider-upload-rackspace) + +You can also find additional community maintained providers on [NPM](https://www.npmjs.com/). + +To install a new provider run: + + + + +```sh +npm install @strapi/provider-upload-aws-s3 --save +``` + + + +```sh +yarn add @strapi/provider-upload-aws-s3 +``` + + + + +### Local server + +By default Strapi accepts `localServer` configurations for locally uploaded files. They will be passed as the options for [koa-static](https://github.com/koajs/static). + +You can provide them by create or edit the file at `./config/plugins.js`. The example below set `max-age` header. + + + + + +```js +// path: ./config/plugins.js + +module.exports = ({ env })=>({ + upload: { + config: { + providerOptions: { + localServer: { + maxage: 300000 + }, + }, + }, + }, +}); +``` + + + + + +```js +// path: ./config/plugins.ts + +export default ({ env })=>({ + upload: { + config: { + providerOptions: { + localServer: { + maxage: 300000 + }, + }, + }, + }, +}); +``` + + + + + + +### Enabling the provider + +To enable the provider, create or edit the file at `./config/plugins.js` + +::: note +When using community providers, pass the full package name to the `provider` key (e.g. `provider: 'strapi-provider-upload-google-cloud-storage'`). Only Strapi-maintained providers can use the shortcode format (e.g. `provider: 'aws-s3'`). +::: + + + + + +```js +// path: ./config/plugins.js + +module.exports = ({ env }) => ({ + // ... + upload: { + config: { + provider: 'aws-s3', + providerOptions: { + accessKeyId: env('AWS_ACCESS_KEY_ID'), + secretAccessKey: env('AWS_ACCESS_SECRET'), + region: env('AWS_REGION'), + params: { + Bucket: env('AWS_BUCKET'), + }, + }, + }, + }, + // ... +}); +``` + + + + + +```js +// path: ./config/plugins.ts + +export default ({ env }) => ({ + // ... + upload: { + config: { + provider: 'aws-s3', + providerOptions: { + accessKeyId: env('AWS_ACCESS_KEY_ID'), + secretAccessKey: env('AWS_ACCESS_SECRET'), + region: env('AWS_REGION'), + params: { + Bucket: env('AWS_BUCKET'), + }, + }, + }, + }, + // ... +}); +``` + + + + + + +Make sure to read the provider's `README` to know what are the possible parameters. + +:::caution +Strapi has a default Security Middleware that has a very strict `contentSecurityPolicy` that limits loading images and media to `"'self'"` only, see the example configuration on the provider page or take a look at our [middleware documentation](/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md#loading-order) for more information. +::: + +### Configuration per environment + +When configuring your upload provider you might want to change the configuration based on the `NODE_ENV` environment variable or use environment specific credentials. + +You can set a specific configuration in the `./config/env/{env}/plugins.js` configuration file and it will be used to overwrite the one in the default configuration. + +## Create providers + +You can create a Node.js module to implement a custom provider. Read the official documentation [here](https://docs.npmjs.com/creating-node-js-modules). + +Your provider needs to export the following interface: + + + + + +```js +module.exports = { + init(providerOptions) { + // init your provider if necessary + + return { + upload(file) { + // upload the file in the provider + // file content is accessible by `file.buffer` + }, + uploadStream(file) { + // upload the file in the provider + // file content is accessible by `file.stream` + }, + delete(file) { + // delete the file in the provider + }, + }; + }, +}; +``` + + + + + +```js +export default { + init(providerOptions) { + // init your provider if necessary + + return { + upload(file) { + // upload the file in the provider + // file content is accessible by `file.buffer` + }, + uploadStream(file) { + // upload the file in the provider + // file content is accessible by `file.stream` + }, + delete(file) { + // delete the file in the provider + }, + }; + }, +}; +``` + + + + + +:::tip +For performance reasons, the upload plugin will only use the `uploadStream` function if it exists, otherwise it will fallback on the `upload` function. +::: + +You can then publish it to make it available to the community. + +### Create a local provider + +If you want to create your own provider without publishing it on **npm** you can follow these steps: + +1. Create a `./providers/upload-{provider-name}` folder in your root application folder. +2. Create your provider as explained in the [documentation](#create-providers) above. +3. Update your `package.json` to link your `upload-{provider-name}` dependency to point to the [local path](https://docs.npmjs.com/files/package.json#local-paths) of your provider: + +```json +// path: ./package.json + +{ + ... + "dependencies": { + ... + "@strapi/provider-upload-{provider-name}": "file:providers/upload-{provider-name}" + ... + } +} +``` + +4. Update the Upload plugin configuration: + + + + + +```js +// path: ./config/plugins.js + +module.exports = ({ env }) => ({ + // ... + upload: { + config: { + provider: '{provider-name}', + providerOptions: {}, + }, + }, + // ... +}); +``` + + + + + +```js +// path: ./config/plugins.ts + +export default ({ env }) => ({ + // ... + upload: { + config: { + provider: '{provider-name}', + providerOptions: {}, + }, + }, + // ... +}); +``` + + + + + +5. Run `yarn install` or `npm install` to install your new custom provider. diff --git a/docs/developer-docs/latest/plugins/users-permissions.md b/docs/developer-docs/latest/plugins/users-permissions.md index 2c1f13f839..d2a2879af8 100644 --- a/docs/developer-docs/latest/plugins/users-permissions.md +++ b/docs/developer-docs/latest/plugins/users-permissions.md @@ -88,6 +88,10 @@ Available options: - `jwt.expiresIn`: expressed in seconds or a string describing a time span zeit/ms.
Eg: 60, "45m", "10h", "2 days", "7d", "2y". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (minutes, hours, days, years, etc), otherwise milliseconds unit is used by default ("120" is equal to "120ms"). + + + + ```js // path: ./config/plugins.js @@ -104,6 +108,31 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/plugins.ts + +export default ({ env }) => ({ + // ... + 'users-permissions': { + config: { + jwt: { + expiresIn: '7d', + }, + }, + }, + // ... +}); +``` + + + + + + :::warning Setting JWT expiry for more than 30 days is **absolutely not recommended** due to massive security concerns. ::: @@ -194,7 +223,13 @@ Before setting up a provider, you need to specify the absolute url of your backe **example -** `config/server.js` + + + + ```js +//path: config/server.js + module.exports = ({ env }) => ({ host: env('HOST', '0.0.0.0'), port: env.int('PORT', 1337), @@ -202,6 +237,24 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +//path: config/server.ts + +export default ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), + url: env('', 'http://localhost:1337'), +}); +``` + + + + + :::tip Later on you will give this url to your provider.
For development, some providers accept the use of localhost urls but many don't. In this case we recommand to use [ngrok](https://ngrok.com/docs) (`ngrok http 1337`) that will make a proxy tunnel from a url it created to your localhost url (ex: `url: env('', 'https://5299e8514242.ngrok.io'),`). ::: @@ -995,14 +1048,36 @@ JWT tokens can be verified and trusted because the information is digitally sign By default you can set a `JWT_SECRET` environment variable and it will be used as secret. If you want to use another variable you can update the configuration file. -**Path -** `./extensions/users-permissions/config/jwt.js`. + + + ```js +//path: ./extensions/users-permissions/config/jwt.js + module.exports = { jwtSecret: process.env.SOME_ENV_VAR, }; ``` + + + + +```js + +//path: ./extensions/users-permissions/config/jwt.ts + +export default { + jwtSecret: process.env.SOME_ENV_VAR, +}; +``` + + + + + + ::: tip You can learn more on configuration in the documentation [here](/developer-docs/latest/setup-deployment-guides/configurations.md). ::: diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/api-tokens.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/api-tokens.md index 40cf83419b..a8764652cc 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/api-tokens.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/api-tokens.md @@ -7,7 +7,7 @@ description: Using API tokens allows executing a request on Strapi's REST API en Authentication strategies in Strapi can either be based on the use of the [Users & Permissions plugin](/user-docs/latest/users-roles-permissions/introduction-to-users-roles-permissions.md) or on the built-in API token feature. -Using API tokens allows executing a request on [REST API](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md) endpoints as an authenticated user. +Using API tokens allows executing a request on [REST API](/developer-docs/latest/developer-resources/database-apis-reference/rest-api.md) endpoints as an authenticated user. ## Creation diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/api.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/api.md index 05759efe99..9e72c8007b 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/api.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/api.md @@ -19,6 +19,10 @@ General settings for API calls can be set in the `./config/api.js` file: **Example:** + + + + ```js // path: ./config/api.js @@ -33,3 +37,28 @@ module.exports = ({ env }) => ({ }, }); ``` + + + + + + + +```js +// path: ./config/api.ts + +export default ({ env }) => ({ + responses: { + privateAttributes: ['_v', 'id', 'created_at'], + }, + rest: { + prefix: '/v1', + defaultLimit: 100, + maxLimit: 250, + }, +}); +``` + + + + diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md index ec39bfa842..8de204304b 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.md @@ -7,7 +7,7 @@ canonicalUrl: https://docs.strapi.io/developer-docs/latest/setup-deployment-guid # Cron jobs :::prerequisites -The `cron.enabled` configuration option should be set to `true` in [the `./config/server.js` file](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md). +The `cron.enabled` configuration option should be set to `true` in the `./config/server.js` (or `./config/server.ts` for TypeScript projects) [file](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md). ::: `cron` allows scheduling arbitrary functions for execution at specific dates, with optional recurrence rules. These functions are named cron jobs. `cron` only uses a single timer at any given time, rather than reevaluating upcoming jobs every second/minute. @@ -43,6 +43,9 @@ Optionally, cron jobs can be directly created in the `cron.tasks` key of the [se To define a cron job, create a file with the following structure: + + + ```js // path: ./config/cron-tasks.js @@ -58,10 +61,38 @@ module.exports = { }; ``` + + + + +```js +// path: ./config/cron-tasks.ts + +export default { + /** + * Simple example. + * Every monday at 1am. + */ + + '0 0 1 * * 1': ({ strapi }) => { + // Add your own logic here (e.g. send a queue of email, create a database backup, etc.). + }, +}; +``` + + + + + If the cron job requires running on a specific timezone: + + + + ```js // path: ./config/cron-tasks.js + module.exports = { /** * Cron job with timezone example. @@ -80,10 +111,41 @@ myJob: { }; ``` + + + + +```js +// path: ./config/cron-tasks.ts + +export default { + /** + * Cron job with timezone example. + * Every Monday at 1am for Asia/Dhaka timezone. + * List of valid timezones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List + */ + + +myJob: { + task: ({ strapi }) => {/* Add your own logic here */ }, + options: { + rule: '0 0 1 * * 1', + tz: 'Asia/Dhaka', + }, + }, + }; +``` + + + + ## Enabling cron jobs To enable cron jobs, set `cron.enabled` to `true` in the [server configuration file](/developer-docs/latest/setup-deployment-guides/configurations/required/server.md) and declare the jobs: + + + ```js // path: ./config/server.js @@ -98,3 +160,28 @@ module.exports = ({ env }) => ({ }, }); ``` + + + + + + +```js +// path: ./config/server.ts + +import cronTasks from "./cron-tasks"; + +export default ({ env }) => ({ + host: env("HOST", "0.0.0.0"), + port: env.int("PORT", 1337), + cron: { + enabled: true, + tasks: cronTasks, + }, +}); +``` + + + + + diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md index d1380a2986..be008f5486 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md @@ -47,6 +47,10 @@ Variables defined in the `.env` file are accessible using `process.env.{variable In configuration files, a `env()` utility allows defining defaults and [casting values](#casting-environment-variables): + + + + ```js // path: ./config/database.js @@ -61,6 +65,28 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/database.ts + +export default ({ env }) => ({ + connections: { + default: { + settings: { + password: env('DATABASE_PASSWORD'), + }, + }, + }, +}); +``` + + + + + ### Casting environment variables The `env()` utility can be used to cast environment varibles to different types: @@ -99,6 +125,9 @@ When starting Strapi with `NODE_ENV=production` it will load the configuration f For instance, using the following configuration files will give you various options to start the server: + + + ```js // path: ./config/server.js @@ -114,6 +143,28 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/server.ts + +export default ({ env }) => ({ + host: '127.0.0.1', +}); + + +// path: ./config/env/production/server.ts + +export default ({ env }) => ({ + host: env('HOST', '0.0.0.0'), +}); +``` + + + + With these configuration files the server will start on various ports depending on the environment variables passed: ```bash diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md index 64d997edc8..d6ba58a529 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md @@ -6,11 +6,11 @@ canonicalUrl: https://docs.strapi.io/developer-docs/latest/setup-deployment-guid # Functions -The `./src/index.js` file includes global [register](#register), [bootstrap](#bootstrap) and [destroy](#destroy) functions that can be used to add dynamic and logic-based configurations. +The `./src/index.js` file (or `./src/index.ts` file in a [TypeScript-based](/developer-docs/latest/development/typescript.md) project) includes global [register](#register), [bootstrap](#bootstrap) and [destroy](#destroy) functions that can be used to add dynamic and logic-based configurations. ## Register -The `register` found in `./src/index.js` lifecycle function is an asynchronous function that runs before the application is initialized. +The `register` lifecycle function, found in `./src/index.js` (or in `./src/index.ts`), is an asynchronous function that runs before the application is initialized. It can be used to: - [extend plugins](/developer-docs/latest/development/plugins-extension.md#extending-a-plugin-s-interface) @@ -19,7 +19,7 @@ It can be used to: ## Bootstrap -The `bootstrap` lifecycle function found in `./src/index.js` is called at every server start. +The `bootstrap` lifecycle function, found in `./src/index.js` (or in `./src/index.ts`), is called at every server start. It can be used to: @@ -31,31 +31,79 @@ The bootstrap function can be synchronous, asynchronous, or return a promise: **Synchronous function** + + + ```js module.exports = () => { // some sync code }; ``` + + + + +```js +export default () => { + // some sync code +}; +``` + + + + **Asynchronous function** + + + ```js module.exports = async () => { await someSetup(); }; ``` + + + + +```js +export default async () => { + await someSetup(); +}; +``` + + + + **Function returning a promise** + + + ```js module.exports = () => { return new Promise(/* some code */); }; ``` + + + + +```js +export default () => { + return new Promise(/* some code */); +}; +``` + + + + ## Destroy -The `destroy` function found in `./src/index.js` is an asynchronous function that runs before the application gets shut down. +The `destroy` function, found in `./src/index.js` (or in `./src/index.ts`), is an asynchronous function that runs before the application gets shut down. It can be used to gracefully: diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md index c4cd9cc64b..e81301f789 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/plugins.md @@ -14,6 +14,10 @@ The configurations for all plugins are stored in `./config/plugins.js` (see [pro | `config`

_Optional_ | Used to override default plugin configuration ([defined in strapi-server.js](/developer-docs/latest/developer-resources/plugin-api-reference/server.md#configuration)) | Object | | `resolve`
_Optional, only required for local plugins_ | Path to the plugin's folder | String | + + + + ```js // path: ./config/plugins.js @@ -38,6 +42,39 @@ module.exports = ({ env }) => ({ }); ``` + + + + + +```js +// path: ./config/plugins.ts + +export default ({ env }) => ({ + // enable a plugin that doesn't require any configuration + i18n: true, + + // enable a custom plugin + myplugin: { + // my-plugin is going to be the internal name used for this plugin + enabled: true, + resolve: './src/plugins/my-local-plugin', + config: { + // user plugin config goes here + }, + }, + + // disable a plugin + myotherplugin: { + enabled: false, // plugin installed but disabled + }, +}); +``` + + + + + :::tip If no specific configuration is required, a plugin can also be declared with the shorthand syntax `'plugin-name': true`. ::: @@ -58,6 +95,9 @@ The [GraphQL plugin](/developer-docs/latest/plugins/graphql.md) has the followin | `shadowCRUD` | Whether type definitions for queries, mutations and resolvers based on models should be created automatically (see [Shadow CRUD documentation](/developer-docs/latest/plugins/graphql.md#shadow-crud)). | Boolean | `true` | | `subscriptions` | Enable GraphQL subscriptions (experimental feature). | Boolean | `false` | + + + ```js // path: ./config/plugins.js @@ -75,3 +115,27 @@ module.exports = () => ({ } }) ``` + + + + + + +```js +// path: ./config/plugins.ts + +export default () => ({ + graphql: { + enabled: true, + config: { + defaultLimit: 10, + maxLimit: 20 + } + } +}) +``` + + + + + diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/rbac.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/rbac.md index d6c90ab3d3..796d92f78f 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/rbac.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/rbac.md @@ -85,9 +85,15 @@ const condition = { ## Registering conditions -To be available in the admin panel, conditions should be declared and registered in the global [`bootstrap` function](/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md#bootstrap) found in `./src/index.js`. Register a single condition with the `conditionProvider.register()` method: +To be available in the admin panel, conditions should be declared and registered in the global [`bootstrap` function](/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md#bootstrap) found in `./src/index`. Register a single condition with the `conditionProvider.register()` method: + + + + ```js +// path: ./src/index.js + module.exports = async () => { await strapi.admin.services.permission.conditionProvider.register({ displayName: 'Billing amount under 10K', @@ -98,9 +104,35 @@ module.exports = async () => { }; ``` + + + + +```js +// path: ./src/index.ts + +export default async () => { + await strapi.admin.services.permission.conditionProvider.register({ + displayName: 'Billing amount under 10K', + name: 'billing-amount-under-10k', + plugin: 'admin', + handler: { amount: { $lt: 10000 } }, + }); +}; +``` + + + + To register multiple conditions, defined as an array of [condition objects](#declaring-new-conditions), use `conditionProvider.registerMany()`: + + + + ```js +// path: ./src/index.js + const conditions = [ { displayName: "Entity has same name as user", @@ -125,3 +157,39 @@ module.exports = async () => { await strapi.admin.services.permission.conditionProvider.registerMany(conditions); }; ``` + + + + + +```js +// path: ./src/index.ts + +const conditions = [ + { + displayName: "Entity has same name as user", + name: "same-name-as-user", + plugin: "name of a plugin if created in a plugin" + handler: (user) => { + return { name: user.name }; + }, + }, + { + displayName: "Email address from strapi.io", + name: "email-strapi-dot-io", + async handler(user) { + return user.email.includes('@strapi.io'); + }, + } +]; + +export default async () => { + // do your boostrap + + await strapi.admin.services.permission.conditionProvider.registerMany(conditions); +}; +``` + + + + diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/sso.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/sso.md index 246a345797..0a49595fcc 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/sso.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/sso.md @@ -29,6 +29,9 @@ The providers' configuration should be written within the `auth.providers` path `auth.providers` is an array of [provider configuration](#setting-up-provider-configuration). + + + ```javascript // path: ./config/admin.js @@ -40,6 +43,27 @@ module.exports = ({ env }) => ({ }); ``` + + + + + +```javascript +// path: ./config/admin.ts + +export default ({ env }) => ({ + // ... + auth: { + providers: [], // The providers' configuration lives there + }, +}); +``` + + + + + + ## Setting up provider configuration A provider's configuration is a JavaScript object built with the following properties: @@ -132,6 +156,10 @@ yarn add passport-google-oauth2 ::: details Configuration example for Google: +
+ + + ```jsx // path: ./config/admin.js @@ -172,6 +200,52 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```jsx +// path: ./config/admin.ts + +import GoogleStrategy from "passport-google-oauth2"; + +export default ({ env }) => ({ + auth: { + // ... + providers: [ + { + uid: "google", + displayName: "Google", + icon: "https://cdn2.iconfinder.com/data/icons/social-icons-33/128/Google-512.png", + createStrategy: (strapi) => + new GoogleStrategy( + { + clientID: env("GOOGLE_CLIENT_ID"), + clientSecret: env("GOOGLE_CLIENT_SECRET"), + scope: [ + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", + ], + callbackURL: + strapi.admin.services.passport.getStrategyCallbackURL("google"), + }, + (request, accessToken, refreshToken, profile, done) => { + done(null, { + email: profile.email, + firstname: profile.given_name, + lastname: profile.family_name, + }); + } + ), + }, + ], + }, +}); +``` + + + + ::: #### Github @@ -196,6 +270,10 @@ yarn add passport-github2 ::: details Configuration example for Github: +
+ + + ```jsx // path: ./config/admin.js @@ -231,6 +309,50 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```jsx +// path: ./config/admin.ts + +import GithubStrategy from "passport-github2"; + +export default ({ env }) => ({ + auth: { + // ... + providers: [ + { + uid: "github", + displayName: "Github", + icon: "https://cdn1.iconfinder.com/data/icons/logotypes/32/github-512.png", + createStrategy: (strapi) => + new GithubStrategy( + { + clientID: env("GITHUB_CLIENT_ID"), + clientSecret: env("GITHUB_CLIENT_SECRET"), + scope: ["user:email"], + callbackURL: + strapi.admin.services.passport.getStrategyCallbackURL("github"), + }, + (accessToken, refreshToken, profile, done) => { + done(null, { + email: profile.emails[0].value, + username: profile.username, + }); + } + ), + }, + ], + }, +}); + +``` + + + + + ::: @@ -255,6 +377,10 @@ yarn add passport-discord ::: details Configuration example for Discord: +
+ + + ```jsx // path: ./config/admin.js @@ -293,6 +419,53 @@ module.exports = ({ env }) => ({ }); ``` + + + + + + +```jsx +// path: ./config/admin.ts + +import DiscordStrategy from "passport-discord"; + +export default ({ env }) => ({ + auth: { + // ... + providers: [ + { + uid: "discord", + displayName: "Discord", + icon: "https://cdn0.iconfinder.com/data/icons/free-social-media-set/24/discord-512.png", + createStrategy: (strapi) => + new DiscordStrategy( + { + clientID: env("DISCORD_CLIENT_ID"), + clientSecret: env("DISCORD_SECRET"), + callbackURL: + strapi.admin.services.passport.getStrategyCallbackURL( + "discord" + ), + scope: ["identify", "email"], + }, + (accessToken, refreshToken, profile, done) => { + done(null, { + email: profile.email, + username: `${profile.username}#${profile.discriminator}`, + }); + } + ), + }, + ], + }, +}); +``` + + + + + ::: #### Microsoft @@ -316,6 +489,10 @@ yarn add passport-azure-ad-oauth2 jsonwebtoken ::: details Configuration example for Microsoft: +
+ + + ```jsx // path: ./config/admin.js @@ -357,6 +534,57 @@ module.exports = ({ env }) => ({ }); ``` + + + + + +```jsx +// path: ./config/admin.ts + +import AzureAdOAuth2Strategy from "passport-azure-ad-oauth2"; +import jwt from "jsonwebtoken"; + +export default ({ env }) => ({ + auth: { + // ... + providers: [ + { + uid: "azure_ad_oauth2", + displayName: "Microsoft", + icon: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Microsoft_logo_%282012%29.svg/320px-Microsoft_logo_%282012%29.svg.png", + createStrategy: (strapi) => + new AzureAdOAuth2Strategy( + { + clientID: env("MICROSOFT_CLIENT_ID", ""), + clientSecret: env("MICROSOFT_CLIENT_SECRET", ""), + scope: ["user:email"], + tenant: env("MICROSOFT_TENANT_ID", ""), + callbackURL: + strapi.admin.services.passport.getStrategyCallbackURL( + "azure_ad_oauth2" + ), + }, + (accessToken, refreshToken, params, profile, done) => { + var waadProfile = jwt.decode(params.id_token, "", true); + done(null, { + email: waadProfile.upn, + username: waadProfile.upn, + }); + } + ), + }, + ], + }, +}); +``` + + + + + + + ::: #### Keycloak (OpenID Connect) @@ -380,6 +608,10 @@ yarn add passport-keycloak-oauth2-oidc ::: details Configuration example for Keycloak (OpenID Connect): +
+ + + ```jsx // path: ./config/admin.js @@ -421,6 +653,52 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```jsx +// path: ./config/admin.ts + +import KeyCloakStrategy from "passport-keycloak-oauth2-oidc"; + +export default ({ env }) => ({ + auth: { + // ... + providers: [ + { + uid: "keycloak", + displayName: "Keycloak", + icon: "https://raw.githubusercontent.com/keycloak/keycloak-admin-ui/main/themes/keycloak/logo.svg", + createStrategy: (strapi) => + new KeyCloakStrategy( + { + clientID: env("KEYCLOAK_CLIENT_ID", ""), + realm: env("KEYCLOAK_REALM", ""), + publicClient: env.bool("KEYCLOAK_PUBLIC_CLIENT", false), + clientSecret: env("KEYCLOAK_CLIENT_SECRET", ""), + sslRequired: env("KEYCLOAK_SSL_REQUIRED", "external"), + authServerURL: env("KEYCLOAK_AUTH_SERVER_URL", ""), + callbackURL: + strapi.admin.services.passport.getStrategyCallbackURL( + "keycloak" + ), + }, + (accessToken, refreshToken, profile, done) => { + done(null, { + email: profile.email, + username: profile.username, + }); + } + ), + }, + ], + }, +}); +``` + + + ::: #### Okta @@ -444,6 +722,10 @@ yarn add passport-okta-oauth20 ::: details Configuration example for Okta: +
+ + + ```jsx // path: ./config/admin.js @@ -478,9 +760,53 @@ module.exports = ({ env }) => ({ }, ], }, +}); + ``` + + + + + +```jsx +// path: ./config/admin.ts + +import { Strategy as OktaOAuth2Strategy } from "passport-okta-oauth20") + +export default ({ env }) => ({ + auth: { + // ... + providers: [ + { + uid: "okta", + displayName: "Okta", + icon: "https://www.okta.com/sites/default/files/Okta_Logo_BrightBlue_Medium-thumbnail.png", + createStrategy: (strapi) => + new OktaOAuth2Strategy( + { + clientID: env("OKTA_CLIENT_ID"), + clientSecret: env("OKTA_CLIENT_SECRET"), + audience: env("OKTA_DOMAIN"), + scope: ["openid", "email", "profile"], + callbackURL: + strapi.admin.services.passport.getStrategyCallbackURL("okta"), + }, + (accessToken, refreshToken, profile, done) => { + done(null, { + email: profile.email, + username: profile.username, + }); + } + ), + }, + ], + }, }); ``` + + + + ::: ## Performing advanced customization @@ -502,6 +828,9 @@ The easiest way to do so is to plug into the verify function of your strategy an For example, if you want to allow only people with an official strapi.io email address, you can instantiate your strategy like this: + + + ```javascript // path: ./config/admin.js @@ -517,6 +846,31 @@ const strategyInstance = new Strategy(configuration, ({ email, username }, done) }); ``` + + + + + +```javascript +// path: ./config/admin.ts + +const strategyInstance = new Strategy(configuration, ({ email, username }, done) => { + // If the email ends with @strapi.io + if (email.endsWith('@strapi.io')) { + // then we continue with the data given by the provider + return done(null, { email, username }); + } + + // Otherwise, we continue by sending an error to the done function + done(new Error('Forbidden email address')); +}); +``` + + + + + + ### Authentication Events The SSO feature adds a new [authentication event](/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md#available-options): `onSSOAutoRegistration`. @@ -524,6 +878,9 @@ The SSO feature adds a new [authentication event](/developer-docs/latest/setup-d This event is triggered whenever a user is created using the auto-register feature added by SSO. It contains the created user (`event.user`), and the provider used to make the registration (`event.provider`). + + + ```javascript // path: ./config/admin.js @@ -545,3 +902,36 @@ module.exports = () => ({ }, }); ``` + + + + + + + +```javascript +// path: ./config/admin.ts + +export default () => ({ + auth: { + // ... + events: { + onConnectionSuccess(e) {}, + onConnectionError(e) {}, + // ... + onSSOAutoRegistration(e) { + const { user, provider } = e; + + console.log( + `A new user (${user.id}) has been automatically registered using ${provider}` + ); + }, + }, + }, +}); +``` + + + + + diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/typescript.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/typescript.md new file mode 100644 index 0000000000..170fa17031 --- /dev/null +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/optional/typescript.md @@ -0,0 +1,17 @@ +--- +title: TypeScript configuration - Strapi Developer Docs +description: Details for TypeScript configuration +sidebarDepth: 3 +canonicalUrl: https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/typescript.html +--- + +# TypeScript project configuration + +[TypeScript](/developer-docs/latest/development/typescript.md)-enabled Strapi applications have a specific [project structure](/developer-docs/latest/setup-deployment-guides/file-structure.md) with the following dedicated folders and configuration files: + +| TypeScript-specific directories and files | Location | Purpose | +|-------------------------------------------|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| `./dist` directory | application root | Adds the location for compiling the project JavaScript source code. | +| `build` directory | `./dist` | Contains the compiled administration panel JavaScript source code. The directory is created on the first `yarn build` or `npm run build` command | +| `tsconfig.json` file | application root | Manages TypeScript compilation for the server. | +| `tsconfig.json` file | `./src/admin/` | Manages TypeScript compilation for the admin panel. | diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md index b575ae401a..3ca8307ecf 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/admin-panel.md @@ -42,13 +42,36 @@ The `./config/admin.js` file should at least include a minimal configuration wit ::: :::: tabs card + ::: tab Minimal configuration The default configuration created with any new project should at least include the following: + + + + ```js // path: ./config/admin.js +module.exports = ({ env }) => ({ + apiToken: { + salt: env('API_TOKEN_SALT', 'someRandomLongString'), + }, + auth: { + secret: env('ADMIN_JWT_SECRET', 'someSecretKey'), + }, +}); + +``` + + + + + +```js +// path: ./config/admin.ts + module.exports = ({ env }) => ({ apiToken: { salt: env('API_TOKEN_SALT', 'someRandomLongString'), @@ -59,10 +82,18 @@ module.exports = ({ env }) => ({ }); ``` + + + + ::: ::: tab Full configuration + + + + ```js // path: ./config/admin.js @@ -98,7 +129,50 @@ module.exports = ({ env }) => ({ replyTo: 'no-reply@example.com', }, }); + ``` -::: -:::: + + + + +```js +// path: ./config/admin.js + +module.exports = ({ env }) => ({ + apiToken: { + salt: env('API_TOKEN_SALT', 'someRandomLongString'), + }, + auth: { + events: { + onConnectionSuccess(e) { + console.log(e.user, e.provider); + }, + onConnectionError(e) { + console.error(e.error, e.provider); + }, + }, + options: { + expiresIn: "7d", + }, + secret: env('ADMIN_JWT_SECRET', 'someSecretKey'), + }, + url: env('PUBLIC_ADMIN_URL', '/dashboard'), + autoOpen: false, + watchIgnoreFiles: [ + './my-custom-folder', // Folder + './scripts/someScript.sh', // File + ], + host: 'localhost', // Only used for --watch-admin + port: 8003, // Only used for --watch-admin + serveAdminPanel: env.bool('SERVE_ADMIN', true), + forgotPassword: { + from: 'no-reply@example.com', + replyTo: 'no-reply@example.com', + }, +}); +``` + + + + diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/databases.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/databases.md index 393d9e5b58..ad10f7b00d 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/databases.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/databases.md @@ -6,7 +6,7 @@ canonicalUrl: https://docs.strapi.io/developer-docs/latest/setup-deployment-guid # Database configuration -The `./config/database.js` file is used to define database connections that will be used to store the application content. +The `./config/database.js` file (or the `./config/database.ts` file for TypeScript) is used to define database connections that will be used to store the application content. :::strapi Supported databases The CLI installation guide details [supported database and versions](/developer-docs/latest/setup-deployment-guides/installation/cli.md#preparing-the-installation). @@ -14,7 +14,7 @@ The CLI installation guide details [supported database and versions](/developer- ## Configuration structure -The `./config/database.js` accepts 2 main configuration objects: +The `./config/database.js` (or `./config/database.ts` for TypeScript) accepts 2 main configuration objects: - [`connection`](#connection-configuration-object) for database configuration options passed to [Knex.js](https://github.com/knex/knex) - [`settings`](#settings-configuration-object) for Strapi-specific database settings @@ -32,7 +32,7 @@ The `./config/database.js` accepts 2 main configuration objects: #### Connection parameters -The `connection.connection` object found in `./config/database.js` is used to pass database connection information and accepts the following parameters: +The `connection.connection` object found in `./config/database.js` (or `./config/database.ts` for TypeScript) is used to pass database connection information and accepts the following parameters: | Parameter | Description | Type | |------------|-------------------------------------------------------------------------------------------------------------------------------|-----------------------| @@ -47,7 +47,7 @@ The `connection.connection` object found in `./config/database.js` is used to pa #### Database pooling options -The `connection.pool` object optionally found in `./config/database.js` is used to pass [Tarn.js](https://github.com/vincit/tarn.js) database pooling options and accepts the following parameters: +The `connection.pool` object optionally found in `./config/database.js` (or `./config/database.ts` for TypeScript) is used to pass [Tarn.js](https://github.com/vincit/tarn.js) database pooling options and accepts the following parameters: ::: caution When using Docker, change the pool `min` value to `0` as Docker will kill any idle connections, making it impossible to keep any open connections to the database (see [Tarn.js's pool](https://knexjs.org/guide/#pool) settings used by Knex.js for more information). @@ -67,7 +67,7 @@ When using Docker, change the pool `min` value to `0` as Docker will kill any id ### `settings` configuration object -The `settings` object found in `./config/database.js` is used to configure Strapi-specific database settings and accepts the following parameter: +The `settings` object found in `./config/database.js` (or `./config/database.ts` for TypeScript) is used to configure Strapi-specific database settings and accepts the following parameter: | Parameter | Description | Type | Default | |------------------|--------------------------------------------------|-----------|---------| @@ -82,6 +82,8 @@ The `settings` object found in `./config/database.js` is used to configure Strap :::: tab PostgreSQL ```js +// path: ./config/database.js + module.exports = ({ env }) => ({ connection: { client: 'postgres', @@ -142,6 +144,8 @@ module.exports = ({ env }) => ({ :::: tab MySQL/MariaDB ```js +// path: ./config/database.js + module.exports = ({ env }) => ({ connection: { client: 'mysql', @@ -163,8 +167,13 @@ module.exports = ({ env }) => ({ :::: :::: tab SQLite + + + ```js +// path: ./config/database.js + module.exports = ({ env }) => ({ connection: { client: 'sqlite', @@ -177,6 +186,35 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/database.ts + +import path from 'path'; + +export default ({ env }) => ({ + connection: { + client: 'sqlite', + connection: { + filename: path.join( + __dirname, + '..', + '..', + env('DATABASE_FILENAME', path.join('.tmp', 'data.db')) + ), + }, + useNullAsDefault: true, + }, +}); +``` + + + + + :::: ::::: diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md index 54fcea962f..12ba11a2ce 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.md @@ -26,6 +26,9 @@ Strapi pre-populates the `./config/middlewares.js` file with built-in, internal The `./config/middlewares.js` file exports an array, where order matters and controls the execution order of the middleware stack: + + + ```js // path: ./config/middlewares.js @@ -72,6 +75,41 @@ module.exports = [ ]; ``` + + + + +```typescript +// path: ./config/middlewares.ts + +export default [ + // The array is pre-populated with internal, built-in middlewares, prefixed by `strapi::` + 'strapi::cors', + 'strapi::body', + 'strapi::errors', + // ... + 'my-custom-node-module', // custom middleware that does not require any configuration + { + // custom name to find a package or a path + name: 'my-custom-node-module', + config: { + foo: 'bar', + }, + }, + { + // custom resolve to find a package or a path + resolve: '../some-dir/custom-middleware', + config: { + foo: 'bar', + }, + }, +]; +``` + + + + + :::tip If you aren't sure where to place a middleware in the stack, add it to the end of the list. ::: diff --git a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/server.md b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/server.md index 9fa2a23cc0..2c865a55cd 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/configurations/required/server.md +++ b/docs/developer-docs/latest/setup-deployment-guides/configurations/required/server.md @@ -47,6 +47,9 @@ The `./config/server.js` file should at least include a minimal configuration wi The default configuration created with any new project should at least include the following: + + + ```js // path: ./config/server.js @@ -59,12 +62,34 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/server.ts + +export default ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), + app: { + keys: env.array('APP_KEYS'), + }, +}); +``` + + + + :::: :::: tab Full configuration The following is an example of a full configuration file. Not all of these keys are required (see [available options](#available-options)). + + + ```js // path: ./config/server.js @@ -84,6 +109,30 @@ module.exports = ({ env }) => ({ }); ``` + + + +```js +// path: ./config/server.ts + +export default ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), + app: { + keys: env.array('APP_KEYS'), + }, + socket: '/tmp/nginx.socket', // only use if absolutely required + emitErrors: false, + url: env('PUBLIC_URL', 'https://api.example.com'), + proxy: env.bool('IS_PROXIED', true), + cron: { + enabled: env.bool('CRON_ENABLED', false), + }, +}); +``` + + + :::: ::::: diff --git a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/amazon-aws.md b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/amazon-aws.md index 63a169011b..9c00ac4017 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/amazon-aws.md +++ b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/amazon-aws.md @@ -307,9 +307,12 @@ npm install pg Copy/paste the following: -`Path: ./my-project/config/database.js`: + + ```js +// path: ./my-project/config/database.js + module.exports = ({ env }) => ({ connection: { client: "postgres", @@ -325,6 +328,33 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./my-project/config/database.ts + +export default ({ env }) => ({ + connection: { + client: "postgres", + connection: { + host: env("DATABASE_HOST", "127.0.0.1"), + port: env.int("DATABASE_PORT", 5432), + database: env("DATABASE_NAME", "strapi"), + user: env("DATABASE_USERNAME", ""), + password: env("DATABASE_PASSWORD", ""), + }, + useNullAsDefault: true, + }, +}); +``` + + + + + + #### 3. Install the **Strapi AWS S3 Upload Provider**: Path: `./my-project/`. @@ -335,6 +365,9 @@ npm install @strapi/provider-upload-aws-s3 To enable and configure the provider, create or edit the file at `./config/plugins.js`. + + + ```js module.exports = ({ env }) => ({ upload: { @@ -362,7 +395,34 @@ module.exports = ({ env }) => ({ }); ``` -Checkout the documentation about using an upload provider [here](/developer-docs/latest/development/providers.md). + + + + +```js +export default ({ env }) => ({ + upload: { + config: { + provider: 'aws-s3', + providerOptions: { + accessKeyId: env('AWS_ACCESS_KEY_ID'), + secretAccessKey: env('AWS_ACCESS_SECRET'), + region: env('AWS_REGION'), + params: { + Bucket: env('AWS_BUCKET_NAME'), + }, + }, + }, + } +}); +``` + + + + + + +Checkout the documentation about using an upload provider [here](/developer-docs/latest/plugins/upload.md#using-a-provider). #### 4. Push your local changes to your project's GitHub repository. @@ -417,6 +477,9 @@ sudo nano ecosystem.config.js - Next, replace the boilerplate content in the file, with the following: + + + ```js module.exports = { apps: [ @@ -442,6 +505,40 @@ module.exports = { }; ``` + + + + +```js +export default { + apps: [ + { + name: 'your-app-name', + cwd: '/home/ubuntu/my-project', + script: 'npm', + args: 'start', + env: { + NODE_ENV: 'production', + DATABASE_HOST: 'your-unique-url.rds.amazonaws.com', // database Endpoint under 'Connectivity & Security' tab + DATABASE_PORT: '5432', + DATABASE_NAME: 'strapi', // DB name under 'Configuration' tab + DATABASE_USERNAME: 'postgres', // default username + DATABASE_PASSWORD: 'Password', + AWS_ACCESS_KEY_ID: 'aws-access-key-id', + AWS_ACCESS_SECRET: 'aws-access-secret', // Find it in Amazon S3 Dashboard + AWS_REGION: 'aws-region', + AWS_BUCKET_NAME: 'my-project-bucket-name', + }, + }, + ], +}; +``` + + + + + + You can also set your environment variables in a `.env` file in your project like so: ``` diff --git a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/azure.md b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/azure.md index 6ef12e727a..8c2d93ad7e 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/azure.md +++ b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/azure.md @@ -487,7 +487,12 @@ nano /srv/strapi/mystrapiapp/config/database.js Using the following example we will remove any private information: + + + ```js +// path: /srv/strapi/mystrapiapp/config/database.js + module.exports = ({ env }) => ({ defaultConnection: 'default', connections: { @@ -507,6 +512,36 @@ module.exports = ({ env }) => ({ }); ``` + + + + + +```js +// path: /srv/strapi/mystrapiapp/config/database.ts + +export default ({ env }) => ({ + defaultConnection: 'default', + connections: { + default: { + connector: 'bookshelf', + settings: { + client: 'mysql', + database: env('DB_NAME'), + host: env('DB_HOST'), + port: env('DB_PORT'), + username: env('DB_USER'), + password: env('DB_PASS'), + }, + options: {}, + }, + }, +}); +``` + + + + #### 3. Installing PM2 and running Strapi as a service Now we will install [PM2](https://pm2.keymetrics.io/docs/usage/quick-start/) to run Strapi as a service, and using the PM2 ecosystem config file we can define our environment variables. diff --git a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/digitalocean.md b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/digitalocean.md index e1027760ff..e590c5a73a 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/digitalocean.md +++ b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/digitalocean.md @@ -177,9 +177,12 @@ You will need the **database name**, **username** and **password** for later use In your code editor, you will need to edit a file called `database.js`. Replace the contents of the file with the following. -`Path: ./config/database.js` + + ```js +// path: ./config/database.js` + module.exports = ({ env }) => ({ defaultConnection: 'default', connections: { @@ -201,6 +204,37 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +export default ({ env }) => ({ + defaultConnection: 'default', + connections: { + default: { + connector: 'bookshelf', + settings: { + client: 'postgres', + host: env('DATABASE_HOST', '127.0.0.1'), + port: env.int('DATABASE_PORT', 5432), + database: env('DATABASE_NAME', 'strapi'), + username: env('DATABASE_USERNAME', ''), + password: env('DATABASE_PASSWORD', ''), + }, + options: { + ssl: false, + }, + }, + }, +}); +``` + + + + + + You are now ready to push these changes to Github: ```bash diff --git a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/google-app-engine.md b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/google-app-engine.md index 4d895e7264..48dd9171c3 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/google-app-engine.md +++ b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/google-app-engine.md @@ -178,6 +178,9 @@ yarn add pg Adding the production database configuration for connect to GCP SQL. + + + ```js // path: ./config/env/production/database.js @@ -194,6 +197,31 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/env/production/database.ts + +export default ({ env }) => ({ + connection: { + client: 'postgres', + connection: { + host: `/cloudsql/${env('INSTANCE_CONNECTION_NAME')}`, + database: env('DATABASE_NAME'), + user: env('DATABASE_USER'), + password: env('DATABASE_PASSWORD'), + }, + }, +}); +``` + + + + + + ### Auto-build after deploy After deployment, the admin UI has to be re-built. This generates the contents of the `build` folder on the server. @@ -252,6 +280,9 @@ Read the documentation [here](/developer-docs/latest/setup-deployment-guides/con config/env/production/server.js ``` + + + ```js module.exports = { admin: { @@ -259,3 +290,20 @@ module.exports = { }, }; ``` + + + + + +```js +export default { + admin: { + path: '/dashboard', + }, +}; +``` + + + + + diff --git a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.md b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.md index 55286efa60..66d56e6be7 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.md +++ b/docs/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.md @@ -198,9 +198,13 @@ yarn add pg-connection-string Create new subfolders in `./config` like so: `/env/production`, then create a new `database.js` in it (see [environment documentation](/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md)). Your path should look like this: `./config/env/production/database.js`. When you run locally you should be using the `./config/database.js` which could be set to use SQLite, however it's recommended you use PostgreSQL locally also, for information on configuring your local database, please see the [database documentation](/developer-docs/latest/setup-deployment-guides/configurations/required/databases.md). -`Path: ./config/env/production/database.js` + + + ```js +// path: ./config/env/production/database.js + const parse = require('pg-connection-string').parse; const config = parse(process.env.DATABASE_URL); @@ -222,6 +226,39 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/env/production/database.ts + +import parse = require('pg-connection-string').parse; +const config = parse(process.env.DATABASE_URL); + +export default ({ env }) => ({ + connection: { + client: 'postgres', + connection: { + host: config.host, + port: config.port, + database: config.database, + user: config.user, + password: config.password, + ssl: { + rejectUnauthorized: false + }, + }, + debug: false, + }, +}); +``` + + + + + + You also need to set the `NODE_ENV` variable on Heroku to `production` to ensure this new database configuration file is used. ```bash @@ -232,21 +269,36 @@ heroku config:set NODE_ENV=production Create a new `server.js` in a new [env](/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.md) folder. In this file you only need one key, the `url`, to notify Strapi what our public Heroku domain is. All other settings will automatically be pulled from the default `./config/server.js`. -`Path: ./config/env/production/server.js` + + + ```js +// Path: ./config/env/production/server.js` + module.exports = ({ env }) => ({ - proxy: true, - url: env('MY_HEROKU_URL'), - app: { - keys: env.array('APP_KEYS') - }, - }) + url: env('MY_HEROKU_URL'), +}); + ``` -You will also need to set the environment variables in Heroku for the `MY_HEROKU_URL`, `APP_KEYS`, `API_TOKEN_SALT`, `ADMIN_JWT_SECRET`, and `JWT_SECRET`. This will populate the variables with something like `https://your-app.herokuapp.com/` and various random keys from the `.env` file locally. In some cases it is recommended to create new random secrets instead and there are various methods to do so. + + + + + +```js +export default ({ env }) => ({ + url: env('MY_HEROKU_URL'), +}); +``` + + + + + -To copy existing secrets from your environment config locally use the following: +You will also need to set the environment variable in Heroku for the `MY_HEROKU_URL`. This will populate the variable with something like `https://your-app.herokuapp.com`. ```bash heroku config:set MY_HEROKU_URL=$(heroku info -s | grep web_url | cut -d= -f2) diff --git a/docs/developer-docs/latest/setup-deployment-guides/deployment/optional-software/snippets/strapi-server.md b/docs/developer-docs/latest/setup-deployment-guides/deployment/optional-software/snippets/strapi-server.md index c56970ccba..f857b7932a 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/deployment/optional-software/snippets/strapi-server.md +++ b/docs/developer-docs/latest/setup-deployment-guides/deployment/optional-software/snippets/strapi-server.md @@ -23,6 +23,9 @@ If the `url` key is changed in the `./config/admin.js` or `./config/server.js` f - Example API: `api.example.com/api` - Example uploaded files (local provider): `api.example.com/uploads` + + + ```js // path: ./config/server.js @@ -33,6 +36,25 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/server.ts + +export default ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), + url: 'https://api.example.com', +}); +``` + + + + + + :::: :::: tab Subfolder unified @@ -46,6 +68,9 @@ module.exports = ({ env }) => ({ - Example API: `example.com/test/api` - Example uploaded Files (local provider): `example.com/test/uploads` + + + ```js // path: ./config/server.js @@ -56,6 +81,25 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/server.ts + +export default ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), + url: 'https://example.com/test', +}); +``` + + + + + + :::: :::: tab Subfolder split @@ -69,6 +113,9 @@ module.exports = ({ env }) => ({ - Example API: `example.com/api` - Example uploaded files (local provider): `example.com/uploads` + + + ```js // path: ./config/server.js @@ -79,6 +126,26 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/server.ts + +export default ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), + url: 'https://example.com', +}); +``` + + + + + + + ```js // path: ./config/admin.js @@ -90,6 +157,26 @@ module.exports = ({ env }) => ({ }); ``` + + + + +```js +// path: ./config/admin.ts + +export default ({ env }) => ({ + auth: { + ... + } + url: 'https://example.com/dashboard', +}); +``` + + + + + + :::: ::::: diff --git a/docs/developer-docs/latest/setup-deployment-guides/file-structure.md b/docs/developer-docs/latest/setup-deployment-guides/file-structure.md index 02e0d3dbdf..bc1e163e72 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/file-structure.md +++ b/docs/developer-docs/latest/setup-deployment-guides/file-structure.md @@ -31,11 +31,16 @@ my-project ::: :::: -The default structure of a Strapi project created without the starter CLI looks like the following: +The default structure of a Strapi project created without the starter CLI depends on whether the project was created with vanilla JavaScript or with [TypeScript](/developer-docs/latest/development/typescript.md), and looks like the following: + +:::: tabs card + +::: tab Vanilla JavaScript-based projects
   
 . # root of the application
+
 ├──── .cache # files used to build the admin panel
 ├──── .tmp
 ├──── build # build of the admin panel
@@ -99,3 +104,87 @@ The default structure of a Strapi project created without the starter CLI looks
 └ package.json
   
 
+ +::: + +::: tab TypeScript-based projects + +
+  
+. # root of the application
+
+├──── .cache # files used to build the admin panel
+├──── .tmp
+├──── config # API configurations
+│     ├ api.ts
+│     ├  admin.ts
+│     ├ cron-tasks.ts
+│     ├ database.ts
+│     ├ middlewares.ts
+│     ├ plugins.ts
+│     └ server.ts
+├──── database
+│     └──── migrations
+├──── dist # build of the backend
+│     └──── build # build of the admin panel
+├──── node_modules # npm packages used by the project
+├──── public # files accessible to the outside world
+│     └──── uploads
+├──── src
+│     ├──── admin # admin customization files
+│           ├──── extensions # files to extend the admin panel
+│     │     ├ app.example.tsx
+│     │     └ webpack.config.ts
+|     |     └ tsconfig.json
+│     ├──── api # business logic of the project split into subfolders per API
+│     │     └──── (api-name)
+│     │          content-types
+│     │           │     └──── (content-type-name)
+│     │           │           └ lifecycles.s
+│     │           │           └ schema.json
+│     │           ├──── controllers
+│     │           ├──── middlewares
+│     │           ├──── policies
+│     │           ├──── routes
+│     │           ├──── services
+│     │           └ index.ts
+│     │
+│     ├──── components
+│     │     └──── (category-name)
+│     │           ├ (componentA).json
+│     │           └ (componentB).json
+│     ├──── extensions # files to extend installed plugins
+│     │     └──── (plugin-to-be-extended)
+│     │           ├──── content-types
+│     │           │     └──── (content-type-name)
+│     │           │           └ schema.json
+│     │           └ strapi-server.js
+│     ├──── middlewares
+│     │     └──── (middleware-name)
+│     │           ├ defaults.json
+│     │           └ index.ts
+│     ├──── plugins # local plugins files
+│     │     └──── (plugin-name)
+│     │           ├──── admin
+│     │           │     └──── src
+│     │           │           └ index.tsx
+│     │           │           └ pluginId.ts
+│     │           ├──── server
+│     │           │     ├──── content-types
+│     │           │     ├──── controllers
+│     │           │     └──── policies
+│     │           ├ package.json
+│     │           ├ strapi-admin.js
+│     │           └ strapi-server.js
+│     ├─── policies
+│     └ index.ts # include register(), bootstrap() and destroy() functions
+├ .env
+├ tsconfig.json
+└ package.json
+
+  
+
+ +::: + +:::: diff --git a/docs/developer-docs/latest/setup-deployment-guides/installation/cli.md b/docs/developer-docs/latest/setup-deployment-guides/installation/cli.md index db808ebd1b..c0eb6fa939 100644 --- a/docs/developer-docs/latest/setup-deployment-guides/installation/cli.md +++ b/docs/developer-docs/latest/setup-deployment-guides/installation/cli.md @@ -31,6 +31,7 @@ The following installation guide covers the most basic installation option using - Using the `--quickstart` flag at the end of the command to directly create the project in quickstart mode. - Using the `--template` flag at the end of the command to create a project with pre-made Strapi configurations (see [Templates](templates.md)). +- Using the `--typescript` flag (or the shorter version `--ts`) at the end of the command to create a project in [TypeScript](/developer-docs/latest/development/typescript.md). - Using the `--no-run` flag will prevent Strapi from automatically starting the server (useful in combination with `--quickstart`) For more information on available flags, see our [CLI documentation](/developer-docs/latest/developer-resources/cli/CLI.md). diff --git a/docs/package.json b/docs/package.json index 5a211870a7..a3acdb8d73 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "strapi-docs", - "version": "4.2.3", + "version": "4.3.0", "main": "index.js", "scripts": { "dev": "yarn create:config-file && vuepress dev", diff --git a/docs/user-docs/latest/assets/icons/crop.svg b/docs/user-docs/latest/assets/icons/crop.svg new file mode 100644 index 0000000000..9a2a222e4b --- /dev/null +++ b/docs/user-docs/latest/assets/icons/crop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/user-docs/latest/assets/icons/download.svg b/docs/user-docs/latest/assets/icons/download.svg new file mode 100644 index 0000000000..373a6a419f --- /dev/null +++ b/docs/user-docs/latest/assets/icons/download.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/user-docs/latest/assets/icons/link.svg b/docs/user-docs/latest/assets/icons/link.svg new file mode 100644 index 0000000000..56ec95fa09 --- /dev/null +++ b/docs/user-docs/latest/assets/icons/link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/user-docs/latest/assets/icons/move.svg b/docs/user-docs/latest/assets/icons/move.svg new file mode 100644 index 0000000000..60bf412878 --- /dev/null +++ b/docs/user-docs/latest/assets/icons/move.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/user-docs/latest/assets/media-library/media-library_add-new-assets.png b/docs/user-docs/latest/assets/media-library/media-library_add-new-assets.png new file mode 100644 index 0000000000..ef47e82eab Binary files /dev/null and b/docs/user-docs/latest/assets/media-library/media-library_add-new-assets.png differ diff --git a/docs/user-docs/latest/assets/media-library/media-library_asset-details.png b/docs/user-docs/latest/assets/media-library/media-library_asset-details.png new file mode 100644 index 0000000000..78c48506ee Binary files /dev/null and b/docs/user-docs/latest/assets/media-library/media-library_asset-details.png differ diff --git a/docs/user-docs/latest/assets/media-library/media-library_filters.png b/docs/user-docs/latest/assets/media-library/media-library_filters.png new file mode 100644 index 0000000000..68d76f9b62 Binary files /dev/null and b/docs/user-docs/latest/assets/media-library/media-library_filters.png differ diff --git a/docs/user-docs/latest/assets/media-library/media-library_folder-content.png b/docs/user-docs/latest/assets/media-library/media-library_folder-content.png new file mode 100644 index 0000000000..66bf153ce7 Binary files /dev/null and b/docs/user-docs/latest/assets/media-library/media-library_folder-content.png differ diff --git a/docs/user-docs/latest/assets/media-library/media-library_move-assets.png b/docs/user-docs/latest/assets/media-library/media-library_move-assets.png new file mode 100644 index 0000000000..1c5b45e545 Binary files /dev/null and b/docs/user-docs/latest/assets/media-library/media-library_move-assets.png differ diff --git a/docs/user-docs/latest/assets/media-library/media-library_overview.png b/docs/user-docs/latest/assets/media-library/media-library_overview.png new file mode 100644 index 0000000000..1ac701195b Binary files /dev/null and b/docs/user-docs/latest/assets/media-library/media-library_overview.png differ diff --git a/docs/user-docs/latest/assets/media-library/media-library_sort.png b/docs/user-docs/latest/assets/media-library/media-library_sort.png new file mode 100644 index 0000000000..1c00193d97 Binary files /dev/null and b/docs/user-docs/latest/assets/media-library/media-library_sort.png differ diff --git a/docs/user-docs/latest/assets/plugins/sentry.png b/docs/user-docs/latest/assets/plugins/sentry.png index 9082ad20b6..2225be472d 100644 Binary files a/docs/user-docs/latest/assets/plugins/sentry.png and b/docs/user-docs/latest/assets/plugins/sentry.png differ diff --git a/docs/user-docs/latest/assets/settings/settings_media-library.png b/docs/user-docs/latest/assets/settings/settings_media-library.png new file mode 100644 index 0000000000..4ce012157f Binary files /dev/null and b/docs/user-docs/latest/assets/settings/settings_media-library.png differ diff --git a/docs/user-docs/latest/content-manager/writing-content.md b/docs/user-docs/latest/content-manager/writing-content.md index 614eba88ab..06767c7021 100644 --- a/docs/user-docs/latest/content-manager/writing-content.md +++ b/docs/user-docs/latest/content-manager/writing-content.md @@ -27,7 +27,7 @@ To write or edit content: | Email | Write a complete and valid email address. | | Password | Write a password.

💡 Click the eye icon, displayed on the right of the box, to show the password. | | Enumeration | 1. Click the drop-down list.
2. Choose an item from the list. | -| Media | 1. Click the media area.
2. Choose an asset from the Media Library, or click the **Add more assets** button to add a new file to the Media Library.

💡 It is possible to drag and drop the chosen file in the media area. | +| Media | 1. Click the media area.
2. Choose an asset from the [Media Library](/user-docs/latest/media-library/introduction-to-media-library.md) or from a [folder](/user-docs/latest/media-library/organizing-assets-with-folders.md) if you created some, or click the **Add more assets** button to add a new file to the Media Library.

💡 It is possible to drag and drop the chosen file in the media area. | | JSON | Write your content, in JSON format, in the code textbox. | | UID | Write a unique identifier in the textbox. A "Regenerate" button, displayed on the right of the box, allows to automatically generate a UID based on the content-type name. | diff --git a/docs/user-docs/latest/media-library/adding-assets.md b/docs/user-docs/latest/media-library/adding-assets.md new file mode 100644 index 0000000000..19add18635 --- /dev/null +++ b/docs/user-docs/latest/media-library/adding-assets.md @@ -0,0 +1,31 @@ +--- +title: Adding assets to the Media Library - Strapi User Guide +description: Instructions to add assets to the Media Library +canonicalUrl: https://docs.strapi.io/user-docs/latest/media-library/adding-assets.html +--- + +# Adding assets + +The Media Library displays all assets uploaded in the application, either via the [Media Library](/user-docs/latest/media-library/introduction-to-media-library.md) or the [Content Manager](/user-docs/latest/content-manager/writing-content.md#filling-up-fields) when managing a media field. + +![🏞 screenshot - "Add new assets" window](../assets/media-library/media-library_add-new-assets.png) + +To add new assets to the media library: + +1. Click the **Add new assets** button in the upper right corner of the Media Library. +2. Choose whether you want to upload the new asset from your computer or from an URL: + - from the computer, either drag & drop the asset directly or browse files on your system, + - from an URL, type or copy and paste an URL(s) in the _URL_ field, making sure multiple URLs are separated by carriage returns, then click **Next**. +3. (optional) Click the edit button ![Edit icon](../assets/icons/edit.svg) to view asset metadata and define a _File name_, _Alternative text_ and a _Caption_ for the asset (see [editing and deleting assets](managing-assets.md)). +4. (optional) Add more assets by clicking **Add new assets** and going back to step 2. +5. Click on **Upload assets to the library**. + +A variety of media types and extensions are supported by the Media Library: + +| Media type | Supported extensions | +| ---------- | --------------------------------------------------------------- | +| Image | - JPEG
- PNG
- GIF
- SVG
- TIFF
- ICO
- DVU | +| Video | - MPEG
- MP4
- MOV (Quicktime)
- WMV
- AVI
- FLV | +| Audio | - MP3
- WAV
- OGG | +| File | - CSV
- ZIP
- PDF
- XLS, XLSX
- JSON | + diff --git a/docs/user-docs/latest/media-library/introduction-to-media-library.md b/docs/user-docs/latest/media-library/introduction-to-media-library.md new file mode 100644 index 0000000000..3a9a22b303 --- /dev/null +++ b/docs/user-docs/latest/media-library/introduction-to-media-library.md @@ -0,0 +1,50 @@ +--- +title: Introduction to the Media Library - Strapi User Guide +description: Introduction to the Media Library which allows to display and manage all assets uploaded in the application. +canonicalUrl: https://docs.strapi.io/user-docs/latest/media-library/introduction-to-media-library.html +--- + +# Introduction to the Media Library + +The Media Library is a Strapi plugin that is always activated by default and cannot be deactivated. It is accessible both when the application is in a development and production environment. + +Administrators can access the Media Library from ![ML icon](../assets/icons/media_library.svg) _Media Library_ in the main navigation of the admin panel. + +![Media Library overview, annotated](../assets/media-library/media-library_overview.png) + +The Media Library displays all assets uploaded in the application, either via the Media Library itself or via the Content Manager when managing a media field. Assets uploaded to the Media Library can be inserted into content-types using the [Content Manager](/user-docs/latest/content-manager/writing-content.md#filling-up-fields). + +From the Media Library, it is possible to: + +- upload a new asset (see [adding assets](/user-docs/latest/media-library/adding-assets.md)) or create a new folder (see [organizing assets with folders](/user-docs/latest/media-library/organizing-assets-with-folders.md)) (1), +- sort the assets and folders or set filters (2) to find assets and folders more easily, +- make a textual search (3) to find a specific asset or folder, +- and view, navigate through, and manage folders (4). + +::: tip +Click the search icon ![Search icon](../assets/icons/search.svg) on the right side of the user interface to use a text search and find one of your assets or folders more quickly! +::: + +## Filtering assets + +Right above the list of folders and assets, on the left side of the interface, a **Filters** button is displayed. It allows setting one or more condition-based filters, which add to one another (i.e. if you set several conditions, only the assets that match all the conditions will be displayed). + +![Filters in the Media Library](../assets/media-library/media-library_filters.png) + +To set a new filter: + +1. Click on the **Filters** button. +2. Click on the 1st drop-down list to choose the field on which the condition will be applied. +3. Click on the 2nd drop-down list to choose the type of condition to apply. +4. For conditions based on the type of asset to filter, click on the 3rd drop-down list and choose a file type to include or exclude. For conditions based on date and time (i.e. _createdAt_ or _updatedAt_ fields), click on the left field to select a date and click on the right field to select a time. +5. Click on the **Add filter** button. + +::: note +When active, filters are displayed next to the **Filters** button. They can be removed by clicking on the delete icon ![Clear icon](../assets/icons/clear.svg). +::: + +## Sorting assets + +![Sort](../assets/media-library/media-library_sort.png) + +Just above the list of folders and assets, on the left side of the interface, a **Sort by** button is diplayed. It allows to display assets by upload date, alphabetical order or date of update. Click on the button and select an option in the list to automatically display the sorted assets. diff --git a/docs/user-docs/latest/media-library/managing-assets.md b/docs/user-docs/latest/media-library/managing-assets.md new file mode 100644 index 0000000000..9252493dc6 --- /dev/null +++ b/docs/user-docs/latest/media-library/managing-assets.md @@ -0,0 +1,64 @@ +--- +title: Managing assets with the Media Library - Strapi User Guide +description: Instructions on how to manage assets uploaded to the Media Library, including editing, moving, and deleting assets, and cropping images. +canonicalUrl: http://docs.strapi.io/user-docs/latest/media-library/managing-assets.html +--- + +# Managing individual assets + +The Media Library allows managing assets, which includes modifying assets' file details and location, downloading and copying the link of the assets file, and deleting assets. Image files can also be cropped. To manage an asset, click on its Edit ![Edit icon](../assets/icons/edit.svg) button. + +## Editing assets + +Clicking on the edit ![Edit icon](../assets/icons/edit.svg) button of an asset opens up a "Details" window, with all the available options. + +![Annotated asset details window screenshot](../assets/media-library/media-library_asset-details.png) + +- On the left, above the preview of the asset, control buttons (1) allow performing various actions: + - click on the delete button ![Delete icon](../assets/icons/delete.svg) to delete the asset, + - click on the download button ![Download icon](../assets/icons/download.svg) to download the asset, + - click on the copy link button ![Copy link icon](../assets/icons/link.svg) to copy the asset's link to the clipboard, + - optionally, click on the crop button ![Copy link icon](../assets/icons/crop.svg) to enter cropping mode for the image (see [cropping images](#cropping-images)). +- On the right, meta data for the asset is displayed at the top of the window (2) and the fields below can be used to update the _File name_, _Alternative text_, _Caption_ and _Asset location_ (see [organizing assets with folders](/user-docs/latest/media-library/organizing-assets-with-folders.md)) for the asset (3). +- At the bottom, the **Replace Media** button (4) can be used to replace the asset file but keep the existing content of the other editable fields, and the **Finish** button is used to confirm any updates to the fields. + +## Moving assets + +An individual asset can be moved to a folder when editing its details. + +To move an asset: + +1. Click on the edit ![Edit icon](../assets/icons/edit.svg) button for the asset to be moved. +2. In the window that pops up, click the _Asset location_ field and choose a different folder from the drop-down list. +3. Click **Save** to confirm. + +::: note +Assets can also be moved to other folders from the main view of the Media Library (see [organizing assets with folders](/user-docs/latest/media-library/organizing-assets-with-folders.md#moving-assets-to-a-folder)). This includes the ability to move several assets simultaneously. +::: + +## Cropping images + +Images can be cropped when editing the asset's details. + +To crop an image: + +1. Click on the edit ![Edit icon](../assets/icons/edit.svg) button for the asset to be cropped. +2. In the window that pops up, click the crop button ![Crop icon](../assets/icons/crop.svg) to enter cropping mode. +3. Crop the image using handles in the corners to resize the frame. The frame can also be moved by drag & drop. +4. Click the crop ![Done icon](../assets/icons/check_icon.svg) button to validate the new dimensions, and choose either to **crop the original asset** or to **duplicate & crop the asset** (i.e. to create a copy with the new dimensions while keeping the original asset untouched). Alternatively, click the stop cropping ![Cancel icon](../assets/icons/close-icon.svg) button to cancel and quit cropping mode. + +5. Click **Finish** to save changes to the file. + +## Deleting assets + +An individual asset can be deleted when editing its details. + +To delete an asset: + +1. Click on the edit ![Edit icon](../assets/icons/edit.svg) button for the asset to be deleted. +2. In the window that pops up, click the delete button ![Delete icon](../assets/icons/delete.svg) in the control buttons bar above the asset's preview. +3. Click **Confirm**. + +::: tip +Assets can also be deleted individually or in bulk from the main view of the Media Library. Select assets by clicking on their checkbox in the top left corner, then click the Delete icon ![Delete icon](../assets/icons/delete.svg) at the top of the window, below the filters and sorting options. +::: diff --git a/docs/user-docs/latest/media-library/organizing-assets-with-folders.md b/docs/user-docs/latest/media-library/organizing-assets-with-folders.md new file mode 100644 index 0000000000..5e4377c952 --- /dev/null +++ b/docs/user-docs/latest/media-library/organizing-assets-with-folders.md @@ -0,0 +1,81 @@ +--- +title: Adding assets to the Media Library - Strapi User Guide +description: Instructions on how to use folders in the Media Library, including adding, editing, and deleting folders, and browsing their content. +canonicalUrl: http://docs.strapi.io/user-docs/latest/media-library/organizing-assets-with-folders.html +--- + +# Organizing assets with folders + +Folders in the Media Library help you organize uploaded assets. Folders sit at the top of the Media Library view or are accessible from the Media field popup when using the [Content Manager](/user-docs/latest/content-manager/introduction-to-content-manager.md). + +From the Media Library, it is possible to view the list of folders and browse a folder's content, create new folders, edit an existing folder, move assets to a folder, and delete a folder. + +::: caution +Downgrading from Strapi v4.3.0-beta to v4.2.0 will delete folders of the Media Library. Even though assets should be preserved, we recommend you test beta versions of Strapi on a new project rather than on an existing one, as unexpected consequences could impact your database. +::: + +## Browsing folders + +By default, the Media Library displays folders and assets created at the root level. Clicking a folder navigates to this folder, and displays the following elements: + +- the folder title and the number of subfolders and assets the current folder contains (1) +- the subfolders (2) the current folder contains +- all assets (3) from this folder + +![🏞 screenshot - Media library one folder deep, with back button and updated folder title](../assets/media-library/media-library_folder-content.png) + +From this dedicated folder view, folders and assets can be managed, filtered, sorted and searched just like from the main Media Library (see [introduction to Media Library](/user-docs/latest/media-library/introduction-to-media-library.md)). + +To navigate back to the parent folder, one level up, use the **Back** button at the top of the interface. + +## Adding folders + +To create a new folder in the Media Library: + +1. Click on **Add new folder** in the upper right of the Media Library interface. +2. In the window that pops up, type a name for the new folder in the _Name_ field. +3. (optional) In the _Location_ drop-down list, choose a location for the new folder. The default location is the active folder. +4. Click **Create**. + +::: note +There is no limit to how deep your folders hierarchy can go, but bear in mind it might take some effort to reach a deeply nested subfolder, as the Media Library currently has no visual hierarchy indication. Searching for files using the ![Search icon](../assets/icons/search.svg) on the right side of the user interface might be a faster alternative to finding the asset you are looking for. +::: + +## Moving assets to a folder + +Assets and folders can be moved to another folder from the root view of the Media Library or from any view for a dedicated folder. + +![🏞 screenshot - "Move elements to" popup](../assets/media-library/media-library_move-assets.png) + +To bulk move assets and folders to another folder: + +1. Select assets and folder to be moved, by clicking the checkbox on the left of the folder name or clicking the asset itself. +2. Click the ![Move icon](../assets/icons/move.svg) **Move** button at the top of the interface. +3. In the _Move elements to_ pop-up window, select the new folder from the _Location_ drop-down list. +4. Click **Move**. + +::: note +An individual asset can also be moved to a folder when [editing the asset](/user-docs/latest/media-library/managing-assets.md). +::: + +## Editing folders + +Once created, a folder can be renamed, moved or deleted. To manage a single folder: + +1. In the Folders part of the Media library, hover the folder to be edited and click its edit button ![Edit icon](../assets/icons/edit.svg). +2. In the window that pops up, update the name and location with the _Name_ field and _Location_ drop-down list, respectively. +3. Click **Save**. + +## Deleting folders + +Deleting a folder can be done either from the list of folders of the Media Library, or when editing a single folder. + +To delete a folder, from the Media Library: + +1. Click the checkbox on the left of the folder name. Multiple folders can be selected. +2. Click the ![Delete icon](../assets/icons/delete.svg) **Delete** button above the Folders list. +3. In the _Confirmation_ dialog, click **Confirm**. + +::: note +A single folder can also be deleted when editing it: hover the folder, click on its edit icon ![Edit icon](../assets/icons/edit.svg), and in the window that pops up, click the **Delete folder** button and confirm the deletion. +::: diff --git a/docs/user-docs/latest/settings/managing-global-settings.md b/docs/user-docs/latest/settings/managing-global-settings.md index 4a7fb1b950..20462a807b 100644 --- a/docs/user-docs/latest/settings/managing-global-settings.md +++ b/docs/user-docs/latest/settings/managing-global-settings.md @@ -79,6 +79,25 @@ To add a new locale: 4. (optional) In the Advanced settings tab, tick the *Set as default locale* setting to make your new locale the default one for your Strapi application. 5. Click on the **Save** button to confirm the addition of your new locale. +## Configuring the Media Library + +The [Media Library](/user-docs/latest/media-library/introduction-to-media-library.md) displays all assets uploaded in the Strapi application. The Media Library settings allow controlling the format, file size, and orientation of uploaded assets. + +![Media Library settings](../assets/settings/settings_media-library.png) + +To configure the Media Library settings: + +1. Go to the *Global settings > Media Library* sub-section of the settings interface. +2. Define your chosen new settings: + + | Setting name | Instructions | Default value | + | -------------------------- | ---------------------------------------------------------------------------------------------------- |---------------| + | Responsive friendly upload | Enabling this option will generate multiple formats (small, medium and large) of the uploaded asset. | True | + | Size optimization | Enabling this option will reduce the image size and slightly reduce its quality. | True | + | Auto orientation | Enabling this option will automatically rotate the image according to EXIF orientation tag. | False | + +3. Click on the **Save** button. + ## Managing API tokens API tokens allow users to authenticate their Content API queries (see [Developer Documentation](/developer-docs/latest/setup-deployment-guides/configurations/optional/api-tokens.md)). Administrators can manage API tokens through the *Global settings > API Tokens* sub-section of the settings interface.