feat: configuration extension points

* added custom config extension points

* Added custom field to documentation

* fix deeprequired issue
This commit is contained in:
Teun Mooij
2023-04-18 20:28:42 +01:00
committed by GitHub
parent 431b04075f
commit 023719d775
38 changed files with 188 additions and 8 deletions

View File

@@ -27,8 +27,9 @@ It's often best practice to write your Collections in separate files and then im
| **`endpoints`** | Add custom routes to the REST API. [More](/docs/rest-api/overview#custom-endpoints) |
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. |
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
| **`pagination`** | Set pagination-specific options for this collection. [More](#pagination) |
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
| **`pagination`** | Set pagination-specific options for this collection. [More](#pagination) |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -25,6 +25,7 @@ As with Collection configs, it's often best practice to write your Globals in se
| **`endpoints`** | Add custom routes to the REST API. [More](/docs/rest-api/overview#custom-endpoints) |
| **`graphQL.name`** | Text used in schema generation. Auto-generated from slug if not defined. |
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._

View File

@@ -43,6 +43,7 @@ Payload is a _config-based_, code-first CMS and application framework. The Paylo
| `hooks` | Tap into Payload-wide hooks. [More](/docs/hooks/overview) |
| `plugins` | An array of Payload plugins. [More](/docs/plugins/overview) |
| `endpoints` | An array of custom API endpoints added to the Payload router. [More](/docs/rest-api/overview#custom-endpoints) |
| `custom` | Extension point for adding custom data (e.g. for plugins) |
#### Simple example

View File

@@ -35,7 +35,8 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
| **`required`** | Require this field to have a value. |
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -37,6 +37,7 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -26,6 +26,7 @@ keywords: checkbox, fields, config, configuration, documentation, Content Manage
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -32,6 +32,7 @@ This field uses the `monaco-react` editor syntax highlighting.
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._

View File

@@ -17,6 +17,7 @@ keywords: row, fields, config, configuration, documentation, Content Management
| **`label`** * | A label to render within the header of the collapsible component. This can be a string, function or react component. Function/components receive `({ data, path })` as args. |
| **`fields`** * | Array of field types to nest within this Collapsible. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -29,6 +29,7 @@ This field uses [`react-datepicker`](https://www.npmjs.com/package/react-datepic
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._

View File

@@ -27,6 +27,7 @@ keywords: email, fields, config, configuration, documentation, Content Managemen
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -25,6 +25,7 @@ keywords: group, fields, config, configuration, documentation, Content Managemen
| **`defaultValue`** | Provide an object of data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Group will be kept, so there is no need to specify each nested field as `localized`. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -30,6 +30,7 @@ This field uses the `monaco-react` editor syntax highlighting.
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._

View File

@@ -29,6 +29,7 @@ keywords: number, fields, config, configuration, documentation, Content Manageme
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -30,6 +30,7 @@ The data structure in the database matches the GeoJSON structure to represent po
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -27,6 +27,7 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -26,7 +26,7 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). |
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. |
| **`min`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. |
| **`max`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. |
| **`max`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](/docs/getting-started/concepts#depth) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
@@ -40,6 +40,7 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._

View File

@@ -31,6 +31,7 @@ The Admin component is built on the powerful [`slatejs`](https://docs.slatejs.or
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -16,6 +16,7 @@ keywords: row, fields, config, configuration, documentation, Content Management
| ---------------- | ----------- |
| **`fields`** * | Array of field types to nest within this Row. |
| **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -30,6 +30,7 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._

View File

@@ -19,6 +19,7 @@ keywords: tabs, fields, config, configuration, documentation, Content Management
| ---------------- | ----------- |
| **`tabs`** * | Array of tabs to render within this Tabs field. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
#### Tab-specific Config

View File

@@ -29,6 +29,7 @@ keywords: text, fields, config, configuration, documentation, Content Management
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -29,6 +29,7 @@ keywords: textarea, fields, config, configuration, documentation, Content Manage
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -29,6 +29,7 @@ With this field, you can also inject custom `Cell` components that appear as add
| **`label`** | Human-readable label for this UI field. |
| **`admin.components.Field`** | React component to be rendered for this field within the Edit view. [More](/docs/admin/components/#field-component) |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](/docs/admin/components/#field-component) |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -41,6 +41,7 @@ keywords: upload, images media, fields, config, configuration, documentation, Co
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.*

View File

@@ -91,6 +91,7 @@ Each endpoint object needs to have:
| **`method`** | The lowercase HTTP verb to use: 'get', 'head', 'post', 'put', 'delete', 'connect' or 'options' |
| **`handler`** | A function or array of functions to be called with **req**, **res** and **next** arguments. [Express](https://expressjs.com/en/guide/routing.html#route-handlers) |
| **`root`** | When `true`, defines the endpoint on the root Express app, bypassing Payload handlers and the `routes.api` subpath. Note: this only applies to top-level endpoints of your Payload config, endpoints defined on `collections` or `globals` cannot be root. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
Example:

View File

@@ -39,6 +39,7 @@ export const defaults = {
auth: false,
upload: false,
versions: false,
custom: {},
};
export const authDefaults = {

View File

@@ -187,6 +187,7 @@ const collectionSchema = joi.object().keys({
}),
joi.boolean(),
),
custom: joi.object().pattern(joi.string(), joi.any()),
});
export default collectionSchema;

View File

@@ -304,13 +304,16 @@ export type CollectionConfig = {
* @default true
*/
timestamps?: boolean
/** Extension point to add your custom data. */
custom?: Record<string, any>;
};
export interface SanitizedCollectionConfig extends Omit<DeepRequired<CollectionConfig>, 'auth' | 'upload' | 'fields' | 'versions'> {
export interface SanitizedCollectionConfig extends Omit<DeepRequired<CollectionConfig>, 'auth' | 'upload' | 'fields' | 'versions'| 'endpoints'> {
auth: Auth;
upload: Upload;
fields: Field[];
versions: SanitizedCollectionVersions
versions: SanitizedCollectionVersions;
endpoints: Omit<Endpoint, 'root'>[];
}
export type Collection = {

View File

@@ -55,4 +55,5 @@ export const defaults: Config = {
hooks: {},
localization: false,
telemetry: true,
custom: {},
};

View File

@@ -13,6 +13,7 @@ export const endpointsSchema = joi.array().items(joi.object({
joi.array().items(joi.func()),
joi.func(),
),
custom: joi.object().pattern(joi.string(), joi.any()),
}));
export default joi.object({
@@ -162,4 +163,5 @@ export default joi.object({
),
onInit: joi.func(),
debug: joi.boolean(),
custom: joi.object().pattern(joi.string(), joi.any()),
});

View File

@@ -189,6 +189,8 @@ export type Endpoint = {
* @default false
*/
root?: boolean;
/** Extension point to add your custom data. */
custom?: Record<string, any>;
};
export type AdminView = React.ComponentType<{
@@ -527,14 +529,17 @@ export type Config = {
telemetry?: boolean;
/** A function that is called immediately following startup that receives the Payload instance as its only argument. */
onInit?: (payload: Payload) => Promise<void> | void;
/** Extension point to add your custom data. */
custom?: Record<string, any>;
};
export type SanitizedConfig = Omit<
DeepRequired<Config>,
'collections' | 'globals'
'collections' | 'globals' | 'endpoint'
> & {
collections: SanitizedCollectionConfig[];
globals: SanitizedGlobalConfig[];
endpoints: Endpoint[];
paths: {
configDir: string
config: string

View File

@@ -52,6 +52,7 @@ export const baseField = joi.object().keys({
afterRead: joi.array().items(joi.func()).default([]),
}).default(),
admin: baseAdminFields.default(),
custom: joi.object().pattern(joi.string(), joi.any()),
}).default();
export const idField = baseField.keys({

View File

@@ -121,6 +121,8 @@ export interface FieldBase {
read?: FieldAccess;
update?: FieldAccess;
};
/** Extension point to add your custom data. */
custom?: Record<string, any>;
}
export type NumberField = FieldBase & {
@@ -241,6 +243,8 @@ export type UIField = {
}
}
type: 'ui';
/** Extension point to add your custom data. */
custom?: Record<string, any>;
}
export type UploadField = FieldBase & {

View File

@@ -51,6 +51,8 @@ const sanitizeGlobals = (collections: CollectionConfig[], globals: GlobalConfig[
}
}
if (!sanitizedGlobal.custom) sanitizedGlobal.custom = {};
// /////////////////////////////////
// Sanitize fields
// /////////////////////////////////

View File

@@ -66,6 +66,7 @@ const globalSchema = joi.object().keys({
}),
joi.boolean(),
),
custom: joi.object().pattern(joi.string(), joi.any()),
}).unknown();
export default globalSchema;

View File

@@ -109,10 +109,13 @@ export type GlobalConfig = {
}
fields: Field[];
admin?: GlobalAdminOptions
/** Extension point to add your custom data. */
custom?: Record<string, any>;
}
export interface SanitizedGlobalConfig extends Omit<DeepRequired<GlobalConfig>, 'fields' | 'versions'> {
export interface SanitizedGlobalConfig extends Omit<DeepRequired<GlobalConfig>, 'fields' | 'versions' | 'endpoints'> {
fields: Field[]
endpoints: Omit<Endpoint, 'root'>[],
versions: SanitizedGlobalVersions
}

65
test/config/config.ts Normal file
View File

@@ -0,0 +1,65 @@
import { buildConfig } from '../buildConfig';
import { openAccess } from '../helpers/configHelpers';
import { Config } from '../../src/config/types';
const config: Config = {
collections: [
{
slug: 'pages',
access: openAccess,
endpoints: [
{
path: '/hello',
method: 'get',
handler: (_, res): void => {
res.json({ message: 'hi' });
},
custom: { examples: [{ type: 'response', value: { message: 'hi' } }] },
},
],
fields: [
{
name: 'title',
type: 'text',
custom: { description: 'The title of this page' },
},
],
custom: { externalLink: 'https://foo.bar' },
},
],
globals: [
{
slug: 'my-global',
endpoints: [{
path: '/greet',
method: 'get',
handler: (req, res): void => {
const { name } = req.query;
res.json({ message: `Hi ${name}!` });
},
custom: { params: [{ in: 'query', name: 'name', type: 'string' }] },
}],
fields: [{
name: 'title',
type: 'text',
custom: { description: 'The title of my global' },
},
],
custom: { foo: 'bar' },
},
],
endpoints: [
{
path: '/config',
method: 'get',
root: true,
handler: (req, res): void => {
res.json(req.payload.config);
},
custom: { description: 'Get the sanitized payload config' },
},
],
custom: { name: 'Customer portal' },
};
export default buildConfig(config);

66
test/config/int.spec.ts Normal file
View File

@@ -0,0 +1,66 @@
import { initPayloadTest } from '../helpers/configHelpers';
import payload from '../../src';
require('isomorphic-fetch');
describe('Config', () => {
beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } });
});
describe('payload config', () => {
it('allows a custom field at the config root', () => {
const { config } = payload;
expect(config.custom).toEqual({ name: 'Customer portal' });
});
it('allows a custom field in the root endpoints', () => {
const [endpoint] = payload.config.endpoints;
expect(endpoint.custom).toEqual({ description: 'Get the sanitized payload config' });
});
});
describe('collection config', () => {
it('allows a custom field in collections', () => {
const [collection] = payload.config.collections;
expect(collection.custom).toEqual({ externalLink: 'https://foo.bar' });
});
it('allows a custom field in collection endpoints', () => {
const [collection] = payload.config.collections;
const [endpoint] = collection.endpoints;
expect(endpoint.custom).toEqual({ examples: [{ type: 'response', value: { message: 'hi' } }] });
});
it('allows a custom field in collection fields', () => {
const [collection] = payload.config.collections;
const [field] = collection.fields;
expect(field.custom).toEqual({ description: 'The title of this page' });
});
});
describe('global config', () => {
it('allows a custom field in globals', () => {
const [global] = payload.config.globals;
expect(global.custom).toEqual({ foo: 'bar' });
});
it('allows a custom field in global endpoints', () => {
const [global] = payload.config.globals;
const [endpoint] = global.endpoints;
expect(endpoint.custom).toEqual({ params: [{ in: 'query', name: 'name', type: 'string' }] });
});
it('allows a custom field in global fields', () => {
const [global] = payload.config.globals;
const [field] = global.fields;
expect(field.custom).toEqual({ description: 'The title of my global' });
});
});
});