}
}
```
Language: JSX
File path: N/A
```jsx
interface EditViewContext {
/**
* This will only be null if the content-type
* does not have draft & publish enabled.
*/
activeTab: 'draft' | 'published' | null;
/**
* Will be either 'single-types' | 'collection-types'
*/
collectionType: string;
/**
* Will be undefined if someone is creating an entry.
*/
document?: Document;
/**
* Will be undefined if someone is creating an entry.
*/
documentId?: string;
/**
* Will be undefined if someone is creating an entry.
*/
meta?: DocumentMetadata;
/**
* The current content-type's model.
*/
model: string;
}
```
## addEditViewSidePanel
Description: !addEditViewSidePanel
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#addeditviewsidepanel)
Language: TypeScript
File path: /github.com/strapi/strapi/blob/develop/packages/core/content-manager/admin/src/content-manager.ts
```ts
addEditViewSidePanel(panels: DescriptionReducer | PanelComponent[])
```
## PanelComponent
Description: 🌐 A PanelComponent receives the properties listed in EditViewContext and returns an object with the following shape:
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#panelcomponent)
Language: TSX
File path: /github.com/strapi/strapi/blob/develop/packages/core/content-manager/admin/src/content-manager.ts
```tsx
type PanelComponent = (props: PanelComponentProps) => {
title: string;
content: React.ReactNode;
};
```
## addDocumentAction
Description: Code example from "addDocumentAction"
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#adddocumentaction)
Language: TypeScript
File path: N/A
```ts
addDocumentAction(actions: DescriptionReducer | DocumentActionComponent[])
```
## DocumentActionDescription
Description: 🌐 The interface and properties of the API look like the following:
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#documentactiondescription)
Language: JSX
File path: N/A
```jsx
interface DocumentActionDescription {
label: string;
onClick?: (event: React.SyntheticEvent) => Promise | boolean | void;
icon?: React.ReactNode;
/**
* @default false
*/
disabled?: boolean;
/**
* @default 'panel'
* @description Where the action should be rendered.
*/
position?: DocumentActionPosition | DocumentActionPosition[];
dialog?: DialogOptions | NotificationOptions | ModalOptions;
/**
* @default 'secondary'
*/
variant?: ButtonProps['variant'];
loading?: ButtonProps['loading'];
}
type DocumentActionPosition = 'panel' | 'header' | 'table-row' | 'preview' | 'relation-modal';
interface DialogOptions {
type: 'dialog';
title: string;
content?: React.ReactNode;
variant?: ButtonProps['variant'];
onConfirm?: () => void | Promise;
onCancel?: () => void | Promise;
}
interface NotificationOptions {
type: 'notification';
title: string;
link?: {
label: string;
url: string;
target?: string;
};
content?: string;
onClose?: () => void;
status?: NotificationConfig['type'];
timeout?: number;
}
interface ModalOptions {
type: 'modal';
title: string;
content: React.ComponentType<{
onClose: () => void;
}> | React.ReactNode;
footer?: React.ComponentType<{
onClose: () => void;
}> | React.ReactNode;
onClose?: () => void;
}
```
## addDocumentHeaderAction
Description: !addEditViewSidePanel
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#adddocumentheaderaction)
Language: TypeScript
File path: N/A
```ts
addDocumentHeaderAction(actions: DescriptionReducer | HeaderActionComponent[])
```
## HeaderActionDescription
Description: 🌐 The interface and properties of the API look like the following:
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#headeractiondescription)
Language: TypeScript
File path: N/A
```ts
interface HeaderActionDescription {
disabled?: boolean;
label: string;
icon?: React.ReactNode;
type?: 'icon' | 'default';
onClick?: (event: React.SyntheticEvent) => Promise | boolean | void;
dialog?: DialogOptions;
options?: Array<{
disabled?: boolean;
label: string;
startIcon?: React.ReactNode;
textValue?: string;
value: string;
}>;
onSelect?: (value: string) => void;
value?: string;
}
interface DialogOptions {
type: 'dialog';
title: string;
content?: React.ReactNode;
footer?: React.ReactNode;
}
```
## addBulkAction
Description: !addEditViewSidePanel
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#addbulkaction)
Language: TypeScript
File path: N/A
```ts
addBulkAction(actions: DescriptionReducer | BulkActionComponent[])
```
## BulkActionDescription
Description: 🌐 The interface and properties of the API look like the following:
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#bulkactiondescription)
Language: JSX
File path: N/A
```jsx
interface BulkActionDescription {
dialog?: DialogOptions | NotificationOptions | ModalOptions;
disabled?: boolean;
icon?: React.ReactNode;
label: string;
onClick?: (event: React.SyntheticEvent) => void;
/**
* @default 'default'
*/
type?: 'icon' | 'default';
/**
* @default 'secondary'
*/
variant?: ButtonProps['variant'];
}
```
## addRichTextBlocks
Description: Code example from "addRichTextBlocks"
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#addrichtextblocks)
Language: JavaScript
File path: src/admin/app.js
```js
export default {
register(app) {
app.getPlugin('content-manager').apis.addRichTextBlocks({
callout: {
renderElement: (props) => {props.children},
icon: Information,
label: { id: 'my-plugin.blocks.callout', defaultMessage: 'Callout' },
matchNode: (node) => node.type === 'callout',
isInBlocksSelector: true,
handleConvert(editor) { /* use Slate Transforms to set node type */ },
snippets: [':::callout'],
},
});
},
};
```
---
Language: TypeScript
File path: src/admin/app.ts
```ts
import type { ContentManagerPlugin } from '@strapi/content-manager/strapi-admin';
export default {
register(app) {
const apis =
app.getPlugin('content-manager').apis as ContentManagerPlugin['config']['apis'];
apis.addRichTextBlocks({
callout: {
renderElement: (props) => {props.children},
icon: Information,
label: { id: 'my-plugin.blocks.callout', defaultMessage: 'Callout' },
matchNode: (node) => node.type === 'callout',
isInBlocksSelector: true,
handleConvert(editor) { /* use Slate Transforms to set node type */ },
snippets: [':::callout'],
},
});
},
};
```
Language: JavaScript
File path: src/admin/app.js
```js
export default {
register(app) {
app.getPlugin('content-manager').apis.addRichTextBlocks((currentBlocks) => {
// Remove the built-in code block
const { code: _removed, ...rest } = currentBlocks;
return rest;
});
},
};
```
---
Language: TypeScript
File path: src/admin/app.ts
```ts
import type {
ContentManagerPlugin,
RichTextBlocksStore,
} from '@strapi/content-manager/strapi-admin';
export default {
register(app) {
const apis =
app.getPlugin('content-manager').apis as ContentManagerPlugin['config']['apis'];
apis.addRichTextBlocks((currentBlocks: RichTextBlocksStore) => {
const { code: _removed, ...rest } = currentBlocks;
return rest;
});
},
};
```
Language: JSX
File path: N/A
```jsx
addRichTextBlocks(blocks: RichTextBlocksStore | ((currentBlocks: RichTextBlocksStore) => RichTextBlocksStore))
```
## 块定义
Description: 🌐 Key handlers each receive the Slate editor instance.
(Source: https://docs.strapi.io/cms/plugins-development/content-manager-apis#block-definition)
Language: JavaScript
File path: N/A
```js
callout: {
// ...
handleEnterKey(editor) {
// Exit the block on Enter and insert a new paragraph below
Transforms.insertNodes(editor, { type: 'paragraph', children: [{ text: '' }] });
},
handleBackspaceKey(editor, event) {
// Convert back to paragraph when backspacing in an empty callout
Transforms.setNodes(editor, { type: 'paragraph' });
},
handleTab(editor) {
// Increase indentation level on Tab
Transforms.setNodes(editor, { indent: (editor.selection ? 1 : 0) });
},
handleShiftTab(editor) {
// Decrease indentation level on Shift+Tab
Transforms.setNodes(editor, { indent: 0 });
},
},
```
# 插件创建与设置
Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin
## 创建插件
Description: 🌐 To create your plugin, ensure you are in the parent directory of where you want it to be created and run the following command:
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#creating-the-plugin)
Language: Bash
File path: N/A
```bash
yarn dlx @strapi/sdk-plugin init my-strapi-plugin
```
---
Language: Bash
File path: N/A
```bash
npx @strapi/sdk-plugin init my-strapi-plugin
```
## 将插件链接到你的项目
Description: 🌐 In a new terminal window, run the following commands:
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#linking-the-plugin-to-your-project)
Language: Bash
File path: N/A
```bash
cd /path/to/strapi/project
yarn dlx yalc add --link my-strapi-plugin && yarn install
```
---
Language: Bash
File path: N/A
```bash
cd /path/to/strapi/project
npx yalc add --link my-strapi-plugin && npm install
```
## 构建用于发布的插件
Description: 🌐 When you are ready to publish your plugin, you will need to build it.
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#building-the-plugin-for-publishing)
Language: Bash
File path: N/A
```bash
yarn build && yarn verify
```
---
Language: Bash
File path: N/A
```bash
npm run build && npm run verify
```
## 在单体仓库环境中使用插件 SDK
Description: 🌐 However, if you are writing admin code, you might add an alias that targets the source code of your plugin to make it easier to work with within the context of the admin panel:
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#monorepo)
Language: TypeScript
File path: .config.ts
```ts
import path from 'node:path';
export default (config, webpack) => {
config.resolve.alias = {
...config.resolve.alias,
'my-strapi-plugin': path.resolve(
__dirname,
// We've assumed the plugin is local.
'../plugins/my-strapi-plugin/admin/src'
),
};
return config;
};
```
## 使用本地插件的配置
Description: 🌐 When developing your plugin locally (using @strapi/sdk-plugin), your plugins configuration file looks like in the following example:
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#configuration-with-a-local-plugin)
Language: JavaScript
File path: /config/plugins.js|ts
```js
myplugin: {
enabled: true,
resolve: `./src/plugins/local-plugin`,
},
```
Language: TypeScript
File path: /src/index.ts
```ts
Error: 'X must be used within StrapiApp';
```
Language: TypeScript
File path: /src/index.ts
```ts
import { unstable_useContentManagerContext as useContentManagerContext } from '@strapi/strapi/admin';
```
## 服务器入口点
Description: 🌐 The server entry point file initializes your plugin's server-side functionalities.
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#server-entry-point)
Language: JavaScript
File path: N/A
```js
module.exports = () => {
return {
register,
config,
controllers,
contentTypes,
routes,
};
};
```
## 管理员入口
Description: 🌐 The admin entry point file sets up your plugin within the Strapi admin panel.
(Source: https://docs.strapi.io/cms/plugins-development/create-a-plugin#admin-entry-point)
Language: JavaScript
File path: N/A
```js
export default {
register(app) {},
bootstrap() {},
registerTrads({ locales }) {},
};
```
# 通过插件扩展 MCP 服务器
Source: https://docs.strapi.io/cms/plugins-development/extend-mcp-server
## 注册自定义工具
Description: 🌐 Use strapi.ai.mcp.registerTool() to expose a custom tool to AI clients:
(Source: https://docs.strapi.io/cms/plugins-development/extend-mcp-server#registering-a-custom-tool)
Language: JavaScript
File path: src/plugins/my-plugin/strapi-server.js
```js
const { z } = require('@strapi/utils');
module.exports = {
register({ strapi }) {
strapi.ai.mcp.registerTool({
name: 'my_custom_tool',
title: 'My Custom Tool',
description: 'A short description shown to the AI client.',
auth: {
// The session gate passes when the token satisfies ANY policy in the array.
policies: [{ action: 'plugin::my-plugin.my-action' }],
},
// resolveInputSchema and resolveOutputSchema are called per request,
// so they can narrow schemas based on the token's permissions.
resolveInputSchema: (context) =>
z.object({
message: z.string().describe('The message to echo.'),
}),
resolveOutputSchema: (context) =>
z.object({
result: z.string(),
}),
createHandler: (strapi, context) => async ({ args }) => ({
content: [{ type: 'text', text: args.message }],
structuredContent: { result: args.message },
}),
});
},
};
```
---
Language: TypeScript
File path: src/plugins/my-plugin/strapi-server.ts
```ts
import { z } from '@strapi/utils';
export default {
register({ strapi }) {
strapi.ai.mcp.registerTool({
name: 'my_custom_tool',
title: 'My Custom Tool',
description: 'A short description shown to the AI client.',
auth: {
// The session gate passes when the token satisfies ANY policy in the array.
policies: [{ action: 'plugin::my-plugin.my-action' }],
},
// resolveInputSchema and resolveOutputSchema are called per request,
// so they can narrow schemas based on the token's permissions.
resolveInputSchema: (context) =>
z.object({
message: z.string().describe('The message to echo.'),
}),
resolveOutputSchema: (context) =>
z.object({
result: z.string(),
}),
createHandler: (strapi, context) => async ({ args }) => ({
content: [{ type: 'text', text: args.message }],
structuredContent: { result: args.message },
}),
});
},
};
```
## 定义一个工具
Description: 🌐 The following example uses devModeOnly for brevity.
(Source: https://docs.strapi.io/cms/plugins-development/extend-mcp-server#defining-a-tool)
Language: TypeScript
File path: src/plugins/my-plugin/mcp/greet.ts
```ts
import { ai } from '@strapi/strapi';
import { z } from '@strapi/utils';
export const greet = ai.mcp.defineTool({
name: 'greet',
title: 'Greet',
description: 'Greets a user by name',
devModeOnly: true,
resolveInputSchema: () => z.object({ name: z.string() }),
resolveOutputSchema: () => z.object({ message: z.string() }),
createHandler: (strapi) => async ({ args }) => {
const message = `Hello, ${args.name}!`;
return { content: [{ type: 'text', text: message }], structuredContent: { message } };
},
});
```
Language: TypeScript
File path: src/plugins/my-plugin/strapi-server.ts
```ts
import { greet } from './mcp/greet';
export default {
register({ strapi }) {
strapi.ai.mcp.registerTool(greet);
},
};
```
## 定义资源
Description: 🌐 A resource exposes read-only data to AI clients through a URI.
(Source: https://docs.strapi.io/cms/plugins-development/extend-mcp-server#defining-a-resource)
Language: TypeScript
File path: src/plugins/my-plugin/mcp/app-info.ts
```ts
import { ai } from '@strapi/strapi';
export const appInfo = ai.mcp.defineResource({
name: 'app-info',
uri: 'strapi://app/info',
metadata: { description: 'Metadata about the app', mimeType: 'application/json' },
devModeOnly: true,
createHandler: (strapi) => async (uri) => ({
contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ ok: true }) }],
}),
});
```
## 定义提示
Description: 🌐 A prompt exposes a reusable prompt template to AI clients.
(Source: https://docs.strapi.io/cms/plugins-development/extend-mcp-server#defining-a-prompt)
Language: TypeScript
File path: src/plugins/my-plugin/mcp/app-context.ts
```ts
import { ai } from '@strapi/strapi';
export const appContext = ai.mcp.definePrompt({
name: 'app-context',
title: 'App Context',
description: 'Provides context about the app',
devModeOnly: true,
createHandler: (strapi) => async () => ({
messages: [{ role: 'user', content: { type: 'text', text: 'You are connected to Strapi.' } }],
}),
});
```
# 如何从插件创建管理员权限
Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins
## 在服务器端注册权限
Description: 🌐 Each individual permission has to registered in the bootstrap function of your plugin, as follows:
(Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins#register-the-permissions-server-side)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/bootstrap.js
```js
'use strict';
const bootstrap = ({ strapi }) => {
// Register permission actions.
const actions = [
{
section: 'plugins',
displayName: 'Access the overview page',
uid: 'overview.access',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Access the content manager sidebar',
uid: 'sidebar.access',
pluginName: 'my-plugin',
},
];
strapi.admin.services.permission.actionProvider.registerMany(actions);
};
module.exports = bootstrap;
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/bootstrap.ts
```ts
import type { Core } from '@strapi/strapi';
const bootstrap = ({ strapi }: { strapi: Core.Strapi }) => {
// Register permission actions.
const actions = [
{
section: 'plugins',
displayName: 'Access the overview page',
uid: 'overview.access',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Access the content manager sidebar',
uid: 'sidebar.access',
pluginName: 'my-plugin',
},
];
strapi.admin.services.permission.actionProvider.registerMany(actions);
};
export default bootstrap;
```
## 在管理面板端实现权限
Description: 🌐 Before we can implement our permissions on the admin panel side we have to define them in a reusable configuration file.
(Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins#implement-permissions-on-the-admin-panel-side)
Language: JavaScript
File path: /src/plugins/my-plugin/admin/src/permissions.js|ts
```js
const pluginPermissions = {
'accessOverview': [{ action: 'plugin::my-plugin.overview.access', subject: null }],
'accessSidebar': [{ action: 'plugin::my-plugin.sidebar.access', subject: null }],
};
export default pluginPermissions;
```
## 页面权限
Description: 🌐 Once you've created the configuration file you are ready to implement your permissions.
(Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins#page-permissions)
Language: JavaScript
File path: /src/plugins/my-plugin/admin/src/pages/HomePage.jsx|tsx
```js
import { Main } from '@strapi/design-system';
import { Page } from '@strapi/strapi/admin';
import { useIntl } from 'react-intl';
import pluginPermissions from '../permissions';
import { getTranslation } from '../utils/getTranslation';
const HomePage = () => {
const { formatMessage } = useIntl();
return (
Welcome to {formatMessage({ id: getTranslation('plugin.name') })}
);
};
export { HomePage };
```
## 菜单链接权限
Description: 🌐 The previous example makes sure that the permissions of a user that visits your page directly will be validated.
(Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins#menu-link-permissions)
Language: JavaScript
File path: /src/plugins/my-plugin/admin/src/index.js|ts
```js
import { getTranslation } from './utils/getTranslation';
import { PLUGIN_ID } from './pluginId';
import { Initializer } from './components/Initializer';
import { PluginIcon } from './components/PluginIcon';
import pluginPermissions from './permissions';
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'),
permissions: [
pluginPermissions.accessOverview[0],
],
});
app.registerPlugin({
id: PLUGIN_ID,
initializer: Initializer,
isReady: false,
name: PLUGIN_ID,
});
},
};
```
## 使用 useRBAC 钩子的自定义权限
Description: 🌐 To get even more control over the permission of the admin user you can use the useRBAC hook.
(Source: https://docs.strapi.io/cms/plugins-development/guides/admin-permissions-for-plugins#custom-permissions-with-the-userbac-hook)
Language: JavaScript
File path: /src/plugins/my-plugin/admin/src/components/Sidebar.jsx|tsx
```js
import React from 'react';
import { useRBAC } from '@strapi/strapi/admin';
import pluginPermissions from '../../permissions';
const Sidebar = () => {
const {
allowedActions: { canAccessSidebar },
} = useRBAC(pluginPermissions);
if (!canAccessSidebar) {
return null;
}
return (
Sidebar component
);
};
export default Sidebar;
```
# 如何为 Strapi 插件创建组件
Source: https://docs.strapi.io/cms/plugins-development/guides/create-components-for-plugins
## 审查组件结构
Description: 🌐 Components in Strapi follow the following format in their definition:
(Source: https://docs.strapi.io/cms/plugins-development/guides/create-components-for-plugins#reviewing-the-component-structure)
Language: JSON
File path: /my-plugin/server/components/category/component-name.json
```json
{
"attributes": {
"myComponent": {
"type": "component",
"repeatable": true,
"component": "category.componentName"
}
}
}
```
## 组件模式示例
Description: 🌐 A component schema defines the structure of a reusable data fragment.
(Source: https://docs.strapi.io/cms/plugins-development/guides/create-components-for-plugins#component-schema-example)
Language: JSON
File path: my-plugin/server/components/my-category/my-component.json
```json
{
"collectionName": "components_my_category_my_components",
"info": {
"displayName": "My Component",
"icon": "align-justify"
},
"attributes": {
"name": {
"type": "string",
"required": true
},
"description": {
"type": "text"
}
}
}
```
# 如何通过 Strapi 插件将数据从服务器传递到管理面板
Source: https://docs.strapi.io/cms/plugins-development/guides/pass-data-from-server-to-admin
## 创建自定义管理员路由
Description: 🌐 The following code will declare a custom admin route for the my-plugin plugin:
(Source: https://docs.strapi.io/cms/plugins-development/guides/pass-data-from-server-to-admin#create-a-custom-admin-route)
Language: JavaScript
File path: /my-plugin/server/routes/index.js
```js
module.exports = {
'pass-data': {
type: 'admin',
routes: [
{
method: 'GET',
path: '/pass-data',
handler: 'myPluginContentType.index',
config: {
policies: [],
auth: false,
},
},
]
}
// ...
};
```
Language: JavaScript
File path: /my-plugin/server/controllers/my-plugin-content-type.js
```js
'use strict';
module.exports = {
async index(ctx) {
ctx.body = 'You are in the my-plugin-content-type controller!';
}
}
```
## 在管理面板中获取数据
Description: 🌐 So for instance, if you create an /admin/src/api/foobar.js file and copy and paste the following code example:
(Source: https://docs.strapi.io/cms/plugins-development/guides/pass-data-from-server-to-admin#get-the-data-in-the-admin-panel)
Language: JavaScript
File path: /my-plugin/admin/src/api/foobar.js
```js
import { getFetchClient } from '@strapi/strapi/admin';
const { get } = getFetchClient();
const foobarRequests = {
getFoobar: async () => {
const { data } = await get('/my-plugin/pass-data');
return data;
},
};
export default foobarRequests;
```
Language: JavaScript
File path: /my-plugin/admin/src/components/MyComponent/index.js
```js
import foobarRequests from "../../api/foobar";
const [foobar, setFoobar] = useState([]);
// …
useEffect(() => {
foobarRequests.getFoobar().then(data => {
setFoobar(data);
});
}, [setFoobar]);
// …
```
# 如何在插件中重用内置的管理面板组件
Source: https://docs.strapi.io/cms/plugins-development/guides/reuse-admin-panel-components
## 从注册表访问组件
Description: 🌐 Built-in admin panel components are stored in the components object of the Strapi app context.
(Source: https://docs.strapi.io/cms/plugins-development/guides/reuse-admin-panel-components#access-a-component-from-the-registry)
Language: JavaScript
File path: N/A
```js
import { useStrapiApp } from '@strapi/admin/strapi-admin';
const components = useStrapiApp('MyCustomComponent', (state) => state.components);
const MediaLibraryDialog = components['media-library'];
```
## 重用媒体库对话框
Description: 🌐 The following example renders the dialog from a custom component and pre-selects assets when it opens:
(Source: https://docs.strapi.io/cms/plugins-development/guides/reuse-admin-panel-components#reuse-the-media-library-dialog)
Language: JavaScript
File path: N/A
```js
import { useState } from 'react';
import { useStrapiApp } from '@strapi/admin/strapi-admin';
export function MyCustomComponent() {
const [isMediaLibraryOpen, setIsMediaLibraryOpen] = useState(false);
const components = useStrapiApp('MyCustomComponent', (state) => state.components);
const MediaLibraryDialog = components['media-library'];
// Assets to pre-select when the dialog opens.
// Each entry is a full Media Library asset object, not just an id and name.
const initialAssets = [
{ id: 1, name: 'image1.jpg' /* ...other asset fields */ },
{ id: 2, name: 'image2.png' /* ...other asset fields */ },
];
const handleSelectAssets = (assets) => {
// Handle the assets the user selected
console.log('Selected assets:', assets);
setIsMediaLibraryOpen(false);
};
return (
<>
{isMediaLibraryOpen && (
setIsMediaLibraryOpen(false)}
/>
)}
>
);
}
```
# 如何从 Strapi 插件存储和访问数据
Source: https://docs.strapi.io/cms/plugins-development/guides/store-and-access-data
## 为你的插件创建一个内容类型
Description: 🌐 To create a content-type with the CLI generator, run the following command in a terminal within the server/src/ directory of your plugin:
(Source: https://docs.strapi.io/cms/plugins-development/guides/store-and-access-data#create-a-content-type-for-your-plugin)
Language: Bash
File path: N/A
```bash
yarn strapi generate content-type
```
---
Language: Bash
File path: N/A
```bash
npm run strapi generate content-type
```
Language: JSON
File path: /server/content-types/my-plugin-content-type/schema.json
```json
{
"kind": "collectionType",
"collectionName": "my_plugin_content_types",
"info": {
"singularName": "my-plugin-content-type",
"pluralName": "my-plugin-content-types",
"displayName": "My Plugin Content-Type"
},
"options": {
"draftAndPublish": false,
"comment": ""
},
"pluginOptions": {
"content-manager": {
"visible": true
},
"content-type-builder": {
"visible": true
}
},
"attributes": {
"name": {
"type": "string"
}
}
}
```
## 确保导入插件内容类型
Description: 在 /server/index.js 文件中,导入内容类型:
(Source: https://docs.strapi.io/cms/plugins-development/guides/store-and-access-data#ensure-plugin-content-types-are-imported)
Language: JavaScript
File path: /server/index.js
```js
'use strict';
const register = require('./register');
const bootstrap = require('./bootstrap');
const destroy = require('./destroy');
const config = require('./config');
const contentTypes = require('./content-types');
const controllers = require('./controllers');
const routes = require('./routes');
const middlewares = require('./middlewares');
const policies = require('./policies');
const services = require('./services');
module.exports = {
register,
bootstrap,
destroy,
config,
controllers,
routes,
services,
contentTypes,
policies,
middlewares,
};
```
Language: JavaScript
File path: /server/content-types/index.js
```js
'use strict';
module.exports = {
// In the line below, replace my-plugin-content-type
// with the actual name and folder path of your content type
"my-plugin-content-type": require('./my-plugin-content-type'),
};
```
Language: JavaScript
File path: /server/content-types/my-plugin-content-type/index.js
```js
'use strict';
const schema = require('./schema');
module.exports = {
schema,
};
```
## 与插件中的数据进行交互
Description: 🌐 Here is how to find all the entries for the my-plugin-content-type collection type created for a plugin called my-plugin:
(Source: https://docs.strapi.io/cms/plugins-development/guides/store-and-access-data#interact-with-data-from-the-plugin)
Language: JavaScript
File path: /server/content-types/index.js
```js
// Using the Document Service API
let data = await strapi.documents('plugin::my-plugin.my-plugin-content-type').findMany();
// Using the Query Engine API
let data = await strapi.db.query('plugin::my-plugin.my-plugin-content-type').findMany();
```
# 插件 SDK 参考
Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk
## npx @strapi/sdk-plugin init
Description: 🌐 Create a new plugin at a given path.
(Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk#npx-strapi-sdk-plugin-init)
Language: Bash
File path: N/A
```bash
npx @strapi/sdk-plugin init
```
## strapi 插件 构建
Description: 🌐 Bundle the Strapi plugin for publishing.
(Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk#strapi-plugin-build)
Language: Bash
File path: N/A
```bash
strapi-plugin build
```
## strapi 插件 watch:link
Description: 🌐 For testing purposes, it is very convenient to link your plugin to an existing application to experiment with it in real condition.
(Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk#strapi-plugin-watchlink)
Language: Bash
File path: .config.ts
```bash
strapi-plugin watch:link
```
## strapi 插件 监视
Description: 🌐 Watch the plugin source code for any change and rebuild it everytime.
(Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk#strapi-plugin-watch)
Language: Bash
File path: .config.ts
```bash
strapi-plugin watch
```
## strapi 插件验证
Description: 🌐 Verify the output of the plugin before publishing it.
(Source: https://docs.strapi.io/cms/plugins-development/plugin-sdk#strapi-plugin-verify)
Language: Bash
File path: .config.ts
```bash
strapi-plugin verify
```
# 插件扩展
Source: https://docs.strapi.io/cms/plugins-development/plugins-extension
## 插件扩展
Description: Code example from "插件扩展"
(Source: https://docs.strapi.io/cms/plugins-development/plugins-extension#plugins-extension)
Language: Bash
File path: N/A
```bash
/extensions
/some-plugin-to-extend
strapi-server.js|ts
/content-types
/some-content-type-to-extend
schema.json
/another-content-type-to-extend
schema.json
/another-plugin-to-extend
strapi-server.js|ts
```
## 在扩展文件夹内
Description: 🌐 To override factory-based controller actions, wrap the factory function itself:
(Source: https://docs.strapi.io/cms/plugins-development/plugins-extension#within-the-extensions-folder)
Language: JavaScript
File path: ./src/extensions/some-plugin-to-extend/strapi-server.js|ts
```js
module.exports = (plugin) => {
plugin.controllers.controllerA.find = (ctx) => {};
plugin.policies[newPolicy] = (ctx) => {};
plugin.routes['content-api'].routes.push({
method: 'GET',
path: '/route-path',
handler: 'controller.action',
});
return plugin;
};
```
Language: JavaScript
File path: ./src/extensions/users-permissions/strapi-server.js
```js
module.exports = (plugin) => {
const originalAuthFactory = plugin.controllers.auth;
plugin.controllers.auth = ({ strapi }) => {
// Resolve the original factory to get the controller methods
const originalAuth = originalAuthFactory({ strapi });
// Store the original action to avoid recursion
const originalCallback = originalAuth.callback;
// Override the action
originalAuth.callback = async (ctx) => {
// Custom pre-auth logic
await originalCallback(ctx);
// Custom post-auth logic
};
return originalAuth;
};
return plugin;
};
```
Language: JavaScript
File path: ./src/extensions/upload/strapi-server.js|ts
```js
module.exports = (plugin) => {
plugin.services['image-manipulation'].generateFileName = (file) => {
// Example: prefix a timestamp before the generated base name
return `${Date.now()}_${name}`;
};
return plugin;
};
```
## 在注册和引导函数中
Description: 在 ./src/index.js|ts 中扩展插件内容类型的示例
(Source: https://docs.strapi.io/cms/plugins-development/plugins-extension#within-the-register-and-bootstrap-functions)
Language: JavaScript
File path: ./src/index.js|ts
```js
module.exports = {
register({ strapi }) {
const contentTypeName = strapi.contentType('plugin::my-plugin.content-type-name')
contentTypeName.attributes = {
// Spread previous defined attributes
...contentTypeName.attributes,
// Add new, or override attributes
'toto': {
type: 'string',
}
}
},
bootstrap({ strapi }) {},
};
```
# 插件的服务器 API
Source: https://docs.strapi.io/cms/plugins-development/server-api
## 入口文件
Description: 🌐 A minimal entry file looks like this:
(Source: https://docs.strapi.io/cms/plugins-development/server-api#entry-file)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/index.js
```js
'use strict';
const register = require('./register');
const bootstrap = require('./bootstrap');
const destroy = require('./destroy');
const config = require('./config');
const contentTypes = require('./content-types');
const routes = require('./routes');
const controllers = require('./controllers');
const services = require('./services');
const policies = require('./policies');
const middlewares = require('./middlewares');
module.exports = () => ({
register,
bootstrap,
destroy,
config,
contentTypes,
routes,
controllers,
services,
policies,
middlewares,
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/index.ts
```ts
import register from './register';
import bootstrap from './bootstrap';
import destroy from './destroy';
import config from './config';
import contentTypes from './content-types';
import routes from './routes';
import controllers from './controllers';
import services from './services';
import policies from './policies';
import middlewares from './middlewares';
export default () => ({
register,
bootstrap,
destroy,
config,
contentTypes,
routes,
controllers,
services,
policies,
middlewares,
});
```
# 服务器配置
Source: https://docs.strapi.io/cms/plugins-development/server-configuration
## 配置示例
Description: 🌐 Configuration example
(Source: https://docs.strapi.io/cms/plugins-development/server-configuration#configuration-example)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/config/index.js
```js
'use strict';
module.exports = {
default: ({ env }) => ({
enabled: true,
maxItems: env.int('MY_PLUGIN_MAX_ITEMS', 10),
endpoint: env('MY_PLUGIN_ENDPOINT', 'https://api.example.com'),
}),
validator: (config) => {
if (typeof config.enabled !== 'boolean') {
throw new Error('"enabled" must be a boolean');
}
if (typeof config.maxItems !== 'number' || config.maxItems < 1) {
throw new Error('"maxItems" must be a positive number');
}
},
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/config/index.ts
```ts
export default {
default: ({ env }: { env: any }) => ({
enabled: true,
maxItems: env.int('MY_PLUGIN_MAX_ITEMS', 10),
endpoint: env('MY_PLUGIN_ENDPOINT', 'https://api.example.com'),
}),
validator: (config: { enabled: unknown; maxItems: unknown }) => {
if (typeof config.enabled !== 'boolean') {
throw new Error('"enabled" must be a boolean');
}
if (typeof config.maxItems !== 'number' || config.maxItems < 1) {
throw new Error('"maxItems" must be a positive number');
}
},
};
```
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = {
'my-plugin': {
enabled: true,
config: {
maxItems: 25,
endpoint: 'https://api.production.example.com',
},
},
};
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default {
'my-plugin': {
enabled: true,
config: {
maxItems: 25,
endpoint: 'https://api.production.example.com',
},
},
};
```
## 运行时访问
Description: 🌐 Once the plugin is loaded, its configuration is available anywhere the strapi object is accessible:
(Source: https://docs.strapi.io/cms/plugins-development/server-configuration#runtime-access)
Language: JavaScript
File path: /plugins.js
```js
// Read one key
const maxItems = strapi.plugin('my-plugin').config('maxItems');
```
---
Language: JavaScript
File path: /plugins.js
```js
// Read the entire plugin config object
const pluginConfig = strapi.config.get('plugin::my-plugin');
```
# 服务器内容类型
Source: https://docs.strapi.io/cms/plugins-development/server-content-types
## 声明
Description: 🌐 The contentTypes export is an object where each key registers a content-type under the plugin namespace.
(Source: https://docs.strapi.io/cms/plugins-development/server-content-types#declaration)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/content-types/index.js
```js
'use strict';
const article = require('./article');
module.exports = {
// highlight-next-line
article: { schema: article }, // recommended: keep key aligned with info.singularName
};
```
---
Language: JSON
File path: /src/plugins/my-plugin/server/src/content-types/article/schema.json
```json
{
"kind": "collectionType",
"collectionName": "my_plugin_articles",
"info": {
// highlight-next-line
"singularName": "article",
"pluralName": "articles",
"displayName": "Article"
},
"options": {
"draftAndPublish": false
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"body": {
"type": "richtext"
}
}
}
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/content-types/index.ts
```ts
import article from './article';
export default {
// highlight-next-line
article: { schema: article }, // recommended: keep key aligned with info.singularName
};
```
---
Language: JSON
File path: /src/plugins/my-plugin/server/src/content-types/article/schema.json
```json
{
"kind": "collectionType",
"collectionName": "my_plugin_articles",
"info": {
// highlight-next-line
"singularName": "article",
"pluralName": "articles",
"displayName": "Article"
},
"options": {
"draftAndPublish": false
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"body": {
"type": "richtext"
}
}
}
```
## UID 和命名规范
Description: 🌐 When a plugin content-type is registered, Strapi builds its runtime UID from the plugin namespace and the key used in the contentTypes export:
(Source: https://docs.strapi.io/cms/plugins-development/server-content-types#uids-and-naming-conventions)
Language: JavaScript
File path: N/A
```
plugin::.
```
Language: JavaScript
File path: N/A
```
plugin::.
```
## 使用文档服务 API 进行查询
Description: 🌐 Use the Document Service API to query plugin content-types from controllers, services, or lifecycle hooks:
(Source: https://docs.strapi.io/cms/plugins-development/server-content-types#querying-with-the-document-service-api)
Language: JavaScript
File path: N/A
```js
module.exports = ({ strapi }) => ({
async findAll(params = {}) {
// highlight-next-line
return strapi.documents('plugin::my-plugin.article').findMany(params);
},
async create(data) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
});
```
---
Language: JavaScript
File path: N/A
```js
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async findAll(params: Record = {}) {
// highlight-next-line
return strapi.documents('plugin::my-plugin.article').findMany(params);
},
async create(data: Record) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
});
```
## 访问架构
Description: 🌐 Use the content-type getter to retrieve the schema object, for example to pass it to the sanitization API:
(Source: https://docs.strapi.io/cms/plugins-development/server-content-types#accessing-the-schema)
Language: JavaScript
File path: N/A
```js
const schema = strapi.contentType('plugin::my-plugin.article');
const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);
```
---
Language: TypeScript
File path: N/A
```ts
const schema = strapi.contentType('plugin::my-plugin.article');
const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);
```
# 服务器控制器和服务
Source: https://docs.strapi.io/cms/plugins-development/server-controllers-services
## 声明
Description: 🌐 The export key used in controllers/index.js|ts must match the handler name used in route definitions.
(Source: https://docs.strapi.io/cms/plugins-development/server-controllers-services#declaration-1)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/controllers/index.js
```js
'use strict';
const article = require('./article');
module.exports = {
article,
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/controllers/article.js
```js
'use strict';
module.exports = ({ strapi }) => ({
async find(ctx) {
const articles = await strapi
.plugin('my-plugin')
.service('article')
.findAll();
ctx.body = articles;
},
async findOne(ctx) {
const { documentId } = ctx.params;
const article = await strapi
.plugin('my-plugin')
.service('article')
.findOne(documentId);
if (!article) {
return ctx.notFound('Article not found');
}
ctx.body = article;
},
async create(ctx) {
const article = await strapi
.plugin('my-plugin')
.service('article')
.create(ctx.request.body);
ctx.status = 201;
ctx.body = article;
},
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/controllers/index.ts
```ts
import article from './article';
export default {
article,
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/controllers/article.ts
```ts
import type { Core } from '@strapi/strapi';
interface ArticleService {
findAll(): Promise;
findOne(id: string): Promise;
create(data: unknown): Promise;
}
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async find(ctx: any) {
// Limitation: in @strapi/types, plugin services are currently typed as unknown.
const articleService = strapi.plugin('my-plugin').service('article') as ArticleService;
ctx.body = await articleService.findAll();
},
async findOne(ctx: any) {
const { documentId } = ctx.params;
const article = await (strapi.plugin('my-plugin').service('article') as ArticleService).findOne(documentId);
if (!article) {
return ctx.notFound('Article not found');
}
ctx.body = article;
},
async create(ctx: any) {
const articleService = strapi.plugin('my-plugin').service('article') as ArticleService;
ctx.status = 201;
ctx.body = await articleService.create(ctx.request.body);
},
});
```
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/services/index.js
```js
'use strict';
const article = require('./article');
module.exports = {
article,
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/services/article.js
```js
'use strict';
module.exports = ({ strapi }) => ({
async findAll(params = {}) {
// highlight-next-line
return strapi.documents('plugin::my-plugin.article').findMany(params);
},
async findOne(documentId) {
return strapi.documents('plugin::my-plugin.article').findOne({
documentId,
});
},
async create(data) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
async update(documentId, data) {
return strapi.documents('plugin::my-plugin.article').update({
documentId,
data,
});
},
async delete(documentId) {
return strapi.documents('plugin::my-plugin.article').delete({
documentId,
});
},
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/services/index.ts
```ts
import article from './article';
export default {
article,
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/services/article.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async findAll(params: Record = {}) {
// highlight-next-line
return strapi.documents('plugin::my-plugin.article').findMany(params);
},
async findOne(documentId: string) {
return strapi.documents('plugin::my-plugin.article').findOne({
documentId,
});
},
async create(data: Record) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
async update(documentId: string, data: Record) {
return strapi.documents('plugin::my-plugin.article').update({
documentId,
data,
});
},
async delete(documentId: string) {
return strapi.documents('plugin::my-plugin.article').delete({
documentId,
});
},
});
```
## 消毒
Description: 🌐 Plugin controllers are plain factory functions and do not extend createCoreController like in the Strapi core (see backend customization for details).
(Source: https://docs.strapi.io/cms/plugins-development/server-controllers-services#sanitization)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/controllers/article.js
```js
module.exports = ({ strapi }) => ({
async find(ctx) {
// highlight-start
const schema = strapi.contentType('plugin::my-plugin.article');
const sanitizedQuery = await strapi.contentAPI.sanitize.query(
ctx.query, schema, { auth: ctx.state.auth }
);
// highlight-end
const articles = await strapi.plugin('my-plugin').service('article').findAll(sanitizedQuery);
// highlight-next-line
ctx.body = await strapi.contentAPI.sanitize.output(articles, schema, { auth: ctx.state.auth });
},
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/controllers/article.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async find(ctx: any) {
// highlight-start
const schema = strapi.contentType('plugin::my-plugin.article');
const sanitizedQuery = await strapi.contentAPI.sanitize.query(
ctx.query, schema, { auth: ctx.state.auth }
);
// highlight-end
const articles = await (strapi.plugin('my-plugin').service('article') as any).findAll(sanitizedQuery);
// highlight-next-line
ctx.body = await strapi.contentAPI.sanitize.output(articles, schema, { auth: ctx.state.auth });
},
});
```
## 端到端示例
Description: Code example from "端到端示例"
(Source: https://docs.strapi.io/cms/plugins-development/server-controllers-services#end-to-end-example)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
module.exports = {
'content-api': {
type: 'content-api',
routes: [
{
method: 'GET',
path: '/articles',
// highlight-next-line
handler: 'article.find', // maps to controllers/article.js → find()
config: { auth: false },
},
{
method: 'POST',
path: '/articles',
// highlight-next-line
handler: 'article.create', // maps to controllers/article.js → create()
config: { auth: false },
},
],
},
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/controllers/article.js
```js
'use strict';
module.exports = ({ strapi }) => ({
// highlight-next-line
async find(ctx) {
ctx.body = await strapi.plugin('my-plugin').service('article').findAll();
// Note: sanitize query and output in production — see the Sanitization section above
},
// highlight-next-line
async create(ctx) {
const article = await strapi
.plugin('my-plugin')
.service('article')
.create(ctx.request.body);
ctx.status = 201;
ctx.body = article;
},
});
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/services/article.js
```js
'use strict';
module.exports = ({ strapi }) => ({
findAll() {
return strapi.documents('plugin::my-plugin.article').findMany();
},
create(data) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
export default {
'content-api': {
type: 'content-api' as const,
routes: [
{
method: 'GET' as const,
path: '/articles',
// highlight-next-line
handler: 'article.find', // maps to controllers/article.ts → find()
config: { auth: false },
},
{
method: 'POST' as const,
path: '/articles',
// highlight-next-line
handler: 'article.create', // maps to controllers/article.ts → create()
config: { auth: false },
},
],
},
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/controllers/article.ts
```ts
import type { Core } from '@strapi/strapi';
interface ArticleService {
findAll(): Promise;
create(data: unknown): Promise;
}
export default ({ strapi }: { strapi: Core.Strapi }) => ({
// highlight-next-line
async find(ctx: any) {
ctx.body = await (strapi.plugin('my-plugin').service('article') as ArticleService).findAll();
// Note: sanitize query and output in production — see the Sanitization section above
},
// highlight-next-line
async create(ctx: any) {
const article = await (strapi.plugin('my-plugin').service('article') as ArticleService)
.create(ctx.request.body);
ctx.status = 201;
ctx.body = article;
},
});
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/services/article.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => ({
findAll() {
// highlight-next-line
return strapi.documents('plugin::my-plugin.article').findMany();
},
create(data: Record) {
return strapi.documents('plugin::my-plugin.article').create({ data });
},
});
```
# 服务器获取器及使用
Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage
## Getter 风格
Description: 顶层 getter 通过插件名称链式调用:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#getter-styles)
Language: Bash
File path: N/A
```bash
strapi.plugin('plugin-name').service('service-name')
strapi.plugin('plugin-name').controller('controller-name')
```
Language: Bash
File path: N/A
```bash
strapi.service('plugin::plugin-name.service-name')
strapi.controller('plugin::plugin-name.controller-name')
```
## 从控制器调用插件服务
Description: 🌐 The most common pattern: a controller delegates to its own plugin's service:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#calling-a-plugin-service-from-a-controller)
Language: JavaScript
File path: /src/plugins/todo/server/src/controllers/task.js
```js
'use strict';
module.exports = ({ strapi }) => ({
async find(ctx) {
// highlight-next-line
const tasks = await strapi.plugin('todo').service('task').findAll(); // top-level getter: preferred inside your own plugin
ctx.body = tasks;
},
async create(ctx) {
const task = await strapi
.plugin('todo')
.service('task')
.create(ctx.request.body);
ctx.status = 201;
ctx.body = task;
},
});
```
---
Language: TypeScript
File path: /src/plugins/todo/server/src/controllers/task.ts
```ts
import type { Context } from 'koa';
import type { Core } from '@strapi/strapi';
type TaskService = {
findAll(): Promise;
create(data: unknown): Promise;
};
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async find(ctx: Context) {
// Narrow cast: plugin services require app-level type augmentation for full typing.
const tasks = await (strapi.plugin('todo').service('task') as TaskService).findAll();
ctx.body = tasks;
},
async create(ctx: Context) {
const task = await (strapi.plugin('todo').service('task') as TaskService).create(
(ctx.request as any).body
);
(ctx as any).status = 201;
ctx.body = task;
},
});
```
## 从 bootstrap 调用插件服务
Description: 🌐 Services called in bootstrap() have access to the full strapi instance, including other plugins' services:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#calling-a-plugin-service-from-bootstrap)
Language: JavaScript
File path: /src/plugins/todo/server/src/bootstrap.js
```js
'use strict';
module.exports = async ({ strapi }) => {
// Call own plugin service to seed initial data
const count = await strapi.plugin('todo').service('task').count();
if (count === 0) {
await strapi.plugin('todo').service('task').create({
title: 'Welcome task',
done: false,
});
}
};
```
---
Language: TypeScript
File path: /src/plugins/todo/server/src/bootstrap.ts
```ts
import type { Core } from '@strapi/strapi';
type TaskService = {
count(): Promise;
create(data: unknown): Promise;
};
export default async ({ strapi }: { strapi: Core.Strapi }) => {
// Narrow cast: plugin services are resolved dynamically unless your project augments Strapi service typings.
const taskService = strapi.plugin('todo').service('task') as TaskService;
// highlight-next-line
const count = await taskService.count();
if (count === 0) {
await taskService.create({ title: 'Welcome task', done: false });
}
};
```
## 在插件之间或从应用代码中调用
Description: 🌐 From application-level controllers or services (outside the plugin), or when calling from another plugin, global getters using the full UID are often clearer:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#calling-across-plugins-or-from-application-code)
Language: JavaScript
File path: /src/api/project/controllers/project.js
```js
'use strict';
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::project.project', ({ strapi }) => ({
async create(ctx) {
const { data, meta } = await super.create(ctx);
// highlight-next-line
await strapi.service('plugin::todo.task').create({ // global getter: preferred in application code
title: `Review project: ${data.attributes.name}`,
done: false,
});
return { data, meta };
},
}));
```
---
Language: TypeScript
File path: /src/api/project/controllers/project.ts
```ts
import { factories } from '@strapi/strapi';
type TaskService = {
create(data: unknown): Promise;
};
export default factories.createCoreController(
'api::project.project',
({ strapi }) => ({
async create(ctx: any) {
const { data, meta } = await super.create(ctx);
// highlight-next-line
// Narrow cast: this generic documentation cannot infer your app-specific service signatures.
await (strapi.service('plugin::todo.task') as TaskService).create({
title: `Review project: ${data.attributes.name}`,
done: false,
});
return { data, meta };
},
})
);
```
## 在运行时读取插件配置
Description: 🌐 Reading plugin configuration at runtime
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#reading-plugin-configuration-at-runtime)
Language: JavaScript
File path: N/A
```js
// Read a single key
const maxItems = strapi.plugin('todo').config('maxItems');
```
---
Language: JavaScript
File path: N/A
```js
// Read the full config object
const todoConfig = strapi.config.get('plugin::todo');
```
---
Language: JavaScript
File path: N/A
```js
// Read a nested key
const endpoint = strapi.config.get('plugin::todo.endpoint');
```
## 访问内容类型架构
Description: 🌐 Use the content-type getter when you need the schema object, for example to pass it to the sanitization API:
(Source: https://docs.strapi.io/cms/plugins-development/server-getters-usage#accessing-a-content-type-schema)
Language: JavaScript
File path: N/A
```js
// Access the content-type schema
const schema = strapi.contentType('plugin::todo.task');
const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);
```
---
Language: TypeScript
File path: N/A
```ts
// highlight-next-line
const schema = strapi.contentType('plugin::todo.task'); // access the content-type schema
const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);
```
# 服务器生命周期
Source: https://docs.strapi.io/cms/plugins-development/server-lifecycle
## register()
Description: 注册 自定义字段 的服务器端 - 注册数据库迁移 - 在 Strapi HTTP 服务器上注册服务器中间件(例如 strapi.server.use(...)) - 在引导之前扩展另一个插件的内容类型或接口
(Source: https://docs.strapi.io/cms/plugins-development/server-lifecycle#register)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/register.js
```js
'use strict';
module.exports = ({ strapi }) => {
// Register a server-level middleware early in startup
strapi.server.use(async (ctx, next) => {
ctx.set('X-Plugin-Version', '1.0.0');
await next();
});
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/register.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => {
// Register a server-level middleware early in startup
strapi.server.use(async (ctx: any, next: () => Promise) => {
ctx.set('X-Plugin-Version', '1.0.0');
await next();
});
};
```
## bootstrap()
Description: 使用 strapi.service('admin::permission').actionProvider.registerMany(...) 注册管理员 RBAC 操作 - 注册 cron 任务 - 订阅数据库生命周期事件 - 从你的插件或其他插件调用服务 - 设置需要先注册其他插件的跨插件集成
(Source: https://docs.strapi.io/cms/plugins-development/server-lifecycle#bootstrap)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/bootstrap.js
```js
'use strict';
module.exports = async ({ strapi }) => {
// Register admin RBAC actions for this plugin
await strapi.service('admin::permission').actionProvider.registerMany([
{
section: 'plugins',
displayName: 'Read',
uid: 'read',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Settings',
uid: 'settings',
pluginName: 'my-plugin',
},
]);
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/bootstrap.ts
```ts
import type { Core } from '@strapi/strapi';
export default async ({ strapi }: { strapi: Core.Strapi }) => {
// Register admin RBAC actions for this plugin
await strapi.service('admin::permission').actionProvider.registerMany([
{
section: 'plugins',
displayName: 'Read',
uid: 'read',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Settings',
uid: 'settings',
pluginName: 'my-plugin',
},
]);
};
```
## destroy()
Description: 关闭外部连接(数据库、消息队列、WebSocket 服务器) - 在 bootstrap() 中清除间隔或超时 - 移除在插件生命周期内注册的事件监听器
(Source: https://docs.strapi.io/cms/plugins-development/server-lifecycle#destroy)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/destroy.js
```js
'use strict';
module.exports = ({ strapi }) => {
// Close an external connection opened in bootstrap()
strapi.plugin('my-plugin').service('queue').disconnect();
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/destroy.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => {
// Close an external connection opened in bootstrap()
strapi.plugin('my-plugin').service('queue').disconnect();
};
```
# 服务器策略和中间件
Source: https://docs.strapi.io/cms/plugins-development/server-policies-middlewares
## 声明
Description: Code example from "声明"
(Source: https://docs.strapi.io/cms/plugins-development/server-policies-middlewares#declaration)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/policies/index.js
```js
'use strict';
const hasRole = require('./has-role');
module.exports = {
hasRole,
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/policies/has-role.js
```js
'use strict';
// Allow the request only if the user has the role specified in the route config
// Usage in route: { name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } }
module.exports = (policyContext, config, { strapi }) => {
const { user } = policyContext.state;
const targetRole = config.role;
if (!user || !targetRole) {
return false;
}
// Supports both `user.role` and `user.roles` shapes depending on auth strategy.
const roles = Array.isArray(user.roles)
? user.roles
: user.role
? [user.role]
: [];
return roles.some((role) => {
if (typeof role === 'string') return role === targetRole;
return role?.code === targetRole || role?.name === targetRole;
});
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/policies/index.ts
```ts
import hasRole from './has-role';
export default {
hasRole,
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/policies/has-role.ts
```ts
import type { Core } from '@strapi/strapi';
type UserRole = { code?: string; name?: string };
// Allow the request only if the user has the role specified in the route config
// Usage in route: { name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } }
export default (
policyContext: Core.PolicyContext,
config: { role?: string },
{ strapi }: { strapi: Core.Strapi }
) => {
const { user } = policyContext.state;
const targetRole = config?.role;
if (!user || !targetRole) {
return false;
}
// Supports both `user.role` and `user.roles` shapes depending on auth strategy.
const userWithRoles = user as { roles?: UserRole[]; role?: UserRole };
const roles: UserRole[] = Array.isArray(userWithRoles.roles)
? userWithRoles.roles
: userWithRoles.role
? [userWithRoles.role]
: [];
return roles.some((role) => role?.code === targetRole || role?.name === targetRole);
};
```
## 在路由中的使用
Description: 🌐 Once declared, reference a plugin policy from a route using the plugin::my-plugin.policy-name namespace:
(Source: https://docs.strapi.io/cms/plugins-development/server-policies-middlewares#usage-in-routes)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
module.exports = [
{
method: 'GET',
path: '/dashboard',
handler: 'dashboard.find',
config: {
// highlight-next-line
policies: ['plugin::my-plugin.isActive'], // simple reference by namespaced name
},
},
{
method: 'DELETE',
path: '/articles/:id',
handler: 'article.delete',
config: {
// highlight-next-line
policies: [{ name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } }], // with per-route config
},
},
{
method: 'GET',
path: '/public',
handler: 'article.findAll',
config: {
// highlight-next-line
policies: [(policyContext, config, { strapi }) => true], // inline policy, no registration needed
},
},
];
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
import type { Core } from '@strapi/strapi';
export default [
{
method: 'GET' as const,
path: '/dashboard',
handler: 'dashboard.find',
config: {
// highlight-next-line
policies: ['plugin::my-plugin.isActive'], // simple reference by namespaced name
},
},
{
method: 'DELETE' as const,
path: '/articles/:id',
handler: 'article.delete',
config: {
// highlight-next-line
policies: [{ name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } }], // with per-route config
},
},
{
method: 'GET' as const,
path: '/public',
handler: 'article.findAll',
config: {
// highlight-next-line
policies: [(policyContext: Core.PolicyContext, config: unknown, { strapi }: { strapi: Core.Strapi }) => true], // inline policy, no registration needed
},
},
];
```
## 路由级中间件
Description: 🌐 Reference a route-level middleware in a route using the same plugin::my-plugin.middleware-name namespace as policies:
(Source: https://docs.strapi.io/cms/plugins-development/server-policies-middlewares#route-level-middlewares)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/middlewares/index.js
```js
'use strict';
const logRequest = require('./log-request');
module.exports = {
logRequest,
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/middlewares/log-request.js
```js
'use strict';
module.exports = (config, { strapi }) => async (ctx, next) => {
strapi.log.info(`[my-plugin] ${ctx.method} ${ctx.url}`);
await next();
strapi.log.info(`[my-plugin] → ${ctx.status}`);
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/middlewares/index.ts
```ts
import logRequest from './log-request';
export default {
logRequest,
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/middlewares/log-request.ts
```ts
import type { Core } from '@strapi/strapi';
export default (config: unknown, { strapi }: { strapi: Core.Strapi }) =>
async (ctx: any, next: () => Promise) => {
strapi.log.info(`[my-plugin] ${ctx.method} ${ctx.url}`);
await next();
strapi.log.info(`[my-plugin] → ${ctx.status}`);
};
```
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
module.exports = [
{
method: 'POST',
path: '/articles',
handler: 'article.create',
config: {
// highlight-next-line
middlewares: ['plugin::my-plugin.logRequest'],
},
},
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
middlewares: [
// highlight-next-line
async (ctx, next) => {
// inline middleware, no registration needed
ctx.query.pageSize = ctx.query.pageSize || '10';
await next();
},
],
},
},
];
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
export default [
{
method: 'POST' as const,
path: '/articles',
handler: 'article.create',
config: {
// highlight-next-line
middlewares: ['plugin::my-plugin.logRequest'],
},
},
{
method: 'GET' as const,
path: '/articles',
handler: 'article.find',
config: {
middlewares: [
async (ctx: any, next: () => Promise) => {
// inline middleware, no registration needed
ctx.query.pageSize = ctx.query.pageSize || '10';
await next();
},
],
},
},
];
```
## 服务器级中间件
Description: 🌐 A server-level middleware is registered on the Strapi HTTP server directly and runs for every request, not just plugin routes.
(Source: https://docs.strapi.io/cms/plugins-development/server-policies-middlewares#server-level-middlewares)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/register.js
```js
'use strict';
module.exports = ({ strapi }) => {
// Attached to the global server pipeline — runs per matching request
strapi.server.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/register.ts
```ts
import type { Core } from '@strapi/strapi';
export default ({ strapi }: { strapi: Core.Strapi }) => {
// Attached to the global server pipeline — runs per matching request
strapi.server.use(async (ctx: any, next: () => Promise) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
};
```
# 服务器路由
Source: https://docs.strapi.io/cms/plugins-development/server-routes
## 数组格式
Description: Code example from "数组格式"
(Source: https://docs.strapi.io/cms/plugins-development/server-routes#array-format)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
module.exports = [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/articles',
handler: 'article.create',
config: {
policies: [],
},
},
];
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
export default [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/articles',
handler: 'article.create',
config: {
policies: [],
},
},
];
```
## 命名路由格式
Description: 🌐 With the named router format, use an object with named keys (admin, content-api, or any custom name) to declare separate router groups.
(Source: https://docs.strapi.io/cms/plugins-development/server-routes#named-router-format)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
const adminRoutes = require('./admin');
const contentApiRoutes = require('./content-api');
module.exports = {
// highlight-start
admin: adminRoutes,
'content-api': contentApiRoutes,
// highlight-end
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/admin/index.js
```js
'use strict';
module.exports = {
type: 'admin',
routes: [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
policies: ['admin::isAuthenticatedAdmin'],
},
},
],
};
```
---
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/content-api/index.js
```js
'use strict';
module.exports = {
type: 'content-api',
routes: [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
policies: [],
},
},
],
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
import adminRoutes from './admin';
import contentApiRoutes from './content-api';
export default {
// highlight-start
admin: adminRoutes,
'content-api': contentApiRoutes,
// highlight-end
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/admin/index.ts
```ts
export default {
type: 'admin' as const,
routes: [
{
method: 'GET' as const,
path: '/articles',
handler: 'article.find',
config: {
policies: ['admin::isAuthenticatedAdmin'],
},
},
],
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/content-api/index.ts
```ts
export default {
type: 'content-api' as const,
routes: [
{
method: 'GET' as const,
path: '/articles',
handler: 'article.find',
config: {
policies: [],
},
},
],
};
```
## 工厂回调格式
Description: Code example from "工厂回调格式"
(Source: https://docs.strapi.io/cms/plugins-development/server-routes#factory-callback-format)
Language: JavaScript
File path: /src/plugins/my-plugin/server/src/routes/index.js
```js
'use strict';
module.exports = {
'content-api': ({ strapi }) => ({
type: 'content-api',
routes: [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
// highlight-next-line
auth: strapi.plugin('my-plugin').config('publicRead') ? false : {},
},
},
],
}),
};
```
---
Language: TypeScript
File path: /src/plugins/my-plugin/server/src/routes/index.ts
```ts
import type { Core } from '@strapi/strapi';
const routes: Record<
string,
Core.RouterConfig | ((args: { strapi: Core.Strapi }) => Core.RouterConfig)
> = {
'content-api': ({ strapi }) => ({
type: 'content-api',
routes: [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
// highlight-next-line
auth: strapi.plugin('my-plugin').config('publicRead') ? false : {},
},
},
],
}),
};
export default routes;
```
## Strapi 应用的默认设置
Description: 🌐 The following 2 declarations are equivalent.
(Source: https://docs.strapi.io/cms/plugins-development/server-routes#defaults-applied-by-strapi)
Language: JavaScript
File path: Implicit
```js
module.exports = [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
},
];
```
---
Language: JavaScript
File path: Equivalent
```js
module.exports = {
admin: {
type: 'admin',
prefix: '/my-plugin',
routes: [
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
auth: {
// highlight-next-line
scope: ['plugin::my-plugin.article.find'], // auto-generated from handler string
},
},
},
],
},
};
```
---
Language: JavaScript
File path: Implicit
```js
export default [
{
method: 'GET' as const,
path: '/articles',
handler: 'article.find',
},
];
```
---
Language: JavaScript
File path: Equivalent
```js
export default {
admin: {
type: 'admin' as const,
prefix: '/my-plugin',
routes: [
{
method: 'GET' as const,
path: '/articles',
handler: 'article.find',
config: {
auth: {
// highlight-next-line
scope: ['plugin::my-plugin.article.find'], // auto-generated from handler string
},
},
},
],
},
};
```
# 文档插件
Source: https://docs.strapi.io/cms/plugins/documentation
## 安装
Description: 🌐 To install the documentation plugin, run following command in your terminal:
(Source: https://docs.strapi.io/cms/plugins/documentation#installation)
Language: Bash
File path: /extensions/documentation/documentation//full_documentation.js
```bash
yarn add @strapi/plugin-documentation
```
---
Language: Bash
File path: /extensions/documentation/documentation//full_documentation.js
```bash
npm install @strapi/plugin-documentation
```
## 基于代码的配置
Description: 🌐 The following is an example configuration:
(Source: https://docs.strapi.io/cms/plugins/documentation#code-based-configuration)
Language: JSON
File path: src/extensions/documentation/config/settings.json
```json
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "DOCUMENTATION",
"description": "",
"termsOfService": "YOUR_TERMS_OF_SERVICE_URL",
"contact": {
"name": "TEAM",
"email": "contact-email@something.io",
"url": "mywebsite.io"
},
"license": {
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"x-strapi-config": {
"plugins": ["upload", "users-permissions"],
"path": "/documentation"
},
"servers": [
{
"url": "http://localhost:1337/api",
"description": "Development server"
}
],
"externalDocs": {
"description": "Find out more",
"url": "https://strapi.nodejs.cn/developer-docs/latest/getting-started/introduction.html"
},
"security": [
{
"bearerAuth": []
}
]
}
```
## 创建文档的新版本
Description: 🌐 To create a new version, change the info.version key in the settings.json file:
(Source: https://docs.strapi.io/cms/plugins/documentation#create-a-new-version-of-the-documentation)
Language: JSON
File path: src/extensions/documentation/config/settings.json
```json
{
"info": {
"version": "2.0.0"
}
}
```
## 定义需要生成文档的插件
Description: 🌐 If you want plugins to be included in documentation generation, they should be included in the plugins array in the x-strapi-config object.
(Source: https://docs.strapi.io/cms/plugins/documentation#define-which-plugins)
Language: JSON
File path: src/extensions/documentation/config/settings.json
```json
{
"x-strapi-config": {
"plugins": ["upload", "users-permissions"]
}
}
```
## excludeFromGeneration() {#从生成中排除}
Description: | 参数 | 类型 | 描述 | | --- | --- | --- | | api | 字符串或字符串数组 | 要排除的 API/插件的名称,或名称列表 |
(Source: https://docs.strapi.io/cms/plugins/documentation#excludefromgeneration)
Language: JavaScript
File path: Application
```js
module.exports = {
register({ strapi }) {
strapi
.plugin("documentation")
.service("override")
.excludeFromGeneration("restaurant");
// or several
strapi
.plugin("documentation")
.service("override")
.excludeFromGeneration(["address", "upload"]);
}
}
```
## registerOverride()
Description: 🌐 If the override should only be applied to a specific version, the override must include a value for info.version.
(Source: https://docs.strapi.io/cms/plugins/documentation#register-override)
Language: JavaScript
File path: Application
```js
module.exports = {
register({ strapi }) {
if (strapi.plugin('documentation')) {
const override = {
// Only run this override for version 1.0.0
info: { version: '1.0.0' },
paths: {
'/answer-to-everything': {
get: {
responses: { 200: { description: "*" }}
}
}
}
}
strapi
.plugin('documentation')
.service('override')
.registerOverride(override, {
// Specify the origin in case the user does not want this plugin documented
pluginOrigin: 'upload',
// The override provides everything don't generate anything
excludeFromGeneration: ['upload'],
});
}
},
}
```
## mutateDocumentation()
Description: | 参数 | 类型 | 描述 | | --- | --- | --- | | generatedDocumentationDraft | 对象 | 应用覆盖后的生成文档,作为可变对象 |
(Source: https://docs.strapi.io/cms/plugins/documentation#mutate-documentation)
Language: JavaScript
File path: config/plugins.js
```js
module.exports = {
documentation: {
config: {
"x-strapi-config": {
mutateDocumentation: (generatedDocumentationDraft) => {
generatedDocumentationDraft.paths[
"/answer-to-everything" // must be an existing path
].get.responses["200"].description = "*";
},
},
},
},
};
```
# GraphQL 插件
Source: https://docs.strapi.io/cms/plugins/graphql
## 安装
Description: 🌐 To install the GraphQL plugin, run the following command in your terminal:
(Source: https://docs.strapi.io/cms/plugins/graphql#installation)
Language: Bash
File path: N/A
```bash
yarn add @strapi/plugin-graphql
```
---
Language: Bash
File path: N/A
```bash
npm install @strapi/plugin-graphql
```
## 可用选项
Description: 🌐 The following is an example custom configuration:
(Source: https://docs.strapi.io/cms/plugins/graphql#available-options)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = {
graphql: {
config: {
endpoint: '/graphql',
shadowCRUD: true,
landingPage: false, // disable Sandbox everywhere
depthLimit: 7,
defaultLimit: 25,
maxLimit: 100,
apolloServer: {
tracing: false,
},
},
},
};
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default () => ({
graphql: {
config: {
endpoint: '/graphql',
shadowCRUD: true,
landingPage: false, // disable Sandbox everywhere
depthLimit: 7,
defaultLimit: 25,
maxLimit: 100,
apolloServer: {
tracing: false,
},
},
},
})
```
## 动态启用 Apollo Sandbox
Description: 🌐 You can use a function to dynamically enable Apollo Sandbox depending on the environment:
(Source: https://docs.strapi.io/cms/plugins/graphql#dynamically-enable-apollo-sandbox)
Language: JavaScript
File path: ./config/plugins.js
```js
module.exports = ({ env }) => {
graphql: {
config: {
endpoint: '/graphql',
shadowCRUD: true,
landingPage: (strapi) => {
if (env("NODE_ENV") !== "production") {
return true;
} else {
return false;
}
},
},
},
};
```
---
Language: TypeScript
File path: ./config/plugins.ts
```ts
export default ({ env }) => {
graphql: {
config: {
endpoint: '/graphql',
shadowCRUD: true,
landingPage: (strapi) => {
if (env("NODE_ENV") !== "production") {
return true;
} else {
return false;
}
},
},
},
};
```
## 登录页面的 CORS 异常
Description: 🌐 To add them globally, you can merge the following into your middleware configuration:
(Source: https://docs.strapi.io/cms/plugins/graphql#cors-exceptions-for-landing-page)
Language: JavaScript
File path: ./middlewares/graphql-security.js
```js
module.exports = (config, { strapi }) => {
return async (ctx, next) => {
if (ctx.request.path === '/graphql') {
ctx.set('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.jsdelivr.net apollo-server-landing-page.cdn.apollographql.com; connect-src 'self' https:; img-src 'self' data: blob: apollo-server-landing-page.cdn.apollographql.com; media-src 'self' data: blob: apollo-server-landing-page.cdn.apollographql.com; frame-src sandbox.embed.apollographql.com; manifest-src apollo-server-landing-page.cdn.apollographql.com;");
}
await next();
};
};
```
---
Language: TypeScript
File path: ./middlewares/graphql-security.ts
```ts
export default (config, { strapi }) => {
return async (ctx, next) => {
if (ctx.request.path === '/graphql') {
ctx.set('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.jsdelivr.net apollo-server-landing-page.cdn.apollographql.com; connect-src 'self' https:; img-src 'self' data: blob: apollo-server-landing-page.cdn.apollographql.com; media-src 'self' data: blob: apollo-server-landing-page.cdn.apollographql.com; frame-src sandbox.embed.apollographql.com; manifest-src apollo-server-landing-page.cdn.apollographql.com;");
}
await next();
};
};
```
Language: JSON
File path: /config/middlewares
```json
{
name: "strapi::security",
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
"connect-src": ["'self'", "https:", "apollo-server-landing-page.cdn.apollographql.com"],
"img-src": ["'self'", "data:", "blob:", "apollo-server-landing-page.cdn.apollographql.com"],
"script-src": ["'self'", "'unsafe-inline'", "apollo-server-landing-page.cdn.apollographql.com"],
"style-src": ["'self'", "'unsafe-inline'", "apollo-server-landing-page.cdn.apollographql.com"],
"frame-src": ["sandbox.embed.apollographql.com"]
}
}
}
}
```
## 影子 CRUD
Description: 🌐 If you've generated an API called Document using the interactive strapi generate CLI or the administration panel, your model looks like this:
(Source: https://docs.strapi.io/cms/plugins/graphql#shadow-crud)
Language: JSON
File path: /src/api/[api-name]/content-types/document/schema.json
```json
{
"kind": "collectionType",
"collectionName": "documents",
"info": {
"singularName": "document",
"pluralName": "documents",
"displayName": "document",
"name": "document"
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {},
"attributes": {
"name": {
"type": "string"
},
"description": {
"type": "richtext"
},
"locked": {
"type": "boolean"
}
}
}
```
Language: GRAPHQL
File path: N/A
```graphql
# Document's Type definition
input DocumentFiltersInput {
name: StringFilterInput
description: StringFilterInput
locked: BooleanFilterInput
createdAt: DateTimeFilterInput
updatedAt: DateTimeFilterInput
publishedAt: DateTimeFilterInput
and: [DocumentFiltersInput]
or: [DocumentFiltersInput]
not: DocumentFiltersInput
}
input DocumentInput {
name: String
description: String
locked: Boolean
createdAt: DateTime
updatedAt: DateTime
publishedAt: DateTime
}
type Document {
name: String
description: String
locked: Boolean
createdAt: DateTime
updatedAt: DateTime
publishedAt: DateTime
}
type DocumentEntity {
id: ID
attributes: Document
}
type DocumentEntityResponse {
data: DocumentEntity
}
type DocumentEntityResponseCollection {
data: [DocumentEntity!]!
meta: ResponseCollectionMeta!
}
type DocumentRelationResponseCollection {
data: [DocumentEntity!]!
}
# Queries to retrieve one or multiple restaurants.
type Query {
document(id: ID): DocumentEntityResponse
documents(
filters: DocumentFiltersInput
pagination: PaginationArg = {}
sort: [String] = []
publicationState: PublicationState = LIVE
):DocumentEntityResponseCollection
}
# Mutations to create, update or delete a restaurant.
type Mutation {
createDocument(data: DocumentInput!): DocumentEntityResponse
updateDocument(id: ID!, data: DocumentInput!): DocumentEntityResponse
deleteDocument(id: ID!): DocumentEntityResponse
}
```
## 自定义
Description: GraphQL 自定义示例
(Source: https://docs.strapi.io/cms/plugins/graphql#customization)
Language: JavaScript
File path: /src/index.js
```js
module.exports = {
/**
* An asynchronous register function that runs before
* your application is initialized.
*
* This gives you an opportunity to extend code.
*/
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.shadowCRUD('api::restaurant.restaurant').disable();
extensionService.shadowCRUD('api::category.category').disableQueries();
extensionService.shadowCRUD('api::address.address').disableMutations();
extensionService.shadowCRUD('api::document.document').field('locked').disable();
extensionService.shadowCRUD('api::like.like').disableActions(['create', 'update', 'delete']);
const extension = ({ nexus }) => ({
// Nexus
types: [
nexus.objectType({
name: 'Book',
definition(t) {
t.string('title');
},
}),
],
plugins: [
nexus.plugin({
name: 'MyPlugin',
onAfterBuild(schema) {
console.log(schema);
},
}),
],
// GraphQL SDL
typeDefs: `
type Article {
name: String
}
`,
resolvers: {
Query: {
address: {
resolve() {
return { value: { city: 'Montpellier' } };
},
},
},
},
resolversConfig: {
'Query.address': {
auth: false,
},
},
});
extensionService.use(extension);
},
};
```
---
Language: TypeScript
File path: /src/index.ts
```ts
export default {
/**
* An asynchronous register function that runs before
* your application is initialized.
*
* This gives you an opportunity to extend code.
*/
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.shadowCRUD('api::restaurant.restaurant').disable();
extensionService.shadowCRUD('api::category.category').disableQueries();
extensionService.shadowCRUD('api::address.address').disableMutations();
extensionService.shadowCRUD('api::document.document').field('locked').disable();
extensionService.shadowCRUD('api::like.like').disableActions(['create', 'update', 'delete']);
const extension = ({ nexus }) => ({
// Nexus
types: [
nexus.objectType({
name: 'Book',
definition(t) {
t.string('title');
},
}),
],
plugins: [
nexus.plugin({
name: 'MyPlugin',
onAfterBuild(schema) {
console.log(schema);
},
}),
],
// GraphQL SDL
typeDefs: `
type Article {
name: String
}
`,
resolvers: {
Query: {
address: {
resolve() {
return { value: { city: 'Montpellier' } };
},
},
},
},
resolversConfig: {
'Query.address': {
auth: false,
},
},
});
extensionService.use(extension);
},
};
```
## 在 Shadow CRUD 中禁用操作
Description: Code example from "在 Shadow CRUD 中禁用操作"
(Source: https://docs.strapi.io/cms/plugins/graphql#disabling-operations-in-the-shadow-crud)
Language: JavaScript
File path: N/A
```js
// Disable the 'find' operation on the 'restaurant' content-type in the 'restaurant' API
strapi
.plugin('graphql')
.service('extension')
.shadowCRUD('api::restaurant.restaurant')
.disableAction('find')
// Disable the 'name' field on the 'document' content-type in the 'document' API
strapi
.plugin('graphql')
.service('extension')
.shadowCRUD('api::document.document')
.field('name')
.disable()
```
## 扩展模式
Description: Code example from "扩展模式"
(Source: https://docs.strapi.io/cms/plugins/graphql#extending-the-schema)
Language: JavaScript
File path: /src/index.js
```js
module.exports = {
register({ strapi }) {
const extension = ({ nexus }) => ({
types: [
nexus.objectType({
…
}),
],
plugins: [
nexus.plugin({
…
})
]
})
strapi.plugin('graphql').service('extension').use(extension)
}
}
```
---
Language: TypeScript
File path: ./src/index.ts
```ts
export default {
register({ strapi }) {
const extension = ({ nexus }) => ({
types: [
nexus.objectType({
…
}),
],
plugins: [
nexus.plugin({
…
})
]
})
strapi.plugin('graphql').service('extension').use(extension)
}
}
```
## 授权配置
Description: Code example from "授权配置"
(Source: https://docs.strapi.io/cms/plugins/graphql#authorization-configuration)
Language: JavaScript
File path: /src/index.js
```js
module.exports = {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
/**
* Querying the Categories content-type
* bypasses the authorization system.
*/
auth: false
},
'Query.restaurants': {
/**
* Querying the Restaurants content-type
* requires the find permission
* on the 'Address' content-type
* of the 'Address' API
*/
auth: {
scope: ['api::address.address.find']
}
},
}
})
}
}
```
---
Language: TypeScript
File path: /src/index.ts
```ts
export default {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
/**
* Querying the Categories content-type
* bypasses the authorization system.
*/
auth: false
},
'Query.restaurants': {
/**
* Querying the Restaurants content-type
* requires the find permission
* on the 'Address' content-type
* of the 'Address' API
*/
auth: {
scope: ['api::address.address.find']
}
},
}
})
}
}
```
## 政策
Description: 应用于解析器的 GraphQL 策略示例
(Source: https://docs.strapi.io/cms/plugins/graphql#policies)
Language: JavaScript
File path: /src/index.js
```js
module.exports = {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
policies: [
(context, { strapi }) => {
console.log('hello', context.parent)
/**
* If 'categories' have a parent, the function returns true,
* so the request won't be blocked by the policy.
*/
return context.parent !== undefined;
}
/**
* Uses a policy already created in Strapi.
*/
"api::model.policy-name",
/**
* Uses a policy already created in Strapi with a custom configuration
*/
{name:"api::model.policy-name", config: {/* all config values I want to pass to the strapi policy */} },
],
auth: false,
},
}
})
}
}
```
---
Language: TypeScript
File path: /src/index.ts
```ts
export default {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
policies: [
(context, { strapi }) => {
console.log('hello', context.parent)
/**
* If 'categories' have a parent, the function returns true,
* so the request won't be blocked by the policy.
*/
return context.parent !== undefined;
}
/**
* Uses a policy already created in Strapi.
*/
"api::model.policy-name",
/**
* Uses a policy already created in Strapi with a custom configuration
*/
{name:"api::model.policy-name", config: {/* all the configuration values to pass to the strapi policy */} },
],
auth: false,
},
}
})
}
}
```
## 中间件
Description: 应用于解析器的 GraphQL 中间件示例
(Source: https://docs.strapi.io/cms/plugins/graphql#middlewares)
Language: JavaScript
File path: N/A
```js
module.exports = {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
middlewares: [
/**
* Basic middleware example #1
* Log resolving time in console
*/
async (next, parent, args, context, info) => {
console.time('Resolving categories');
// call the next resolver
const res = await next(parent, args, context, info);
console.timeEnd('Resolving categories');
return res;
},
/**
* Basic middleware example #2
* Enable server-side shared caching
*/
async (next, parent, args, context, info) => {
info.cacheControl.setCacheHint({ maxAge: 60, scope: "PUBLIC" });
return next(parent, args, context, info);
},
/**
* Basic middleware example #3
* change the 'name' attribute of parent with id 1 to 'foobar'
*/
(resolve, parent, ...rest) => {
if (parent.id === 1) {
return resolve({...parent, name: 'foobar' }, ...rest);
}
return resolve(parent, ...rest);
}
/**
* Basic middleware example #4
* Uses a middleware already created in Strapi.
*/
"api::model.middleware-name",
/**
* Basic middleware example #5
* Uses a middleware already created in Strapi with a custom configuration
*/
{ name: "api::model.middleware-name", options: { /* all config values I want to pass to the strapi middleware */ } },
],
auth: false,
},
}
})
}
}
```
---
Language: JavaScript
File path: N/A
```js
export default {
register({ strapi }) {
const extensionService = strapi.plugin('graphql').service('extension');
extensionService.use({
resolversConfig: {
'Query.categories': {
middlewares: [
/**
* Basic middleware example #1
* Log resolving time in console
*/
async (next, parent, args, context, info) => {
console.time('Resolving categories');
// call the next resolver
const res = await next(parent, args, context, info);
console.timeEnd('Resolving categories');
return res;
},
/**
* Basic middleware example #2
* Enable server-side shared caching
*/
async (next, parent, args, context, info) => {
info.cacheControl.setCacheHint({ maxAge: 60, scope: "PUBLIC" });
return next(parent, args, context, info);
},
/**
* Basic middleware example #3
* change the 'name' attribute of parent with id 1 to 'foobar'
*/
(resolve, parent, ...rest) => {
if (parent.id === 1) {
return resolve({...parent, name: 'foobar' }, ...rest);
}
return resolve(parent, ...rest);
}
/**
* Basic middleware example #4
* Uses a middleware already created in Strapi.
*/
"api::model.middleware-name",
/**
* Basic middleware example #5
* Uses a middleware already created in Strapi with a custom configuration
*/
{name:"api::model.middleware-name", options: {/* all the configuration values to pass to the middleware */} },
],
auth: false,
},
}
})
}
}
```
## 使用
Description: 🌐 Strapi uses documentId as the unique identifier for entities instead of id.
(Source: https://docs.strapi.io/cms/plugins/graphql#usage)
Language: JavaScript
File path: N/A
```js
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
const client = new ApolloClient({
link: new HttpLink({ uri: "http://localhost:1337/graphql" }),
cache: new InMemoryCache({
dataIdFromObject: (o) => `${o.__typename}:${o["documentId"]}`,
}),
});
```
## 注册
Description: Code example from "注册"
(Source: https://docs.strapi.io/cms/plugins/graphql#registration)
Language: DOCKERFILE
File path: N/A
```dockerfile
mutation {
register(input: { username: "username", email: "email", password: "password" }) {
jwt
user {
username
email
}
}
}
```
## 身份验证
Description: Code example from "身份验证"
(Source: https://docs.strapi.io/cms/plugins/graphql#authentication)
Language: GRAPHQL
File path: N/A
```graphql
mutation {
login(input: { identifier: "email", password: "password" }) {
jwt
}
}
```
## 使用 API 令牌
Description: 🌐 Using API tokens in the the GraphQL Sandbox requires adding the authorization header with your token in the HTTP HEADERS tab:
(Source: https://docs.strapi.io/cms/plugins/graphql#api-tokens)
Language: JSON
File path: N/A
```json
{
"Authorization" : "Bearer "
}
```
# Sentry 插件
Source: https://docs.strapi.io/cms/plugins/sentry
## 安装
Description: 🌐 Install the Sentry plugin by adding the dependency to your Strapi application as follows:
(Source: https://docs.strapi.io/cms/plugins/sentry#installation)
Language: Bash
File path: N/A
```bash
yarn add @strapi/plugin-sentry
```
---
Language: Bash
File path: N/A
```bash
npm install @strapi/plugin-sentry
```
## 配置
Description: 🌐 The following is an example basic configuration:
(Source: https://docs.strapi.io/cms/plugins/sentry#configuration)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// ...
sentry: {
enabled: true,
config: {
dsn: env('SENTRY_DSN'),
sendMetadata: true,
},
},
// ...
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// ...
sentry: {
enabled: true,
config: {
dsn: env('SENTRY_DSN'),
sendMetadata: true,
},
},
// ...
});
```
## 为非生产环境禁用
Description: 🌐 You can make use of that by using the env utility to set the dsn configuration property depending on the environment.
(Source: https://docs.strapi.io/cms/plugins/sentry#disabling-for-non-production-environments)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// …
sentry: {
enabled: true,
config: {
// Only set `dsn` property in production
dsn: env('NODE_ENV') === 'production' ? env('SENTRY_DSN') : null,
},
},
// …
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// …
sentry: {
enabled: true,
config: {
// Only set `dsn` property in production
dsn: env('NODE_ENV') === 'production' ? env('SENTRY_DSN') : null,
},
},
// …
});
```
## 完全禁用插件
Description: 🌐 Like every other Strapi plugin, you can also disable this plugin in the plugins configuration file.
(Source: https://docs.strapi.io/cms/plugins/sentry#disabling-the-plugin-completely)
Language: JavaScript
File path: /config/plugins.js
```js
module.exports = ({ env }) => ({
// …
sentry: {
enabled: false,
},
// …
});
```
---
Language: TypeScript
File path: /config/plugins.ts
```ts
export default ({ env }) => ({
// …
sentry: {
enabled: false,
},
// …
});
```
## 使用
Description: 🌐 After installing and configuring the plugin, you can access a Sentry service in your Strapi application as follows:
(Source: https://docs.strapi.io/cms/plugins/sentry#usage)
Language: JavaScript
File path: N/A
```js
const sentryService = strapi.plugin('sentry').service('sentry');
```
Language: JavaScript
File path: N/A
```js
try {
// Your code here
} catch (error) {
// Either send a simple error
strapi
.plugin('sentry')
.service('sentry')
.sendError(error);
// Or send an error with a customized Sentry scope
strapi
.plugin('sentry')
.service('sentry')
.sendError(error, (scope, sentryInstance) => {
// Customize the scope here
scope.setTag('my_custom_tag', 'Tag value');
});
throw error;
}
```
Language: JavaScript
File path: N/A
```js
const sentryInstance = strapi
.plugin('sentry')
.service('sentry')
.getInstance();
```
# 快速入门指南 - Strapi 开发者文档
Source: https://docs.strapi.io/cms/quick-start
## A 部分:使用Strapi创建一个新项目
Description: 🌐 Once the installation is complete, you need to start the server.
(Source: https://docs.strapi.io/cms/quick-start#part-a-create-a-new-project-with-strapi)
Language: Bash
File path: N/A
```bash
npx create-strapi@latest my-strapi-project
```
---
Language: Bash
File path: .strapi-cloud.js
```bash
cd my-strapi-project && npm run develop
```
## D 部分:使用内容管理器向你的Strapi Cloud项目添加内容
Description: 点击我查看 API 响应示例:
(Source: https://docs.strapi.io/cms/quick-start#part-d-add-content-to-your-strapi-cloud-project-with-the-content-manager)
Language: JSON
File path: N/A
```json
{
"data": [
{
"id": 3,
"documentId": "wf7m1n3g8g22yr5k50hsryhk",
"Name": "Biscotte Restaurant",
"Description": [
{
"type": "paragraph",
"children": [
{
"type": "text",
"text": "Welcome to Biscotte restaurant! Restaurant Biscotte offers a cuisine based on fresh, quality products, often local, organic when possible, and always produced by passionate producers."
}
]
}
],
"createdAt": "2024-09-10T12:49:32.350Z",
"updatedAt": "2024-09-10T13:14:18.275Z",
"publishedAt": "2024-09-10T13:14:18.280Z",
"locale": null
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 1,
"total": 1
}
}
}
```
# strapi-utils
Source: https://docs.strapi.io/cms/strapi-utils
## async
Description: 🌐 The async namespace provides asynchronous utility functions.
(Source: https://docs.strapi.io/cms/strapi-utils#async)
Language: JavaScript
File path: N/A
```js
const { async } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const { async: asyncUtils } = require('@strapi/utils');
// Compose async functions into a pipeline
const result = await asyncUtils.pipe(
fetchUser,
enrichWithProfile,
formatResponse
)(userId);
// Reduce an array asynchronously (note the curried call)
const total = await asyncUtils.reduce([1, 2, 3])(
async (sum, n) => sum + n,
0
); // 6
```
## contentTypes
Description: 🌐 The contentTypes namespace exposes constants and helper functions for working with Strapi content-type schemas.
(Source: https://docs.strapi.io/cms/strapi-utils#contenttypes)
Language: JavaScript
File path: N/A
```js
const { contentTypes } = require('@strapi/utils');
```
## 模式检查函数
Description: 🌐 The following example iterates over a content type's attributes to find relations and writable fields:
(Source: https://docs.strapi.io/cms/strapi-utils#schema-inspection-functions)
Language: JavaScript
File path: N/A
```js
const { contentTypes } = require('@strapi/utils');
const articleSchema = strapi.contentType('api::article.article');
// List all relation fields
for (const [name, attribute] of Object.entries(articleSchema.attributes)) {
if (contentTypes.isRelationalAttribute(attribute)) {
console.log(`${name} is a relation`);
}
}
// Get only the fields that can be written to
const writableFields = contentTypes.getWritableAttributes(articleSchema);
// Check if draft and publish is enabled
if (contentTypes.hasDraftAndPublish(articleSchema)) {
console.log('This content type supports drafts');
}
```
## env
Description: 🌐 A helper function to read environment variables with type-safe parsing.
(Source: https://docs.strapi.io/cms/strapi-utils#env)
Language: JavaScript
File path: N/A
```js
const { env } = require('@strapi/utils');
// or in TypeScript: import { env } from '@strapi/utils';
```
Language: JavaScript
File path: /config/server.js
```js
const { env } = require('@strapi/utils');
module.exports = {
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
};
```
## errors
Description: 🌐 The error classes are imported as follows:
(Source: https://docs.strapi.io/cms/strapi-utils#errors)
Language: JavaScript
File path: N/A
```js
const { errors } = require('@strapi/utils');
// or in TypeScript: import { errors } from '@strapi/utils';
```
Language: JavaScript
File path: N/A
```js
const { errors } = require('@strapi/utils');
// In a service or lifecycle hook
throw new errors.ApplicationError('Something went wrong', { foo: 'bar' });
// In a policy
throw new errors.PolicyError('Access denied', { policy: 'is-owner' });
```
## file
Description: 🌐 The file namespace provides helpers for working with streams and file sizes.
(Source: https://docs.strapi.io/cms/strapi-utils#file)
Language: JavaScript
File path: N/A
```js
const { file } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const { file } = require('@strapi/utils');
const buffer = await file.streamToBuffer(uploadStream);
const sizeInKb = file.bytesToKbytes(buffer.length);
console.log(`Uploaded ${file.bytesToHumanReadable(buffer.length)} (${sizeInKb} KB)`);
```
## hooks
Description: 🌐 Factory functions to create hook registries.
(Source: https://docs.strapi.io/cms/strapi-utils#hooks)
Language: JavaScript
File path: N/A
```js
const { hooks } = require('@strapi/utils');
```
## 可用的钩子工厂
Description: 下面的例子注册并调用带有 series hook 的处理程序 series hook 按顺序依次执行处理程序,按照它们注册的顺序一个接一个地运行。其他模式包括 waterfall(每个处理程序接收前一个处理程序的返回值)、parallel(所有处理程序并发运行)以及 bail(在第一个返回值的处理程序处停止)。更多详情请参见 admin hooks :
(Source: https://docs.strapi.io/cms/strapi-utils#available-hook-factories)
Language: JavaScript
File path: N/A
```js
const { hooks } = require('@strapi/utils');
const myHook = hooks.createAsyncSeriesHook();
myHook.register(async (context) => {
console.log('First handler', context);
});
myHook.register(async (context) => {
console.log('Second handler', context);
});
// Execute all handlers in order
await myHook.call({ data: 'example' });
```
## pagination
Description: 🌐 The pagination namespace provides helpers for handling pagination parameters.
(Source: https://docs.strapi.io/cms/strapi-utils#pagination)
Language: JavaScript
File path: N/A
```js
const { pagination } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const { pagination } = require('@strapi/utils');
const params = pagination.withDefaultPagination({ page: 2 }, { maxLimit: 100 });
const info = pagination.transformPagedPaginationInfo(params, 250);
// { page: 2, pageSize: 25, pageCount: 10, total: 250 }
```
## parseType
Description: 🌐 Cast a value to a specific Strapi field type.
(Source: https://docs.strapi.io/cms/strapi-utils#parsetype)
Language: JavaScript
File path: N/A
```js
const { parseType } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
parseType({ type: 'boolean', value: 'true' }); // true
parseType({ type: 'integer', value: '42' }); // 42
parseType({ type: 'date', value: '2024-01-15T10:30:00Z' }); // '2024-01-15'
```
## policy
Description: 🌐 Helpers to create and manage policies.
(Source: https://docs.strapi.io/cms/strapi-utils#policy)
Language: JavaScript
File path: N/A
```js
const { policy } = require('@strapi/utils');
```
## createPolicy
Description: 🌐 The following example creates a policy with a configuration validator:
(Source: https://docs.strapi.io/cms/strapi-utils#createpolicy)
Language: JavaScript
File path: N/A
```js
const myPolicy = policy.createPolicy({
name: 'is-owner',
validator: (config) => {
if (!config.field) throw new Error('Missing field');
},
handler: (ctx, config, { strapi }) => {
// policy logic
return true;
},
});
```
## createPolicyContext
Description: 🌐 The createPolicyContext function creates a typed context object for use within a policy handler.
(Source: https://docs.strapi.io/cms/strapi-utils#createpolicycontext)
Language: JavaScript
File path: N/A
```js
const policyCtx = policy.createPolicyContext('admin', ctx);
policyCtx.is('admin'); // true
policyCtx.type; // 'admin'
```
## primitives
Description: 🌐 Low-level data transformation helpers.
(Source: https://docs.strapi.io/cms/strapi-utils#primitives)
Language: JavaScript
File path: N/A
```js
const { strings, objects, arrays, dates } = require('@strapi/utils');
```
## providerFactory
Description: 🌐 Create a pluggable registry that stores and retrieves items by key, with lifecycle hooks.
(Source: https://docs.strapi.io/cms/strapi-utils#providerfactory)
Language: JavaScript
File path: N/A
```js
const { providerFactory } = require('@strapi/utils');
```
## 提供者钩子
Description: 🌐 The following example creates a provider and registers an item with a lifecycle hook:
(Source: https://docs.strapi.io/cms/strapi-utils#provider-hooks)
Language: JavaScript
File path: N/A
```js
const { providerFactory } = require('@strapi/utils');
const registry = providerFactory();
registry.hooks.willRegister.register(async ({ key, value }) => {
console.log(`About to register: ${key}`);
});
await registry.register('my-provider', { execute: () => {} });
registry.get('my-provider'); // { execute: [Function] }
registry.has('my-provider'); // true
registry.size(); // 1
```
## relations
Description: 🌐 The relations namespace provides helpers to inspect the cardinality of relation attributes (e.g., one-to-many vs.
(Source: https://docs.strapi.io/cms/strapi-utils#relations)
Language: JavaScript
File path: N/A
```js
const { relations } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const { relations, contentTypes } = require('@strapi/utils');
const schema = strapi.contentType('api::article.article');
for (const [name, attribute] of Object.entries(schema.attributes)) {
if (contentTypes.isRelationalAttribute(attribute) && relations.isAnyToMany(attribute)) {
console.log(`${name} is a *-to-many relation`);
}
}
```
## sanitize
Description: 🌐 The namespace is imported as follows:
(Source: https://docs.strapi.io/cms/strapi-utils#sanitize)
Language: JavaScript
File path: N/A
```js
const { sanitize } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const sanitizers = sanitize.createAPISanitizers({
getModel: strapi.getModel.bind(strapi),
});
```
## setCreatorFields
Description: 在一个实体上设置 createdBy 和 updatedBy 字段。当构建自定义控制器或服务以在 Strapi 默认的 Document Service 之外创建或更新条目时使用。该函数返回一个柯里化函数 柯里化函数是一个不会一次接受所有参数的函数。相反,它会为每个参数返回一个新函数。这允许你先固定一些参数,然后稍后传入其余参数。例如,setCreatorFields({ user }) 返回一个可重用函数,你可以在任何实体数据上调用它。 : 先用选项调用它,然后再用实体数据调用。它的导入方式如下:
(Source: https://docs.strapi.io/cms/strapi-utils#setcreatorfields)
Language: JavaScript
File path: N/A
```js
const { setCreatorFields } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const { setCreatorFields } = require('@strapi/utils');
const addCreator = setCreatorFields({ user: { id: 1 } });
const data = addCreator({ title: 'My Article' });
// { title: 'My Article', createdBy: 1, updatedBy: 1 }
const updateCreator = setCreatorFields({ user: { id: 2 }, isEdition: true });
const updated = updateCreator(data);
// { title: 'My Article', createdBy: 1, updatedBy: 2 }
```
## validate
Description: 🌐 The namespace is imported as follows:
(Source: https://docs.strapi.io/cms/strapi-utils#validate)
Language: JavaScript
File path: N/A
```js
const { validate } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const validators = validate.createAPIValidators({
getModel: strapi.getModel.bind(strapi),
});
```
Language: JavaScript
File path: N/A
```js
const { validate, errors } = require('@strapi/utils');
const validators = validate.createAPIValidators({
getModel: strapi.getModel.bind(strapi),
});
try {
await validators.query(ctx.query, 'api::article.article', {
auth: ctx.state.auth,
});
} catch (error) {
// error is a ValidationError with details about which fields failed
console.error(error.message, error.details);
}
```
## yup
Description: yup 命名空间重新导出带有 Strapi 特定扩展的 。其导入方式如下:
(Source: https://docs.strapi.io/cms/strapi-utils#yup)
Language: JavaScript
File path: N/A
```js
const { yup } = require('@strapi/utils');
```
## 模式验证助手
Description: validateYupSchema 和 validateYupSchemaSync 是来自 @strapi/utils 的顶层导出,而不是 yup 命名空间的一部分:
(Source: https://docs.strapi.io/cms/strapi-utils#schema-validation-helpers)
Language: JavaScript
File path: N/A
```js
const { validateYupSchema, validateYupSchemaSync } = require('@strapi/utils');
```
## zod
Description: Strapi 重新导出来自 的 z 实例,并提供一个 validateZod 辅助工具,将 Zod 模式封装成 Strapi 风格的验证器。Strapi 不会向 Zod 添加自定义方法。z 是标准的 Zod API。辅助工具的导入方式如下:
(Source: https://docs.strapi.io/cms/strapi-utils#zod)
Language: JavaScript
File path: N/A
```js
const { validateZod, z } = require('@strapi/utils');
```
Language: JavaScript
File path: N/A
```js
const schema = z.object({
name: z.string().min(1),
age: z.number().positive(),
});
const validate = validateZod(schema);
const parsed = validate({ name: 'Alice', age: 30 }); // returns parsed data
validate({ name: '' }); // throws ValidationError
```
# 模板
Source: https://docs.strapi.io/cms/templates
## 使用模板
Description: 🌐 To create a new Strapi project based on a template, run the following command:
(Source: https://docs.strapi.io/cms/templates#using-a-template)
Language: Bash
File path: N/A
```bash
yarn create strapi-app my-project --template
```
---
Language: Bash
File path: N/A
```bash
npx create-strapi-app@latest my-project --template
```
# 测试
Source: https://docs.strapi.io/cms/testing
## 安装工具
Description: 通过在终端中运行以下命令安装 Jest 和 Supertest:
(Source: https://docs.strapi.io/cms/testing#install-tools)
Language: Bash
File path: N/A
```bash
yarn add jest supertest --dev
```
---
Language: Bash
File path: N/A
```bash
npm install jest supertest --save-dev
```
Language: JSON
File path: N/A
```json
"scripts": {
"build": "strapi build",
"console": "strapi console",
"deploy": "strapi deploy",
"dev": "strapi develop",
"develop": "strapi develop",
"seed:example": "node ./scripts/seed.js",
"start": "strapi start",
"strapi": "strapi",
"upgrade": "npx @strapi/upgrade latest",
"upgrade:dry": "npx @strapi/upgrade latest --dry",
"test": "jest --forceExit --detectOpenHandles"
},
```
Language: JSON
File path: N/A
```json
"jest": {
"testPathIgnorePatterns": [
"/node_modules/",
".tmp",
".cache"
],
"testEnvironment": "node",
"moduleNameMapper": {
"^/create-service$": "/create-service"
}
}
```
## 控制器示例
Description: 🌐 Create a test file such as ./tests/todo-controller.test.js that instantiates your controller with a mocked Strapi object and verifies every call the controller performs:
(Source: https://docs.strapi.io/cms/testing#controller-example)
Language: JavaScript
File path: ./tests/todo-controller.test.js
```js
const todoController = require('./todo-controller');
describe('Todo controller', () => {
let strapi;
beforeEach(() => {
strapi = {
plugin: jest.fn().mockReturnValue({
service: jest.fn().mockReturnValue({
create: jest.fn().mockReturnValue({
data: {
name: 'test',
status: false,
},
}),
complete: jest.fn().mockReturnValue({
data: {
id: 1,
status: true,
},
}),
}),
}),
};
});
it('creates a todo item', async () => {
const ctx = {
request: {
body: {
name: 'test',
},
},
body: null,
};
await todoController({ strapi }).index(ctx);
expect(ctx.body).toBe('created');
expect(strapi.plugin('todo').service('create').create).toHaveBeenCalledTimes(1);
});
it('completes a todo item', async () => {
const ctx = {
request: {
body: {
id: 1,
},
},
body: null,
};
await todoController({ strapi }).complete(ctx);
expect(ctx.body).toBe('todo completed');
expect(strapi.plugin('todo').service('complete').complete).toHaveBeenCalledTimes(1);
});
});
```
## 服务示例
Description: 🌐 Services can be tested in the same test suite or in a dedicated file by mocking only the Strapi query layer they call into.
(Source: https://docs.strapi.io/cms/testing#service-example)
Language: JavaScript
File path: ./tests/create-service.test.js
```js
const createService = require('./create-service');
describe('Create service', () => {
let strapi;
beforeEach(() => {
strapi = {
query: jest.fn().mockReturnValue({
create: jest.fn().mockReturnValue({
data: {
name: 'test',
status: false,
},
}),
}),
};
});
it('persists a todo item', async () => {
const todo = await createService({ strapi }).create({ name: 'test' });
expect(strapi.query('plugin::todo.todo').create).toHaveBeenCalledTimes(1);
expect(todo.data.name).toBe('test');
});
});
```
## 建立测试环境
Description: 🌐 Once jest is running it uses the test environment, so create ./config/env/test/database.js with the following:
(Source: https://docs.strapi.io/cms/testing#set-up-a-testing-environment)
Language: JavaScript
File path: ./config/env/test/database.js
```js
module.exports = ({ env }) => {
const filename = env('DATABASE_FILENAME', '.tmp/test.db');
const rawClient = env('DATABASE_CLIENT', 'sqlite');
const client = ['sqlite3', 'better-sqlite3'].includes(rawClient) ? 'sqlite' : rawClient;
return {
connection: {
client,
connection: {
filename,
},
useNullAsDefault: true,
},
};
};
```
## TypeScript 编译器配置
Description: 🌐 Create tests/ts-compiler-options.js with the following content:
(Source: https://docs.strapi.io/cms/testing#typescript-compiler-configuration)
Language: JavaScript
File path: ./tests/ts-compiler-options.js
```js
const fs = require('fs');
const path = require('path');
const ts = require('typescript');
const projectRoot = path.resolve(__dirname, '..');
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
const baseCompilerOptions = {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2019,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
esModuleInterop: true,
jsx: ts.JsxEmit.React,
};
const loadCompilerOptions = () => {
let options = { ...baseCompilerOptions };
if (!fs.existsSync(tsconfigPath)) {
return options;
}
try {
const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
const parsed = ts.parseConfigFileTextToJson(tsconfigPath, tsconfigContent);
if (!parsed.error && parsed.config && parsed.config.compilerOptions) {
options = {
...options,
...parsed.config.compilerOptions,
};
}
} catch (error) {
// Ignore tsconfig parsing errors and fallback to defaults
}
return options;
};
module.exports = {
compilerOptions: loadCompilerOptions(),
loadCompilerOptions,
};
```
## TypeScript 运行时加载器
Description: 🌐 Create tests/ts-runtime.js with the following content:
(Source: https://docs.strapi.io/cms/testing#typescript-runtime-loader)
Language: JavaScript
File path: ./tests/ts-runtime.js
```js
const Module = require('module');
const { compilerOptions } = require('./ts-compiler-options');
const fs = require('fs');
const ts = require('typescript');
const extensions = Module._extensions;
if (!extensions['.ts']) {
extensions['.ts'] = function compileTS(module, filename) {
const source = fs.readFileSync(filename, 'utf8');
const output = ts.transpileModule(source, {
compilerOptions,
fileName: filename,
reportDiagnostics: false,
});
return module._compile(output.outputText, filename);
};
}
if (!extensions['.tsx']) {
extensions['.tsx'] = extensions['.ts'];
}
module.exports = {
compilerOptions,
};
```
## 主测试框架
Description: Code example from "主测试框架"
(Source: https://docs.strapi.io/cms/testing#main-test-harness)
Language: JavaScript
File path: ./tests/strapi.js
```js
try {
require('ts-node/register/transpile-only');
} catch (err) {
try {
require('@strapi/typescript-utils/register');
} catch (strapiRegisterError) {
require('./ts-runtime');
}
}
const fs = require('fs');
const path = require('path');
const Module = require('module');
const ts = require('typescript');
const databaseConnection = require('@strapi/database/dist/connection.js');
const knexFactory = require('knex');
const strapiCoreRoot = path.dirname(require.resolve('@strapi/core/package.json'));
const loadConfigFilePath = path.join(strapiCoreRoot, 'dist', 'utils', 'load-config-file.js');
const loadConfigFileModule = require(loadConfigFilePath);
const { compilerOptions: baseCompilerOptions } = require('./ts-compiler-options');
// ============================================
// 1. PATCH: TypeScript Configuration Loader
// ============================================
// This section patches Strapi's configuration loader to support TypeScript config files
// (.ts, .cts, .mts). Without this, Strapi would only load .js and .json config files.
if (!loadConfigFileModule.loadConfigFile.__tsRuntimePatched) {
const strapiUtils = require('@strapi/utils');
const originalLoadConfigFile = loadConfigFileModule.loadConfigFile;
const loadTypeScriptConfig = (file) => {
const source = fs.readFileSync(file, 'utf8');
const options = {
...baseCompilerOptions,
module: ts.ModuleKind.CommonJS,
};
const output = ts.transpileModule(source, {
compilerOptions: options,
fileName: file,
reportDiagnostics: false,
});
const moduleInstance = new Module(file);
moduleInstance.filename = file;
moduleInstance.paths = Module._nodeModulePaths(path.dirname(file));
moduleInstance._compile(output.outputText, file);
const exported = moduleInstance.exports;
const resolved = exported && exported.__esModule ? exported.default : exported;
if (typeof resolved === 'function') {
return resolved({ env: strapiUtils.env });
}
return resolved;
};
const patchedLoadConfigFile = (file) => {
const extension = path.extname(file).toLowerCase();
if (extension === '.ts' || extension === '.cts' || extension === '.mts') {
return loadTypeScriptConfig(file);
}
return originalLoadConfigFile(file);
};
patchedLoadConfigFile.__tsRuntimePatched = true;
loadConfigFileModule.loadConfigFile = patchedLoadConfigFile;
require.cache[loadConfigFilePath].exports = loadConfigFileModule;
}
// ============================================
// 2. PATCH: Configuration Directory Scanner
// ============================================
// This section patches how Strapi scans the config directory to:
// - Support TypeScript extensions (.ts, .cts, .mts)
// - Validate config file names
// - Prevent loading of restricted filenames
const configLoaderPath = path.join(strapiCoreRoot, 'dist', 'configuration', 'config-loader.js');
const originalLoadConfigDir = require(configLoaderPath);
const validExtensions = ['.js', '.json', '.ts', '.cts', '.mts'];
const mistakenFilenames = {
middleware: 'middlewares',
plugin: 'plugins',
};
const restrictedFilenames = [
'uuid',
'hosting',
'license',
'enforce',
'disable',
'enable',
'telemetry',
'strapi',
'internal',
'launchedAt',
'serveAdminPanel',
'autoReload',
'environment',
'packageJsonStrapi',
'info',
'dirs',
...Object.keys(mistakenFilenames),
];
const strapiConfigFilenames = ['admin', 'server', 'api', 'database', 'middlewares', 'plugins', 'features'];
if (!originalLoadConfigDir.__tsRuntimePatched) {
const patchedLoadConfigDir = (dir) => {
if (!fs.existsSync(dir)) {
return {};
}
const entries = fs.readdirSync(dir, { withFileTypes: true });
const seenFilenames = new Set();
const configFiles = entries.reduce((acc, entry) => {
if (!entry.isFile()) {
return acc;
}
const extension = path.extname(entry.name);
const extensionLower = extension.toLowerCase();
const baseName = path.basename(entry.name, extension);
const baseNameLower = baseName.toLowerCase();
if (!validExtensions.includes(extensionLower)) {
console.warn(`Config file not loaded, extension must be one of ${validExtensions.join(',')}): ${entry.name}`);
return acc;
}
if (restrictedFilenames.includes(baseNameLower)) {
console.warn(`Config file not loaded, restricted filename: ${entry.name}`);
if (baseNameLower in mistakenFilenames) {
console.log(`Did you mean ${mistakenFilenames[baseNameLower]}?`);
}
return acc;
}
const restrictedPrefix = [...restrictedFilenames, ...strapiConfigFilenames].find(
(restrictedName) => restrictedName.startsWith(baseNameLower) && restrictedName !== baseNameLower
);
if (restrictedPrefix) {
console.warn(`Config file not loaded, filename cannot start with ${restrictedPrefix}: ${entry.name}`);
return acc;
}
if (seenFilenames.has(baseNameLower)) {
console.warn(`Config file not loaded, case-insensitive name matches other config file: ${entry.name}`);
return acc;
}
seenFilenames.add(baseNameLower);
acc.push(entry);
return acc;
}, []);
return configFiles.reduce((acc, entry) => {
const extension = path.extname(entry.name);
const key = path.basename(entry.name, extension);
const filePath = path.resolve(dir, entry.name);
acc[key] = loadConfigFileModule.loadConfigFile(filePath);
return acc;
}, {});
};
patchedLoadConfigDir.__tsRuntimePatched = true;
require.cache[configLoaderPath].exports = patchedLoadConfigDir;
}
// ============================================
// 3. PATCH: Database Connection Handler
// ============================================
// This section normalizes database client names for testing.
// Maps Strapi's client names (sqlite, mysql, postgres) to actual driver names
// (sqlite3, mysql2, pg) and handles connection pooling.
databaseConnection.createConnection = (() => {
const clientMap = {
sqlite: 'sqlite3',
mysql: 'mysql2',
postgres: 'pg',
};
return (userConfig, strapiConfig) => {
if (!clientMap[userConfig.client]) {
throw new Error(`Unsupported database client ${userConfig.client}`);
}
const knexConfig = {
...userConfig,
client: clientMap[userConfig.client],
};
if (strapiConfig?.pool?.afterCreate) {
knexConfig.pool = knexConfig.pool || {};
const userAfterCreate = knexConfig.pool?.afterCreate;
const strapiAfterCreate = strapiConfig.pool.afterCreate;
knexConfig.pool.afterCreate = (conn, done) => {
strapiAfterCreate(conn, (err, nativeConn) => {
if (err) {
return done(err, nativeConn);
}
if (userAfterCreate) {
return userAfterCreate(nativeConn, done);
}
return done(null, nativeConn);
});
};
}
return knexFactory(knexConfig);
};
})();
// ============================================
// 4. TEST ENVIRONMENT SETUP
// ============================================
// Configure Jest timeout and set required environment variables for testing
if (typeof jest !== 'undefined' && typeof jest.setTimeout === 'function') {
jest.setTimeout(30000);
}
const { createStrapi } = require('@strapi/strapi');
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
process.env.APP_KEYS = process.env.APP_KEYS || 'testKeyOne,testKeyTwo';
process.env.API_TOKEN_SALT = process.env.API_TOKEN_SALT || 'test-api-token-salt';
process.env.ADMIN_JWT_SECRET = process.env.ADMIN_JWT_SECRET || 'test-admin-jwt-secret';
process.env.TRANSFER_TOKEN_SALT = process.env.TRANSFER_TOKEN_SALT || 'test-transfer-token-salt';
process.env.ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || '0123456789abcdef0123456789abcdef';
process.env.JWT_SECRET = process.env.JWT_SECRET || 'test-jwt-secret';
process.env.DATABASE_CLIENT = process.env.DATABASE_CLIENT || 'sqlite';
process.env.DATABASE_FILENAME = process.env.DATABASE_FILENAME || ':memory:';
process.env.STRAPI_DISABLE_CRON = 'true';
process.env.PORT = process.env.PORT || '0';
const databaseClient = process.env.DATABASE_CLIENT || 'sqlite';
const clientMap = {
sqlite: 'sqlite3',
'better-sqlite3': 'sqlite3',
mysql: 'mysql2',
postgres: 'pg',
};
const driver = clientMap[databaseClient];
if (!driver) {
throw new Error(`Unsupported database client "${databaseClient}".`);
}
if (databaseClient === 'better-sqlite3') {
process.env.DATABASE_CLIENT = 'sqlite';
}
require(driver);
let instance;
// ============================================
// 5. STRAPI INSTANCE MANAGEMENT
// ============================================
// Functions to set up and tear down a Strapi instance for testing
async function setupStrapi() {
if (!instance) {
instance = await createStrapi().load();
// Register the /api/hello test route automatically
const contentApi = instance.server?.api?.('content-api');
if (contentApi && !instance.__helloRouteRegistered) {
const createHelloService = require(path.join(
__dirname,
'..',
'src',
'api',
'hello',
'services',
'hello'
));
const helloService = createHelloService({ strapi: instance });
contentApi.routes([
{
method: 'GET',
path: '/hello',
handler: async (ctx) => {
ctx.body = await helloService.getMessage();
},
config: {
auth: false,
},
},
]);
contentApi.mount(instance.server.router);
instance.__helloRouteRegistered = true;
}
await instance.start();
global.strapi = instance;
// Optionally seed example data for tests if requested
if (process.env.TEST_SEED === 'true') {
try {
const { seedExampleApp } = require(path.join(__dirname, '..', 'scripts', 'seed'));
await seedExampleApp();
} catch (e) {
console.warn('Seeding failed:', e);
}
}
// Patch the user service to automatically assign the authenticated role
const userService = strapi.plugins['users-permissions']?.services?.user;
if (userService) {
const originalAdd = userService.add.bind(userService);
userService.add = async (values) => {
const data = { ...values };
if (!data.role) {
const defaultRole = await strapi.db
.query('plugin::users-permissions.role')
.findOne({ where: { type: 'authenticated' } });
if (defaultRole) {
data.role = defaultRole.id;
}
}
return originalAdd(data);
};
}
}
return instance;
}
async function cleanupStrapi() {
if (!global.strapi) {
return;
}
const dbSettings = strapi.config.get('database.connection');
await strapi.server.httpServer.close();
await strapi.db.connection.destroy();
if (typeof strapi.destroy === 'function') {
await strapi.destroy();
}
if (dbSettings && dbSettings.connection && dbSettings.connection.filename) {
const tmpDbFile = dbSettings.connection.filename;
if (fs.existsSync(tmpDbFile)) {
fs.unlinkSync(tmpDbFile);
}
}
}
module.exports = { setupStrapi, cleanupStrapi };
```
## (可选) 生成可预测的测试数据
Description: 从你的项目脚本中导出一个播种函数(例如 ./scripts/seed.js):
(Source: https://docs.strapi.io/cms/testing#optional-seed-predictable-test-data)
Language: JavaScript
File path: ./scripts/seed.js
```js
TEST_SEED=true yarn test
```
---
Language: JavaScript
File path: ./scripts/seed.js
```js
TEST_SEED=true npm run test
```
Language: JavaScript
File path: ./scripts/seed.js
```js
async function seedExampleApp() {
// In test environment, skip complex seeding and just log
if (process.env.NODE_ENV === 'test') {
console.log('Test seeding: Skipping complex data import (not needed for basic tests)');
return;
}
const shouldImportSeedData = await isFirstRun();
if (shouldImportSeedData) {
try {
console.log('Setting up the template...');
await importSeedData();
console.log('Ready to go');
} catch (error) {
console.log('Could not import seed data');
console.error(error);
}
}
}
// Allow usage both as a CLI and as a library from tests
if (require.main === module) {
main().catch((error) => {
console.error(error);
process.exit(1);
});
}
module.exports = { seedExampleApp };
```
## TypeScript 烟雾测试必须先加载 tests/strapi.js
Description: 🌐 The harness patches both the directory scan and the per-file loader so .ts, .cts, and .mts configs work under Jest.
(Source: https://docs.strapi.io/cms/testing#typescript-smoke-tests-must-load-testsstrapijs-first)
Language: TypeScript
File path: ./tests/app.test.ts
```ts
import type { Strapi } from '@strapi/strapi';
const { setupStrapi, cleanupStrapi } = require('./strapi');
declare global {
var strapi: Strapi;
}
beforeAll(async () => {
await setupStrapi();
});
afterAll(async () => {
await cleanupStrapi();
});
it('strapi is defined', () => {
expect(strapi).toBeDefined();
});
```
Language: JavaScript
File path: ./tests/app.test.js
```js
const { setupStrapi, cleanupStrapi } = require('./strapi');
/** this code is called once before any test is called */
beforeAll(async () => {
await setupStrapi(); // Singleton so it can be called many times
});
/** this code is called once before all the tests are finished */
afterAll(async () => {
await cleanupStrapi();
});
it('strapi is defined', () => {
expect(strapi).toBeDefined();
});
require('./hello');
require('./user');
```
Language: YAML
File path: /strapi.js
```yaml
PASS tests/create-service.test.js
PASS tests/todo-controller.test.js
Test Suites: 6 passed, 6 total
Tests: 7 passed, 7 total
Snapshots: 0 total
Time: 7.952 s
Ran all test suites.
✨ Done in 8.63s.
```
## 测试一个基本的 API 端点
Description: 🌐 Create tests/hello.test.js with the following:
(Source: https://docs.strapi.io/cms/testing#test-a-basic-api-endpoint)
Language: JavaScript
File path: ./tests/hello.test.js
```js
const { setupStrapi, cleanupStrapi } = require('./strapi');
const request = require('supertest');
beforeAll(async () => {
await setupStrapi();
});
afterAll(async () => {
await cleanupStrapi();
});
it('should return hello world', async () => {
await request(strapi.server.httpServer)
.get('/api/hello')
.expect(200)
.then((data) => {
expect(data.text).toBe('Hello World!');
});
});
```
## 测试 API 身份验证
Description: 🌐 Create tests/auth.test.js:
(Source: https://docs.strapi.io/cms/testing#test-api-authentication)
Language: JavaScript
File path: ./tests/auth.test.js
```js
const { setupStrapi, cleanupStrapi } = require('./strapi');
const request = require('supertest');
beforeAll(async () => {
await setupStrapi();
});
afterAll(async () => {
await cleanupStrapi();
});
// User mock data
const mockUserData = {
username: 'tester',
email: 'tester@strapi.com',
provider: 'local',
password: '1234abc',
confirmed: true,
blocked: null,
};
it('should login user and return JWT token', async () => {
await strapi.plugins['users-permissions'].services.user.add({
...mockUserData,
});
await request(strapi.server.httpServer)
.post('/api/auth/local')
.set('accept', 'application/json')
.set('Content-Type', 'application/json')
.send({
identifier: mockUserData.email,
password: mockUserData.password,
})
.expect('Content-Type', /json/)
.expect(200)
.then((data) => {
expect(data.body.jwt).toBeDefined();
});
});
```
## 具有用户权限的高级API测试
Description: 🌐 Create tests/user.test.js:
(Source: https://docs.strapi.io/cms/testing#advanced-api-testing-with-user-permissions)
Language: JavaScript
File path: ./tests/user.test.js
```js
const { setupStrapi, cleanupStrapi } = require('./strapi');
const request = require('supertest');
beforeAll(async () => {
await setupStrapi();
});
afterAll(async () => {
await cleanupStrapi();
});
let authenticatedUser = {};
// User mock data
const mockUserData = {
username: 'tester',
email: 'tester@strapi.com',
provider: 'local',
password: '1234abc',
confirmed: true,
blocked: null,
};
describe('User API', () => {
beforeAll(async () => {
await strapi.plugins['users-permissions'].services.user.add({
...mockUserData,
});
const response = await request(strapi.server.httpServer)
.post('/api/auth/local')
.set('accept', 'application/json')
.set('Content-Type', 'application/json')
.send({
identifier: mockUserData.email,
password: mockUserData.password,
});
authenticatedUser.jwt = response.body.jwt;
authenticatedUser.user = response.body.user;
});
it('should return users data for authenticated user', async () => {
await request(strapi.server.httpServer)
.get('/api/users/me')
.set('accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Authorization', 'Bearer ' + authenticatedUser.jwt)
.expect('Content-Type', /json/)
.expect(200)
.then((data) => {
expect(data.body).toBeDefined();
expect(data.body.id).toBe(authenticatedUser.user.id);
expect(data.body.username).toBe(authenticatedUser.user.username);
expect(data.body.email).toBe(authenticatedUser.user.email);
});
});
});
```
## 使用 GitHub Actions 自动化测试
Description: 更进一步,你可以使用 在每次推送和拉取请求时自动运行你的Jest测试套件。在你的项目中创建一个.github/workflows/test.yaml文件,并按如下方式添加工作流:
(Source: https://docs.strapi.io/cms/testing#automate-tests-with-github-actions)
Language: DOCKERFILE
File path: ./.github/workflows/test.yaml
```dockerfile
name: 'Tests'
on:
pull_request:
push:
jobs:
run-tests:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install modules
run: npm ci
- name: Run Tests
run: npm run test
```
# TypeScript
Source: https://docs.strapi.io/cms/typescript
## 在 Strapi 中使用 TypeScript 入门
Description: 通过在终端中运行以下命令,在 Strapi 中创建一个新的 TypeScript 项目(更多详细信息请参见 CLI 安装 文档):
(Source: https://docs.strapi.io/cms/typescript#getting-started-with-typescript-in-strapi)
Language: Bash
File path: N/A
```bash
yarn create strapi-app my-project --typescript
```
---
Language: Bash
File path: N/A
```bash
npx create-strapi-app@latest my-project --typescript
```
# 添加 TypeScript 支持
Source: https://docs.strapi.io/cms/typescript/adding-support-to-existing-project
## 为现有的 Strapi 项目添加 TypeScript 支持
Description: 在项目根目录添加一个 tsconfig.json 文件,并将以下带有 allowJs 标志的代码复制到该文件中:
(Source: https://docs.strapi.io/cms/typescript/adding-support-to-existing-project#adding-typescript-support-to-existing-strapi-projects)
Language: Bash
File path: N/A
```bash
yarn build
yarn develop
```
---
Language: Bash
File path: N/A
```bash
npm run build
npm run develop
```
Language: JSON
File path: ./tsconfig.json
```json
{
"extends": "@strapi/typescript-utils/tsconfigs/server",
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"allowJs": true //enables the build without .ts files
},
"include": [
"./",
"src/**/*.json"
],
"exclude": [
"node_modules/",
"build/",
"dist/",
".cache/",
".tmp/",
"src/admin/",
"**/*.test.ts",
"src/plugins/**"
]
}
```
Language: JSON
File path: ./src/admin/tsconfig.json
```json
{
"extends": "@strapi/typescript-utils/tsconfigs/admin",
"include": [
"../plugins/**/admin/src/**/*",
"./"
],
"exclude": [
"node_modules/",
"build/",
"dist/",
"**/*.test.ts"
]
}
```
Language: DOCKERFILE
File path: ./config/database.ts
```dockerfile
const path = require('path');
module.exports = ({ env }) => ({
connection: {
client: 'sqlite',
connection: {
filename: path.join(
__dirname,
"..",
"..",
env("DATABASE_FILENAME", ".tmp/data.db")
),
},
useNullAsDefault: true,
},
});
```
# TypeScript 开发
Source: https://docs.strapi.io/cms/typescript/development
## 使用 Strapi TypeScript 类型定义
Description: 从你的代码编辑器中打开 ./src/index.ts 文件。 2.
(Source: https://docs.strapi.io/cms/typescript/development#use-strapi-typescript-typings)
Language: TypeScript
File path: ./src/index.ts
```ts
import type { Core } from '@strapi/strapi';
export default {
register({ strapi }: { strapi: Core.Strapi }) {
// ...
},
};
```
## 为内容类型模式生成类型定义
Description: 🌐 To use ts:generate-typesrun the following code in a terminal at the project root:
(Source: https://docs.strapi.io/cms/typescript/development#generate-typings-for-content-types-schemas)
Language: Bash
File path: ./src/index.ts
```bash
npm run strapi ts:generate-types --debug #optional flag to display additional logging
```
---
Language: Bash
File path: ./src/index.ts
```bash
yarn strapi ts:generate-types --debug #optional flag to display additional logging
```
## 修复生成类型的构建问题
Description: 🌐 To do that, edit the tsconfig.json of the Strapi project and add types/generated/** to the exclude array:
(Source: https://docs.strapi.io/cms/typescript/development#fix-build-issues-with-the-generated-types)
Language: JSON
File path: ./tsconfig.json
```json
// ...
"exclude": [
"node_modules/",
"build/",
"dist/",
".cache/",
".tmp/",
".strapi/",
"src/admin/",
"**/*.test.ts",
"src/plugins/**",
"types/generated/**"
]
// ...
```
## 使用 strapi() 工厂
Description: 🌐 Strapi can be run programmatically by using the strapi() factory.
(Source: https://docs.strapi.io/cms/typescript/development#use-the-createstrapi-factory)
Language: JavaScript
File path: ./server.js
```js
const strapi = require('@strapi/strapi');
const app = strapi.createStrapi({ distDir: './dist' });
app.start();
```
## 使用 strapi.compile() 函数
Description: 🌐 The strapi.compile() function should be mostly used for developing tools that need to start a Strapi instance and detect whether the project includes TypeScript code.
(Source: https://docs.strapi.io/cms/typescript/development#use-the-strapicompile-function)
Language: JavaScript
File path: N/A
```js
const strapi = require('@strapi/strapi');
strapi.compile().then(appContext => strapi(appContext).start());
```
# TypeScript - 操作文档和条目
Source: https://docs.strapi.io/cms/typescript/documents-and-entries
## 类型导入
Description: 🌐 The UID namespace contains literal unions representing the available resources in the application.
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#type-imports)
Language: JavaScript
File path: N/A
```js
import type { UID } from '@strapi/strapi';
```
Language: JavaScript
File path: N/A
```js
import type { Data } from '@strapi/strapi';
```
## 通用文件
Description: 🌐 Generic documents
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#generic-documents)
Language: TypeScript
File path: N/A
```typescript
async function save(name: string, document: Data.ContentType) {
await writeCSV(name, document);
// ^ {
// id: Data.ID;
// documentId: string;
// createdAt?: DateTimeValue;
// updatedAt?: DateTimeValue;
// publishedAt?: DateTimeValue;
// ...
// }
}
```
Language: TypeScript
File path: N/A
```typescript
if ('my_prop' in document) {
return document.my_prop;
}
```
## 通用组件
Description: 🌐 Generic components
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#generic-components)
Language: Fish
File path: N/A
```fish
function renderComponent(parent: Node, component: Data.Component) {
const elements: Element[] = [];
const properties = Object.entries(component);
for (const [name, value] of properties) {
// ^ ^
// string any
const paragraph = document.createElement('p');
paragraph.textContent = `Key: ${name}, Value: ${value}`;
elements.push(paragraph);
}
parent.append(...elements);
}
```
## 已知文件
Description: 🌐 Known documents
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#known-documents)
Language: Fish
File path: N/A
```fish
const ALL_CATEGORIES = ['food', 'tech', 'travel'];
function validateArticle(article: Data.ContentType<'api::article.article'>) {
const { title, category } = article;
// ^? ^?
// string Data.ContentType<'api::category.category'>
if (title.length < 5) {
throw new Error('Title too short');
}
if (!ALL_CATEGORIES.includes(category.name)) {
throw new Error(`Unknown category ${category.name}`);
}
}
```
## 已知组件
Description: 🌐 Known components
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#known-components)
Language: Fish
File path: N/A
```fish
function processUsageMetrics(
id: string,
metrics: Data.Component<'app.metrics'>
) {
telemetry.send(id, { clicks: metrics.clicks, views: metrics.views });
}
```
## 实体子集
Description: 🌐 Using the types' second parameter (TKeys), it is possible to obtain a subset of an entity.
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#entities-subsets)
Language: TypeScript
File path: N/A
```typescript
type Credentials = Data.ContentType<'api::account.account', 'email' | 'password'>;
// ^? { email: string; password: string }
```
---
Language: TypeScript
File path: N/A
```typescript
type UsageMetrics = Data.Component<'app.metrics', 'clicks' | 'views'>;
// ^? { clicks: number; views: number }
```
## 类型参数推断
Description: 🌐 In the following example, the uid type is inferred upon usage as T and used as a type parameter for the document.
(Source: https://docs.strapi.io/cms/typescript/documents-and-entries#type-argument-inference)
Language: JavaScript
File path: N/A
```js
import type { UID } from '@strapi/strapi';
function display(
uid: T,
document: Data.ContentType
) {
switch (uid) {
case 'api::article.article': {
return document.title;
// ^? string
// ^? Data.ContentType<'api::article.article'>
}
case 'api::category.category': {
return document.name;
// ^? string
// ^? Data.ContentType<'api::category.category'>
}
case 'api::account.account': {
return document.email;
// ^? string
// ^? Data.ContentType<'api::account.account'>
}
default: {
throw new Error(`unknown content-type uid: "${uid}"`);
}
}
}
```
Language: TypeScript
File path: N/A
```typescript
declare const article: Data.Document<'api::article.article'>;
declare const category: Data.Document<'api::category.category'>;
declare const account: Data.Document<'api::account.account'>;
display('api::article.article', article);
display('api::category.category', category);
display('api::account.account', account);
// ^ ✅
display('api::article.article', category);
// ^ Error: "category" is not assignable to parameter of type ContentType<'api::article.article'>
```
# 升级工具
Source: https://docs.strapi.io/cms/upgrade-tool
## 升级到主要版本
Description: 🌐 Run the upgrade tool with the major parameter to upgrade the project to the next major version of Strapi:
(Source: https://docs.strapi.io/cms/upgrade-tool#upgrade-to-a-major-version)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade major
```
## 升级到小版本
Description: 🌐 Run the upgrade tool with the minor parameter to upgrade the project to the latest minor and patch version of Strapi:
(Source: https://docs.strapi.io/cms/upgrade-tool#upgrade-to-a-minor-version)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade minor
```
## 升级到补丁版本
Description: 🌐 Run the upgrade tool with the patch parameter to upgrade the project to the latest patch version in the current minor and major version of Strapi:
(Source: https://docs.strapi.io/cms/upgrade-tool#upgrade-to-a-patch-version)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade patch
```
## 升级到最新版本
Description: 🌐 Run the upgrade tool with the latest parameter to upgrade the project to the latest available version regardless of the current Strapi version:
(Source: https://docs.strapi.io/cms/upgrade-tool#upgrade-to-the-latest-version)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade latest
```
## 升级到特定版本
Description: 🌐 Run the upgrade tool with the to parameter followed by a target version to upgrade the project to that specific published version of Strapi:
(Source: https://docs.strapi.io/cms/upgrade-tool#upgrade-to-a-specific-version)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade to 5.42.0
```
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade to 5.0.0-beta.951 --codemods-target 5.0.0
```
## 仅运行 codemods
Description: 🌐 To view a list of the available codemods, use the ls command:
(Source: https://docs.strapi.io/cms/upgrade-tool#run-codemods-only)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade codemods ls
```
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade codemods run
```
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade codemods run 5.0.0-strapi-codemod-uid
```
## 模拟升级而不更新任何文件(演练运行)
Description: 🌐 Examples:
(Source: https://docs.strapi.io/cms/upgrade-tool#simulate-the-upgrade-without-updating-any-files-dry-run)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade major --dry
npx @strapi/upgrade minor --dry
npx @strapi/upgrade patch --dry
```
## 为 Strapi 应用文件夹选择一个路径
Description: 🌐 Example:
(Source: https://docs.strapi.io/cms/upgrade-tool#select-a-path-for-the-strapi-application-folder)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade major -p /path/to/the/Strapi/application/folder
```
## 获取当前版本
Description: 🌐 Example:
(Source: https://docs.strapi.io/cms/upgrade-tool#get-the-current-version)
Language: Bash
File path: N/A
```sh
$ npx @strapi/upgrade -V
4.15.1
```
## 获取详细的调试信息
Description: 🌐 When passing the --debug option (or its -d shorthand), the upgrade tool provides more detailed logs while running:
(Source: https://docs.strapi.io/cms/upgrade-tool#get-detailed-debugging-information)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade --debug
```
## 静默执行升级
Description: 🌐 When passing the --silent option (or its -s shorthand), the tool executes the upgrade without providing any log:
(Source: https://docs.strapi.io/cms/upgrade-tool#execute-the-upgrade-silently)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade --silent
```
## 对每一个提示都回答是
Description: 🌐 When passing the --yes option (or its -y shorthand), the tool automatically answers "yes" to every prompt:
(Source: https://docs.strapi.io/cms/upgrade-tool#answer-yes-to-every-prompt)
Language: Bash
File path: N/A
```bash
npx @strapi/upgrade --yes`
```
## 获取帮助
Description: 🌐 Examples:
(Source: https://docs.strapi.io/cms/upgrade-tool#get-help)
Language: Bash
File path: N/A
```sh
$ npx @strapi/upgrade -h
Usage: upgrade [options]
Options:
-V, --version output the version number
-h, --help Print command line options
Commands:
latest [options] Upgrade to the latest available version of Strapi
major [options] Upgrade to the next available major version of Strapi
minor [options] Upgrade to ...
patch [options] Upgrade to ...
to [options] Upgrade to a specific version of Strapi
help [command] Print options for a specific command
```
---
Language: Bash
File path: N/A
```sh
$ npx @strapi/upgrade major -h
Usage: upgrade major [options]
Upgrade to the next available major version of Strapi
Options:
-p, --project-path Path to the Strapi project
-n, --dry Simulate the upgrade without updating any files (default: false)
-d, --debug Get more logs in debug mode (default: false)
-s, --silent Don't log anything (default: false)
-h, --help Display help for command
-y, --yes Automatically answer yes to every prompt
```
# 使用信息
Source: https://docs.strapi.io/cms/usage-information
## 选择退出
Description: 🌐 The default data collection feature can be disabled using the following CLI command:
(Source: https://docs.strapi.io/cms/usage-information#opt-out)
Language: Bash
File path: N/A
```bash
yarn strapi telemetry:disable
```
---
Language: Bash
File path: N/A
```bash
npm run strapi telemetry:disable
```