diff --git a/docs/.vuepress/config/markdown.js b/docs/.vuepress/config/markdown.js index 92e66410a7..44669407e4 100644 --- a/docs/.vuepress/config/markdown.js +++ b/docs/.vuepress/config/markdown.js @@ -2,5 +2,10 @@ const markdown = { extendMarkdown: md => { // use more markdown-it plugins! md.use(require('markdown-it-include')); + md.use(require('markdown-it-multimd-table'), { + multiline: true, + rowspan: true, + headerless: true + }); }, }; diff --git a/docs/.vuepress/config/sidebar-developer.js b/docs/.vuepress/config/sidebar-developer.js index 542ecdee85..a9eae8faa5 100644 --- a/docs/.vuepress/config/sidebar-developer.js +++ b/docs/.vuepress/config/sidebar-developer.js @@ -214,9 +214,7 @@ const developer = [ ['/developer-docs/latest/development/admin-customization', 'Admin panel customization'], ['/developer-docs/latest/development/plugins-extension.md', 'Plugins extension'], ['/developer-docs/latest/development/plugins-development.md', 'Plugins development'], - { - title: 'Custom Fields', - }, + ['/developer-docs/latest/development/custom-fields.md', 'Custom fields'], ['/developer-docs/latest/development/typescript.md', 'TypeScript'], ['/developer-docs/latest/development/providers.md', 'Providers'], ], diff --git a/docs/developer-docs/latest/development/custom-fields.md b/docs/developer-docs/latest/development/custom-fields.md new file mode 100644 index 0000000000..44bf2a9f8d --- /dev/null +++ b/docs/developer-docs/latest/development/custom-fields.md @@ -0,0 +1,340 @@ +--- +title: Custom fields reference - Strapi Developer Docs +description: Learn how you can use custom fields to extend Strapi's content-types capabilities. +sidebarDepth: 3 +canonicalUrl: https://docs.strapi.io/developer-docs/latest/development/custom-fields.html +--- + +# Custom fields + +Custom fields extend Strapi’s capabilities by adding new types of fields to content-types and components. Once created or installed, custom fields can be used in the Content-Type Builder and Content Manager just like built-in fields. + +The present documentation is intended for custom field creators: it describes which APIs and functions developers must use to create a new custom field. The [user guide](/user-docs/latest/plugins/introduction-to-plugins.md#custom-fields) describes how to install and use custom fields from Strapi's admin panel. + + + + +It is recommended that you develop a dedicated [plugin](/developer-docs/latest/development/plugins-development.md) for custom fields. Custom-field plugins include both a server and admin panel part. The custom field must be registered in both parts before it is usable in Strapi's admin panel. + +Once created and used, custom fields are defined like any other attribute in the model's schema. An attribute using a custom field will have its type represented as `customField` (i.e. `type: 'customField'`). Depending on the custom field being used a few additional properties may be present in the attribute's definition (see [models documentation](/developer-docs/latest/development/backend-customization/models.md#custom-fields)). + +::: note NOTES +* Though the recommended way to add a custom field is through creating a plugin, app-specific custom fields can also be registered within the global `register` [function](/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.md) found in `src/index.js` and `src/admin/app/js` files. +* Custom fields can only be shared using plugins. +::: + +## Registering a custom field on the server + +::: prerequisites +!!!include(developer-docs/latest/development/snippets/custom-field-requires-plugin.md)!!! +::: + +Strapi's server needs to be aware of all the custom fields to ensure that an attribute using a custom field is valid. + +The `strapi.customFields` object exposes a `register()` method on the `Strapi` instance. This method is used to register custom fields on the server during the plugin's server [register lifecycle](/developer-docs/latest/developer-resources/plugin-api-reference/server.md#register). + +`strapi.customFields.register()` registers one or several custom field(s) on the server by passing an object (or an array of objects) with the following parameters: + +| Parameter | Description | Type | +| ------------------------------ | ------------------------------------------------- | -------- | +| `name` | The name of the custom field | `String` | +| `plugin`

(_optional_) | The name of the plugin creating the custom fields | `String` | +| `type` | The data type the custom field will use | `String` | + +::: note +Currently, custom fields cannot add new data types to Strapi and must use existing, built-in Strapi data types described in the [models' attributes](/developer-docs/latest/development/backend-customization/models.md#model-attributes) documentation. Special data types unique to Strapi, such as relation, media, component, or dynamic zone data types, cannot be used in custom fields. +::: + +::: details Example: Registering an example "color" custom field on the server: + +```js +// path: ./src/plugins/my-custom-field-plugin/strapi-server.js + +module.exports = { + register({ strapi }) { + strapi.customFields.register({ + name: 'color', + plugin: 'color-picker', + type: 'text', + }); + }, +}; +``` + +::: + +## Registering a custom field in the admin panel + +::: prerequisites +!!!include(developer-docs/latest/development/snippets/custom-field-requires-plugin.md)!!! +::: + +Custom fields must be registered in Strapi's admin panel to be available in the Content-type Builder and the Content Manager. + +The `app.customFields` object exposes a `register()` method on the `StrapiApp` instance. This method is used to register custom fields in the admin panel during the plugin's admin [register lifecycle](/developer-docs/latest/developer-resources/plugin-api-reference/admin-panel.md#register). + +`app.customFields.register()` registers one or several custom field(s) in the admin panel by passing an object (or an array of objects) with the following parameters: + +| Parameter | Description | Type | +| -------------------------------- | ------------------------------------------------------------------------ | --------------------- | +| `name` | Name of the custom field | `String` | +| `pluginId`

(_optional_) | Name of the plugin creating the custom field | `String` | +| `type` | Existing Strapi data type the custom field will use

❗️ Relations, media, components, or dynamic zones cannot be used. | `String` | +| `icon`

(_optional_) | Icon for the custom field | `React.ComponentType` | +| `intlLabel` | Translation for the name | [`IntlObject`](https://formatjs.io/docs/react-intl/) | +| `intlDescription` | Translation for the description | [`IntlObject`](https://formatjs.io/docs/react-intl/) | +| `components` | Components needed to display the custom field in the Content Manager (see [components](#components)) | +| `options`

(_optional_) | Options to be used by the Content-type Builder (see [options](#options)) | `Object` | + +::: details Example: Registering an example "color" custom field in the admin panel: + +```jsx +// path: ./src/plugins/color-picker/strapi-admin.js + +register(app) { + app.customFields.register({ + name: "color", + pluginId: "color-picker", // the custom field is created by a color-picker plugin + type: "string", // the color will be stored as a string + intlLabel: { + id: "color-picker.color.label", + defaultMessage: "Color", + }, + intlDescription: { + id: "color-picker.color.description", + defaultMessage: "Select any color", + } + icon: ColorIcon, + components: { + Input: async () => import(/* webpackChunkName: "input-component" */ "./Input"), + } + options: { + base: [ + /* + Declare settings to be added to the "Base settings" section + of the field in the Content-Type Builder + */ + { + sectionTitle: { // Add a "Format" settings section + id: 'color-picker.color.section.format', + defaultMessage: 'Format', + }, + items: [ // Add settings items to the section + { + /* + Add a "Color format" dropdown + to choose between 2 different format options + for the color value: hexadecimal or RGBA + */ + intlLabel: { + id: 'color-picker.color.format.label', + defaultMessage: 'Color format', + }, + name: 'options.format', + type: 'select', + value: 'hex', // option selected by default + options: [ // List all available "Color format" options + { + key: 'hex', + value: 'hex', + metadatas: { + intlLabel: { + id: 'color-picker.color.format.hex', + defaultMessage: 'Hexadecimal', + }, + }, + }, + { + key: 'rgba', + value: 'rgba', + metadatas: { + intlLabel: { + id: 'color-picker.color.format.rgba', + defaultMessage: 'RGBA', + }, + }, + }, + ], + }, + ], + }, + ], + advanced: [ + /* + Declare settings to be added to the "Advanced settings" section + of the field in the Content-Type Builder + */ + ], + validator: args => ({ + format: yup.string().required({ + id: 'options.color-picker.format.error', + defaultMessage: 'The color format is required', + }), + }) + }), + }, + }); +} +``` + +::: + +### Components + +`app.customFields.register()` must pass a `components` object with an `Input` React component to use in the Content Manager's edit view. + +::: details Example: Registering an Input component + +```js +// path: ./src/plugins/my-custom-field-plugin/strapi-admin.js + +register(app) { + app.customFields.register({ + // … + components: { + Input: async () => import(/* webpackChunkName: "input-component" */ "./Input"), + } + // … + }); +} +``` + +::: + +::: tip +The `Input` React component receives several props. The [`ColorPickerInput` file](https://github.com/strapi/strapi/blob/features/custom-fields/examples/getstarted/src/plugins/mycustomfields/admin/src/components/ColorPicker/ColorPickerInput/index.js#L10-L21) in the Strapi codebase gives you an example of how they can be used. +::: + + +### Options + +`app.customFields.register()` can pass an additional `options` object with the following parameters: + +| Options parameter | Description | Type | +| -------------- | ------------------------------------------------------------------------------- | ----------------------- | +| `base` | Settings available in the _Base settings_ tab of the field in the Content-type Builder | `Object` or `Array of Objects` | +| `advanced` | Settings available in the _Advanced settings_ tab of the field in the Content-type Builder | `Object` or `Array of Objects` | +| `validator` | Validator function returning an object, used to sanitize input. Uses a [`yup` schema object](https://github.com/jquense/yup/tree/pre-v1). | `Function` | + +Both `base` and `advanced` settings accept an object or an array of objects, each object being a settings section. Each settings section could include: + +- a `sectionTitle` to declare the title of the section as an [`IntlObject`](https://formatjs.io/docs/react-intl/) +- and a list of `items` as an array of objects. + +Each object in the `items` array can contain the following parameters: + +| Items parameter | Description | Type | +| --------------- | ------------------------------------------------------------------ | ---------------------------------------------------- | +| `name` | Label of the input.
Must use the `options.settingName` format. | `String` | +| `description` | Description of the input to use in the Content-type Builder | `String` | +| `intlLabel` | Translation for the label of the input | [`IntlObject`](https://formatjs.io/docs/react-intl/) | +| `type` | Type of the input (e.g., `select`, `checkbox`) | `String` | + + + +::: details Example: Declaring options for an example "color" custom field: + +```jsx +// path: ./src/plugins/my-custom-field-plugin/strapi-admin.js + +register(app) { + app.customFields.register({ + // … + options: { + base: [ + { + intlLabel: { + id: 'color-picker.color.format.label', + defaultMessage: 'Color format', + }, + name: 'options.format', + type: 'select', + value: 'hex', + options: [ + { + key: '__null_reset_value__', + value: '', + metadatas: { + intlLabel: { + id: 'color-picker.color.format.placeholder', + defaultMessage: 'Select a format', + }, + hidden: true, + }, + }, + { + key: 'hex', + value: 'hex', + metadatas: { + intlLabel: { + id: 'color-picker.color.format.hex', + defaultMessage: 'Hexadecimal', + }, + }, + }, + { + key: 'rgba', + value: 'rgba', + metadatas: { + intlLabel: { + id: 'color-picker.color.format.rgba', + defaultMessage: 'RGBA', + }, + }, + }, + ], + }, + ], + advanced: [ + { + sectionTitle: { + id: 'global.settings', + defaultMessage: 'Settings', + }, + items: [ + { + name: 'required', + type: 'checkbox', + intlLabel: { + id: 'form.attribute.item.requiredField', + defaultMessage: 'Required field', + }, + description: { + id: 'form.attribute.item.requiredField.description', + defaultMessage: "You won't be able to create an entry if this field is empty", + }, + }, + { + name: 'private', + type: 'checkbox', + intlLabel: { + id: 'form.attribute.item.privateField', + defaultMessage: 'Private field', + }, + description: { + id: 'form.attribute.item.privateField.description', + defaultMessage: 'This field will not show up in the API response', + }, + }, + ], + }, + ], + validator: args => ({ + format: yup.string().required({ + id: 'options.color-picker.format.error', + defaultMessage: 'The color format is required', + }), + }), + }, + }); +} +``` + +::: + + +::: tip +The Strapi codebase gives an example of how settings objects can be described: check the [`baseForm.js`](https://github.com/strapi/strapi/blob/main/packages/core/content-type-builder/admin/src/components/FormModal/attributes/baseForm.js) file for the `base` settings and the [`advancedForm.js`](https://github.com/strapi/strapi/blob/main/packages/core/content-type-builder/admin/src/components/FormModal/attributes/advancedForm.js) file for the `advanced` settings. The base form lists the settings items inline but the advanced form gets the items from an [`attributeOptions.js`](https://github.com/strapi/strapi/blob/main/packages/core/content-type-builder/admin/src/components/FormModal/attributes/attributeOptions.js) file. +::: diff --git a/docs/developer-docs/latest/development/snippets/custom-field-requires-plugin.md b/docs/developer-docs/latest/development/snippets/custom-field-requires-plugin.md new file mode 100644 index 0000000000..7828ce9979 --- /dev/null +++ b/docs/developer-docs/latest/development/snippets/custom-field-requires-plugin.md @@ -0,0 +1 @@ +Registering a custom field through a plugin requires creating and enabling a plugin (see [Plugins development](/developer-docs/latest/development/plugins-development.md#create-a-plugin)). diff --git a/docs/package.json b/docs/package.json index 15ed50f517..1eec6fdf7d 100644 --- a/docs/package.json +++ b/docs/package.json @@ -15,7 +15,9 @@ "dependencies": { "@vuepress/plugin-html-redirect": "^0.1.4", "@vuepress/plugin-medium-zoom": "^1.8.2", + "markdown-it": "^13.0.1", "markdown-it-include": "^2.0.0", + "markdown-it-multimd-table": "^4.2.0", "vuepress": "^1.8.2", "vuepress-plugin-code-copy": "^1.0.6", "vuepress-plugin-element-tabs": "^0.2.8", diff --git a/docs/yarn.lock b/docs/yarn.lock index f5c814459a..a84ba4e9dc 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -1602,6 +1602,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -3298,6 +3303,11 @@ entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== +entities@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" + integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== + envify@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/envify/-/envify-4.1.0.tgz#f39ad3db9d6801b4e6b478b61028d3f0b6819f7e" @@ -5013,6 +5023,13 @@ linkify-it@^2.0.0: dependencies: uc.micro "^1.0.1" +linkify-it@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec" + integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw== + dependencies: + uc.micro "^1.0.1" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -5225,11 +5242,27 @@ markdown-it-include@^2.0.0: resolved "https://registry.yarnpkg.com/markdown-it-include/-/markdown-it-include-2.0.0.tgz#e86e3b3c68c8f0e0437e179ba919ffd28443127a" integrity sha512-wfgIX92ZEYahYWiCk6Jx36XmHvAimeHN420csOWgfyZjpf171Y0xREqZWcm/Rwjzyd0RLYryY+cbNmrkYW2MDw== +markdown-it-multimd-table@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/markdown-it-multimd-table/-/markdown-it-multimd-table-4.2.0.tgz#4b74c352a422dc5b2dad5c6b1f8c54443e53a692" + integrity sha512-wFpb8TSQ9josQrAlOg1toEAHHSGYQZ4krBKfpejPQ+lq3oudnxCNW4S6gYMcRbkrtrfX/ND2njr4belnKt3fBg== + markdown-it-table-of-contents@^0.4.0: version "0.4.4" resolved "https://registry.yarnpkg.com/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.4.tgz#3dc7ce8b8fc17e5981c77cc398d1782319f37fbc" integrity sha512-TAIHTHPwa9+ltKvKPWulm/beozQU41Ab+FIefRaQV1NRnpzwcV9QOe6wXQS5WLivm5Q/nlo0rl6laGkMDZE7Gw== +markdown-it@^13.0.1: + version "13.0.1" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.1.tgz#c6ecc431cacf1a5da531423fc6a42807814af430" + integrity sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q== + dependencies: + argparse "^2.0.1" + entities "~3.0.1" + linkify-it "^4.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + markdown-it@^8.4.1: version "8.4.2" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54"