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"