# Cloud email provider Source: https://docs.strapi.io/cloud/advanced/email ## Configure the Provider Description: In your Strapi project, create a /config/env/production/plugins.js or /config/env/production/plugins.ts file with the following content: (Source: https://docs.strapi.io/cloud/advanced/email#configure-the-provider) Language: JavaScript File path: /config/env/production/plugins.js ```js module.exports = ({ env }) => ({ // … some unrelated plugins configuration options // highlight-start email: { config: { // … provider-specific upload configuration options go here } // highlight-end // … some other unrelated plugins configuration options } }); ``` --- Language: TypeScript File path: /config/env/production/plugins.ts ```ts export default ({ env }) => ({ // … some unrelated plugins configuration options // highlight-start email: { config: { // … provider-specific upload configuration options go here } // highlight-end // … some other unrelated plugins configuration options } }); ``` Language: JavaScript File path: /config/env/production/plugins.js ```js module.exports = ({ env }) => ({ // ... email: { config: { provider: 'sendgrid', providerOptions: { apiKey: env('SENDGRID_API_KEY'), }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` --- Language: JavaScript File path: /config/env/production/plugins.js ```js module.exports = ({ env }) => ({ // ... email: { config: { provider: 'amazon-ses', providerOptions: { key: env('AWS_SES_KEY'), secret: env('AWS_SES_SECRET'), amazon: 'https://email.us-east-1.amazonaws.com', }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` --- Language: JavaScript File path: /config/env/production/plugins.js ```js module.exports = ({ env }) => ({ // ... email: { config: { provider: 'mailgun', providerOptions: { key: env('MAILGUN_API_KEY'), // Required domain: env('MAILGUN_DOMAIN'), // Required url: env('MAILGUN_URL', 'https://api.mailgun.net'), //Optional. If domain region is Europe use 'https://api.eu.mailgun.net' }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` Language: TypeScript File path: /config/env/production/plugins.ts ```ts export default ({ env }) => ({ // ... email: { config: { provider: 'sendgrid', providerOptions: { apiKey: env('SENDGRID_API_KEY'), }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` --- Language: TypeScript File path: /config/env/production/plugins.ts ```ts export default ({ env }) => ({ // ... email: { config: { provider: 'amazon-ses', providerOptions: { key: env('AWS_SES_KEY'), secret: env('AWS_SES_SECRET'), amazon: 'https://email.us-east-1.amazonaws.com', }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` --- Language: TypeScript File path: /config/env/production/plugins.ts ```ts export default ({ env }) => ({ // ... email: { config: { provider: 'mailgun', providerOptions: { key: env('MAILGUN_API_KEY'), // Required domain: env('MAILGUN_DOMAIN'), // Required url: env('MAILGUN_URL', 'https://api.mailgun.net'), //Optional. If domain region is Europe use 'https://api.eu.mailgun.net' }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` # Middleware Configuration for Strapi Cloud Source: https://docs.strapi.io/cloud/advanced/middlewares ## Middleware Configuration for Strapi Cloud Description: To apply custom middleware configuration on Strapi Cloud, place your changes in: (Source: https://docs.strapi.io/cloud/advanced/middlewares#middleware-configuration-for-strapi-cloud) Language: JavaScript File path: N/A ``` config/env/production/middlewares.js ``` --- Language: JavaScript File path: N/A ``` config/env/production/middlewares.ts ``` ## Custom Content Security Policy (CSP) Description: Create or update config/env/production/middlewares: (Source: https://docs.strapi.io/cloud/advanced/middlewares#custom-content-security-policy-csp) Language: JavaScript File path: config/env/production/middlewares.js ```js module.exports = [ 'strapi::errors', { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'your-custom-domain.com', // replace with your provider domain ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'your-custom-domain.com', // replace with your provider domain ], upgradeInsecureRequests: null, }, }, }, }, 'strapi::cors', 'strapi::poweredBy', 'strapi::logger', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` --- Language: TypeScript File path: config/env/production/middlewares.ts ```ts export default [ 'strapi::errors', { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'your-custom-domain.com', // replace with your provider domain ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'your-custom-domain.com', // replace with your provider domain ], upgradeInsecureRequests: null, }, }, }, }, 'strapi::cors', 'strapi::poweredBy', 'strapi::logger', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` ## Custom CORS headers Description: If your frontend sends custom request headers (e.g. (Source: https://docs.strapi.io/cloud/advanced/middlewares#custom-cors-headers) Language: JavaScript File path: config/env/production/middlewares.js ```js module.exports = ({ env }) => [ 'strapi::errors', 'strapi::security', { name: 'strapi::cors', config: { enabled: true, origin: [env('CLIENT_URL')], headers: [ 'Content-Type', 'Authorization', 'Origin', 'Accept', 'X-Requested-With', 'your-custom-header', // add any custom headers your frontend sends ], }, }, 'strapi::poweredBy', 'strapi::logger', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` --- Language: TypeScript File path: config/env/production/middlewares.ts ```ts export default ({ env }) => [ 'strapi::errors', 'strapi::security', { name: 'strapi::cors', config: { enabled: true, origin: [env('CLIENT_URL')], headers: [ 'Content-Type', 'Authorization', 'Origin', 'Accept', 'X-Requested-With', 'your-custom-header', // add any custom headers your frontend sends ], }, }, 'strapi::poweredBy', 'strapi::logger', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` # Upload Provider Configuration for Strapi Cloud Source: https://docs.strapi.io/cloud/advanced/upload ## Configure the provider Description: To configure a third-party upload provider in your Strapi project, create or edit the plugins configuration file for your production environment /config/env/production/plugins.js|ts by adding upload configuration options as follows: (Source: https://docs.strapi.io/cloud/advanced/upload#configure-the-provider) Language: JavaScript File path: /config/env/production/plugins.js ```js module.exports = ({ env }) => ({ // … some unrelated plugins configuration options // highlight-start upload: { config: { // … provider-specific upload configuration options go here } // highlight-end // … some other unrelated plugins configuration options } }); ``` --- Language: TypeScript File path: /config/env/production/plugins.ts ```ts export default ({ env }) => ({ // … some unrelated plugins configuration options // highlight-start upload: { config: { // … provider-specific upload configuration options go here } // highlight-end // … some other unrelated plugins configuration options } }); ``` Language: JavaScript File path: /config/env/production/plugins.js ```js module.exports = ({ env }) => ({ // ... upload: { config: { provider: 'cloudinary', providerOptions: { cloud_name: env('CLOUDINARY_NAME'), api_key: env('CLOUDINARY_KEY'), api_secret: env('CLOUDINARY_SECRET'), }, actionOptions: { upload: {}, uploadStream: {}, delete: {}, }, }, }, // ... }); ``` --- Language: JavaScript File path: /config/env/production/plugins.js ```js module.exports = ({ env }) => ({ // ... upload: { config: { provider: 'aws-s3', providerOptions: { baseUrl: env('CDN_URL'), rootPath: env('CDN_ROOT_PATH'), s3Options: { credentials: { accessKeyId: env('AWS_ACCESS_KEY_ID'), secretAccessKey: env('AWS_ACCESS_SECRET'), }, region: env('AWS_REGION'), params: { ACL: env('AWS_ACL', 'public-read'), signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60), Bucket: env('AWS_BUCKET'), }, }, }, actionOptions: { upload: {}, uploadStream: {}, delete: {}, }, }, }, // ... }); ``` Language: TypeScript File path: /config/env/production/plugins.ts ```ts export default ({ env }) => ({ // ... upload: { config: { provider: 'cloudinary', providerOptions: { cloud_name: env('CLOUDINARY_NAME'), api_key: env('CLOUDINARY_KEY'), api_secret: env('CLOUDINARY_SECRET'), }, actionOptions: { upload: {}, uploadStream: {}, delete: {}, }, }, }, // ... }); ``` --- Language: TypeScript File path: /config/env/production/plugins.ts ```ts export default ({ env }) => ({ // ... upload: { config: { provider: 'aws-s3', providerOptions: { baseUrl: env('CDN_URL'), rootPath: env('CDN_ROOT_PATH'), s3Options: { credentials: { accessKeyId: env('AWS_ACCESS_KEY_ID'), secretAccessKey: env('AWS_ACCESS_SECRET'), }, region: env('AWS_REGION'), params: { ACL: env('AWS_ACL', 'public-read'), signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60), Bucket: env('AWS_BUCKET'), }, }, }, actionOptions: { upload: {}, uploadStream: {}, delete: {}, }, }, }, // ... }); ``` ## Configure the security middleware Description: Example: (Source: https://docs.strapi.io/cloud/advanced/upload#configure-the-security-middleware) Language: JavaScript File path: /config/env/production/middlewares.js ```js module.exports = [ // ... { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'res.cloudinary.com' ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'res.cloudinary.com', ], upgradeInsecureRequests: null, }, }, }, }, // ... ]; ``` --- Language: JavaScript File path: /config/env/production/middlewares.js ```js module.exports = [ // ... { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'yourBucketName.s3.yourRegion.amazonaws.com', ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'yourBucketName.s3.yourRegion.amazonaws.com', ], upgradeInsecureRequests: null, }, }, }, }, // ... ]; ``` Language: TypeScript File path: /config/env/production/middlewares.ts ```ts export default [ // ... { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'res.cloudinary.com' ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'res.cloudinary.com', ], upgradeInsecureRequests: null, }, }, }, }, // ... ]; ``` --- Language: TypeScript File path: /config/env/production/middlewares.ts ```ts export default [ // ... { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'yourBucketName.s3.yourRegion.amazonaws.com', ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'yourBucketName.s3.yourRegion.amazonaws.com', ], upgradeInsecureRequests: null, }, }, }, }, // ... ]; ``` # Cloud Cli Source: https://docs.strapi.io/cloud/cli/cloud-cli ## strapi login Description: Log in Strapi Cloud. (Source: https://docs.strapi.io/cloud/cli/cloud-cli#strapi-login) Language: Bash File path: N/A ```bash strapi login ``` ## strapi deploy Description: Deploy a new local project (< 100MB) in Strapi Cloud. (Source: https://docs.strapi.io/cloud/cli/cloud-cli#strapi-deploy) Language: Bash File path: N/A ```bash strapi deploy ``` ## strapi link Description: Links project in the current folder to an existing project in Strapi Cloud. (Source: https://docs.strapi.io/cloud/cli/cloud-cli#strapi-link) Language: Bash File path: N/A ```bash strapi link ``` ## strapi projects Description: Lists all Strapi Cloud projects associated with your account. (Source: https://docs.strapi.io/cloud/cli/cloud-cli#strapi-projects) Language: Bash File path: N/A ```bash strapi projects ``` ## strapi logout Description: Log out of Strapi Cloud. (Source: https://docs.strapi.io/cloud/cli/cloud-cli#strapi-logout) Language: Bash File path: N/A ```bash strapi logout ``` # Caching Source: https://docs.strapi.io/cloud/getting-started/caching ## Cache-Control Header in Strapi Cloud Description: Responses from dynamic apps served by Strapi Cloud are not cached by default. (Source: https://docs.strapi.io/cloud/getting-started/caching#cache-control-header-in-strapi-cloud) Language: Fish File path: N/A ```fish function myHandler(req, res) { // Set the Cache-Control header to cache responses for 1 day res.setHeader('Cache-Control', 'max-age=86400'); // Add your logic to generate the response here } ``` --- Language: JavaScript File path: N/A ```js import { Request, Response } from 'express'; function myHandler(req: Request, res: Response) { // Set the Cache-Control header to cache responses for 1 day res.setHeader('Cache-Control', 'max-age=86400'); // Add your logic to generate the response here } ``` # Strapi Cloud - CLI deployment Source: https://docs.strapi.io/cloud/getting-started/deployment-cli ## Logging in to Strapi Cloud Description: Enter the following command to log into Strapi Cloud: (Source: https://docs.strapi.io/cloud/getting-started/deployment-cli#logging-in-to-strapi-cloud) Language: Bash File path: N/A ```bash yarn strapi login ``` --- Language: Bash File path: N/A ```bash npx run strapi login ``` ## Deploying your project Description: From your terminal, still from the folder of your Strapi project, enter the following command to deploy the project: (Source: https://docs.strapi.io/cloud/getting-started/deployment-cli#deploying-your-project) Language: Bash File path: N/A ```bash yarn strapi deploy ``` --- Language: Bash File path: N/A ```bash npx run strapi deploy ``` # Admin panel customization Source: https://docs.strapi.io/cms/admin-panel-customization ## General considerations Description: Before deployment, the admin panel needs to be built, by running the following command from the project's root directory: (Source: https://docs.strapi.io/cms/admin-panel-customization#general-considerations) Language: Bash File path: /src/admin/app.js ```bash yarn build ``` --- Language: Bash File path: /src/admin/app.js ```bash npm run build ``` ## Basic example Description: The following is an example of a basic customization of the admin panel: (Source: https://docs.strapi.io/cms/admin-panel-customization#basic-example) Language: JavaScript File path: /src/admin/app.js ```js import AuthLogo from "./extensions/my-logo.png"; import MenuLogo from "./extensions/logo.png"; import favicon from "./extensions/favicon.png"; 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: { // overwrite light theme properties light: { colors: { primary100: "#f6ecfc", primary200: "#e0c1f4", primary500: "#ac73e6", primary600: "#9736e8", primary700: "#8312d1", danger700: "#b72b1a", }, }, // overwrite dark theme properties dark: { // ... }, }, // Extend the translations translations: { fr: { "Auth.form.email.label": "test", Users: "Utilisateurs", City: "CITY (FRENCH)", // Customize the label of the Content Manager table. Id: "ID french", }, }, // Disable video tutorials tutorials: false, // Disable notifications about new Strapi releases notifications: { releases: false }, }, bootstrap() {}, }; ``` --- Language: JSON File path: /src/admin/tsconfig.json ```json { "compilerOptions": { "types": ["vite/client"] } } ``` --- Language: TypeScript File path: /src/admin/app.ts ```ts import AuthLogo from "./extensions/my-logo.png"; import MenuLogo from "./extensions/logo.png"; import favicon from "./extensions/favicon.png"; 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: { dark:{ colors: { alternative100: '#f6ecfc', alternative200: '#e0c1f4', alternative500: '#ac73e6', alternative600: '#9736e8', alternative700: '#8312d1', buttonNeutral0: '#ffffff', buttonPrimary500: '#7b79ff', // you can see other colors in the link below }, }, light:{ // you can see the light color here just like dark colors https://github.com/strapi/design-system/blob/main/packages/design-system/src/themes/lightTheme/light-colors.ts }, }, }, // Extend the translations // you can see the traslations keys here https://github.com/strapi/strapi/blob/develop/packages/core/admin/admin/src/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: { releases: false }, }, bootstrap() {}, }; ``` # Admin panel bundlers Source: https://docs.strapi.io/cms/admin-panel-customization/bundlers ## Vite Description: To extend the usage of Vite, define a function that extends its configuration inside /src/admin/vite.config: (Source: https://docs.strapi.io/cms/admin-panel-customization/bundlers#vite) Language: JavaScript File path: /src/admin/vite.config.js ```js const { mergeConfig } = require("vite"); module.exports = (config) => { // Important: always return the modified config return mergeConfig(config, { resolve: { alias: { "@": "/src", }, }, }); }; ``` --- Language: TypeScript File path: /src/admin/vite.config.ts ```ts import { mergeConfig } from "vite"; export default (config) => { // Important: always return the modified config return mergeConfig(config, { resolve: { alias: { "@": "/src", }, }, }); }; ``` ## Webpack Description: In Strapi 5, the default bundler is Vite. (Source: https://docs.strapi.io/cms/admin-panel-customization/bundlers#webpack) Language: JavaScript File path: /src/admin/webpack.config.js ```js module.exports = (config, webpack) => { // Note: we provide webpack above so you should not `require` it // Perform customizations to webpack config config.plugins.push(new webpack.IgnorePlugin(/\/__tests__\//)); // Important: return the modified config return config; }; ``` --- Language: TypeScript File path: /src/admin/webpack.config.ts ```ts export default (config, webpack) => { // Note: we provide webpack above so you should not `require` it // Perform customizations to webpack config config.plugins.push(new webpack.IgnorePlugin(/\/__tests__\//)); // Important: return the modified config return config; }; ``` Language: Bash File path: /webpack.js ```bash strapi develop --bundler=webpack ``` # Favicon Source: https://docs.strapi.io/cms/admin-panel-customization/favicon ## Favicon Description: Replace the favicon.png file at the root of a Strapi project Edit the strapi::favicon middleware configuration with the following code: (Source: https://docs.strapi.io/cms/admin-panel-customization/favicon#favicon) Language: JavaScript File path: /config/middlewares.js ```js // … { name: 'strapi::favicon', config: { path: 'my-custom-favicon.png', }, }, // … ``` # Homepage customization Source: https://docs.strapi.io/cms/admin-panel-customization/homepage ## Registering custom widgets Description: :::info The examples on the present page will cover registering a widget through a plugin. (Source: https://docs.strapi.io/cms/admin-panel-customization/homepage#registering-custom-widgets) Language: JavaScript File path: src/plugins/my-plugin/admin/src/index.js ```js import pluginId from './pluginId'; import MyWidgetIcon from './components/MyWidgetIcon'; export default { register(app) { // Register the plugin itself app.registerPlugin({ id: pluginId, name: 'My Plugin', }); // Register a widget for the Homepage app.widgets.register({ icon: MyWidgetIcon, title: { id: `${pluginId}.widget.title`, defaultMessage: 'My Widget', }, component: async () => { const component = await import('./components/MyWidget'); return component.default; }, /** * Use this instead if you used a named export for your component */ // component: async () => { // const { Component } = await import('./components/MyWidget'); // return Component; // }, id: 'my-custom-widget', pluginId: pluginId, }); }, bootstrap() {}, // ... }; ``` --- Language: TypeScript File path: src/plugins/my-plugin/admin/src/index.ts ```ts import pluginId from './pluginId'; import MyWidgetIcon from './components/MyWidgetIcon'; import type { StrapiApp } from '@strapi/admin/strapi-admin'; export default { register(app: StrapiApp) { // Register the plugin itself app.registerPlugin({ id: pluginId, name: 'My Plugin', }); // Register a widget for the Homepage app.widgets.register({ icon: MyWidgetIcon, title: { id: `${pluginId}.widget.title`, defaultMessage: 'My Widget', }, component: async () => { const component = await import('./components/MyWidget'); return component.default; }, /** * Use this instead if you used a named export for your component */ // component: async () => { // const { Component } = await import('./components/MyWidget'); // return Component; // }, id: 'my-custom-widget', pluginId: pluginId, }); }, bootstrap() {}, // ... }; ``` Language: JavaScript File path: N/A ```js if ('widgets' in app) { // proceed with the registration } ``` ## Creating a widget component Description: Here's how to implement a basic widget component: (Source: https://docs.strapi.io/cms/admin-panel-customization/homepage#creating-a-widget-component) Language: JavaScript File path: src/plugins/my-plugin/admin/src/components/MyWidget/index.js ```js import React, { useState, useEffect } from 'react'; import { Widget } from '@strapi/admin/strapi-admin'; const MyWidget = () => { const [loading, setLoading] = useState(true); const [data, setData] = useState(null); const [error, setError] = useState(null); useEffect(() => { // Fetch your data here const fetchData = async () => { try { // Replace with your actual API call const response = await fetch('/my-plugin/data'); const result = await response.json(); setData(result); setLoading(false); } catch (err) { setError(err); setLoading(false); } }; fetchData(); }, []); if (loading) { return ; } if (error) { return ; } if (!data || data.length === 0) { return ; } return (
{/* Your widget content here */}
    {data.map((item) => (
  • {item.name}
  • ))}
); }; export default MyWidget; ``` --- Language: TypeScript File path: src/plugins/my-plugin/admin/src/components/MyWidget/index.tsx ```ts import React, { useState, useEffect } from 'react'; import { Widget } from '@strapi/admin/strapi-admin'; interface DataItem { id: number; name: string; } const MyWidget: React.FC = () => { const [loading, setLoading] = useState(true); const [data, setData] = useState(null); const [error, setError] = useState(null); useEffect(() => { // Fetch your data here const fetchData = async () => { try { // Replace with your actual API call const response = await fetch('/my-plugin/data'); const result = await response.json(); setData(result); setLoading(false); } catch (err) { setError(err instanceof Error ? err : new Error(String(err))); setLoading(false); } }; fetchData(); }, []); if (loading) { return ; } if (error) { return ; } if (!data || data.length === 0) { return ; } return (
{/* Your widget content here */}
    {data.map((item) => (
  • {item.name}
  • ))}
); }; export default MyWidget; ``` ## Example: Adding a content metrics widget Description: The following file registers the plugin and the widget: (Source: https://docs.strapi.io/cms/admin-panel-customization/homepage#example-adding-a-content-metrics-widget) Language: JavaScript File path: src/plugins/content-metrics/admin/src/index.js ```js import { PLUGIN_ID } from './pluginId'; import { Initializer } from './components/Initializer'; import { PluginIcon } from './components/PluginIcon'; import { Stethoscope } from '@strapi/icons' export default { register(app) { app.addMenuLink({ to: `plugins/${PLUGIN_ID}`, icon: PluginIcon, intlLabel: { id: `${PLUGIN_ID}.plugin.name`, defaultMessage: PLUGIN_ID, }, Component: () => import('./pages/App'), }); app.registerPlugin({ id: PLUGIN_ID, initializer: Initializer, isReady: false, name: PLUGIN_ID, }); // Registers the widget app.widgets.register({ icon: Stethoscope, title: { id: `${PLUGIN_ID}.widget.metrics.title`, defaultMessage: 'Content Metrics', }, component: async () => { const component = await import('./components/MetricsWidget'); return component.default; }, id: 'content-metrics', pluginId: PLUGIN_ID, }); }, async registerTrads({ locales }) { return Promise.all( locales.map(async (locale) => { try { const { default: data } = await import(`./translations/${locale}.json`); return { data, locale }; } catch { return { data: {}, locale }; } }) ); }, bootstrap() {}, }; ``` --- Language: JavaScript File path: src/plugins/content-metrics/admin/src/components/MetricsWidget/index.js ```js import React, { useState, useEffect } from 'react'; import { Table, Tbody, Tr, Td, Typography, Box } from '@strapi/design-system'; import { Widget } from '@strapi/admin/strapi-admin' const MetricsWidget = () => { const [loading, setLoading] = useState(true); const [metrics, setMetrics] = useState(null); const [error, setError] = useState(null); useEffect(() => { const fetchMetrics = async () => { try { const response = await fetch('/api/content-metrics/count'); const data = await response.json(); console.log("data:", data); const formattedData = {}; if (data && typeof data === 'object') { Object.keys(data).forEach(key => { const value = data[key]; formattedData[key] = typeof value === 'number' ? value : String(value); }); } setMetrics(formattedData); setLoading(false); } catch (err) { console.error(err); setError(err.message || 'An error occurred'); setLoading(false); } }; fetchMetrics(); }, []); if (loading) { return ( ); } if (error) { return ( ); } if (!metrics || Object.keys(metrics).length === 0) { return No content types found; } return ( {Object.entries(metrics).map(([contentType, count], index) => ( ))}
{String(contentType)} {String(count)}
); }; export default MetricsWidget; ``` --- Language: JavaScript File path: src/plugins/content-metrics/server/src/controllers/metrics.js ```js 'use strict'; module.exports = ({ strapi }) => ({ async getContentCounts(ctx) { try { // Get all content types const contentTypes = Object.keys(strapi.contentTypes) .filter(uid => uid.startsWith('api::')) .reduce((acc, uid) => { const contentType = strapi.contentTypes[uid]; acc[contentType.info.displayName || uid] = 0; return acc; }, {}); // Count entities for each content type for (const [name, _] of Object.entries(contentTypes)) { const uid = Object.keys(strapi.contentTypes) .find(key => strapi.contentTypes[key].info.displayName === name || key === name ); if (uid) { // Using the count() method from the Document Service API const count = await strapi.documents(uid).count(); contentTypes[name] = count; } } ctx.body = contentTypes; } catch (err) { ctx.throw(500, err); } } }); ``` --- Language: JavaScript File path: src/plugins/content-metrics/server/src/routes/index.js ```js export default { 'content-api': { type: 'content-api', routes: [ { method: 'GET', path: '/count', handler: 'metrics.getContentCounts', config: { policies: [], }, }, ], }, }; ``` --- Language: TypeScript File path: src/plugins/content-metrics/admin/src/index.ts ```ts import { PLUGIN_ID } from './pluginId'; import { Initializer } from './components/Initializer'; import { PluginIcon } from './components/PluginIcon'; import { Stethoscope } from '@strapi/icons' export default { register(app) { app.addMenuLink({ to: `plugins/${PLUGIN_ID}`, icon: PluginIcon, intlLabel: { id: `${PLUGIN_ID}.plugin.name`, defaultMessage: PLUGIN_ID, }, Component: () => import('./pages/App'), }); app.registerPlugin({ id: PLUGIN_ID, initializer: Initializer, isReady: false, name: PLUGIN_ID, }); // Registers the widget app.widgets.register({ icon: Stethoscope, title: { id: `${PLUGIN_ID}.widget.metrics.title`, defaultMessage: 'Content Metrics', }, component: async () => { const component = await import('./components/MetricsWidget'); return component.default; }, id: 'content-metrics', pluginId: PLUGIN_ID, }); }, async registerTrads({ locales }) { return Promise.all( locales.map(async (locale) => { try { const { default: data } = await import(`./translations/${locale}.json`); return { data, locale }; } catch { return { data: {}, locale }; } }) ); }, bootstrap() {}, }; ``` --- Language: TypeScript File path: src/plugins/content-metrics/admin/src/components/MetricsWidget/index.ts ```ts import React, { useState, useEffect } from 'react'; import { Table, Tbody, Tr, Td, Typography, Box } from '@strapi/design-system'; import { Widget } from '@strapi/admin/strapi-admin' const MetricsWidget = () => { const [loading, setLoading] = useState(true); const [metrics, setMetrics] = useState(null); const [error, setError] = useState(null); useEffect(() => { const fetchMetrics = async () => { try { const response = await fetch('/api/content-metrics/count'); const data = await response.json(); console.log("data:", data); const formattedData = {}; if (data && typeof data === 'object') { Object.keys(data).forEach(key => { const value = data[key]; formattedData[key] = typeof value === 'number' ? value : String(value); }); } setMetrics(formattedData); setLoading(false); } catch (err) { console.error(err); setError(err.message || 'An error occurred'); setLoading(false); } }; fetchMetrics(); }, []); if (loading) { return ( ); } if (error) { return ( ); } if (!metrics || Object.keys(metrics).length === 0) { return No content types found; } return ( {Object.entries(metrics).map(([contentType, count], index) => ( ))}
{String(contentType)} {String(count)}
); }; export default MetricsWidget; ``` --- Language: JavaScript File path: src/plugins/content-metrics/server/src/controllers/metrics.js ```js 'use strict'; module.exports = ({ strapi }) => ({ async getContentCounts(ctx) { try { // Get all content types const contentTypes = Object.keys(strapi.contentTypes) .filter(uid => uid.startsWith('api::')) .reduce((acc, uid) => { const contentType = strapi.contentTypes[uid]; acc[contentType.info.displayName || uid] = 0; return acc; }, {}); // Count entities for each content type using Document Service for (const [name, _] of Object.entries(contentTypes)) { const uid = Object.keys(strapi.contentTypes) .find(key => strapi.contentTypes[key].info.displayName === name || key === name ); if (uid) { // Using the count() method from Document Service instead of strapi.db.query const count = await strapi.documents(uid).count(); contentTypes[name] = count; } } ctx.body = contentTypes; } catch (err) { ctx.throw(500, err); } } }); ``` --- Language: JavaScript File path: src/plugins/content-metrics/server/src/routes/index.js ```js export default { 'content-api': { type: 'content-api', routes: [ { method: 'GET', path: '/count', handler: 'metrics.getContentCounts', config: { policies: [], }, }, ], }, }; ``` # Admin panel customization - URL, host, and path configuration Source: https://docs.strapi.io/cms/admin-panel-customization/host-port-path ## Update the admin panel's path only Description: To make the admin panel accessible at another path, for instance at http://localhost:1337/dashboard, define or update the url property in the admin panel configuration file as follows: (Source: https://docs.strapi.io/cms/admin-panel-customization/host-port-path#update-the-admin-panel-s-path-only) Language: JavaScript File path: /config/server.js ```js module.exports = ({ env }) => ({ host: env("HOST", "0.0.0.0"), port: env.int("PORT", 1337), }); ``` --- Language: TypeScript File path: /config/server.ts ```ts export default ({ env }) => ({ host: env("HOST", "0.0.0.0"), port: env.int("PORT", 1337), }); ``` Language: JavaScript File path: /config/admin.js ```js module.exports = ({ env }) => ({ // … other configuration properties url: "/dashboard", }); ``` ## Update the admin panel's host and port Description: This is done in the admin panel configuration file, for example to host the admin panel on my-host.com:3000 properties should be updated follows: (Source: https://docs.strapi.io/cms/admin-panel-customization/host-port-path#update-the-admin-panel-s-host-and-port) Language: JavaScript File path: ./config/admin.js ```js module.exports = ({ env }) => ({ host: "my-host.com", port: 3000, // Additionally you can define another path instead of the default /admin one 👇 // url: '/dashboard' }); ``` --- Language: TypeScript File path: ./config/admin.ts ```ts export default ({ env }) => ({ host: "my-host.com", port: 3000, // Additionally you can define another path instead of the default /admin one 👇 // url: '/dashboard' }); ``` # Locales & translations Source: https://docs.strapi.io/cms/admin-panel-customization/locales-translations ## Defining locales Description: To update the list of available locales in the admin panel, set the config.locales array in src/admin/app file: (Source: https://docs.strapi.io/cms/admin-panel-customization/locales-translations#defining-locales) Language: JavaScript File path: /src/admin/app.js ```js export default { config: { locales: ["ru", "zh"], }, bootstrap() {}, }; ``` --- Language: TypeScript File path: /src/admin/app.ts ```ts export default { config: { locales: ["ru", "zh"], }, bootstrap() {}, }; ``` ## Extending translations Description: These keys can be extended through the config.translations key in src/admin/app file: (Source: https://docs.strapi.io/cms/admin-panel-customization/locales-translations#extending-translations) Language: JavaScript File path: /src/admin/app.js ```js 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() {}, }; ``` --- Language: TypeScript File path: /src/admin/app.ts ```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() {}, }; ``` Language: JavaScript File path: /src/admin/app.js ```js export default { config: { locales: ["fr"], translations: { fr: { "Auth.form.email.label": "test", // Translate a plugin's key/value pair by adding the plugin's name as a prefix // In this case, we translate the "plugin.name" key of plugin "content-type-builder" "content-type-builder.plugin.name": "Constructeur de Type-Contenu", }, }, }, bootstrap() {}, }; ``` --- Language: TypeScript File path: /src/admin/app.ts ```ts export default { config: { locales: ["fr"], translations: { fr: { "Auth.form.email.label": "test", // Translate a plugin's key/value pair by adding the plugin's name as a prefix // In this case, we translate the "plugin.name" key of plugin "content-type-builder" "content-type-builder.plugin.name": "Constructeur de Type-Contenu", }, }, }, bootstrap() {}, }; ``` # Logos Source: https://docs.strapi.io/cms/admin-panel-customization/logos ## Updating logos Description: To update the logos, put image files in the /src/admin/extensions folder, import these files in src/admin/app and update the corresponding keys as in the following example: (Source: https://docs.strapi.io/cms/admin-panel-customization/logos#updating-logos) Language: JavaScript File path: /src/admin/app.js ```js import AuthLogo from "./extensions/my-auth-logo.png"; import MenuLogo from "./extensions/my-menu-logo.png"; export default { config: { // … other configuration properties auth: { // Replace the Strapi logo in auth (login) views logo: AuthLogo, }, menu: { // Replace the Strapi logo in the main navigation logo: MenuLogo, }, // … other configuration properties bootstrap() {}, }; ``` --- Language: TypeScript File path: /src/admin/app.ts ```ts import AuthLogo from "./extensions/my-auth-logo.png"; import MenuLogo from "./extensions/my-menu-logo.png"; export default { config: { // … other configuration properties auth: { // Replace the Strapi logo in auth (login) views logo: AuthLogo, }, menu: { // Replace the Strapi logo in the main navigation logo: MenuLogo, }, // … other configuration properties bootstrap() {}, }; ``` # Theme extension Source: https://docs.strapi.io/cms/admin-panel-customization/theme-extension ## Theme extension Description: The following example shows how to override the primary color by customizing the light and dark theme keys in the admin panel configuration: (Source: https://docs.strapi.io/cms/admin-panel-customization/theme-extension#theme-extension) Language: JavaScript File path: /src/admin/app.js ```js export default { config: { theme: { light: { colors: { primary600: "#4A6EFF", }, }, dark: { colors: { primary600: "#9DB2FF", }, }, }, }, bootstrap() {}, } ``` --- Language: TypeScript File path: /src/admin/app.ts ```ts export default { config: { theme: { light: { colors: { primary600: '#4A6EFF', }, }, dark: { colors: { primary600: '#9DB2FF', }, }, }, }, bootstrap() {}, } ``` # Docs MCP server Source: https://docs.strapi.io/cms/ai/docs-mcp-server ## Connection details Description: Add to your .cursor/mcp.json file: (Source: https://docs.strapi.io/cms/ai/docs-mcp-server#connection-details) Language: JSON File path: .cursor/mcp.json ```json { "mcpServers": { "strapi-docs": { "url": "https://strapi-docs.mcp.kapa.ai" } } } ``` --- Language: JSON File path: .vscode/mcp.json ```json { "servers": { "strapi-docs": { "type": "http", "url": "https://strapi-docs.mcp.kapa.ai" } } } ``` --- Language: JSON File path: ~/.codeium/windsurf/mcp_config.json ```json { "mcpServers": { "strapi-docs": { "serverUrl": "https://strapi-docs.mcp.kapa.ai" } } } ``` # AI for content managers Source: https://docs.strapi.io/cms/ai/for-content-managers ## Activation and configuration Description: All Strapi AI features can be enabled or disabled globally through the admin panel configuration: (Source: https://docs.strapi.io/cms/ai/for-content-managers#activation) Language: JavaScript File path: /config/admin.js|ts ```js module.exports = { // ... ai: { enabled: true, // set to false to disable all Strapi AI features }, }; ``` # Strapi Client Source: https://docs.strapi.io/cms/api/client ## Installation Description: To use the Strapi Client in your project, install it as a dependency using your preferred package manager: (Source: https://docs.strapi.io/cms/api/client#installation) Language: Bash File path: N/A ```bash yarn add @strapi/client ``` --- Language: Bash File path: N/A ```bash npm install @strapi/client ``` --- Language: Bash File path: N/A ```bash pnpm add @strapi/client ``` ## Basic configuration Description: With Javascript, import the strapi function and create a client instance: (Source: https://docs.strapi.io/cms/api/client#basic-configuration) Language: JavaScript File path: N/A ```js import { strapi } from '@strapi/client'; const client = strapi({ baseURL: 'http://localhost:1337/api' }); ``` --- Language: JavaScript File path: N/A ```js import { strapi } from '@strapi/client'; const client = strapi({ baseURL: 'http://localhost:1337/api' }); ``` --- Language: TypeScript File path: ./src/api/[apiName]/routes/[routerName].ts ```ts ``` ## Authentication Description: If your Strapi instance uses API tokens, configure the Strapi Client as follows: (Source: https://docs.strapi.io/cms/api/client#authentication) Language: JavaScript File path: N/A ```js const client = strapi({ baseURL: 'http://localhost:1337/api', auth: 'your-api-token-here', }); ``` ## General purpose fetch Description: The Strapi Client provides access to the underlying JavaScript fetch function to make direct API requests. (Source: https://docs.strapi.io/cms/api/client#general-purpose-fetch) Language: JavaScript File path: N/A ```js const result = await client.fetch('articles', { method: 'GET' }); ``` ## Working with collection types Description: Usage examples: (Source: https://docs.strapi.io/cms/api/client#working-with-collection-types) Language: JavaScript File path: N/A ```js const articles = client.collection('articles'); // Fetch all english articles sorted by title const allArticles = await articles.find({ locale: 'en', sort: 'title', }); // Fetch a single article const singleArticle = await articles.findOne('article-document-id'); // Create a new article const newArticle = await articles.create({ title: 'New Article', content: '...' }); // Update an existing article const updatedArticle = await articles.update('article-document-id', { title: 'Updated Title' }); // Delete an article await articles.delete('article-id'); ``` ## Working with single types Description: Usage examples: (Source: https://docs.strapi.io/cms/api/client#working-with-single-types) Language: JavaScript File path: N/A ```js const homepage = client.single('homepage'); // Fetch the default homepage content const defaultHomepage = await homepage.find(); // Fetch the Spanish version of the homepage const spanishHomepage = await homepage.find({ locale: 'es' }); // Update the homepage draft content const updatedHomepage = await homepage.update( { title: 'Updated Homepage Title' }, { status: 'draft' } ); // Delete the homepage content await homepage.delete(); ``` ## find Description: The method can be used as follows: (Source: https://docs.strapi.io/cms/api/client#find) Language: JavaScript File path: N/A ```js // Initialize the client const client = strapi({ baseURL: 'http://localhost:1337/api', auth: 'your-api-token', }); // Find all file metadata const allFiles = await client.files.find(); console.log(allFiles); // Find file metadata with filtering and sorting const imageFiles = await client.files.find({ filters: { mime: { $contains: 'image' }, // Only get image files name: { $contains: 'avatar' }, // Only get files with 'avatar' in the name }, sort: ['name:asc'], // Sort by name in ascending order }); ``` ## findOne Description: The method can be used as follows: (Source: https://docs.strapi.io/cms/api/client#findone) Language: JavaScript File path: N/A ```js // Initialize the client const client = strapi({ baseURL: 'http://localhost:1337/api', auth: 'your-api-token', }); // Find file metadata by ID const file = await client.files.findOne(1); console.log(file.name); console.log(file.url); console.log(file.mime); // The file MIME type ``` ## update Description: The methods can be used as follows: (Source: https://docs.strapi.io/cms/api/client#update) Language: JavaScript File path: N/A ```js // Initialize the client const client = strapi({ baseURL: 'http://localhost:1337/api', auth: 'your-api-token', }); // Update file metadata const updatedFile = await client.files.update(1, { name: 'New file name', alternativeText: 'Descriptive alt text for accessibility', caption: 'A caption for the file', }); ``` ## Method Signature Description: The method supports uploading files as Blob (in browsers or Node.js) or as Buffer (in Node.js only). (Source: https://docs.strapi.io/cms/api/client#method-signature) Language: TypeScript File path: /github.com/strapi/client/blob/60a0117e361346073bed1959d354c7facfb963b3/src/files/types.ts ```ts const client = strapi({ baseURL: 'http://localhost:1337/api' }); const fileInput = document.querySelector('input[type="file"]'); const file = fileInput.files[0]; try { const result = await client.files.upload(file, { fileInfo: { alternativeText: 'A user uploaded image', caption: 'Uploaded via browser', }, }); console.log('Upload successful:', result); } catch (error) { console.error('Upload failed:', error); } ``` Language: TypeScript File path: /github.com/strapi/client/blob/60a0117e361346073bed1959d354c7facfb963b3/src/files/types.ts ```ts import { readFile } from 'fs/promises'; const client = strapi({ baseURL: 'http://localhost:1337/api' }); const filePath = './image.png'; const mimeType = 'image/png'; const fileContentBuffer = await readFile(filePath); const fileBlob = new Blob([fileContentBuffer], { type: mimeType }); try { const result = await client.files.upload(fileBlob, { fileInfo: { name: 'Image uploaded as Blob', alternativeText: 'Uploaded from Node.js Blob', caption: 'Example upload', }, }); console.log('Blob upload successful:', result); } catch (error) { console.error('Blob upload failed:', error); } ``` --- Language: TypeScript File path: /github.com/strapi/client/blob/60a0117e361346073bed1959d354c7facfb963b3/src/files/types.ts ```ts import { readFile } from 'fs/promises'; const client = strapi({ baseURL: 'http://localhost:1337/api' }); const filePath = './image.png'; const fileContentBuffer = await readFile(filePath); try { const result = await client.files.upload(fileContentBuffer, { filename: 'image.png', mimetype: 'image/png', fileInfo: { name: 'Image uploaded as Buffer', alternativeText: 'Uploaded from Node.js Buffer', caption: 'Example upload', }, }); console.log('Buffer upload successful:', result); } catch (error) { console.error('Buffer upload failed:', error); } ``` Language: TypeScript File path: N/A ```ts async upload(file: Blob, options?: BlobUploadOptions): Promise async upload(file: Buffer, options: BufferUploadOptions): Promise ``` ## Response Structure Description: The strapi.client.files.upload() method returns an array of file objects, each with fields such as: (Source: https://docs.strapi.io/cms/api/client#response-structure) Language: JSON File path: /github.com/strapi/client/blob/60a0117e361346073bed1959d354c7facfb963b3/src/files/types.ts ```json { "id": 1, "name": "image.png", "alternativeText": "Uploaded from Node.js Buffer", "caption": "Example upload", "mime": "image/png", "url": "/uploads/image.png", "size": 12345, "createdAt": "2025-07-23T12:34:56.789Z", "updatedAt": "2025-07-23T12:34:56.789Z" } ``` ## delete Description: The method can be used as follows: (Source: https://docs.strapi.io/cms/api/client#delete) Language: TypeScript File path: /github.com/strapi/client/blob/main/src/files/types.ts ```ts // Initialize the client const client = strapi({ baseURL: 'http://localhost:1337/api', auth: 'your-api-token', }); // Delete a file by ID const deletedFile = await client.files.delete(1); console.log('File deleted successfully'); console.log('Deleted file ID:', deletedFile.id); console.log('Deleted file name:', deletedFile.name); ``` # Document Service API Source: https://docs.strapi.io/cms/api/document-service ## `findOne()` Description: GET strapi.documents().findOne() — findOne() (Source: https://docs.strapi.io/cms/api/document-service#findone) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').findOne({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn' }) ``` ## `findFirst()` Description: GET strapi.documents().findFirst() — findFirst() (Source: https://docs.strapi.io/cms/api/document-service#findfirst) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').findFirst() ``` --- Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').findFirst( { filters: { name: { $startsWith: "Pizzeria" } } } ) ``` ## `findMany()` Description: GET strapi.documents().findMany() — findMany() (Source: https://docs.strapi.io/cms/api/document-service#findmany) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').findMany() ``` --- Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').findMany( { filters: { name: { $startsWith: 'Pizzeria' } } } ) ``` ## `create()` Description: GET strapi.documents().create() — create() (Source: https://docs.strapi.io/cms/api/document-service#create) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').create({ data: { name: 'Restaurant B' } }) ``` ## `update()` Description: GET strapi.documents().update() — update() (Source: https://docs.strapi.io/cms/api/document-service#update) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').update({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn', data: { name: "New restaurant name" } }) ``` ## `delete()` Description: GET strapi.documents().delete() — delete() (Source: https://docs.strapi.io/cms/api/document-service#delete) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').delete({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn', }) ``` ## `deleteMany()` Description: GET strapi.documents().deleteMany() — deleteMany() (Source: https://docs.strapi.io/cms/api/document-service#deletemany) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').deleteMany({ filters: { city: { name: { $eq: 'New York' } } } }); ``` ## `publish()` Description: GET strapi.documents().publish() — publish() (Source: https://docs.strapi.io/cms/api/document-service#publish) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').publish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn', }); ``` ## `unpublish()` Description: GET strapi.documents().unpublish() — unpublish() (Source: https://docs.strapi.io/cms/api/document-service#unpublish) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').unpublish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn' }); ``` ## `discardDraft()` Description: GET strapi.documents().discardDraft() — discardDraft() (Source: https://docs.strapi.io/cms/api/document-service#discarddraft) Language: JavaScript File path: N/A ```javascript strapi.documents('api::restaurant.restaurant').discardDraft({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn', }); ``` ## `count()` Description: GET strapi.documents().count() — count() (Source: https://docs.strapi.io/cms/api/document-service#count) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').count() ``` --- Language: Bash File path: N/A ```bash strapi.documents('api::restaurant.restaurant').count({ status: 'published' }) ``` --- Language: JavaScript File path: N/A ```javascript /** * Count number of draft documents (default if status is omitted) * in English (default locale) * whose name starts with 'Pizzeria' */ strapi.documents('api::restaurant.restaurant').count({ filters: { name: { $startsWith: "Pizzeria" }}}) ``` # Using fields with the Document Service API Source: https://docs.strapi.io/cms/api/document-service/fields ## Select fields with `findOne()` queries Description: GET strapi.documents( — Select fields with findOne() (Source: https://docs.strapi.io/cms/api/document-service/fields#select-fields-with-findone-queries) Language: JavaScript File path: N/A ```javascript const document = await strapi.documents("api::restaurant.restaurant").findOne({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', fields: ["name", "description"], }); ``` Language: JSON File path: N/A ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", description: "Welcome to Biscotte restaurant! …" } ``` ## Select fields with `findFirst()` queries Description: GET strapi.documents( — Select fields with findFirst() (Source: https://docs.strapi.io/cms/api/document-service/fields#select-fields-with-findfirst-queries) Language: JavaScript File path: N/A ```javascript const document = await strapi.documents("api::restaurant.restaurant").findFirst({ fields: ["name", "description"], }); ``` Language: JSON File path: N/A ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", description: "Welcome to Biscotte restaurant! …" } ``` ## Select fields with `findMany()` queries Description: GET strapi.documents( — Select fields with findMany() (Source: https://docs.strapi.io/cms/api/document-service/fields#select-fields-with-findmany-queries) Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::restaurant.restaurant").findMany({ fields: ["name", "description"], }); ``` Language: JSON File path: N/A ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", description: "Welcome to Biscotte restaurant! …" } // ... ] ``` ## Select fields with `create()` queries Description: GET strapi.documents( — Select fields with create() (Source: https://docs.strapi.io/cms/api/document-service/fields#select-fields-with-create-queries) Language: JavaScript File path: N/A ```javascript const document = await strapi.documents("api::restaurant.restaurant").create({ data: { name: "Restaurant B", description: "Description for the restaurant", }, fields: ["name", "description"], }); ``` Language: JSON File path: N/A ```json { id: 4, documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', name: 'Restaurant B', description: 'Description for the restaurant' } ``` ## Select fields with `update()` queries Description: GET strapi.documents( — Select fields with update() (Source: https://docs.strapi.io/cms/api/document-service/fields#select-fields-with-update-queries) Language: JavaScript File path: N/A ```javascript const document = await strapi.documents("api::restaurant.restaurant").update({ documentId: "fmtr6d7ktzpgrijqaqgr6vxs", data: { name: "Restaurant C", }, fields: ["name"], }); ``` Language: JSON File path: N/A ```json { documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', name: 'Restaurant C' } ``` ## Select fields with `delete()` queries Description: GET strapi.documents( — Select fields with delete() (Source: https://docs.strapi.io/cms/api/document-service/fields#select-fields-with-delete-queries) Language: JavaScript File path: N/A ```javascript const document = await strapi.documents("api::restaurant.restaurant").delete({ documentId: "fmtr6d7ktzpgrijqaqgr6vxs", fields: ["name"], }); ``` Language: JSON File path: N/A ```json documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', // All of the deleted document's versions are returned entries: [ { id: 4, documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', name: 'Restaurant C', // … } ] } ``` ## Select fields with `publish()` queries Description: GET strapi.documents( — Select fields with publish() (Source: https://docs.strapi.io/cms/api/document-service/fields#select-fields-with-publish-queries) Language: JavaScript File path: N/A ```javascript const document = await strapi.documents("api::restaurant.restaurant").publish({ documentId: "fmtr6d7ktzpgrijqaqgr6vxs", fields: ["name"], }); ``` Language: JSON File path: N/A ```json { documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', // All of the published locale entries are returned entries: [ { documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', name: 'Restaurant B' } ] } ``` ## Select fields with `unpublish()` queries Description: GET strapi.documents( — Select fields with unpublish() (Source: https://docs.strapi.io/cms/api/document-service/fields#select-fields-with-unpublish-queries) Language: JavaScript File path: N/A ```javascript const document = await strapi.documents("api::restaurant.restaurant").unpublish({ documentId: "cjld2cjxh0000qzrmn831i7rn", fields: ["name"], }); ``` Language: JSON File path: N/A ```json { documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', // All of the published locale entries are returned entries: [ { documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', name: 'Restaurant B' } ] } ``` ## Select fields with `discardDraft()` queries Description: GET strapi.documents( — Select fields with discardDraft() (Source: https://docs.strapi.io/cms/api/document-service/fields#select-fields-with-discarddraft-queries) Language: JavaScript File path: N/A ```javascript const document = await strapi.documents("api::restaurant.restaurant").discardDraft({ documentId: "fmtr6d7ktzpgrijqaqgr6vxs", fields: ["name"], }); ``` Language: JSON File path: N/A ```json { documentId: "fmtr6d7ktzpgrijqaqgr6vxs", // All of the discarded draft entries are returned entries: [ { "name": "Restaurant B" } ] } ``` # Using filters with the Document Service API Source: https://docs.strapi.io/cms/api/document-service/filters ## Attribute operators Description: GET strapi.documents().findMany() — $not (Source: https://docs.strapi.io/cms/api/document-service/filters#attribute-operators) Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $not: { $contains: 'Hello World', }, }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $eq: 'Hello World', }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript // $eq can be omitted: const entries = await strapi.documents('api::article.article').findMany({ filters: { title: 'Hello World', }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $eqi: 'HELLO World', }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $ne: 'ABCD', }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $nei: 'abcd', }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $in: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript // $in can be omitted when passing an array of values: const entries = await strapi.documents('api::article.article').findMany({ filters: { title: ['Hello', 'Hola', 'Bonjour'], }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $notIn: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { rating: { $lt: 10, }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { rating: { $lte: 10, }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { rating: { $gt: 5, }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { rating: { $gte: 5, }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { rating: { $between: [1, 20], }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $contains: 'Hello', }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $notContains: 'Hello', }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $containsi: 'hello', }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $notContainsi: 'hello', }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $startsWith: 'ABCD', }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $startsWithi: 'ABCD', // will return the same as filtering with 'abcd' }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $endsWith: 'ABCD', }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $endsWith: 'ABCD', // will return the same as filtering with 'abcd' }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $null: true, }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $notNull: true, }, }, }); ``` ## Logical operators Description: GET strapi.documents().findMany() — $and (Source: https://docs.strapi.io/cms/api/document-service/filters#logical-operators) Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { $and: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` --- Language: JavaScript File path: N/A ```javascript // $and will be used implicitly when passing an object with nested conditions: const entries = await strapi.documents('api::article.article').findMany({ filters: { title: 'Hello World', createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { $or: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` --- Language: JavaScript File path: N/A ```javascript const entries = await strapi.documents('api::article.article').findMany({ filters: { $not: { title: 'Hello World', }, }, }); ``` # Using the locale parameter with the Document Service API Source: https://docs.strapi.io/cms/api/document-service/locale ## count() documents for a locale Description: If no status parameter is passed, draft documents are counted (which is the total of available documents for the locale since even published documents are counted as having a draft version): (Source: https://docs.strapi.io/cms/api/document-service/locale#count) Language: JavaScript File path: N/A ```js // Count number of published documents in French strapi.documents('api::restaurant.restaurant').count({ locale: 'fr' }); ``` ## Get a locale version with `findOne()` Description: GET strapi.documents().findOne() — Get a locale version with findOne() (Source: https://docs.strapi.io/cms/api/document-service/locale#get-a-locale-version-with-findone) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').findOne({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'fr', }); ``` Language: JSON File path: N/A ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: null, // draft version (default) locale: "fr", // as asked from the parameters // … } ``` ## Get a locale version with `findFirst()` Description: GET strapi.documents().findFirst() — Get a locale version with findFirst() (Source: https://docs.strapi.io/cms/api/document-service/locale#get-a-locale-version-with-findfirst) Language: JavaScript File path: N/A ```javascript const document = await strapi.documents('api::article.article').findFirst({ locale: 'fr', }); ``` Language: JSON File path: N/A ```json { "documentId": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article" // … } ``` ## Get locale versions with `findMany()` Description: GET strapi.documents().findMany() — Get locale versions with findMany() (Source: https://docs.strapi.io/cms/api/document-service/locale#get-locale-versions-with-findmany) Language: JavaScript File path: N/A ```javascript // Defaults to status: draft await strapi.documents('api::restaurant.restaurant').findMany({ locale: 'fr' }); ``` Language: JSON File path: N/A ```json [ { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Restaurant Biscotte', publishedAt: null, locale: 'fr', // … }, // … ] ``` ## `create()` a document for a locale Description: GET strapi.documents().create() — Create a document for a locale (Source: https://docs.strapi.io/cms/api/document-service/locale#create-a-document-for-a-locale) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').create({ locale: 'es' // if not passed, the draft is created for the default locale data: { name: 'Restaurante B' } }) ``` Language: JSON File path: N/A ```json { documentId: "pw2s0nh5ub1zmnk0d80vgqrh", name: "Restaurante B", publishedAt: null, locale: "es" // … } ``` ## `update()` a locale version Description: GET strapi.documents().update() — Update a locale version (Source: https://docs.strapi.io/cms/api/document-service/locale#update-a-locale-version) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').update({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'es', data: { name: 'Nuevo nombre del restaurante' }, }); ``` Language: JSON File path: N/A ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Nuevo nombre del restaurante", locale: "es", publishedAt: null, // … } ``` ## Delete a locale version Description: GET strapi.documents().delete() — Delete a locale version (Source: https://docs.strapi.io/cms/api/document-service/locale#delete-a-locale-version) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').delete({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', // documentId, locale: 'es', }); ``` ## Delete all locale versions Description: GET strapi.documents().delete() — Delete all locale versions (Source: https://docs.strapi.io/cms/api/document-service/locale#delete-all-locale-versions) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').delete({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', // documentId, locale: '*', }); // for all existing locales ``` Language: JSON File path: N/A ```json { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", // All of the deleted locale versions are returned "versions": [ { "title": "Test Article" } ] } ``` ## Publish a locale version Description: GET strapi.documents().publish() — Publish a locale version (Source: https://docs.strapi.io/cms/api/document-service/locale#publish-a-locale-version) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').publish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'fr', }); ``` Language: JSON File path: N/A ```json { versions: [ { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Restaurant Biscotte', publishedAt: '2024-03-14T18:38:05.674Z', locale: 'fr', // … }, ] } ``` ## Publish all locale versions Description: GET strapi.documents().publish() — Publish all locale versions (Source: https://docs.strapi.io/cms/api/document-service/locale#publish-all-locale-versions) Language: JavaScript File path: N/A ```javascript await strapi .documents('api::restaurant.restaurant') .publish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: '*' }); ``` Language: JSON File path: N/A ```json { "versions": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "publishedAt": "2024-03-14T18:45:21.857Z", "locale": "en" // … }, { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "publishedAt": "2024-03-14T18:45:21.857Z", "locale": "es" // … }, { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "publishedAt": "2024-03-14T18:45:21.857Z", "locale": "fr" // … } ] } ``` ## Unpublish a locale version Description: GET strapi.documents().unpublish() — Unpublish a locale version (Source: https://docs.strapi.io/cms/api/document-service/locale#unpublish-a-locale-version) Language: JavaScript File path: N/A ```javascript await strapi .documents('api::restaurant.restaurant') .unpublish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'fr' }); ``` Language: JSON File path: N/A ```json { versions: 1 } ``` ## Unpublish all locale versions Description: GET strapi.documents().unpublish() — Unpublish all locale versions (Source: https://docs.strapi.io/cms/api/document-service/locale#unpublish-all-locale-versions) Language: JavaScript File path: N/A ```javascript await strapi .documents('api::restaurant.restaurant') .unpublish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: '*' }); ``` --- Language: JavaScript File path: N/A ```javascript const document = await strapi.documents('api::article.article').unpublish({ documentId: 'cjld2cjxh0000qzrmn831i7rn', fields: ['title'], }); ``` Language: JSON File path: N/A ```json { versions: 3 } ``` --- Language: JSON File path: N/A ```json { "documentId": "cjld2cjxh0000qzrmn831i7rn", // All of the unpublished locale versions are returned "versions": [ { "title": "Test Article" } ] } ``` ## Discard draft for a locale version Description: GET strapi.documents().discardDraft() — Discard draft for a locale version (Source: https://docs.strapi.io/cms/api/document-service/locale#discard-draft-for-a-locale-version) Language: JavaScript File path: N/A ```javascript await strapi .documents('api::restaurant.restaurant') .discardDraft({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'fr' }); ``` Language: JSON File path: N/A ```json { versions: [ { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Restaurant Biscotte', publishedAt: null, locale: 'fr', // … }, ] } ``` ## Discard drafts for all locale versions Description: GET strapi.documents().discardDraft() — Discard drafts for all locale versions (Source: https://docs.strapi.io/cms/api/document-service/locale#discard-drafts-for-all-locale-versions) Language: JavaScript File path: N/A ```javascript await strapi .documents('api::restaurant.restaurant') .discardDraft({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: '*' }); ``` Language: JSON File path: N/A ```json { versions: [ { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Biscotte Restaurant', publishedAt: null, locale: 'en', // … }, { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Restaurant Biscotte', publishedAt: null, locale: 'fr', // … }, { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Biscotte Restaurante', publishedAt: null, locale: 'es', // … }, ] } ``` # Extending the Document Service behavior Source: https://docs.strapi.io/cms/api/document-service/middlewares ## context Description: The following examples show what context might include depending on the method called: (Source: https://docs.strapi.io/cms/api/document-service/middlewares#context) Language: JSON File path: N/A ```json { uid: "api::restaurant.restaurant", contentType: { kind: "collectionType", collectionName: "restaurants", info: { singularName: "restaurant", pluralName: "restaurants", displayName: "restaurant" }, options: { draftAndPublish: true }, pluginOptions: {}, attributes: { name: { /*...*/ }, description: { /*...*/ }, createdAt: { /*...*/ }, updatedAt: { /*...*/ }, publishedAt: { /*...*/ }, createdBy: { /*...*/ }, updatedBy: { /*...*/ }, locale: { /*...*/ }, }, apiName: "restaurant", globalId: "Restaurants", uid: "api::restaurant.restaurant", modelType: "contentType", modelName: "restaurant", actions: { /*...*/ }, lifecycles: { /*...*/ }, }, action: "findOne", params: { documentId: 'hp7hjvrbt8rcgkmabntu0aoq', locale: undefined, status: "publish" populate: { /*...*/ }, } } ``` --- Language: JSON File path: N/A ```json { uid: "api::restaurant.restaurant", contentType: { kind: "collectionType", collectionName: "restaurants", info: { singularName: "restaurant", pluralName: "restaurants", displayName: "restaurant" }, options: { draftAndPublish: true }, pluginOptions: {}, attributes: { name: { /*...*/ }, description: { /*...*/ }, createdAt: { /*...*/ }, updatedAt: { /*...*/ }, publishedAt: { /*...*/ }, createdBy: { /*...*/ }, updatedBy: { /*...*/ }, locale: { /*...*/ }, }, apiName: "restaurant", globalId: "Restaurants", uid: "api::restaurant.restaurant", modelType: "contentType", modelName: "restaurant", actions: { /*...*/ }, lifecycles: { /*...*/ }, }, action: "findMany", params: { filters: { /*...*/ }, status: "draft", locale: null, fields: ['name', 'description'], } } ``` --- Language: JSON File path: N/A ```json { uid: "api::restaurant.restaurant", contentType: { kind: "collectionType", collectionName: "restaurants", info: { singularName: "restaurant", pluralName: "restaurants", displayName: "restaurant" }, options: { draftAndPublish: true }, pluginOptions: {}, attributes: { name: { /*...*/ }, description: { /*...*/ }, createdAt: { /*...*/ }, updatedAt: { /*...*/ }, publishedAt: { /*...*/ }, createdBy: { /*...*/ }, updatedBy: { /*...*/ }, locale: { /*...*/ }, }, apiName: "restaurant", globalId: "Restaurants", uid: "api::restaurant.restaurant", modelType: "contentType", modelName: "restaurant", actions: { /*...*/ }, lifecycles: { /*...*/ }, }, action: "create", params: { data: { /*...*/ }, status: "draft", populate: { /*...*/ }, } } ``` --- Language: JSON File path: N/A ```json { uid: "api::restaurant.restaurant", contentType: { kind: "collectionType", collectionName: "restaurants", info: { singularName: "restaurant", pluralName: "restaurants", displayName: "restaurant" }, options: { draftAndPublish: true }, pluginOptions: {}, attributes: { name: { /*...*/ }, description: { /*...*/ }, createdAt: { /*...*/ }, updatedAt: { /*...*/ }, publishedAt: { /*...*/ }, createdBy: { /*...*/ }, updatedBy: { /*...*/ }, locale: { /*...*/ }, }, apiName: "restaurant", globalId: "Restaurants", uid: "api::restaurant.restaurant", modelType: "contentType", modelName: "restaurant", actions: { /*...*/ }, lifecycles: { /*...*/ }, }, action: "update", params: { data: { /*...*/ }, documentId: 'hp7hjvrbt8rcgkmabntu0aoq', locale: undefined, status: "draft" populate: { /*...*/ }, } } ``` --- Language: JSON File path: N/A ```json { uid: "api::restaurant.restaurant", contentType: { kind: "collectionType", collectionName: "restaurants", info: { singularName: "restaurant", pluralName: "restaurants", displayName: "restaurant" }, options: { draftAndPublish: true }, pluginOptions: {}, attributes: { name: { /*...*/ }, description: { /*...*/ }, createdAt: { /*...*/ }, updatedAt: { /*...*/ }, publishedAt: { /*...*/ }, createdBy: { /*...*/ }, updatedBy: { /*...*/ }, locale: { /*...*/ }, }, apiName: "restaurant", globalId: "Restaurants", uid: "api::restaurant.restaurant", modelType: "contentType", modelName: "restaurant", actions: { /*...*/ }, lifecycles: { /*...*/ }, }, action: "delete", params: { data: { /*...*/ }, documentId: 'hp7hjvrbt8rcgkmabntu0aoq', locale: "*", populate: { /*...*/ }, } } ``` ## next Description: Example (Source: https://docs.strapi.io/cms/api/document-service/middlewares#next) Language: JavaScript File path: N/A ```js strapi.documents.use((context, next) => { return next(); }); ``` ## Users Description: The middleware must be registered in the general register() lifecycle method: (Source: https://docs.strapi.io/cms/api/document-service/middlewares#users) Language: JavaScript File path: /src/index.js|ts ```js module.exports = { register({ strapi }) { strapi.documents.use((context, next) => { // your logic return next(); }); }, // bootstrap({ strapi }) {}, // destroy({ strapi }) {}, }; ``` ## Plugin developers Description: The middleware must be registered in the plugin's register() lifecycle method: (Source: https://docs.strapi.io/cms/api/document-service/middlewares#plugin-developers) Language: JavaScript File path: /(plugin-root-folder)/strapi-server.js|ts ```js module.exports = { register({ strapi }) { strapi.documents.use((context, next) => { // your logic return next(); }); }, // bootstrap({ strapi }) {}, // destroy({ strapi }) {}, }; ``` ## Examples Description: When implementing a middleware, always return the response from next(). (Source: https://docs.strapi.io/cms/api/document-service/middlewares#examples) Language: JavaScript File path: N/A ```js const applyTo = ['api::article.article']; strapi.documents.use((context, next) => { // Only run for certain content types if (!applyTo.includes(context.uid)) { return next(); } // Only run for certain actions if (['create', 'update'].includes(context.action)) { context.params.data.fullName = `${context.params.data.firstName} ${context.params.data.lastName}`; } const result = await next(); // do something with the result before returning it return result }); ``` # Using Populate with the Document Service API Source: https://docs.strapi.io/cms/api/document-service/populate ## Populate 1 level for all relations Description: GET strapi.documents( — Populate 1 level for all relations (Source: https://docs.strapi.io/cms/api/document-service/populate#populate-1-level-for-all-relations) Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::article.article").findMany({ populate: "*", }); ``` Language: JSON File path: N/A ```json { [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... "headerImage": { "data": { "id": 1, "attributes": { "name": "17520.jpg", "alternativeText": "17520.jpg", "formats": { // ... } // ... } } }, "author": { // ... }, "categories": { // ... } } // ... ] } ``` ## Populate 1 level for specific relations Description: GET strapi.documents( — Populate 1 level for specific relations (Source: https://docs.strapi.io/cms/api/document-service/populate#populate-1-level-for-specific-relations) Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::article.article").findMany({ populate: ["headerImage"], }); ``` Language: JSON File path: N/A ```json [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... "headerImage": { "id": 2, "name": "17520.jpg" // ... } } // ... ] ``` ## Populate several levels deep for specific relations Description: GET strapi.documents( — Populate several levels deep for specific relations (Source: https://docs.strapi.io/cms/api/document-service/populate#populate-several-levels-deep-for-specific-relations) Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::article.article").findMany({ populate: { categories: { populate: ["articles"], }, }, }); ``` Language: JSON File path: N/A ```json [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... "categories": { "id": 1, "name": "Test Category", "slug": "test-category", "description": "Test 1" // ... "articles": [ { "id": 1, "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... } // ... ] } } // ... ] ``` ## Sort populated relations Description: GET strapi.documents( — Sort populated relations (Source: https://docs.strapi.io/cms/api/document-service/populate#sort-populated-relations) Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::article.article").findMany({ populate: { categories: { sort: 'name:asc', }, }, }); ``` Language: JSON File path: N/A ```json [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", // ... "categories": [ { "id": 1, "name": "Architecture" // ... }, { "id": 3, "name": "Technology" // ... } ] } // ... ] ``` ## Components & Dynamic Zones Description: GET strapi.documents( — Populate components (Source: https://docs.strapi.io/cms/api/document-service/populate#components-dynamic-zones) Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::article.article").findMany({ populate: ["testComp"], }); ``` --- Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::article.article").findMany({ populate: { testDZ: { on: { "test.test-compo": { fields: ["testString"], populate: ["testNestedCompo"], }, }, }, }, }); ``` Language: JSON File path: N/A ```json [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... "testComp": { "id": 1, "name": "Test Component" // ... } } // ... ] ``` --- Language: JSON File path: N/A ```json [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... "testDZ": [ { "id": 3, "__component": "test.test-compo", "testString": "test1", "testNestedCompo": { "id": 3, "testNestedString": "testNested1" } } ] } // ... ] ``` ## Populating with `create()` Description: GET strapi.documents( — Populate with create (Source: https://docs.strapi.io/cms/api/document-service/populate#populating-with-create) Language: JavaScript File path: N/A ```javascript strapi.documents("api::article.article").create({ data: { title: "Test Article", slug: "test-article", body: "Test 1", headerImage: 2, }, populate: ["headerImage"], }); ``` Language: JSON File path: N/A ```json { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", "headerImage": { "id": 2, "name": "17520.jpg" // ... } } ``` ## Populating with `update()` Description: GET strapi.documents( — Populate with update (Source: https://docs.strapi.io/cms/api/document-service/populate#populating-with-update) Language: JavaScript File path: N/A ```javascript strapi.documents("api::article.article").update({ documentId: "cjld2cjxh0000qzrmn831i7rn", data: { title: "Test Article Update", }, populate: ["headerImage"], }); ``` Language: JSON File path: N/A ```json { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article Update", "slug": "test-article", "body": "Test 1", "headerImage": { "id": 2, "name": "17520.jpg" // ... } } ``` ## Populating with `publish()` Description: GET strapi.documents( — Populate with publish (Source: https://docs.strapi.io/cms/api/document-service/populate#populating-with-publish) Language: JavaScript File path: N/A ```javascript strapi.documents("api::article.article").publish({ documentId: "cjld2cjxh0000qzrmn831i7rn", populate: ["headerImage"], }); ``` Language: JSON File path: N/A ```json { "id": "cjld2cjxh0000qzrmn831i7rn", "versions": [ { "id": "cjld2cjxh0001qzrm1q1i7rn", "locale": "en", // ... "headerImage": { "id": 2, "name": "17520.jpg" // ... } } ] } ``` ## Populating with `delete()` Description: GET strapi.documents( — Populate with delete (Source: https://docs.strapi.io/cms/api/document-service/populate#populating-with-delete) Language: JavaScript File path: N/A ```javascript strapi.documents("api::article.article").delete({ documentId: "cjld2cjxh0000qzrmn831i7rn", populate: ["headerImage"], }); ``` Language: JSON File path: N/A ```json { "documentId": "cjld2cjxh0000qzrmn831i7rn", "entries": [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", "headerImage": { "id": 2, "name": "17520.jpg" // ... } // ... } ] } ``` # Using publicationFilter with the Document Service API Source: https://docs.strapi.io/cms/api/document-service/publication-filter ## Default status when publicationFilter is used Description: The following example compares Document Service and REST behavior when only publicationFilter: 'modified' is passed: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#default-status) Language: JavaScript File path: N/A ```js // Document Service API → draft rows in the modified cohort await strapi.documents('api::restaurant.restaurant').findMany({ publicationFilter: 'modified', }); // REST: GET /api/restaurants?publicationFilter=modified → published rows in the modified cohort ``` ## Query never-published drafts Description: Return draft rows for (documentId, locale) pairs with no published version for that locale: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#never-published) Language: JavaScript File path: N/A ```js const documents = await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published', }); ``` ## Query has-published-version drafts Description: Return draft rows where a published row also exists for the same (documentId, locale). (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#has-published-version) Language: JavaScript File path: N/A ```js const documents = await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'has-published-version', }); ``` ## Query modified or unmodified documents Description: Compare updatedAt on the draft and published rows for the same pair: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#modified-unmodified) Language: JavaScript File path: N/A ```js // Draft side of modified pairs await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'modified', }); // Published side of unmodified pairs await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'unmodified', }); ``` ## Query document-scoped cohorts Description: Return draft rows for documents that have never been published in any locale: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#document-scoped) Language: JavaScript File path: N/A ```js await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published-document', }); ``` Language: JavaScript File path: N/A ```js await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'has-published-version-document', }); ``` ## Query published rows without or with a draft peer Description: published-without-draft and published-with-draft partition published rows per (documentId, locale) (excluding pairs with no published row): (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#published-slice) Language: JavaScript File path: N/A ```js // Orphan published rows (published row, no draft sibling for the same pair) await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-without-draft', }); // Published rows that still have a draft sibling await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-with-draft', }); ``` ## Use with findOne() and findFirst() Description: publicationFilter applies the same cohort rules. (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#find-one-find-first) Language: JavaScript File path: N/A ```js await strapi.documents('api::restaurant.restaurant').findOne({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', status: 'draft', publicationFilter: 'never-published', }); ``` ## Count documents in a cohort Description: Count draft rows in the never-published cohort: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#count) Language: JavaScript File path: N/A ```js const neverPublishedCount = await strapi .documents('api::restaurant.restaurant') .count({ status: 'draft', publicationFilter: 'never-published', }); ``` # Using Sort & Pagination with the Document Service API Source: https://docs.strapi.io/cms/api/document-service/sort-pagination ## Sort on a single field Description: GET strapi.documents().findMany() — Sort on a single field (Source: https://docs.strapi.io/cms/api/document-service/sort-pagination#sort-on-a-single-field) Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::article.article").findMany({ sort: "title:asc", }); ``` Language: JSON File path: N/A ```json [ { "documentId": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1" }, { "documentId": "cjld2cjxh0001qzrm5q1j5q7m", "title": "Test Article 2", "slug": "test-article-2", "body": "Test 2" } ] ``` ## Sort on multiple fields Description: GET strapi.documents().findMany() — Sort on multiple fields (Source: https://docs.strapi.io/cms/api/document-service/sort-pagination#sort-on-multiple-fields) Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::article.article").findMany({ sort: [{ title: "asc" }, { slug: "desc" }], }); ``` Language: JSON File path: N/A ```json [ { "documentId": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1" }, { "documentId": "cjld2cjxh0001qzrm5q1j5q7m", "title": "Test Article 2", "slug": "test-article-2", "body": "Test 2" } ] ``` ## Pagination Description: GET strapi.documents().findMany() — Pagination (Source: https://docs.strapi.io/cms/api/document-service/sort-pagination#pagination) Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::article.article").findMany({ limit: 10, start: 0, }); ``` Language: JSON File path: N/A ```json [ { "documentId": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1" }, { "documentId": "cjld2cjxh0001qzrm5q1j5q7m", "title": "Test Article 2", "slug": "test-article-2", "body": "Test 2" } ] ``` # Using Draft & Publish with the Document Service API Source: https://docs.strapi.io/cms/api/document-service/status ## count() only draft or published versions Description: To take into account only draft or published versions of documents while counting documents with the Document Service API, pass the corresponding status parameter: (Source: https://docs.strapi.io/cms/api/document-service/status#count) Language: JavaScript File path: N/A ```js // Count draft documents (also actually includes published documents) const draftsCount = await strapi.documents("api::restaurant.restaurant").count({ status: 'draft' }); ``` --- Language: JavaScript File path: N/A ```js // Count only published documents const publishedCount = await strapi.documents("api::restaurant.restaurant").count({ status: 'published' }); ``` ## Get the published version with `findOne()` Description: GET strapi.documents().findOne() — findOne() with status: (Source: https://docs.strapi.io/cms/api/document-service/status#get-the-published-version-with-findone) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').findOne({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', status: 'published' }); ``` Language: JSON File path: N/A ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: "2024-03-14T15:40:45.330Z", locale: "en", // default locale // … } ``` ## Get the published version with `findFirst()` Description: GET strapi.documents().findFirst() — findFirst() with status: (Source: https://docs.strapi.io/cms/api/document-service/status#get-the-published-version-with-findfirst) Language: JavaScript File path: N/A ```javascript const document = await strapi.documents("api::restaurant.restaurant").findFirst({ status: 'published', }); ``` Language: JSON File path: N/A ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: "2024-03-14T15:40:45.330Z", locale: "en", // default locale // … } ``` ## Get the published version with `findMany()` Description: GET strapi.documents().findMany() — findMany() with status: (Source: https://docs.strapi.io/cms/api/document-service/status#get-the-published-version-with-findmany) Language: JavaScript File path: N/A ```javascript const documents = await strapi.documents("api::restaurant.restaurant").findMany({ status: 'published' }); ``` Language: JSON File path: N/A ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: "2024-03-14T15:40:45.330Z", locale: "en", // default locale // … } // … ] ``` ## Create a draft and publish it Description: GET strapi.documents().create() — create() with status: (Source: https://docs.strapi.io/cms/api/document-service/status#create) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').create({ data: { name: "New Restaurant", }, status: 'published', }) ``` Language: JSON File path: N/A ```json { documentId: "d41r46wac4xix5vpba7561at", name: "New Restaurant", publishedAt: "2024-03-14T17:29:03.399Z", locale: "en" // default locale // … } ``` ## Update a draft and publish it Description: GET strapi.documents().update() — update() with status: (Source: https://docs.strapi.io/cms/api/document-service/status#update) Language: JavaScript File path: N/A ```javascript await strapi.documents('api::restaurant.restaurant').update({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', data: { name: "Biscotte Restaurant (closed)", }, status: 'published', }) ``` Language: JSON File path: N/A ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant (closed)", publishedAt: "2024-03-14T17:29:03.399Z", locale: "en" // default locale // … } ``` # Entity Service API Source: https://docs.strapi.io/cms/api/entity-service ## Basic usage Description: The Entity Service is available through strapi.entityService: (Source: https://docs.strapi.io/cms/api/entity-service#basic-usage) Language: JavaScript File path: N/A ```js const entry = await strapi.entityService.findOne('api::article.article', 1, { populate: { someRelation: true }, }); ``` # Components and Dynamic Zones Source: https://docs.strapi.io/cms/api/entity-service/components-dynamic-zones ## Creation Description: A component can be created while creating an entry with the Entity Service API: (Source: https://docs.strapi.io/cms/api/entity-service/components-dynamic-zones#creation) Language: JavaScript File path: N/A ```js strapi.entityService.create('api::article.article', { data: { myComponent: { foo: 'bar', }, }, }); ``` Language: JavaScript File path: N/A ```js strapi.entityService.create('api::article.article', { data: { myDynamicZone: [ { __component: 'compo.type', foo: 'bar', }, { __component: 'compo.type2', foo: 'bar', }, ], }, }); ``` ## Update Description: A component can be updated while updating an entry with the Entity Service API. (Source: https://docs.strapi.io/cms/api/entity-service/components-dynamic-zones#update) Language: JavaScript File path: N/A ```js strapi.entityService.update('api::article.article', 1, { data: { myComponent: { id: 1, // will update component with id: 1 (if not specified, would have deleted it and created a new one) foo: 'bar', }, }, }); ``` Language: JavaScript File path: N/A ```js strapi.entityService.update('api::article.article', 1, { data: { myDynamicZone: [ { // will update id: 2, __component: 'compo.type', foo: 'bar', }, { // will add a new & delete old ones __component: 'compo.type2', foo: 'bar2', }, ], }, }); ``` # CRUD operations Source: https://docs.strapi.io/cms/api/entity-service/crud ## Example Description: | Parameter | Description | Type | | ---------- | --------------- | --------------- | | fields | Attributes to return | String[] | | populate | Relations, components and dynamic zones to populate | PopulateParameter | | locale | Locale code (for example fr-FR) when the Internationalization plugin is enabled. (Source: https://docs.strapi.io/cms/api/entity-service/crud#example) Language: JavaScript File path: N/A ```js const entry = await strapi.entityService.findOne('api::article.article', 1, { fields: ['title', 'description'], populate: { category: true }, }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { fields: ['title', 'description'], filters: { title: 'Hello World' }, sort: { createdAt: 'DESC' }, populate: { category: true }, }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { publicationState: 'preview', filters: { publishedAt: { $null: true, }, }, }); ::: ## create() Creates one entry and returns it Syntax: `create(uid: string, parameters: Params)` ⇒ `Entry` ### Parameters | Parameter | Description | Type | | ---------- | ----------- | ---------- | | `fields` | Attributes to return | `String[]` | | `populate` | Relations, components and dynamic zones to [populate](/cms/api/entity-service/populate) | [`PopulateParameter`](/cms/api/entity-service/populate) | | `locale` | Locale code when the Internationalization plugin is enabled. Creates the entry for that locale. | `string` | | `data` | Input data | `Object` | ### Example ``` Language: JavaScript File path: N/A ``` ## update() Updates one entry and returns it. :::note `update()` only performs a partial update, so existing fields that are not included won't be replaced. ::: Syntax: `update(uid: string, id: ID, parameters: Params)` ⇒ `Entry` ### Parameters | Parameter | Description | Type | | ---------- | ------------- | ---------- | | `fields` | Attributes to return | `String[]` | | `populate` | Relations, components and dynamic zones to [populate](/cms/api/entity-service/populate) | [`PopulateParameter`](/cms/api/entity-service/populate) | | `locale` | Locale code when the Internationalization plugin is enabled. Updates the matching localized variant. | `string` | | `data` | Input data | `object` | ### Example ``` Language: JavaScript File path: N/A ``` ## delete() Deletes one entry and returns it. Syntax: `delete(uid: string, id: ID, parameters: Params)` ⇒ `Entry` ### Parameters | Parameter | Description | Type | | ---------- | --------- | -------- | | `fields` | Attributes to return | `String[]` | | `populate` | Relations, components and dynamic zones to [populate](/cms/api/entity-service/populate) | [`PopulateParameter`](/cms/api/entity-service/populate) | | `locale` | Locale code when the Internationalization plugin is enabled. Deletes the localized variant that matches this locale. | `string` | ### Example ``` # Filtering with the Entity Service API Source: https://docs.strapi.io/cms/api/entity-service/filter ## $and Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#and) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { $and: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: 'Hello World', createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, }); ``` ## $or Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#or) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { $or: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` ## $not Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#not) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { $not: { title: 'Hello World', }, }, }); ``` --- Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $not: { $contains: 'Hello World', }, }, }, }); ``` ## $eq Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#eq) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $eq: 'Hello World', }, }, }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: 'Hello World', }, }); ``` ## $eqi Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#eqi) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $eqi: 'HELLO World', }, }, }); ``` ## $ne Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#ne) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $ne: 'ABCD', }, }, }); ``` ## $nei Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#nei) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $nei: 'abcd', }, }, }); ``` ## $in Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#in) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $in: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: ['Hello', 'Hola', 'Bonjour'], }, }); ``` ## $notIn Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#notin) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $notIn: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` ## $lt Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#lt) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { rating: { $lt: 10, }, }, }); ``` ## $lte Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#lte) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { rating: { $lte: 10, }, }, }); ``` ## $gt Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#gt) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { rating: { $gt: 5, }, }, }); ``` ## $gte Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#gte) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { rating: { $gte: 5, }, }, }); ``` ## $between Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#between) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { rating: { $between: [1, 20], }, }, }); ``` ## $contains Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#contains) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $contains: 'Hello', }, }, }); ``` ## $notContains Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#notcontains) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $notContains: 'Hello', }, }, }); ``` ## $containsi Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#containsi) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $containsi: 'hello', }, }, }); ``` ## $notContainsi Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#notcontainsi) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $notContainsi: 'hello', }, }, }); ``` ## $startsWith Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#startswith) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $startsWith: 'ABCD', }, }, }); ``` ## $endsWith Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#endswith) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $endsWith: 'ABCD', }, }, }); ``` ## $null Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#null) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $null: true, }, }, }); ``` ## $notNull Description: Example (Source: https://docs.strapi.io/cms/api/entity-service/filter#notnull) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $notNull: true, }, }, }); ``` # Ordering & Pagination with the Entity Service API Source: https://docs.strapi.io/cms/api/entity-service/order-pagination ## Single Description: as a string to sort with the default ascending order, or - as an object to define both the field name and the order (i.e. (Source: https://docs.strapi.io/cms/api/entity-service/order-pagination#single) Language: JavaScript File path: N/A ```js strapi.entityService.findMany('api::article.article', { sort: 'id', }); // single with direction strapi.entityService.findMany('api::article.article', { sort: { id: 'desc' }, }); ``` ## Multiple Description: as an array of strings to sort multiple fields using the default ascending order, or - as an array of objects to define both the field name and the order (i.e. (Source: https://docs.strapi.io/cms/api/entity-service/order-pagination#multiple) Language: JavaScript File path: N/A ```js strapi.entityService.findMany('api::article.article', { sort: ['publishDate', 'name'], }); // multiple with direction strapi.entityService.findMany('api::article.article', { sort: [{ title: 'asc' }, { publishedAt: 'desc' }], }); ``` ## Relational ordering Description: Fields can also be sorted based on fields from relations: (Source: https://docs.strapi.io/cms/api/entity-service/order-pagination#relational-ordering) Language: JavaScript File path: N/A ```js strapi.entityService.findMany('api::article.article', { sort: { author: { name: 'asc', }, }, }); ``` ## Pagination Description: To paginate results returned by the Entity Service API, you can use the start and limit parameters: (Source: https://docs.strapi.io/cms/api/entity-service/order-pagination#pagination) Language: JavaScript File path: N/A ```js strapi.entityService.findMany('api::article.article', { start: 10, limit: 15, }); ``` Language: JavaScript File path: N/A ```js strapi.entityService.findMany('api::article.article', { page: 1, pageSize: 15, }); ``` # Populating with the Entity Service API Source: https://docs.strapi.io/cms/api/entity-service/populate ## Basic populating Description: To populate all the root level relations, use populate: '*': (Source: https://docs.strapi.io/cms/api/entity-service/populate#basic-populating) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { populate: '*', }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { populate: ['componentA', 'relationA'], }); ``` ## Advanced populating Description: An object can be passed for more advanced populating: (Source: https://docs.strapi.io/cms/api/entity-service/populate#advanced-populating) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { populate: { relationA: true, repeatableComponent: { fields: ['fieldA'], filters: {}, sort: 'fieldA:asc', populate: { relationB: true, }, }, }, }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { populate: { relationA: { filters: { name: { $contains: 'Strapi', }, }, }, repeatableComponent: { fields: ['someAttributeName'], sort: ['someAttributeName'], populate: { componentRelationA: true, }, }, }, }); ``` ## Populate fragments Description: When dealing with polymorphic content structures (dynamic zones, polymorphic relations, etc...), it is possible to use populate fragments to have a better granularity on the populate strategy. (Source: https://docs.strapi.io/cms/api/entity-service/populate#populate-fragments) Language: JavaScript File path: N/A ```js const entries = await strapi.entityService.findMany('api::article.article', { populate: { dynamicZone: { on: { 'components.foo': { fields: ['title'], filters: { title: { $contains: 'strapi' } }, }, 'components.bar': { fields: ['name'], }, }, }, morphAuthor: { on: { 'plugin::users-permissions.user': { fields: ['username'], }, 'api::author.author': { fields: ['name'], }, }, }, }, }); ``` # GraphQL API Source: https://docs.strapi.io/cms/api/graphql ## GraphQL API Description: :::prerequisites To use the GraphQL API, install the GraphQL plugin: (Source: https://docs.strapi.io/cms/api/graphql#graphql-api) Language: Bash File path: N/A ```bash yarn add @strapi/plugin-graphql ``` --- Language: Bash File path: N/A ```bash npm install @strapi/plugin-graphql ``` Language: JavaScript File path: /config/plugins.js|ts ```js export default { shadowCRUD: true, endpoint: '/graphql', // <— single GraphQL endpoint subscriptions: false, maxLimit: -1, apolloServer: {}, v4CompatibilityMode: process.env.STRAPI_GRAPHQL_V4_COMPATIBILITY_MODE ?? false, }; ``` ## Fetch a single document Description: Documents can be fetched by their documentId. (Source: https://docs.strapi.io/cms/api/graphql#fetch-a-single-document) Language: JSON File path: Example ```json { restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl") { name description } } ``` ## Fetch multiple documents Description: To fetch multiple documents you can use flat queries like the following: (Source: https://docs.strapi.io/cms/api/graphql#fetch-multiple-documents) Language: GRAPHQL File path: Example ```graphql restaurants { documentId title } ``` --- Language: GRAPHQL File path: Example ```graphql { restaurants_connection { nodes { documentId name } pageInfo { pageSize page pageCount total } } } ``` ## Fetch relations Description: The following example fetches all documents from the "Restaurant" content-type, and for each of them, also returns some fields for the many-to-many relation with the "Category" content-type: (Source: https://docs.strapi.io/cms/api/graphql#fetch-relations) Language: GRAPHQL File path: Example ```graphql { restaurants { documentId name description # categories is a many-to-many relation categories { documentId name } } } ``` --- Language: GRAPHQL File path: Example ```graphql { restaurants_connection { nodes { documentId name description # categories is a many-to-many relation categories_connection { nodes { documentId name } } } pageInfo { page pageCount pageSize total } } } ``` --- Language: GRAPHQL File path: N/A ```graphql { restaurants_connection { nodes { documentId name description # many-to-many relation categories_connection { nodes { documentId name } } } pageInfo { page pageCount pageSize total } } } ``` --- Language: GRAPHQL File path: N/A ```graphql { restaurants_connection { nodes { documentId name description # many-to-many relation categories_connection { nodes { documentId name } # not supported pageInfo { page pageCount pageSize total } } } pageInfo { page pageCount pageSize total } } }} ``` ## Fetch media fields Description: The following example fetches the url attribute value for each cover media field attached to each document from the "Restaurants" content-type: (Source: https://docs.strapi.io/cms/api/graphql#fetch-media-fields) Language: GRAPHQL File path: N/A ```graphql { restaurants { images_connection { nodes { documentId url } } } } ``` --- Language: GRAPHQL File path: N/A ```graphql { restaurants { images_connection { nodes { documentId url } } } } ``` Language: GRAPHQL File path: N/A ```graphql { restaurants { images { documentId url } } } ``` ## Fetch components Description: The following example fetches the label, startdate, and enddate attributes values for each closingPeriod component added to each document from the "Restaurants" content-type: (Source: https://docs.strapi.io/cms/api/graphql#fetch-components) Language: GRAPHQL File path: N/A ```graphql { restaurants { closingPeriod { label start_date end_date } } } ``` ## Fetch dynamic zone data Description: The following example fetches data for the label attribute of a "Closingperiod" component from the "Default" components category that can be added to the "dz" dynamic zone: (Source: https://docs.strapi.io/cms/api/graphql#fetch-dynamic-zone-data) Language: GRAPHQL File path: N/A ```graphql { restaurants { dz { __typename ...on ComponentDefaultClosingperiod { # define which attributes to return for the component label } } } } ``` ## Fetch draft or published versions Description: If the Draft & Publish feature is enabled for the content-type, you can add a status parameter to queries to fetch draft or published versions of documents : (Source: https://docs.strapi.io/cms/api/graphql#status) Language: GRAPHQL File path: Example: ```graphql query Query($status: PublicationStatus) { restaurants(status: DRAFT) { documentId name publishedAt # should return null } } ``` --- Language: GRAPHQL File path: Example: ```graphql query Query($status: PublicationStatus) { restaurants(status: PUBLISHED) { documentId name publishedAt } } ``` ## Filter by derived publication cohort Description: Built-in root queries (for example restaurants, restaurants_connection) pass publicationFilter down to populated draft & publish relations on nested fields so relation results match the parent query cohort. (Source: https://docs.strapi.io/cms/api/graphql#publication-filter) Language: GRAPHQL File path: Example: ```graphql query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { restaurants(status: DRAFT, publicationFilter: NEVER_PUBLISHED) { documentId name publishedAt } } ``` --- Language: GRAPHQL File path: Example: ```graphql query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { restaurants(status: PUBLISHED, publicationFilter: PUBLISHED_WITHOUT_DRAFT) { documentId name publishedAt } } ``` --- Language: GRAPHQL File path: Example: ```graphql query Query { restaurants(publicationFilter: MODIFIED) { documentId name publishedAt } } ``` ## Create a new document Description: The following example creates a new document for the "Restaurant" content-type and returns its name and documentId: (Source: https://docs.strapi.io/cms/api/graphql#create-a-new-document) Language: GRAPHQL File path: N/A ```graphql mutation CreateRestaurant($data: RestaurantInput!) { createRestaurant(data: { name: "Pizzeria Arrivederci" }) { name documentId } } ``` Language: GRAPHQL File path: N/A ```graphql mutation CreateCategory { createCategory(data: { Name: "Italian Food" restaurants: ["a1b2c3d4e5d6f7g8h9i0jkl", "bf97tfdumkcc8ptahkng4puo"] }) { documentId Name restaurants { documentId name } } } ``` ## Update an existing document Description: For instance, the following example updates an existing document from the "Restaurants" content-type and give it a new name: (Source: https://docs.strapi.io/cms/api/graphql#update-an-existing-document) Language: GRAPHQL File path: N/A ```graphql mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!) { updateRestaurant( documentId: "bf97tfdumkcc8ptahkng4puo", data: { name: "Pizzeria Amore" } ) { documentId name } } ``` ## Update relations Description: For instance, the following example updates a document from the "Restaurant" content-type and adds a relation to a document from the "Category" content-type through the categories relation field: (Source: https://docs.strapi.io/cms/api/graphql#update-relations) Language: GRAPHQL File path: N/A ```graphql mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!) { updateRestaurant( documentId: "slwsiopkelrpxpvpc27953je", data: { categories: ["kbbvj00fjiqoaj85vmylwi17"] } ) { documentId name categories { documentId Name } } } ``` ## Delete a document Description: To delete a document , pass its documentId: (Source: https://docs.strapi.io/cms/api/graphql#delete-a-document) Language: GRAPHQL File path: N/A ```graphql mutation DeleteRestaurant { deleteRestaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl") { documentId } } ``` ## Update an uploaded media file Description: For instance, the following example updates the alternativeText attribute for a media file whose id is 3: (Source: https://docs.strapi.io/cms/api/graphql#update-an-uploaded-media-file) Language: GRAPHQL File path: N/A ```graphql mutation Mutation($updateUploadFileId: ID!, $info: FileInfoInput) { updateUploadFile( id: 3, info: { alternativeText: "New alt text" } ) { documentId url alternativeText } } ``` ## Delete an uploaded media file Description: When deleting an uploaded media file, pass the media's id (not its documentId). (Source: https://docs.strapi.io/cms/api/graphql#delete-an-uploaded-media-file) Language: GRAPHQL File path: Example: ```graphql mutation DeleteUploadFile($deleteUploadFileId: ID!) { deleteUploadFile(id: 4) { documentId # return its documentId } } ``` ## Filters Description: Code example from "Filters" (Source: https://docs.strapi.io/cms/api/graphql#filters) Language: GRAPHQL File path: Simple ```graphql # eq - returns restaurants with the exact name "Biscotte" { restaurants(filters: { name: { eq: "Biscotte" } }) { name } } # eqi - returns restaurants whose name equals "Biscotte", # comparison is case-insensitive { restaurants(filters: { name: { eqi: "Biscotte" } }) { name } } # ne - returns restaurants whose name is not "Biscotte" { restaurants(filters: { name: { ne: "Biscotte" } }) { name } } # nei - returns restaurants whose name is not "Biscotte", # comparison is case-insensitive { restaurants(filters: { name: { nei: "Biscotte" } }) { name } } # lt - returns restaurants with averagePrice less than 20 { restaurants(filters: { averagePrice: { lt: 20 } }) { name } } # lte - returns restaurants with averagePrice less than or equal to 20 { restaurants(filters: { averagePrice: { lte: 20 } }) { name } } # gt - returns restaurants with averagePrice greater than 20 { restaurants(filters: { averagePrice: { gt: 20 } }) { name } } # gte - returns restaurants with averagePrice greater than or equal to 20 { restaurants(filters: { averagePrice: { gte: 20 } }) { name } } # between - returns restaurants with averagePrice between 10 and 30 { restaurants(filters: { averagePrice: { between: [10, 30] } }) { name } } ``` --- Language: GRAPHQL File path: Simple ```graphql # in - returns restaurants with category either "pizza" or "burger" { restaurants(filters: { category: { in: ["pizza", "burger"] } }) { name } } # notIn - returns restaurants whose category is neither "pizza" nor "burger" { restaurants(filters: { category: { notIn: ["pizza", "burger"] } }) { name } } ``` --- Language: GRAPHQL File path: Simple ```graphql # contains - returns restaurants whose name contains "Pizzeria" { restaurants(filters: { name: { contains: "Pizzeria" } }) { name } } # notContains - returns restaurants whose name does NOT contain "Pizzeria" { restaurants(filters: { name: { notContains: "Pizzeria" } }) { name } } # containsi - returns restaurants whose name contains "pizzeria" (case‑insensitive) { restaurants(filters: { name: { containsi: "pizzeria" } }) { name } } # notContainsi - returns restaurants whose name does NOT contain "pizzeria" (case‑insensitive) { restaurants(filters: { name: { notContainsi: "pizzeria" } }) { name } } # startsWith - returns restaurants whose name starts with "Pizza" { restaurants(filters: { name: { startsWith: "Pizza" } }) { name } } # endsWith - returns restaurants whose name ends with "Inc" { restaurants(filters: { name: { endsWith: "Inc" } }) { name } } ``` --- Language: GRAPHQL File path: Simple ```graphql # null - returns restaurants where description is null { restaurants(filters: { description: { null: true } }) { name } } # notNull - returns restaurants where description is not null { restaurants(filters: { description: { notNull: true } }) { name } } ``` --- Language: GRAPHQL File path: Simple ```graphql # and - both category must be "pizza" AND averagePrice must be < 20 { restaurants(filters: { and: [ { category: { eq: "pizza" } }, { averagePrice: { lt: 20 } } ] }) { name } } # or - category is "pizza" OR category is "burger" { restaurants(filters: { or: [ { category: { eq: "pizza" } }, { category: { eq: "burger" } } ] }) { name } } # not - category must NOT be "pizza" { restaurants(filters: { not: { category: { eq: "pizza" } } }) { name } } ``` Language: JSON File path: Example ```json { restaurants( filters: { and: [ { not: { averagePrice: { gte: 20 } } } { or: [ { name: { eq: "Pizzeria" } } { name: { startsWith: "Pizzeria" } } ] } ] } ) { documentId name averagePrice } } ``` ## Sorting Description: The sorting order can be defined with :asc (ascending order, default, can be omitted) or :desc (for descending order). (Source: https://docs.strapi.io/cms/api/graphql#sorting) Language: JSON File path: Example: ```json { restaurants(sort: "name") { documentId name } } ``` --- Language: JSON File path: Example: ```json { restaurants(sort: "averagePrice:desc") { documentId name averagePrice } } ``` Language: JSON File path: N/A ```json { restaurants(sort: ["name:asc", "averagePrice:desc"]) { documentId name averagePrice } } ``` ## Pagination by page Description: | Parameter | Description | Default | | ---------------------- | ----------- | ------- | | pagination.page | Page number | 1 | | pagination.pageSize | Page size | 10 | (Source: https://docs.strapi.io/cms/api/graphql#pagination-by-page) Language: JSON File path: Example ```json { restaurants_connection(pagination: { page: 1, pageSize: 10 }) { nodes { documentId name } pageInfo { page pageSize pageCount total } } } ``` ## Pagination by offset Description: | Parameter | Description | Default | Maximum | | ------------------ | ---------------------------- | ------- | ------- | | pagination.start | Start value | 0 | - | | pagination.limit | Number of entities to return | 10 | -1 | (Source: https://docs.strapi.io/cms/api/graphql#pagination-by-offset) Language: JSON File path: Example ```json { restaurants_connection(pagination: { start: 10, limit: 19 }) { nodes { documentId name } pageInfo { page pageSize pageCount total } } } ``` ## Fetch all documents in a specific locale Description: Request (Source: https://docs.strapi.io/cms/api/graphql#locale-fetch-all) Language: GRAPHQL File path: N/A ```graphql query { restaurants(locale: "fr") { documentId name locale } } ``` Language: JSON File path: N/A ```json { "data": { "restaurants": [ { "documentId": "a1b2c3d4e5d6f7g8h9i0jkl", "name": "Restaurant Biscotte", "locale": "fr" }, { "documentId": "m9n8o7p6q5r4s3t2u1v0wxyz", "name": "Pizzeria Arrivederci", "locale": "fr" }, ] } } ``` Language: GRAPHQL File path: ./config/plugins.js ```graphql query { restaurants(locale: "fr") { documentId name locale } } ``` --- Language: JSON File path: ./config/plugins.js ```json { "data": { "restaurants": [ { "documentId": "a1b2c3d4e5d6f7g8h9i0jkl", "name": "Restaurant Biscotte", "locale": "fr" }, { "documentId": "m9n8o7p6q5r4s3t2u1v0wxyz", "name": "Pizzeria Arrivederci", "locale": "fr" }, ] } } ``` ## Fetch a document in a specific locale Description: Request (Source: https://docs.strapi.io/cms/api/graphql#locale-fetch) Language: GRAPHQL File path: N/A ```graphql query Restaurant($documentId: ID!, $locale: I18NLocaleCode) { restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl", locale: "fr") { documentId name description locale } } ``` Language: JSON File path: N/A ```json { "data": { "restaurant": { "documentId": "lviw819d5htwvga8s3kovdij", "name": "Restaurant Biscotte", "description": "Bienvenue au restaurant Biscotte!", "locale": "fr" } } } ``` Language: GRAPHQL File path: ./config/plugins.js ```graphql query Restaurant($documentId: ID!, $locale: I18NLocaleCode) { restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl", locale: "fr") { documentId name description locale } } ``` --- Language: JSON File path: ./config/plugins.js ```json { "data": { "restaurant": { "documentId": "lviw819d5htwvga8s3kovdij", "name": "Restaurant Biscotte", "description": "Bienvenue au restaurant Biscotte!", "locale": "fr" } } } ``` ## Create a new localized document Description: The locale field can be passed to create a localized document for a specific locale (for more information about mutations with GraphQL, see the GraphQL API documentation). (Source: https://docs.strapi.io/cms/api/graphql#locale-create) Language: GRAPHQL File path: Example: ```graphql mutation CreateRestaurant($data: RestaurantInput!, $locale: I18NLocaleCode) { createRestaurant( data: { name: "Brasserie Bonjour", description: "Description in French goes here" }, locale: "fr" ) { documentId name description locale } ``` ## Update a document for a specific locale Description: A locale argument can be passed in the mutation to update a document for a given locale (for more information about mutations with GraphQL, see the GraphQL API documentation). (Source: https://docs.strapi.io/cms/api/graphql#locale-update) Language: GRAPHQL File path: Example: ```graphql mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!, $locale: I18NLocaleCode) { updateRestaurant( documentId: "a1b2c3d4e5d6f7g8h9i0jkl" data: { description: "New description in French" }, locale: "fr" ) { documentId name description locale } ``` ## Delete a locale for a document Description: Pass the locale argument in the mutation to delete a specific localization for a document : (Source: https://docs.strapi.io/cms/api/graphql#locale-delete) Language: GRAPHQL File path: N/A ```graphql mutation DeleteRestaurant($documentId: ID!, $locale: I18NLocaleCode) { deleteRestaurant(documentId: "xzmzdo4k0z73t9i68a7yx2kk", locale: "fr") { documentId } } ``` # Advanced policies for GraphQL Source: https://docs.strapi.io/cms/api/graphql/advanced-policies ## Conditional visibility Description: To limit the number of returned entries for unauthenticated users you can write a policy that modifies resolver arguments: (Source: https://docs.strapi.io/cms/api/graphql/advanced-policies#conditional-visibility) Language: TypeScript File path: /src/policies/limit-public-results.ts ```ts export default async (policyContext, config, { strapi }) => { const { state, args } = policyContext; if (!state.user) { args.limit = 4; // only return 4 results for public } return true; }; ``` Language: TypeScript File path: /config/policies.ts ```ts export default { 'api::restaurant.restaurant': { find: [ 'global::limit-public-results' ], }, }; ``` ## Group membership Description: Policies can access policyContext.state.user to check group membership, as in the following example: (Source: https://docs.strapi.io/cms/api/graphql/advanced-policies#group-membership) Language: TypeScript File path: /src/policies/is-group-member.ts ```ts export default async ({ state }, config, { strapi }) => { const userGroups = await strapi.query('plugin::users-permissions.group').findMany({ where: { users: { id: state.user.id } }, }); return userGroups.some(g => g.name === config.group); }; ``` Language: TypeScript File path: /config/policies.ts ```ts export default { 'api::restaurant.restaurant': { find: [{ name: 'global::is-group-member', config: { group: 'editors' } }], }, }; ``` # Advanced queries for GraphQL Source: https://docs.strapi.io/cms/api/graphql/advanced-queries ## Multi-level queries Description: Use nested selection sets to fetch relations several levels deep, as in the following example: (Source: https://docs.strapi.io/cms/api/graphql/advanced-queries#multi-level-queries) Language: GRAPHQL File path: N/A ```graphql { restaurants { documentId name categories { documentId name parent { documentId name } } } } ``` ## Resolver chains Description: Custom resolvers can call other resolvers to reuse existing logic. (Source: https://docs.strapi.io/cms/api/graphql/advanced-queries#resolver-chains) Language: TypeScript File path: /src/api/restaurant/resolvers/restaurant.ts ```ts export default { Query: { restaurants: async (parent, args, ctx) => { const documents = await strapi.documents('api::restaurant.restaurant').findMany(args); return documents.map(doc => ctx.request.graphql.resolve('Restaurant', doc)); }, }, }; ``` # Using the locale parameter with the GraphQL API Source: https://docs.strapi.io/cms/api/graphql/locale ## Fetch all documents in a specific locale Description: Request (Source: https://docs.strapi.io/cms/api/graphql/locale#graphql-fetch-all) Language: GRAPHQL File path: N/A ```graphql query { restaurants(locale: "fr") { documentId name locale } } ``` Language: JSON File path: N/A ```json { "data": { "restaurants": [ { "documentId": "a1b2c3d4e5d6f7g8h9i0jkl", "name": "Restaurant Biscotte", "locale": "fr" }, { "documentId": "m9n8o7p6q5r4s3t2u1v0wxyz", "name": "Pizzeria Arrivederci", "locale": "fr" }, ] } } ``` Language: GRAPHQL File path: N/A ```graphql query { restaurants(locale: "fr") { documentId name locale } } ``` --- Language: JSON File path: N/A ```json { "data": { "restaurants": [ { "documentId": "a1b2c3d4e5d6f7g8h9i0jkl", "name": "Restaurant Biscotte", "locale": "fr" }, { "documentId": "m9n8o7p6q5r4s3t2u1v0wxyz", "name": "Pizzeria Arrivederci", "locale": "fr" }, ] } } ``` ## Fetch a document in a specific locale Description: Request (Source: https://docs.strapi.io/cms/api/graphql/locale#graphql-fetch) Language: GRAPHQL File path: N/A ```graphql query Restaurant($documentId: ID!, $locale: I18NLocaleCode) { restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl", locale: "fr") { documentId name description locale } } ``` Language: JSON File path: N/A ```json { "data": { "restaurant": { "documentId": "lviw819d5htwvga8s3kovdij", "name": "Restaurant Biscotte", "description": "Bienvenue au restaurant Biscotte!", "locale": "fr" } } } ``` Language: GRAPHQL File path: N/A ```graphql query Restaurant($documentId: ID!, $locale: I18NLocaleCode) { restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl", locale: "fr") { documentId name description locale } } ``` --- Language: JSON File path: N/A ```json { "data": { "restaurant": { "documentId": "lviw819d5htwvga8s3kovdij", "name": "Restaurant Biscotte", "description": "Bienvenue au restaurant Biscotte!", "locale": "fr" } } } ``` ## Create a new localized document Description: The locale field can be passed to create a localized document for a specific locale (for more information about mutations with GraphQL, see the GraphQL API documentation). (Source: https://docs.strapi.io/cms/api/graphql/locale#graphql-create) Language: GRAPHQL File path: Example: ```graphql mutation CreateRestaurant($data: RestaurantInput!, $locale: I18NLocaleCode) { createRestaurant( data: { name: "Brasserie Bonjour", description: "Description in French goes here" }, locale: "fr" ) { documentId name description locale } ``` ## Update a document for a specific locale Description: A locale argument can be passed in the mutation to update a document for a given locale (for more information about mutations with GraphQL, see the GraphQL API documentation). (Source: https://docs.strapi.io/cms/api/graphql/locale#graphql-update) Language: GRAPHQL File path: Example: ```graphql mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!, $locale: I18NLocaleCode) { updateRestaurant( documentId: "a1b2c3d4e5d6f7g8h9i0jkl" data: { description: "New description in French" }, locale: "fr" ) { documentId name description locale } ``` ## Delete a locale for a document Description: Pass the locale argument in the mutation to delete a specific localization for a document : (Source: https://docs.strapi.io/cms/api/graphql/locale#graphql-delete) Language: GRAPHQL File path: N/A ```graphql mutation DeleteRestaurant($documentId: ID!, $locale: I18NLocaleCode) { deleteRestaurant(documentId: "xzmzdo4k0z73t9i68a7yx2kk", locale: "fr") { documentId } } ``` # OpenAPI specification Source: https://docs.strapi.io/cms/api/openapi ## CLI usage Description: Executing the command without any arguments will generate a specification.json file at the root of your Strapi folder project: (Source: https://docs.strapi.io/cms/api/openapi#cli-usage) Language: Bash File path: N/A ```bash yarn strapi openapi generate ``` --- Language: Bash File path: N/A ```bash npm run strapi openapi generate ``` Language: Bash File path: N/A ```bash yarn strapi openapi generate --output ./docs/api-spec.json ``` --- Language: Bash File path: N/A ```bash npm run strapi openapi generate -- --output ./docs/api-spec.json ``` ## Specification structure and content Description: Code example from "Specification structure and content" (Source: https://docs.strapi.io/cms/api/openapi#specification-structure-and-content) Language: JSON File path: N/A ```json { "openapi": "3.1.0", "x-powered-by": "strapi", "x-strapi-version": "5.21.0", "info": { "title": "My Strapi API", "description": "API documentation for My Strapi API", "version": "1.0.0" }, "paths": { "/api/articles": { "get": { "operationId": "article/get/articles", "parameters": [ { "name": "fields", "in": "query", "schema": { "type": "array", "items": { "type": "string" } } } ], "responses": { "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Article" } } } } } } } } } } }, "components": { "schemas": { "Article": { "type": "object", "properties": { "id": { "type": "string" }, "title": { "type": "string" }, "content": { "type": "string" } } } } } } ``` ## Integrating with Swagger UI Description: Generate a specification: (Source: https://docs.strapi.io/cms/api/openapi#integrating-with-swagger-ui) Language: Bash File path: /example-openapi-spec.js ```bash yarn strapi openapi generate --output ./public/swagger-spec.json ``` --- Language: Bash File path: /example-openapi-spec.js ```bash npm run strapi openapi generate -- --output ./public/swagger-spec.json ``` Language: JavaScript File path: /config/middlewares.js ```js module.exports = [ 'strapi::logger', 'strapi::errors', { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'script-src': ["'self'", "'unsafe-inline'", 'https://unpkg.com'], 'style-src': ["'self'", "'unsafe-inline'", 'https://unpkg.com'], 'connect-src': ["'self'", 'https:'], 'img-src': ["'self'", 'data:', 'blob:', 'https:'], 'media-src': ["'self'", 'data:', 'blob:'], upgradeInsecureRequests: null, }, }, }, }, 'strapi::cors', 'strapi::poweredBy', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` --- Language: TypeScript File path: /config/middlewares.ts ```ts export default [ 'strapi::logger', 'strapi::errors', { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'script-src': ["'self'", "'unsafe-inline'", 'https://unpkg.com'], 'style-src': ["'self'", "'unsafe-inline'", 'https://unpkg.com'], 'connect-src': ["'self'", 'https:'], 'img-src': ["'self'", 'data:', 'blob:', 'https:'], 'media-src': ["'self'", 'data:', 'blob:'], upgradeInsecureRequests: null, }, }, }, }, 'strapi::cors', 'strapi::poweredBy', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` Language: JavaScript File path: /config/middlewares.js ```js API Documentation
``` # Query Engine API Source: https://docs.strapi.io/cms/api/query-engine ## Basic usage Description: The Query Engine is available through strapi.db.query: (Source: https://docs.strapi.io/cms/api/query-engine#basic-usage) Language: JavaScript File path: N/A ```js strapi.db.query('api::blog.article').findMany({ // uid syntax: 'api::api-name.content-type-name' where: { title: { $startsWith: '2021', $endsWith: 'v4', }, }, populate: { category: true, }, }); ``` # Bulk Operations Source: https://docs.strapi.io/cms/api/query-engine/bulk-operations ## Example Description: :::caution MySQL will only return an array of one id containing the last inserted id, not the entire list. (Source: https://docs.strapi.io/cms/api/query-engine/bulk-operations#example) Language: JavaScript File path: N/A ```js await strapi.db.query("api::blog.article").createMany({ data: [ { title: "ABCD", }, { title: "EFGH", }, ], }); // { count: 2 , ids: [1,2]} ``` Language: JavaScript File path: N/A ```js await strapi.db.query("api::shop.article").updateMany({ where: { price: 20, }, data: { price: 18, }, }); // { count: 42 } ``` Language: JavaScript File path: N/A ```js await strapi.db.query("api::blog.article").deleteMany({ where: { title: { $startsWith: "v3", }, }, }); // { count: 42 } ``` ## Parameters Description: | Parameter | Type | Description | | --------- | --------------------------------------------------------- | ------------------------------------------------------- | | where | WhereParameter | Filters to use | (Source: https://docs.strapi.io/cms/api/query-engine/bulk-operations#parameters) Language: JavaScript File path: N/A ```js const count = await strapi.db.query("api::blog.article").count({ where: { title: { $startsWith: "v3", }, }, }); // 12 ``` # Filtering with the Query Engine API Source: https://docs.strapi.io/cms/api/query-engine/filtering ## $and Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#and) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { $and: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: 'Hello World', createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, }); ``` ## $or Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#or) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { $or: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` ## $not Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#not) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { $not: { title: 'Hello World', }, }, }); ``` --- Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $not: { $contains: 'Hello World', }, }, }, }); ``` ## $eq Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#eq) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $eq: 'Hello World', }, }, }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: 'Hello World', }, }); ``` ## $eqi Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#eqi) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $eqi: 'HELLO World', }, }, }); ``` ## $ne Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#ne) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $ne: 'ABCD', }, }, }); ``` ## $nei Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#nei) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $nei: 'abcd', }, }, }); ``` ## $in Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#in) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $in: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: ['Hello', 'Hola', 'Bonjour'], }, }); ``` ## $notIn Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#notin) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $notIn: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` ## $lt Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#lt) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { rating: { $lt: 10, }, }, }); ``` ## $lte Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#lte) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { rating: { $lte: 10, }, }, }); ``` ## $gt Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#gt) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { rating: { $gt: 5, }, }, }); ``` ## $gte Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#gte) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { rating: { $gte: 5, }, }, }); ``` ## $between Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#between) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { rating: { $between: [1, 20], }, }, }); ``` ## $contains Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#contains) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $contains: 'Hello', }, }, }); ``` ## $notContains Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#notcontains) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $notContains: 'Hello', }, }, }); ``` ## $containsi Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#containsi) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $containsi: 'hello', }, }, }); ``` ## $notContainsi Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#notcontainsi) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $notContainsi: 'hello', }, }, }); ``` ## $startsWith Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#startswith) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $startsWith: 'ABCD', }, }, }); ``` ## $endsWith Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#endswith) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $endsWith: 'ABCD', }, }, }); ``` ## $null Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#null) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $null: true, }, }, }); ``` ## $notNull Description: Example (Source: https://docs.strapi.io/cms/api/query-engine/filtering#notnull) Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $notNull: true, }, }, }); ``` # Ordering & Pagination with the Query Engine API Source: https://docs.strapi.io/cms/api/query-engine/order-pagination ## Single Description: To order results returned by the Query Engine, use the orderBy parameter. (Source: https://docs.strapi.io/cms/api/query-engine/order-pagination#single) Language: JavaScript File path: N/A ```js strapi.db.query('api::article.article').findMany({ orderBy: 'id', }); // single with direction strapi.db.query('api::article.article').findMany({ orderBy: { id: 'asc' }, }); ``` ## Multiple Description: To order results returned by the Query Engine, use the orderBy parameter. (Source: https://docs.strapi.io/cms/api/query-engine/order-pagination#multiple) Language: JavaScript File path: N/A ```js strapi.db.query('api::article.article').findMany({ orderBy: ['id', 'name'], }); // multiple with direction strapi.db.query('api::article.article').findMany({ orderBy: [{ title: 'asc' }, { publishedAt: 'desc' }], }); ``` ## Relational ordering Description: To order results returned by the Query Engine, use the orderBy parameter. (Source: https://docs.strapi.io/cms/api/query-engine/order-pagination#relational-ordering) Language: JavaScript File path: N/A ```js strapi.db.query('api::article.article').findMany({ orderBy: { author: { name: 'asc', }, }, }); ``` ## Pagination Description: To paginate results returned by the Query Engine API, use the offset and limit parameters: (Source: https://docs.strapi.io/cms/api/query-engine/order-pagination#pagination) Language: JavaScript File path: N/A ```js strapi.db.query('api::article.article').findMany({ offset: 15, limit: 10, }); ``` # Populating with the Query Engine API Source: https://docs.strapi.io/cms/api/query-engine/populating ## Populating with the Query Engine API Description: To populate all the root level relations, use populate: true: (Source: https://docs.strapi.io/cms/api/query-engine/populating#populating-with-the-query-engine-api) Language: JavaScript File path: N/A ```js strapi.db.query('api::article.article').findMany({ populate: true, }); ``` Language: JavaScript File path: N/A ```js strapi.db.query('api::article.article').findMany({ populate: ['componentA', 'relationA'], }); ``` Language: JavaScript File path: N/A ```js strapi.db.query('api::article.article').findMany({ populate: { componentB: true, dynamiczoneA: true, relation: someLogic || true, }, }); ``` Language: JavaScript File path: N/A ```js strapi.db.query('api::article.article').findMany({ populate: { relationA: { where: { name: { $contains: 'Strapi', }, }, }, repeatableComponent: { select: ['someAttributeName'], orderBy: ['someAttributeName'], populate: { componentRelationA: true, }, }, dynamiczoneA: true, }, }); ``` Language: JavaScript File path: N/A ```js strapi.db.query('api::article.article').findMany('api::article.article', { populate: { dynamicZone: { on: { 'components.foo': { select: ['title'], where: { title: { $contains: 'strapi' } }, }, 'components.bar': { select: ['name'], }, }, }, morphAuthor: { on: { 'plugin::users-permissions.user': { select: ['username'], }, 'api::author.author': { select: ['name'], }, }, }, }, }); ``` # Single Operations Source: https://docs.strapi.io/cms/api/query-engine/single-operations ## Example Description: | select | String, or Array of strings | Attributes to return | | where | WhereParameter | Filters to use | | offset | Integer | Number of entries to skip | | orderBy | OrderByParameter | Order definition | | populate | PopulateParameter | Relations to populate | (Source: https://docs.strapi.io/cms/api/query-engine/single-operations#example) Language: JavaScript File path: N/A ```js const entry = await strapi.db.query('api::blog.article').findOne({ select: ['title', 'description'], where: { title: 'Hello World' }, populate: { category: true }, }); ``` Language: JavaScript File path: N/A ```js const entries = await strapi.db.query('api::blog.article').findMany({ select: ['title', 'description'], where: { title: 'Hello World' }, orderBy: { publishedAt: 'DESC' }, populate: { category: true }, }); ``` --- Language: JavaScript File path: N/A ```js const [entries, count] = await strapi.db.query('api::blog.article').findWithCount({ select: ['title', 'description'], where: { title: 'Hello World' }, orderBy: { title: 'DESC' }, populate: { category: true }, }); ``` Language: JavaScript File path: N/A ```js const entry = await strapi.db.query('api::blog.article').create({ data: { title: 'My Article', }, }); ``` Language: JavaScript File path: N/A ```js const entry = await strapi.db.query('api::blog.article').update({ where: { id: 1 }, data: { title: 'xxx', }, }); ``` Language: JavaScript File path: N/A ```js const entry = await strapi.db.query('api::blog.article').delete({ where: { id: 1 }, }); ``` # REST API reference Source: https://docs.strapi.io/cms/api/rest ## Get documents Description: GET /api/:pluralApiId — List documents (Source: https://docs.strapi.io/cms/api/rest#get-all) Language: Bash File path: N/A ```bash curl 'http://localhost:1337/api/restaurants' \\ -H 'Authorization: Bearer ' ``` --- Language: JavaScript File path: N/A ```javascript const response = await fetch( 'http://localhost:1337/api/restaurants', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ## Get a document Description: GET /api/:pluralApiId/:documentId — Get a document (Source: https://docs.strapi.io/cms/api/rest#get) Language: Bash File path: N/A ```bash curl 'http://localhost:1337/api/restaurants/znrlzntu9ei5onjvwfaalu2v' \\ -H 'Authorization: Bearer ' ``` --- Language: JavaScript File path: N/A ```javascript const response = await fetch( 'http://localhost:1337/api/restaurants/znrlzntu9ei5onjvwfaalu2v', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ## Create a document Description: POST /api/:pluralApiId — Create a document (Source: https://docs.strapi.io/cms/api/rest#create) Language: Bash File path: N/A ```bash curl -X POST \\ 'http://localhost:1337/api/restaurants' \\ -H 'Authorization: Bearer ' \\ -H 'Content-Type: application/json' \\ -d '{ "data": { "Name": "Restaurant D", "Description": [ { "type": "paragraph", "children": [{ "type": "text", "text": "A very short description goes here." }] } ] } }' ``` --- Language: JavaScript File path: N/A ```javascript const response = await fetch( 'http://localhost:1337/api/restaurants', { method: 'POST', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json', }, body: JSON.stringify({ data: { Name: 'Restaurant D', Description: [ { type: 'paragraph', children: [{ type: 'text', text: 'A very short description goes here.' }], }, ], }, }), } ); const data = await response.json(); ``` ## Update a document Description: PUT /api/:pluralApiId/:documentId — Update a document (Source: https://docs.strapi.io/cms/api/rest#update) Language: Bash File path: N/A ```bash curl -X PUT \\ 'http://localhost:1337/api/restaurants/hgv1vny5cebq2l3czil1rpb3' \\ -H 'Authorization: Bearer ' \\ -H 'Content-Type: application/json' \\ -d '{ "data": { "Name": "BMK Paris Bamako", "Description": [ { "type": "paragraph", "children": [{ "type": "text", "text": "A very short description goes here." }] } ] } }' ``` --- Language: JavaScript File path: N/A ```javascript const response = await fetch( 'http://localhost:1337/api/restaurants/hgv1vny5cebq2l3czil1rpb3', { method: 'PUT', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json', }, body: JSON.stringify({ data: { Name: 'BMK Paris Bamako', Description: [ { type: 'paragraph', children: [{ type: 'text', text: 'A very short description goes here.' }], }, ], }, }), } ); const data = await response.json(); ``` ## Delete a document Description: DELETE /api/:pluralApiId/:documentId — Delete a document (Source: https://docs.strapi.io/cms/api/rest#delete) Language: Bash File path: N/A ```bash curl -X DELETE \\ 'http://localhost:1337/api/restaurants/bw64dnu97i56nq85106yt4du' \\ -H 'Authorization: Bearer ' ``` --- Language: JavaScript File path: N/A ```javascript const response = await fetch( 'http://localhost:1337/api/restaurants/bw64dnu97i56nq85106yt4du', { method: 'DELETE', headers: { Authorization: 'Bearer ', }, } ); ``` # Filters Source: https://docs.strapi.io/cms/api/rest/filters ## Example: Find users having 'John' as a first name Description: GET /api/users — Find users having (Source: https://docs.strapi.io/cms/api/rest/filters#example-find-users-having-john-as-a-first-name) Language: Bash File path: N/A ```bash GET /api/users?filters[username][$eq]=John ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ filters: { username: { $eq: 'John', }, }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/users?${query}`); ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "znrlzntu9ei5onjvwfaalu2v", "username": "John", "email": "john@test.com", "provider": "local", "confirmed": true, "blocked": false, "createdAt": "2021-12-03T20:08:17.740Z", "updatedAt": "2021-12-03T20:08:17.740Z" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ## Example: Find multiple restaurants with ids 3, 6,8 Description: GET /api/restaurants — Find multiple restaurants with ids 3, 6, 8 (Source: https://docs.strapi.io/cms/api/rest/filters#example-find-multiple-restaurants-with-ids-3-6-8) Language: Bash File path: N/A ```bash GET /api/restaurants?filters[id][$in][0]=3&filters[id][$in][1]=6&filters[id][$in][2]=8 ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ filters: { id: { $in: [3, 6, 8], }, }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` Language: JSON File path: N/A ```json { "data": [ { "id": 3, "documentId": "ethwxjxtvuxl89jq720e38uk", "name": "test3" }, { "id": 6, "documentId": "ethwxjxtvuxl89jq720e38uk", "name": "test6" }, { "id": 8, "documentId": "cf07g1dbusqr8mzmlbqvlegx", "name": "test8" } ], "meta": {} } ``` ## Complex filtering Description: GET /api/books — Find books with 2 possible dates and a specific author (Source: https://docs.strapi.io/cms/api/rest/filters#complex-filtering) Language: Bash File path: N/A ```bash GET /api/books?filters[$and][0][$or][0][date][$eq]=2020-01-01&filters[$and][0][$or][1][date][$eq]=2020-01-02&filters[$and][1][author][name][$eq]=Kai%20doe ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ filters: { $and: [ { $or: [ { date: { $eq: '2020-01-01', }, }, { date: { $eq: '2020-01-02', }, }, ], }, { author: { name: { $eq: 'Kai doe', }, }, }, ], }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/books?${query}`); ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "rxngxzclq0zdaqtvz67hj38d", "name": "test1", "date": "2020-01-01" }, { "id": 2, "documentId": "kjkhff4e269a50b4vi16stst", "name": "test2", "date": "2020-01-02" } ], "meta": {} } ``` ## Deep filtering Description: GET /api/restaurants — Find restaurants owned by a chef who belongs to a 5-star restaurant (Source: https://docs.strapi.io/cms/api/rest/filters#deep-filtering) Language: Bash File path: N/A ```bash GET /api/restaurants?filters[chef][restaurants][stars][$eq]=5 ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ filters: { chef: { restaurants: { stars: { $eq: 5, }, }, }, }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "cvsz61qg33rtyv1qljb1nrtg", "name": "GORDON RAMSAY STEAK", "stars": 5 }, { "id": 2, "documentId": "uh17h7ibw0g8thit6ivi71d8", "name": "GORDON RAMSAY BURGER", "stars": 5 } ], "meta": {} } ``` # How to populate creator fields Source: https://docs.strapi.io/cms/api/rest/guides/populate-creator-fields ## How to populate creator fields such as createdBy and updatedBy Description: Open the content-type schema.json file. (Source: https://docs.strapi.io/cms/api/rest/guides/populate-creator-fields#how-to-populate-creator-fields-such-as-createdby-and-updatedby) Language: JSON File path: N/A ```json "options": { "draftAndPublish": true, "populateCreatorFields": true }, ``` Language: JavaScript File path: ./src/api/test/middlewares/defaultTestPopulate.js ```js "use strict"; module.exports = (config, { strapi }) => { return async (ctx, next) => { if (!ctx.query.populate) { ctx.query.populate = ["createdBy", "updatedBy"]; } await next(); }; }; ``` Language: JavaScript File path: ./src/api/test/routes/test.js ```js "use strict"; const { createCoreRouter } = require("@strapi/strapi").factories; module.exports = createCoreRouter("api::test.test", { config: { find: { middlewares: ["api::test.default-test-populate"], }, findOne: { middlewares: ["api::test.default-test-populate"], }, }, }); ``` # Understanding populate Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate ## Populate specific relations and fields Description: Populate as an object (to populate 1 relation several levels deep): (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#populate-specific-relations-and-fields) Language: JSON File path: N/A ```json { populate: { category: { populate: ['restaurants'], }, }, } ``` Language: JSON File path: N/A ```json { populate: [ 'articles', 'restaurants' ], } ``` ## Populate several levels deep for specific relations Description: :::tip The syntax for advanced query parameters can be quite complex to build manually. (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#populate-several-levels-deep-for-specific-relations) Language: JSON File path: N/A ```json { populate: { category: { populate: ['restaurants'], }, }, } ``` ## Populate components Description: :::tip The syntax for advanced query parameters can be quite complex to build manually. (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#populate-components) Language: JSON File path: N/A ```json { populate: [ 'seoData', 'seoData.sharedImage', 'seoData.sharedImage.media', ], }, ``` ## Populate dynamic zones Description: :::tip The syntax for advanced query parameters can be quite complex to build manually. (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#populate-dynamic-zones) Language: Bash File path: N/A ```bash GET /api/articles?populate[0]=blocks ``` --- Language: Bash File path: N/A ```bash GET /api/articles?populate[blocks][populate]=* ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "e8cnux5ejxyqrejd5addfv", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "blocks": [ { "id": 2, "documentId": "it9bbhcgc6mcfsqas7h1dp", "__component": "blocks.related-articles" }, { "id": 2, "documentId": "ugagwkoce7uqb0k2yof4lz", "__component": "blocks.cta-command-line", "theme": "primary", "title": "Want to give a try to a Strapi starter?", "text": "❤️", "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git" } ] }, { "id": 2, "// …": true }, { "id": 3, "// …": true }, { "id": 4, "// …": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` --- Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "c14dwiff3b4os6gs4yyrag", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "blocks": [ { "id": 2, "documentId": "lu16w9g4jri8ppiukg542j", "__component": "blocks.related-articles", "header": { "id": 2, "documentId": "c2imt19iywk27hl2ftph7s", "theme": "primary", "label": "More, I want more!", "title": "Similar articles" }, "articles": { "data": [ { "id": 2, "documentId": "isn91s2bxk3jib97evvjni", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "…" }, { "id": 3, "documentId": "yz6lg7tp5ph8dr79gidoyl", "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "…" }, { "id": 4, "documentId": "z5jnfvyuj07fogzh1kcbd3", "title": "If you don't finish your plate in these countries, you might offend someone", "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone", "createdAt": "2021-11-15T13:33:19.948Z", "updatedAt": "2023-06-02T10:59:35.148Z", "publishedAt": "2022-09-22T12:35:53.899Z", "locale": "en", "ckeditor_content": "…" } ] } }, { "id": 2, "documentId": "vpihrdqj5984k8ynrc39p0", "__component": "blocks.cta-command-line", "theme": "primary", "title": "Want to give a try to a Strapi starter?", "text": "❤️", "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git" } ] }, { "id": 2, "// …": true }, { "id": 3, "// …": true }, { "id": 4, "// …": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` Language: JSON File path: N/A ```json { populate: { blocks: { // asking to populate the blocks dynamic zone populate: '*' // populating all first-level fields in all components } }, } ``` ## Example: Populating the dynamic zone and applying a shared strategy to its components Description: :::tip The syntax for advanced query parameters can be quite complex to build manually. (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#example-populating-the-dynamic-zone-and-applying-a-shared-strategy-to-its-components) Language: JSON File path: N/A ```json { populate: { blocks: { // asking to populate the blocks dynamic zone on: { // using a detailed population strategy to explicitly define what you want 'blocks.related-articles': { populate: { 'articles': { populate: ['image'] } } }, 'blocks.cta-command-line': { populate: '*' } }, }, }, } ``` ## Example: Without `populate` Description: GET /api/articles — Without populate (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#example-without-populate) Language: Bash File path: N/A ```bash GET /api/articles ``` --- Language: Bash File path: N/A ```bash GET /api/articles ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "t3q2i3v1z2j7o8p6d0o4xxg", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "// truncated content" }, { "id": 2, "documentId": "k2r5l0i9g3u2j3b4p7f0sed", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "// truncated content" }, { "id": 3, "documentId": "k6m6l9q0n6v9z2m3i0z5jah", "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "// truncated content" }, { "id": 4, "documentId": "d5m4b6z6g5d9e3v1k9n5gbn", "title": "If you don't finish your plate in these countries, you might offend someone", "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone", "createdAt": "2021-11-15T13:33:19.948Z", "updatedAt": "2023-06-02T10:59:35.148Z", "publishedAt": "2022-09-22T12:35:53.899Z", "locale": "en", "ckeditor_content": "// truncated content" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` --- Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "x2m0d7d9o4m2z3u2r2l9yes", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…" }, { "id": 2, "documentId": "k6m6l9q0n6v9z2m3i0z5jah", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "…" }, { "id": 3, "documentId": "o5d4b0l4p8l4o4k5n1l3rxa", "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "…" }, { "id": 4, "documentId": "t3q2i3v1z2j7o8p6d0o4xxg", "title": "If you don't finish your plate in these countries, you might offend someone", "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone", "createdAt": "2021-11-15T13:33:19.948Z", "updatedAt": "2023-06-02T10:59:35.148Z", "publishedAt": "2022-09-22T12:35:53.899Z", "locale": "en", "ckeditor_content": "…" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ## Example: With `populate=*` Description: GET /api/articles — With populate=* (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#example-with-populate) Language: Bash File path: N/A ```bash GET /api/articles?populate=* ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "// truncated content", "image": { "data": { "id": 12, "documentId": "o5d4b0l4p8l4o4k5n1l3rxa", "name": "Basque dish", "alternativeText": "Basque dish", "caption": "Basque dish", "width": 758, "height": 506, "formats": { "thumbnail": { "name": "thumbnail_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg", "hash": "thumbnail_basque_cuisine_17fa4567e0_f033424240", "ext": ".jpeg", "mime": "image/jpeg", "width": 234, "height": 156, "size": 11.31, "path": null, "url": "/uploads/thumbnail_basque_cuisine_17fa4567e0_f033424240.jpeg" }, "medium": { "name": "medium_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg", "hash": "medium_basque_cuisine_17fa4567e0_f033424240", "ext": ".jpeg", "mime": "image/jpeg", "width": 750, "height": 501, "size": 82.09, "path": null, "url": "/uploads/medium_basque_cuisine_17fa4567e0_f033424240.jpeg" }, "small": { "name": "small_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg", "hash": "small_basque_cuisine_17fa4567e0_f033424240", "ext": ".jpeg", "mime": "image/jpeg", "width": 500, "height": 334, "size": 41.03, "path": null, "url": "/uploads/small_basque_cuisine_17fa4567e0_f033424240.jpeg" } }, "hash": "basque_cuisine_17fa4567e0_f033424240", "ext": ".jpeg", "mime": "image/jpeg", "size": 58.209999999999994, "url": "/uploads/basque_cuisine_17fa4567e0_f033424240.jpeg", "previewUrl": null, "provider": "local", "provider_metadata": null, "createdAt": "2021-11-23T14:05:33.460Z", "updatedAt": "2021-11-23T14:05:46.084Z" } } }, "blocks": [ { "id": 2, "__component": "blocks.related-articles" }, { "id": 2, "documentId": "w8r5k8o8v0t9l9e0d7y6vco", "__component": "blocks.cta-command-line", "theme": "primary", "title": "Want to give a try to a Strapi starter?", "text": "❤️", "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git" } ], "seo": { "id": 1, "documentId": "h7c8d0u3i3q5v1j3j3r4cxf", "metaTitle": "Articles - FoodAdvisor", "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor", "keywords": "food", "metaRobots": null, "structuredData": null, "metaViewport": null, "canonicalURL": null }, "category": { "data": { "id": 4, "documentId": "t1t3d9k6n1k5a6r8l7f8rox", "name": "European", "slug": "european", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } }, "localizations": { "data": [ { "id": 10, "documentId": "h7c8d0u3i3q5v1j3j3r4cxf", "title": "Voici pourquoi il faut essayer la cuisine basque, selon un chef basque", "slug": "voici-pourquoi-il-faut-essayer-la-cuisine-basque-selon-un-chef-basque", "createdAt": "2021-11-18T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.606Z", "publishedAt": "2022-09-22T13:00:00.069Z", "locale": "fr-FR", "ckeditor_content": "// truncated content" } ] } } }, { "id": 2, "// truncated content": true }, { "id": 3, "// truncated content": true }, { "id": 4, "// truncated content": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ## Example: With `populate[0]=category` Description: GET /api/articles — With populate[0]=category (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#example-with-populate-0-category) Language: Bash File path: N/A ```bash GET /api/articles?populate[0]=category ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "w8r5k8o8v0t9l9e0d7y6vco", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 4, "documentId": "u6x8u7o7j5q1l5y3t8j9yxi", "name": "European", "slug": "european", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 2, "documentId": "k6m6l9q0n6v9z2m3i0z5jah", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 13, "documentId": "x2m0d7d9o4m2z3u2r2l9yes", "name": "Chinese", "slug": "chinese", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 3, "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 3, "documentId": "h7c8d0u3i3q5v1j3j3r4cxf", "name": "International", "slug": "international", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 4, "documentId": "t1t3d9k6n1k5a6r8l7f8rox", "title": "If you don't finish your plate in these countries, you might offend someone", "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone", "createdAt": "2021-11-15T13:33:19.948Z", "updatedAt": "2023-06-02T10:59:35.148Z", "publishedAt": "2022-09-22T12:35:53.899Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 3, "documentId": "u6x8u7o7j5q1l5y3t8j9yxi", "name": "International", "slug": "international", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ## Example: With 1-level deep population Description: GET /api/articles — With 1-level deep population (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#example-with-1-level-deep-population) Language: Bash File path: N/A ```bash GET /api/articles?populate[0]=category ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "9ih6hy1bnma3q3066kdwt3", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 4, "name": "European", "slug": "european", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 2, "documentId": "sen6qfgxcac13pwchf8xbu", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 13, "documentId": "r3rhzcxd7gjx07vkq3pia5", "name": "Chinese", "slug": "chinese", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 3, "documentId": "s9uu7rkukhfcsmj2e60b67", "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 3, "documentId": "4sevz15w6bdol6y4t8kblk", "name": "International", "slug": "international", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 4, "documentId": "iy5ifm3xj8q0t8vlq6l23h", "title": "If you don't finish your plate in these countries, you might offend someone", "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone", "createdAt": "2021-11-15T13:33:19.948Z", "updatedAt": "2023-06-02T10:59:35.148Z", "publishedAt": "2022-09-22T12:35:53.899Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 3, "documentId": "0eor603u8qej933maphdv3", "name": "International", "slug": "international", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ## Example: With 2-level deep population Description: GET /api/articles — With 2-level deep population (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#example-with-2-level-deep-population) Language: Bash File path: N/A ```bash GET /api/articles?populate[category][populate][0]=restaurants ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "iy5ifm3xj8q0t8vlq6l23h", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 4, "name": "European", "slug": "european", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z", "restaurants": { "data": [ { "id": 1, "documentId": "ozlqrdxpnjb7wtvf6lp74v", "name": "Mint Lounge", "slug": "mint-lounge", "price": "p3", "createdAt": "2021-11-09T14:07:47.125Z", "updatedAt": "2021-11-23T16:41:30.504Z", "publishedAt": "2021-11-23T16:41:30.501Z", "locale": "en" }, { "id": 9, "// truncated content": true }, { "id": 10, "// truncated content": true }, { "id": 12, "// truncated content": true }, { "id": 21, "// truncated content": true }, { "id": 26, "// truncated content": true } ] } } } }, { "id": 2, "// truncated content": true }, { "id": 3, "// truncated content": true }, { "id": 4, "// truncated content": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ## Example: Only 1st level component Description: GET /api/articles — Only 1st level component (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#example-only-1st-level-component) Language: Bash File path: N/A ```bash GET /api/articles?populate[0]=seo ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "md60m5cy3dula5g87x1uar", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "seo": { "id": 1, "documentId": "kqcwhq6hes25kt9ebj8x7j", "metaTitle": "Articles - FoodAdvisor", "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor", "keywords": "food", "metaRobots": null, "structuredData": null, "metaViewport": null, "canonicalURL": null } }, { "id": 2, "// truncated content": true }, { "id": 3, "// truncated content": true }, { "id": 4, "// truncated content": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ## Example: 1st level and 2nd level component Description: GET /api/articles — 1st level and 2nd level component (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#example-1st-level-and-2nd-level-component) Language: Bash File path: N/A ```bash GET /api/articles?populate[0]=seo&populate[1]=seo.metaSocial ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "c2imt19iywk27hl2ftph7s", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "seo": { "id": 1, "documentId": "e8cnux5ejxyqrejd5addfv", "metaTitle": "Articles - FoodAdvisor", "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor", "keywords": "food", "metaRobots": null, "structuredData": null, "metaViewport": null, "canonicalURL": null, "metaSocial": [ { "id": 1, "documentId": "ks7xsp9fewoi0qljcz9qa0", "socialNetwork": "Facebook", "title": "Browse our best articles about food and restaurants ", "description": "Discover our articles about food, restaurants, bars and more!" } ] } }, { "id": 2, "// truncated content": true }, { "id": 3, "// truncated content": true }, { "id": 4, "// truncated content": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ## Example Description: GET /api/articles — Detailed population strategy for dynamic zones (Source: https://docs.strapi.io/cms/api/rest/guides/understanding-populate#example) Language: Bash File path: N/A ```bash GET /api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=* ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "it9bbhcgc6mcfsqas7h1dp", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "// truncated content", "blocks": [ { "id": 2, "documentId": "e8cnux5ejxyqrejd5addfv", "__component": "blocks.related-articles", "articles": { "data": [ { "id": 2, "documentId": "wkgojrcg5bkz8teqx1foz7", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "// truncated content", "image": { "data": { "// …": true } } } }, { "id": 3, "// …": true }, { "id": 4, "// …": true } ] } }, { "id": 2, "__component": "blocks.cta-command-line", "theme": "primary", "title": "Want to give a try to a Strapi starter?", "text": "❤️", "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git" } ] }, { "id": 2, "// …": true }, { "id": 3, "documentId": "z5jnfvyuj07fogzh1kcbd3", "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "// truncated content", "blocks": [ { "id": 1, "documentId": "ks7xsp9fewoi0qljcz9qa0", "__component": "blocks.related-articles", "articles": { "// …": true } }, { "id": 1, "documentId": "c2imt19iywk27hl2ftph7s", "__component": "blocks.cta-command-line", "theme": "secondary", "title": "Want to give it a try with a brand new project?", "text": "Up & running in seconds 🚀", "commandLine": "npx create-strapi-app my-project --quickstart" } ] }, { "id": 4, "// …": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` # Locale Source: https://docs.strapi.io/cms/api/rest/locale ## `GET` Get all documents in a specific locale Description: GET /api/restaurants?locale=fr — Get all documents in a specific locale (Source: https://docs.strapi.io/cms/api/rest/locale#get-get-all-documents-in-a-specific-locale) Language: Bash File path: N/A ```bash GET http://localhost:1337/api/restaurants?locale=fr ``` ## Collection types Description: GET /api/restaurants/:documentId?locale=fr — Get a document in a specific locale (collection type) (Source: https://docs.strapi.io/cms/api/rest/locale#get-one-collection-type) Language: Bash File path: N/A ```bash GET /api/restaurants/lr5wju2og49bf820kj9kz8c3?locale=fr ``` ## Single types Description: GET /api/homepage?locale=fr — Get a document in a specific locale (single type) (Source: https://docs.strapi.io/cms/api/rest/locale#get-one-single-type) Language: Bash File path: N/A ```bash GET /api/homepage?locale=fr ``` ## For the default locale Description: POST /api/restaurants — Create a document for the default locale (Source: https://docs.strapi.io/cms/api/rest/locale#rest-create-default-locale) Language: Bash File path: N/A ```bash POST http://localhost:1337/api/restaurants { "data": { "Name": "Oplato" } } ``` ## For a specific locale Description: POST /api/restaurants?locale=fr — Create a document for a specific locale (Source: https://docs.strapi.io/cms/api/rest/locale#rest-create-specific-locale) Language: Bash File path: N/A ```bash POST http://localhost:1337/api/restaurants?locale=fr { "data": { "Name": "She's Cake" } } ``` ## In a collection type Description: PUT /api/restaurants/:documentId?locale=fr — Create or update a locale version (collection type) (Source: https://docs.strapi.io/cms/api/rest/locale#rest-delete-collection-type) Language: Bash File path: N/A ```bash PUT http://localhost:1337/api/restaurants/lr5wju2og49bf820kj9kz8c3?locale=fr { "data": { "Name": "She's Cake in French" } } ``` --- Language: Bash File path: N/A ```bash DELETE /api/restaurants/abcdefghijklmno456?locale=fr ``` ## In a single type Description: PUT /api/homepage?locale=fr — Create or update a locale version (single type) (Source: https://docs.strapi.io/cms/api/rest/locale#rest-delete-single-type) Language: Bash File path: N/A ```bash PUT http://localhost:1337/api/homepage?locale=fr { "data": { "Title": "Page d'accueil" } } ``` --- Language: Bash File path: N/A ```bash DELETE /api/homepage?locale=fr ``` # Populate and Select Source: https://docs.strapi.io/cms/api/rest/populate-select ## Field selection Description: GET /api/restaurants — Return only name and description fields (Source: https://docs.strapi.io/cms/api/rest/populate-select#field-selection) Language: Bash File path: N/A ```bash GET /api/restaurants?fields[0]=name&fields[1]=description ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify( { fields: ['name', 'description'], }, { encodeValuesOnly: true, // prettify URL } ); await request(`/api/users?${query}`); ``` ## Populate with field selection Description: GET /api/articles — Populate with field selection (Source: https://docs.strapi.io/cms/api/rest/populate-select#populate-with-field-selection) Language: Bash File path: N/A ```bash GET /api/articles?fields[0]=title&fields[1]=slug&populate[headerImage][fields][0]=name&populate[headerImage][fields][1]=url ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify( { fields: ['title', 'slug'], populate: { headerImage: { fields: ['name', 'url'], }, }, }, { encodeValuesOnly: true, // prettify URL } ); await request(`/api/articles?${query}`); ``` ## Populate with filtering Description: GET /api/articles — Populate with filtering (Source: https://docs.strapi.io/cms/api/rest/populate-select#populate-with-filtering) Language: Bash File path: N/A ```bash GET /api/articles?populate[categories][sort][0]=name%3Aasc&populate[categories][filters][name][$eq]=Cars ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify( { populate: { categories: { sort: ['name:asc'], filters: { name: { $eq: 'Cars', }, }, }, }, }, { encodeValuesOnly: true, // prettify URL } ); await request(`/api/articles?${query}`); ``` # Publication filter Source: https://docs.strapi.io/cms/api/rest/publication-filter ## Get never-published draft documents Description: Response (Source: https://docs.strapi.io/cms/api/rest/publication-filter#never-published) Language: JSON File path: N/A ```json {6} { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "New Restaurant", "publishedAt": null, "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify( { status: 'draft', publicationFilter: 'never-published', }, { encodeValuesOnly: true, } ); await request(`/api/restaurants?${query}`); ``` --- Language: JSON File path: N/A ```json { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "New Restaurant", "publishedAt": null, "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ## Get modified documents Description: Response (Source: https://docs.strapi.io/cms/api/rest/publication-filter#modified) Language: JSON File path: N/A ```json {6} { "data": [ { "documentId": "znrlzntu9ei5onjvwfaalu2v", "name": "Biscotte Restaurant", "publishedAt": "2024-03-14T15:40:45.330Z", "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify( { publicationFilter: 'modified', }, { encodeValuesOnly: true, } ); await request(`/api/restaurants?${query}`); ``` --- Language: JSON File path: N/A ```json { "data": [ { "documentId": "znrlzntu9ei5onjvwfaalu2v", "name": "Biscotte Restaurant", "publishedAt": "2024-03-14T15:40:45.330Z", "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ## Get published rows without a draft peer Description: Response (Source: https://docs.strapi.io/cms/api/rest/publication-filter#published-without-draft) Language: JSON File path: N/A ```json {6} { "data": [ { "documentId": "abcdefghijklmno456", "name": "Legacy Restaurant", "publishedAt": "2024-01-10T09:15:00.000Z", "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify( { publicationFilter: 'published-without-draft', }, { encodeValuesOnly: true, } ); await request(`/api/restaurants?${query}`); ``` --- Language: JSON File path: N/A ```json { "data": [ { "documentId": "abcdefghijklmno456", "name": "Legacy Restaurant", "publishedAt": "2024-01-10T09:15:00.000Z", "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` # Relations Source: https://docs.strapi.io/cms/api/rest/relations ## Managing relations with API requests Description: :::note When Internationalization (i18n) is enabled on the content-type, you can also pass a locale to set relations for a specific locale, as in this Document Service API example: (Source: https://docs.strapi.io/cms/api/rest/relations#managing-relations-with-api-requests) Language: JavaScript File path: N/A ```js await strapi.documents('api::restaurant.restaurant').update({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'fr', data: { category: { connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'] } } }) ``` Language: JavaScript File path: N/A ```js await strapi.documents('api::cart.cart').update({ documentId: cart.documentId, data: { status: 'checked_out', order: { connect: [order.documentId] } as any, // Temporary workaround }, }); ``` ## connect Description: PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm (Source: https://docs.strapi.io/cms/api/rest/relations#connect) Language: JSON File path: N/A ```json { data: { categories: { connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'] } } } ``` --- Language: JavaScript File path: N/A ```js const fetch = require('node-fetch'); const response = await fetch( 'http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm', { method: 'put', body: { data: { categories: { connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'] } } } } ); ``` --- Language: JSON File path: N/A ```json { data: { categories: { connect: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } ] } } } ``` --- Language: JavaScript File path: N/A ```js const fetch = require('node-fetch'); const response = await fetch( 'http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm', { method: 'put', body: { data: { categories: { connect: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } ] } } } } ); ``` ## Relations reordering Description: Consider the following record in the database: (Source: https://docs.strapi.io/cms/api/rest/relations#relations-reordering) Language: JavaScript File path: N/A ```js categories: [ { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' } ] ``` --- Language: JSON File path: N/A ```json { data: { categories: { connect: [ { documentId: 'ma12bc34de56fg78hi90jkl', position: { before: 'z0y2x4w6v8u1t3s5r7q9onm' } }, ] } } } ``` --- Language: JavaScript File path: N/A ```js categories: [ { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' } ] ``` --- Language: JSON File path: N/A ```json { data: { categories: { connect: [ { id: '6u86wkc6x3parjd4emikhmx', position: { after: 'j9k8l7m6n5o4p3q2r1s0tuv'} }, { id: '3r1wkvyjwv0b9b36s7hzpxl', position: { before: 'z0y2x4w6v8u1t3s5r7q9onm' } }, { id: 'rkyqa499i84197l29sbmwzl', position: { end: true } }, { id: 'srkvrr77k96o44d9v6ef1vu' }, { id: 'nyk7047azdgbtjqhl7btuxw', position: { start: true } }, ] } } } ``` --- Language: JavaScript File path: N/A ```js categories: [ { id: 'nyk7047azdgbtjqhl7btuxw' }, { id: 'j9k8l7m6n5o4p3q2r1s0tuv' }, { id: '6u86wkc6x3parjd4emikhmx6' }, { id: '3r1wkvyjwv0b9b36s7hzpxl7' }, { id: 'a1b2c3d4e5f6g7h8i9j0klm' }, { id: 'rkyqa499i84197l29sbmwzl' }, { id: 'srkvrr77k96o44d9v6ef1vu9' } ] ``` ## Edge cases: Draft & Publish or i18n disabled Description: In this situation you can select which locale you are connecting to: (Source: https://docs.strapi.io/cms/api/rest/relations#edge-cases-draft-publish-or-i18n-disabled) Language: JavaScript File path: N/A ```js data: { categories: { connect: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm', locale: 'en' }, // Connect to the same document id but with a different locale 👇 { documentId: 'z0y2x4w6v8u1t3s5r7q9onm', locale: 'fr' }, ] } } ``` Language: JavaScript File path: N/A ```js data: { categories: { connect: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm', status: 'draft' }, // Connect to the same document id but with different publication states 👇 { documentId: 'z0y2x4w6v8u1t3s5r7q9onm', status: 'published' }, ] } } ``` ## disconnect Description: PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm (Source: https://docs.strapi.io/cms/api/rest/relations#disconnect) Language: JSON File path: N/A ```json { data: { categories: { disconnect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'], } } } ``` --- Language: JSON File path: N/A ```json { data: { categories: { disconnect: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } ], } } } ``` ## set Description: PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm (Source: https://docs.strapi.io/cms/api/rest/relations#set) Language: JSON File path: N/A ```json { data: { categories: { set: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv4'], } } } ``` --- Language: JSON File path: N/A ```json { data: { categories: { set: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } ], } } } ``` # Sort and Pagination Source: https://docs.strapi.io/cms/api/rest/sort-pagination ## Example: Sort using 2 fields Description: GET /api/restaurants — Sort using 2 fields (Source: https://docs.strapi.io/cms/api/rest/sort-pagination#example-sort-using-2-fields) Language: Bash File path: N/A ```bash GET /api/restaurants?sort[0]=Description&sort[1]=Name ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ sort: ['Description', 'Name'], }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` Language: JSON File path: N/A ```json { "data": [ { "id": 9, "documentId": "hgv1vny5cebq2l3czil1rpb3", "Name": "BMK Paris Bamako", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "A very short description goes here." } ] } ] // … }, { "id": 8, "documentId": "flzc8qrarj19ee0luix8knxn", "Name": "Restaurant D", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "A very short description goes here." } ] } ] // … } // … ], "meta": { // … } } ``` ## Example: Sort using 2 fields and set the order Description: GET /api/restaurants — Sort using 2 fields and set the order (Source: https://docs.strapi.io/cms/api/rest/sort-pagination#example-sort-using-2-fields-and-set-the-order) Language: Bash File path: N/A ```bash GET /api/restaurants?sort[0]=Description:asc&sort[1]=Name:desc ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ sort: ['Description:asc', 'Name:desc'], }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` Language: JSON File path: N/A ```json { "data": [ { "id": 8, "documentId": "flzc8qrarj19ee0luix8knxn", "Name": "Restaurant D", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "A very short description goes here." } ] } ] // … }, { "id": 9, "documentId": "hgv1vny5cebq2l3czil1rpb3", "Name": "BMK Paris Bamako", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "A very short description goes here." } ] } ] // … } // … ], "meta": { // … } } ``` ## Pagination by page Description: GET /api/articles — Pagination by page (Source: https://docs.strapi.io/cms/api/rest/sort-pagination#pagination-by-page) Language: Bash File path: N/A ```bash GET /api/articles?pagination[page]=1&pagination[pageSize]=10 ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ pagination: { page: 1, pageSize: 10, }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/articles?${query}`); ``` Language: JSON File path: N/A ```json { "data": [ // ... ], "meta": { "pagination": { "page": 1, "pageSize": 10, "pageCount": 5, "total": 48 } } } ``` ## Pagination by offset Description: GET /api/articles — Pagination by offset (Source: https://docs.strapi.io/cms/api/rest/sort-pagination#pagination-by-offset) Language: Bash File path: N/A ```bash GET /api/articles?pagination[start]=0&pagination[limit]=10 ``` --- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ pagination: { start: 0, limit: 10, }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/articles?${query}`); ``` Language: JSON File path: N/A ```json { "data": [ // ... ], "meta": { "pagination": { "start": 0, "limit": 10, "total": 42 } } } ``` # Status Source: https://docs.strapi.io/cms/api/rest/status ## Status Description: GET /api/articles?status=draft — Get draft versions of restaurants (Source: https://docs.strapi.io/cms/api/rest/status#status) Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ status: 'draft', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/articles?${query}`); ``` # Upload files Source: https://docs.strapi.io/cms/api/rest/upload ## Get all files Description: GET /api/upload/files returns a flat array of all files in the Media Library: (Source: https://docs.strapi.io/cms/api/rest/upload#get-all-files) Language: JSON File path: N/A ```json [ { "id": 1, "documentId": "a1b2c3...", "name": "photo.jpg", "url": "/uploads/photo.jpg", "mime": "image/jpeg", "size": 12.34 // ...other file fields } // ... ] ``` ## Get a paginated list of files Description: Response (Source: https://docs.strapi.io/cms/api/rest/upload#get-a-paginated-list-of-files) Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "a1b2c3...", "name": "photo.jpg", "url": "/uploads/photo.jpg", "mime": "image/jpeg", "size": 12.34 // ...other file fields } ], "meta": { "pagination": { "page": 2, "pageSize": 10, "pageCount": 4, "total": 100 } } } ``` --- Language: JSON File path: N/A ```json { "data": [ // ... ], "meta": { "pagination": { "start": 20, "limit": 5 } } } ``` Language: JSON File path: N/A ```json { "data": [ { "id": 1, "documentId": "a1b2c3...", "name": "photo.jpg", "url": "/uploads/photo.jpg", "mime": "image/jpeg", "size": 12.34 // ...other file fields } ], "meta": { "pagination": { "page": 2, "pageSize": 10, "pageCount": 4, "total": 100 } } } ``` --- Language: JSON File path: N/A ```json { "data": [ // ... ], "meta": { "pagination": { "start": 20, "limit": 5 } } } ``` ## Upload files Description: :::tip When uploading an image, include a fileInfo object to set the file name, alt text, and caption. (Source: https://docs.strapi.io/cms/api/rest/upload#upload-files) Language: HTML File path: N/A ```html
``` --- Language: JavaScript File path: N/A ```js import { FormData } from 'formdata-node'; import fetch, { blobFrom } from 'node-fetch'; const file = await blobFrom('./1.png', 'image/png'); const form = new FormData(); form.append('files', file, "1.png"); form.append( 'fileInfo', JSON.stringify({ name: 'Homepage hero', alternativeText: 'Person smiling while holding laptop', caption: 'Hero image used on the homepage', }) ); const response = await fetch('http://localhost:1337/api/upload', { method: 'post', body: form, }); ``` ## Upload entry files Description: For example, given the Restaurant model attributes: (Source: https://docs.strapi.io/cms/api/rest/upload#upload-entry-files) Language: JSON File path: /src/api/restaurant/content-types/restaurant/schema.json ```json { // ... "attributes": { "name": { "type": "string" }, "cover": { "type": "media", "multiple": false, } } // ... } ``` Language: HTML File path: N/A ```html
``` ## Update fileInfo Description: fileInfo is the only accepted parameter, and describes the fileInfo to update: (Source: https://docs.strapi.io/cms/api/rest/upload#update-fileinfo) Language: JavaScript File path: N/A ```js import { FormData } from 'formdata-node'; import fetch from 'node-fetch'; const fileId = 50; const newFileData = { alternativeText: 'My new alternative text for this image!', }; const form = new FormData(); form.append('fileInfo', JSON.stringify(newFileData)); const response = await fetch(`http://localhost:1337/api/upload?id=${fileId}`, { method: 'post', body: form, }); ``` ## Models definition Description: The following example lets you upload and attach one file to the avatar attribute: (Source: https://docs.strapi.io/cms/api/rest/upload#models-definition) Language: JSON File path: /src/api/restaurant/content-types/restaurant/schema.json ```json { // ... { "attributes": { "pseudo": { "type": "string", "required": true }, "email": { "type": "email", "required": true, "unique": true }, "avatar": { "type": "media", "multiple": false, } } } // ... } ``` Language: JSON File path: /src/api/restaurant/content-types/restaurant/schema.json ```json { // ... { "attributes": { "name": { "type": "string", "required": true }, "covers": { "type": "media", "multiple": true, } } } // ... } ``` # Controllers Source: https://docs.strapi.io/cms/backend-customization/controllers ## Adding a new controller Description: with the interactive CLI command strapi generate - or manually by creating a JavaScript file: - 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) (Source: https://docs.strapi.io/cms/backend-customization/controllers#adding-a-new-controller) Language: JavaScript File path: ./src/api/restaurant/controllers/restaurant.js ```js const { createCoreController } = require('@strapi/strapi').factories; module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({ // Method 1: Creating an entirely custom action async exampleAction(ctx) { try { ctx.body = 'ok'; } catch (err) { ctx.body = err; } }, // Method 2: Wrapping a core action (leaves core logic in place) async find(ctx) { // some custom logic here ctx.query = { ...ctx.query, local: 'en' } // Calling the default core action const { data, meta } = await super.find(ctx); // some more custom logic meta.date = Date.now() return { data, meta }; }, // Method 3: Replacing a core action with proper sanitization async find(ctx) { // validateQuery (optional) // to throw an error on query params that are invalid or the user does not have access to await this.validateQuery(ctx); // sanitizeQuery to remove any query params that are invalid or the user does not have access to // It is strongly recommended to use sanitizeQuery even if validateQuery is used const sanitizedQueryParams = await this.sanitizeQuery(ctx); const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams); const sanitizedResults = await this.sanitizeOutput(results, ctx); return this.transformResponse(sanitizedResults, { pagination }); } })); ``` --- Language: TypeScript File path: ./src/api/restaurant/controllers/restaurant.ts ```ts import { factories } from '@strapi/strapi'; export default factories.createCoreController('api::restaurant.restaurant', ({ strapi }) => ({ // Method 1: Creating an entirely custom action async exampleAction(ctx) { try { ctx.body = 'ok'; } catch (err) { ctx.body = err; } }, // Method 2: Wrapping a core action (leaves core logic in place) async find(ctx) { // some custom logic here ctx.query = { ...ctx.query, local: 'en' } // Calling the default core action const { data, meta } = await super.find(ctx); // some more custom logic meta.date = Date.now() return { data, meta }; }, // Method 3: Replacing a core action with proper sanitization async find(ctx) { // validateQuery (optional) // to throw an error on query params that are invalid or the user does not have access to await this.validateQuery(ctx); // sanitizeQuery to remove any query params that are invalid or the user does not have access to // It is strongly recommended to use sanitizeQuery even if validateQuery is used const sanitizedQueryParams = await this.sanitizeQuery(ctx); const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams); // sanitizeOutput to ensure the user does not receive any data they do not have access to const sanitizedResults = await this.sanitizeOutput(results, ctx); return this.transformResponse(sanitizedResults, { pagination }); } })); ``` Language: JavaScript File path: N/A ```js module.exports = { routes: [ { method: 'GET', path: '/hello', handler: 'api::hello.hello.index', } ] } ``` --- Language: JavaScript File path: ./src/api/hello/controllers/hello.js ```js module.exports = { async index(ctx, next) { // called by GET /hello ctx.body = 'Hello World!'; // we could also send a JSON }, }; ``` --- Language: TypeScript File path: ./src/api/hello/routes/hello.ts ```ts export default { routes: [ { method: 'GET', path: '/hello', handler: 'api::hello.hello.index', } ] } ``` --- Language: TypeScript File path: ./src/api/hello/controllers/hello.ts ```ts export default { async index(ctx, next) { // called by GET /hello ctx.body = 'Hello World!'; // we could also send a JSON }, }; ``` ## Controllers & Routes: How routes reach controller actions Description: The example below adds a new controller action and exposes it through a custom route without duplicating the existing CRUD route definitions: (Source: https://docs.strapi.io/cms/backend-customization/controllers#controllers-routes-how-routes-reach-controller-actions) Language: JavaScript File path: ./src/api/restaurant/controllers/restaurant.js ```js const { createCoreController } = require('@strapi/strapi').factories; module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({ async exampleAction(ctx) { const specials = await strapi.service('api::restaurant.restaurant').find({ filters: { isSpecial: true } }); return this.transformResponse(specials.results); }, })); ``` Language: JavaScript File path: ./src/api/restaurant/routes/01-custom-restaurant.js ```js module.exports = { routes: [ { method: 'GET', path: '/restaurants/specials', handler: 'api::restaurant.restaurant.exampleAction', }, ], }; ``` ## Sanitization when utilizing controller factories Description: :::warning Because these methods use the model associated with the current controller, if you query data that is from another model (i.e., doing a find for "menus" within a "restaurant" controller method), you must instead use the strapi.contentAPI methods, such as strapi.contentAPI.sanitize.query described in Sanitizing Custom Controllers, or else the result of your query will be sanitized against the wrong model. (Source: https://docs.strapi.io/cms/backend-customization/controllers#sanitization-when-utilizing-controller-factories) Language: JavaScript File path: ./src/api/restaurant/controllers/restaurant.js ```js const { createCoreController } = require('@strapi/strapi').factories; module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({ async find(ctx) { await this.validateQuery(ctx); const sanitizedQueryParams = await this.sanitizeQuery(ctx); const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams); const sanitizedResults = await this.sanitizeOutput(results, ctx); return this.transformResponse(sanitizedResults, { pagination }); } })); ``` --- Language: TypeScript File path: ./src/api/restaurant/controllers/restaurant.ts ```ts import { factories } from '@strapi/strapi'; export default factories.createCoreController('api::restaurant.restaurant', ({ strapi }) => ({ async find(ctx) { const sanitizedQueryParams = await this.sanitizeQuery(ctx); const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams); const sanitizedResults = await this.sanitizeOutput(results, ctx); return this.transformResponse(sanitizedResults, { pagination }); } })); ``` ## Sanitization and validation when building custom controllers Description: :::note Depending on the complexity of your custom controllers, you may need additional sanitization that Strapi cannot currently account for, especially when combining the data from multiple sources. (Source: https://docs.strapi.io/cms/backend-customization/controllers#sanitize-validate-custom-controllers) Language: JavaScript File path: ./src/api/restaurant/controllers/restaurant.js ```js module.exports = { async findCustom(ctx) { const contentType = strapi.contentType('api::test.test'); await strapi.contentAPI.validate.query(ctx.query, contentType, { auth: ctx.state.auth }); const sanitizedQueryParams = await strapi.contentAPI.sanitize.query(ctx.query, contentType, { auth: ctx.state.auth }); const documents = await strapi.documents(contentType.uid).findMany(sanitizedQueryParams); return await strapi.contentAPI.sanitize.output(documents, contentType, { auth: ctx.state.auth }); } } ``` --- Language: TypeScript File path: ./src/api/restaurant/controllers/restaurant.ts ```ts export default { async findCustom(ctx) { const contentType = strapi.contentType('api::test.test'); await strapi.contentAPI.validate.query(ctx.query, contentType, { auth: ctx.state.auth }); const sanitizedQueryParams = await strapi.contentAPI.sanitize.query(ctx.query, contentType, { auth: ctx.state.auth }); const documents = await strapi.documents(contentType.uid).findMany(sanitizedQueryParams); return await strapi.contentAPI.sanitize.output(documents, contentType, { auth: ctx.state.auth }); } } ``` ## Extending core controllers Description: :::tip The backend customization examples cookbook shows how you can overwrite a default controller action, for instance for the create action. (Source: https://docs.strapi.io/cms/backend-customization/controllers#extending-core-controllers) Language: JavaScript File path: N/A ```js async find(ctx) { // some logic here const { data, meta } = await super.find(ctx); // some more logic return { data, meta }; } ``` --- Language: JavaScript File path: N/A ```js async findOne(ctx) { // some logic here const response = await super.findOne(ctx); // some more logic return response; } ``` --- Language: JavaScript File path: N/A ```js async create(ctx) { // some logic here const response = await super.create(ctx); // some more logic return response; } ``` --- Language: JavaScript File path: N/A ```js async update(ctx) { // some logic here const response = await super.update(ctx); // some more logic return response; } ``` --- Language: JavaScript File path: N/A ```js async delete(ctx) { // some logic here const response = await super.delete(ctx); // some more logic return response; } ``` Language: JavaScript File path: N/A ```js async find(ctx) { // some logic here const response = await super.find(ctx); // some more logic return response; } ``` --- Language: JavaScript File path: N/A ```js async update(ctx) { // some logic here const response = await super.update(ctx); // some more logic return response; } ``` --- Language: JavaScript File path: N/A ```js async delete(ctx) { // some logic here const response = await super.delete(ctx); // some more logic return response; } ``` ## Usage Description: Controllers are declared and attached to a route. (Source: https://docs.strapi.io/cms/backend-customization/controllers#usage) Language: JavaScript File path: N/A ```js // access an API controller strapi.controller('api::api-name.controller-name'); // access a plugin controller strapi.controller('plugin::plugin-name.controller-name'); ``` # Authentication flow with JWT Source: https://docs.strapi.io/cms/backend-customization/examples/authentication ## Examples cookbook: Authentication flow with JWT Description: This file uses the formik package - install it using yarn add formik and restart the dev server. (Source: https://docs.strapi.io/cms/backend-customization/examples/authentication#examples-cookbook-authentication-flow-with-jwt) Language: JavaScript File path: /client/pages/auth/login.js ```js import React from 'react'; import { useFormik } from 'formik'; import { Button, Input } from '@nextui-org/react'; import Layout from '@/components/layout'; import { getStrapiURL } from '@/utils'; const Login = () => { const { handleSubmit, handleChange } = useFormik({ initialValues: { identifier: '', password: '', }, onSubmit: async (values) => { /** * API URLs in Strapi are by default prefixed with /api, * but because the API prefix can be configured * with the rest.prefix property in the config/api.js file, * we use the getStrapiURL() method to build the proper full auth URL. **/ const res = await fetch(getStrapiURL('/auth/local'), { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(values), }); /** * Gets the JWT from the server response. * The actual response is { jwt, user }, but we only need the JWT here. */ const { jwt } = await res.json(); /** * Stores the JWT in the localStorage of the browser. * A better implementation would be to do this with an authentication context provider * or something more sophisticated, but it's not the purpose of this tutorial. */ localStorage.setItem('token', jwt); }, }); /** * The following code renders a basic login form * accessible from the localhost:3000/auth/login page. */ return (

Login

); }; export default Login; ``` ## Configuration Description: First, enable session management in your /config/plugins.js: (Source: https://docs.strapi.io/cms/backend-customization/examples/authentication#configuration) Language: JavaScript File path: /config/plugins.js ```js module.exports = ({ env }) => ({ 'users-permissions': { config: { jwtManagement: 'refresh', sessions: { accessTokenLifespan: 600, // 10 minutes (default) maxRefreshTokenLifespan: 2592000, // 30 days (default) idleRefreshTokenLifespan: 1209600, // 14 days (default) maxSessionLifespan: 86400, // 1 day (default) idleSessionLifespan: 7200, // 2 hours (default) }, }, }, }); ``` ## Enhanced login component Description: Here's an updated login component that handles both JWT and refresh tokens: (Source: https://docs.strapi.io/cms/backend-customization/examples/authentication#enhanced-login-component) Language: JavaScript File path: /client/pages/auth/enhanced-login.js ```js import React, { useState } from 'react'; import { useFormik } from 'formik'; import { Button, Input } from '@nextui-org/react'; import Layout from '@/components/layout'; import { getStrapiURL } from '@/utils'; const EnhancedLogin = () => { const [isLoading, setIsLoading] = useState(false); const { handleSubmit, handleChange } = useFormik({ initialValues: { identifier: '', password: '', }, onSubmit: async (values) => { setIsLoading(true); try { const res = await fetch(getStrapiURL('/auth/local'), { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(values), }); const data = await res.json(); if (res.ok) { // Store both tokens (session management mode) if (data.refreshToken) { localStorage.setItem('accessToken', data.jwt); localStorage.setItem('refreshToken', data.refreshToken); } else { // Legacy mode - single JWT localStorage.setItem('token', data.jwt); } // Redirect to protected area window.location.href = '/dashboard'; } else { console.error('Login failed:', data.error); } } catch (error) { console.error('Login error:', error); } finally { setIsLoading(false); } }, }); return (

Enhanced Login

); }; export default EnhancedLogin; ``` # Custom middlewares Source: https://docs.strapi.io/cms/backend-customization/examples/middlewares ## Populating an analytics dashboard in Google Sheets with a custom middleware Description: Additional information can be found in the official . (Source: https://docs.strapi.io/cms/backend-customization/examples/middlewares#populating-an-analytics-dashboard-in-google-sheets-with-a-custom-middleware) Language: JavaScript File path: src/api/restaurant/middlewares/utils.js ```js const { google } = require('googleapis'); const createGoogleSheetClient = async ({ keyFile, sheetId, tabName, range, }) => { async function getGoogleSheetClient() { const auth = new google.auth.GoogleAuth({ keyFile, scopes: ['https://www.googleapis.com/auth/spreadsheets'], }); const authClient = await auth.getClient(); return google.sheets({ version: 'v4', auth: authClient, }); } const googleSheetClient = await getGoogleSheetClient(); const writeGoogleSheet = async (data) => { googleSheetClient.spreadsheets.values.append({ spreadsheetId: sheetId, range: `${tabName}!${range}`, valueInputOption: 'USER_ENTERED', insertDataOption: 'INSERT_ROWS', resource: { majorDimension: 'ROWS', values: data, }, }); }; const updateoogleSheet = async (cell, data) => { googleSheetClient.spreadsheets.values.update({ spreadsheetId: sheetId, range: `${tabName}!${cell}`, valueInputOption: 'USER_ENTERED', resource: { majorDimension: 'ROWS', values: data, }, }); }; const readGoogleSheet = async () => { const res = await googleSheetClient.spreadsheets.values.get({ spreadsheetId: sheetId, range: `${tabName}!${range}`, }); return res.data.values; }; return { writeGoogleSheet, updateoogleSheet, readGoogleSheet, }; }; module.exports = { createGoogleSheetClient, }; ``` Language: JavaScript File path: src/api/restaurant/middlewares/analytics.js ```js 'use strict'; const { createGoogleSheetClient } = require('./utils'); const serviceAccountKeyFile = './gs-keys.json'; // Replace the sheetId value with the corresponding id found in your own URL const sheetId = '1P7Oeh84c18NlHp1Zy-5kXD8zgpoA1WmvYL62T4GWpfk'; const tabName = 'Restaurants'; const range = 'A2:C'; const VIEWS_CELL = 'C'; const transformGSheetToObject = (response) => response.reduce( (acc, restaurant) => ({ ...acc, [restaurant[0]]: { id: restaurant[0], name: restaurant[1], views: restaurant[2], cellNum: Object.keys(acc).length + 2 // + 2 because we need to consider the header and that the initial length is 0, so our first real row would be 2, }, }), {} ); module.exports = (config, { strapi }) => { return async (context, next) => { // Generating google sheet client const { readGoogleSheet, updateoogleSheet, writeGoogleSheet } = await createGoogleSheetClient({ keyFile: serviceAccountKeyFile, range, sheetId, tabName, }); // Get the restaurant documentId from the params in the URL const restaurantId = context.params.id; const restaurant = await strapi.documents('api::restaurant.restaurant').findOne({ documentId: restaurantId, }); // Read the spreadsheet to get the current data const restaurantAnalytics = await readGoogleSheet(); /** * The returned data comes in the shape [1, "Mint Lounge", 23], * and we need to transform it into an object: {id: 1, name: "Mint Lounge", views: 23, cellNum: 2} */ const requestedRestaurant = transformGSheetToObject(restaurantAnalytics)[restaurantId]; if (requestedRestaurant) { await updateoogleSheet( `${VIEWS_CELL}${requestedRestaurant.cellNum}:${VIEWS_CELL}${requestedRestaurant.cellNum}`, [[Number(requestedRestaurant.views) + 1]] ); } else { /** If we don't have the restaurant in the spreadsheet already, * we create it with 1 view. */ const newRestaurant = [[restaurant.id, restaurant.name, 1]]; await writeGoogleSheet(newRestaurant); } // Call next to continue with the flow and get to the controller await next(); }; }; ``` Language: JavaScript File path: src/api/restaurant/routes/restaurant.js ```js 'use strict'; const { createCoreRouter } = require('@strapi/strapi').factories; module.exports = createCoreRouter('api::restaurant.restaurant', { config: { findOne: { auth: false, policies: [], middlewares: ['api::restaurant.analytics'], }, }, }); ``` # Custom policies Source: https://docs.strapi.io/cms/backend-customization/examples/policies ## Creating a custom policy Description: In the /api folder of the project, create a new src/api/review/policies/is-owner-review.js file with the following code: (Source: https://docs.strapi.io/cms/backend-customization/examples/policies#creating-a-custom-policy) Language: JavaScript File path: src/api/review/policies/is-owner-review.js ```js module.exports = async (policyContext, config, { strapi }) => { const { body } = policyContext.request; const { user } = policyContext.state; // Return an error if there is no authenticated user with the request if (!user) { return false; } /** * Queries the Restaurants collection type * using the Document Service API * to retrieve information about the restaurant's owner. */ const [restaurant] = await strapi.documents('api::restaurant.restaurant').findMany({ filters: { slug: body.restaurant, }, populate: ['owner'], }); if (!restaurant) { return false; } /** * If the user submitting the request is the restaurant's owner, * we don't allow the review creation. */ if (user.id === restaurant.owner.id) { return false; } return true; }; ``` ## Sending custom errors through policies Description: In the /api folder of the project, update the previously created is-owner-review custom policy as follows (highlighted lines are the only modified lines): (Source: https://docs.strapi.io/cms/backend-customization/examples/policies#sending-custom-errors-through-policies) Language: JSON File path: /api/review/policies/is-owner-review.js ```json { "data": null, "error": { "status": 403, "name": "PolicyError", "message": "Policy Failed", "details": {} } } ``` --- Language: JSON File path: N/A ```json { "data": null, "error": { "status": 403, "name": "PolicyError", "message": "The owner of the restaurant cannot submit reviews", "details": { "policy": "is-owner-review", "errCode": "RESTAURANT_OWNER_REVIEW" } } } ``` Language: JavaScript File path: src/api/review/policies/is-owner-review.js ```js const { errors } = require('@strapi/utils'); const { PolicyError } = errors; module.exports = async (policyContext, config, { strapi }) => { const { body } = policyContext.request; const { user } = policyContext.state; // Return an error if there is no authenticated user with the request if (!user) { return false; } /** * Queries the Restaurants collection type * using the Document Service API * to retrieve information about the restaurant's owner. */ const filteredRestaurants = await strapi.documents('api::restaurant.restaurant').findMany({ filters: { slug: body.restaurant, }, populate: ['owner'], }); const restaurant = filteredRestaurants[0]; if (!restaurant) { return false; } /** * If the user submitting the request is the restaurant's owner, * we don't allow the review creation. */ if (user.id === restaurant.owner.id) { // highlight-start /** * Throws a custom policy error * instead of just returning false * (which would result into a generic Policy Error). */ throw new PolicyError('The owner of the restaurant cannot submit reviews', { errCode: 'RESTAURANT_OWNER_REVIEW', // can be useful for identifying different errors on the front end }); // highlight-end } return true; }; ``` ## Using custom errors on the front end Description: Example front-end code to display toast notifications for custom errors or successful review creation: (Source: https://docs.strapi.io/cms/backend-customization/examples/policies#using-custom-errors-on-the-front-end) Language: JavaScript File path: /client/components/pages/restaurant/RestaurantContent/Reviews/new-review.js ```js import { Button, Input, Textarea } from '@nextui-org/react'; import { useFormik } from 'formik'; import { useRouter } from 'next/router'; import React from 'react'; import { getStrapiURL } from '../../../../../utils'; // highlight-start /** * A notification will be displayed on the front-end using React Hot Toast * (See https://github.com/timolins/react-hot-toast). * React Hot Toast should be added to your project's dependencies; * Use yarn or npm to install it and it will be added to your package.json file. */ import toast from 'react-hot-toast'; class UnauthorizedError extends Error { constructor(message) { super(message); } } // highlight-end const NewReview = () => { const router = useRouter(); const { handleSubmit, handleChange, values } = useFormik({ initialValues: { note: '', content: '', }, onSubmit: async (values) => { // highlight-start /** * The previously added code is wrapped in a try/catch block. */ try { // highlight-end const res = await fetch(getStrapiURL('/reviews'), { method: 'POST', body: JSON.stringify({ restaurant: router.query.slug, ...values, }), headers: { Authorization: `Bearer ${localStorage.getItem('token')}`, 'Content-Type': 'application/json', }, }); // highlight-start const { data, error } = await res.json(); /** * If the Strapi backend server returns an error, * we use the custom error message to throw a custom error. * If the request is a success, we display a success message. * In both cases, a toast notification is displayed on the front-end. */ if (error) { throw new UnauthorizedError(error.message); } toast.success('Review created!'); return data; } catch (err) { toast.error(err.message); console.error(err); } }, // highlight-end }); return (

Write your review