diff --git a/docs/access-control/fields.mdx b/docs/access-control/fields.mdx index 11a0a2d71..2dec00607 100644 --- a/docs/access-control/fields.mdx +++ b/docs/access-control/fields.mdx @@ -1,7 +1,7 @@ --- title: Field-level Access Control label: Fields -order: 30 +order: 40 desc: Field-level Access Control is specified within a field's config, and allows you to define which users can create, read or update Fields. keywords: fields, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- diff --git a/docs/access-control/globals.mdx b/docs/access-control/globals.mdx index 043724bb4..a04cb1a1e 100644 --- a/docs/access-control/globals.mdx +++ b/docs/access-control/globals.mdx @@ -1,7 +1,7 @@ --- title: Globals Access Control label: Globals -order: 40 +order: 30 desc: Global-level Access Control is specified within each Global's `access` property and allows you to define which users can read or update Globals. keywords: globals, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- diff --git a/docs/admin/views.mdx b/docs/admin/views.mdx index ac6858bf8..349629aa5 100644 --- a/docs/admin/views.mdx +++ b/docs/admin/views.mdx @@ -8,7 +8,7 @@ keywords: Views are the individual pages that make up the [Admin Panel](./overview), such as the Dashboard, List, and Edit views. One of the most powerful ways to customize the Admin Panel is to create Custom Views. These are [Custom Components](./components) that can either replace built-in views or can be entirely new. -Within the Admin Panel, there are four types of views: +There are four types of views within the Admin Panel: - [Root Views](#root-views) - [Collection Views](#collection-views) diff --git a/docs/configuration/collections.mdx b/docs/configuration/collections.mdx index 41b54098b..4096d0850 100644 --- a/docs/configuration/collections.mdx +++ b/docs/configuration/collections.mdx @@ -6,7 +6,9 @@ desc: Structure your Collections for your needs by defining fields, adding slugs keywords: collections, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- -A Collection is a group of records, called Documents, that share a common schema. You can define as many Collections as your application needs. Each Collection saves to the [Database](../database/overview) based on the [Fields](../fields/overview) that you define, and automatically generates the [Local API](../local-api/overview), [REST API](../rest-api/overview), and [GraphQL API](../graphql/overview) so you can query and mutate your Documents. +A Collection is a group of records, called Documents, that all share a common schema. You can define as many Collections as your application needs. Each Collection saves to the [Database](../database/overview) based on the [Fields](../fields/overview) that you define, and automatically generates the [Local API](../local-api/overview), [REST API](../rest-api/overview), and [GraphQL API](../graphql/overview) to manage your Documents. + +Collections are also the primary way to achieve [Authentication](../authentication/overview) in Payload. By defining a Collection with `auth` options, that Collection receives additional operations to support user authentication. Collections are the primary way to structure recurring data in your application, such as users, products, pages, posts, and other types of content that you might want to manage. Each Collection can have its own unique [Access Control](../access-control/overview), [Hooks](../hooks/overview), [Admin Options](#admin-options), and more. @@ -83,7 +85,7 @@ Fields define the schema of the Documents within a Collection. To learn more, go ### Access Control -Access Control determines what a user can and cannot do with a Document. To learn more, go to the [Access Control](../access-control/overview) docs. +Access Control determines what a user can and cannot do with any given Document. To learn more, go to the [Access Control](../access-control/overview) docs. ### Hooks diff --git a/docs/configuration/globals.mdx b/docs/configuration/globals.mdx index 4d2c22fc4..d717dfa7b 100644 --- a/docs/configuration/globals.mdx +++ b/docs/configuration/globals.mdx @@ -6,7 +6,7 @@ desc: Set up your Global config for your needs by defining fields, adding slugs keywords: globals, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- -Globals are in many ways similar to [Collections](../configuration/collections), except they correspond to only a single Document. You can define as many Globals as your application needs. Each Global saves to the [Database](../database/overview) based on the [Fields](../fields/overview) that you define, and automatically generates the [Local API](../local-api/overview), [REST API](../rest-api/overview), and [GraphQL API](../graphql/overview) so you can query and mutate your Documents. +Globals are in many ways similar to [Collections](../configuration/collections), except they correspond to only a single Document. You can define as many Globals as your application needs. Each Global saves to the [Database](../database/overview) based on the [Fields](../fields/overview) that you define, and automatically generates the [Local API](../local-api/overview), [REST API](../rest-api/overview), and [GraphQL API](../graphql/overview) to manage your Documents. Globals are the primary way to structure singletons in Payload, such as a header navigation, site-wide banner alerts, or app-wide localized strings. Each Global can have its own unique [Access Control](../access-control/overview), [Hooks](../hooks/overview), [Admin Options](#admin-options), and more. @@ -89,7 +89,7 @@ Fields define the schema of the Global. To learn more, go to the [Fields](../fie ### Access Control -Access Control determines what a user can and cannot do with a Document. To learn more, go to the [Access Control](../access-control/overview) docs. +Access Control determines what a user can and cannot do with any given Document. To learn more, go to the [Access Control](../access-control/overview) docs. ### Hooks diff --git a/docs/fields/overview.mdx b/docs/fields/overview.mdx index 61a0aa5b5..5cb6d6875 100644 --- a/docs/fields/overview.mdx +++ b/docs/fields/overview.mdx @@ -8,7 +8,7 @@ keywords: overview, fields, config, configuration, documentation, Content Manage Fields are the building blocks of Payload. They define the schema of the Documents that will be stored in the [Database](../database/overview), as well as automatically generate the corresponding UI within the [Admin Panel](../admin/overview). -There are many [Field Types](#field-types) to choose from, ranging anywhere from simple text strings to nested arrays and blocks. Most fields save data to the database, while others are strictly presentational. Fields can have [Custom Validations](#validations), [Conditional Logic](../admin/fields#conditional-logic), [Access Control](#field-level-access-control), [Hooks](#field-level-hooks), and so much more. +There are many [Field Types](#field-types) to choose from, ranging anywhere from simple text strings to nested arrays and blocks. Most fields save data to the database, while others are strictly presentational. Fields can have [Custom Validations](#validation), [Conditional Logic](../admin/fields#conditional-logic), [Access Control](#field-level-access-control), [Hooks](#field-level-hooks), and so much more. To configure fields, use the `fields` property in your [Collection](../configuration/collections) or [Global](../configuration/globals) config: @@ -234,7 +234,7 @@ export const myField: Field = { You can use async `defaultValue` functions to fill fields with data from API requests. -### Validations +### Validation Fields are automatically validated based on their [Field Type](#field-types) and other [Field Options](#field-options) such as `required` or `min` and `max` value constraints. If needed, however, field validations can be customized or entirely replaced by providing your own custom validation functions. diff --git a/docs/getting-started/concepts.mdx b/docs/getting-started/concepts.mdx index 2184e5f4b..d4d846f20 100644 --- a/docs/getting-started/concepts.mdx +++ b/docs/getting-started/concepts.mdx @@ -6,15 +6,11 @@ desc: Payload is based around a small and intuitive set of concepts. Key concept keywords: documentation, getting started, guide, Content Management System, cms, headless, javascript, node, react, nextjs --- -Payload is based around a small and intuitive set of concepts. Before starting to work with Payload, it's a good idea to familiarize yourself with the following: +Payload is based around a small and intuitive set of high-level concepts. Before starting to work with Payload, it's a good idea to familiarize yourself with these concepts in order to establish a common language and understanding when discussing Payload. ## Config -The Payload Config is allows for the deep configuration of your application through a simple and intuitive API. The Payload Config is a fully-typed JavaScript object that can be infinitely extended upon. [More details](../configuration/overview). - -## Admin Panel - -Payload dynamically generates a beautiful, fully type-safe interface to manage your users and data. The Admin Panel is a React application built using the Next.js App Router. [More details](../admin/overview). +The Payload Config is central to everything that Payload does. It allows for the deep configuration of your application through a simple and intuitive API. The Payload Config is a fully-typed JavaScript object that can be infinitely extended upon. [More details](../configuration/overview). ## Database @@ -22,7 +18,7 @@ Payload is database agnostic, meaning you can use any type of database behind Pa ## Collections -A Collection is a group of records, called Documents, that share a common schema. Each Collection saves to the [Database](../database/overview) based on the [Fields](../fields/overview) that you define. [More details](../configuration/collections). +A Collection is a group of records, called Documents, that all share a common schema. Each Collection saves to the [Database](../database/overview) based on the [Fields](../fields/overview) that you define. [More details](../configuration/collections). ## Globals @@ -34,11 +30,19 @@ Fields are the building blocks of Payload. They define the schema of the Documen ## Hooks -Hooks allow you to tie into the lifecycle of your Documents so you can execute your own logic during specific events. [More details](../hooks/overview). +Hooks allow you to execute your own logic during specific events of the Document lifecycle, such as read or update. [More details](../hooks/overview). ## Access Control -Access Control determines what a user can and cannot do with a Document. [More details](../access-control/overview). +Access Control determines what a user can and cannot do with any given Document. [More details](../access-control/overview). + +## Admin Panel + +Payload dynamically generates a beautiful, fully type-safe interface to manage your users and data. The Admin Panel is a React application built using the Next.js App Router. [More details](../admin/overview). + +## Plugins + +Plugins allow for developers to easily inject custom—sometimes complex—functionality into Payload apps from a very small touch-point. [More details](../plugins/overview). ## Retrieving Data @@ -48,11 +52,16 @@ Everything Payload does (create, read, update, delete, login, logout, etc) is ex - [REST API](#rest-api) - Standard HTTP endpoints for querying and mutating data - [GraphQL](#graphql) - A full GraphQL API with a GraphQL Playground + + Note: + All of these APIs share the exact same query language. [More details](../queries/overview). + + ### Local API -By far one of the most powerful aspects of Payload is the fact that it gives you direct-to-database access to your data through the [Local API](../local-api/overview). It's _extremely_ fast and does not incur any typical REST API / GraphQL / HTTP overhead - you query your database directly in Node.js / TypeScript. +By far one of the most powerful aspects of Payload is the fact that it gives you direct-to-database access to your data through the [Local API](../local-api/overview). It's _extremely_ fast and does not incur any typical REST API / GraphQL / HTTP overhead—you query your database directly in Node.js. -Everything is strongly typed and it's extremely nice to use. It works anywhere on the server, including custom Next.js route handlers, Payload hooks, Payload access control, and React Server Components. +The Local API is written in TypeScript, and so it is strongly typed and extremely nice to use. It works anywhere on the server, including custom Next.js Routes, Payload Hooks, Payload Access Control, and React Server Components. Here's a quick example of a React Server Component fetching page data with Payload's Local API: @@ -93,15 +102,12 @@ const MyServerComponent: React.FC = () => { By default, the Payload [REST API](../rest-api/overview) will be mounted automatically for you at the `/api` path of your app. -For example, if you have a collection with the `slug` as `pages`, you'd fetch pages via `http://localhost:3000/api/pages`. - -Here's an example: +For example, if you have a [Collection](../configuration/collections) called `pages`: ```ts -const pages = await fetch('http://localhost:3000/api/pages', { - // Include cookies, like the Payload auth token - credentials: 'include', -}).then((res) => res.json()) +fetch('https://localhost:3000/api/pages') // highlight-line + .then((res) => res.json()) + .then((data) => console.log(data)) ``` @@ -127,27 +133,22 @@ If you don't use GraphQL, you can delete those files! But if you do, you'll find For more information about the GraphQL API, [click here](../graphql/overview). -## Depth +### Depth Documents can be related to other Documents through a concept called "relationships". When you query a Document, you can specify these relationships are populated. [More details](../queries/depth). -## Plugins - -Plugins allow for developers to easily inject custom—sometimes complex—functionality into Payload apps from a very small touch-point. [More details](../plugins/overview). - ## Package Structure Payload is abstracted into a set of dedicated packages to keep the core `payload` package as lightweight as possible. This allows you to only install the parts of Payload based on your unique project requirements. - - Note: -
- Version numbers of all official Payload packages are kept in sync - and you should always make sure that you use matching versions for all official Payload packages. + + Important: + Version numbers of all official Payload packages are always published in sync. You should make sure that you always use matching versions for all official Payload packages. `payload` -The `payload` package is where core business logic for Payload lives. You can think of Payload as an ORM with superpowers - it contains the logic for all Payload "operations" like `find`, `create`, `update`, and `delete`. It executes access control, hooks, validation, and more. +The `payload` package is where core business logic for Payload lives. You can think of Payload as an ORM with superpowers—it contains the logic for all Payload "operations" like `find`, `create`, `update`, and `delete` and exposes a [Local API](../local-api/overview). It executes [Access Control](../access-control/overview), [Hooks](../hooks/overview), [Validation](../fields/overview#validation), and more. Payload itself is extremely compact, and can be used in any Node environment. As long as you have `payload` installed and you have access to your Payload Config, you can query and mutate your database directly without going through an unnecessary HTTP layer. @@ -156,12 +157,12 @@ Payload also contains all TypeScript definitions, which can be imported from `pa Here's how to import some common Payload types: ```ts -import { CollectionConfig, Field, GlobalConfig, Config } from 'payload' +import { Config, CollectionConfig, GlobalConfig, Field } from 'payload' ``` `@payloadcms/next` -Whereas Payload itself is responsible for direct database access, and control over Payload business logic, the `@payloadcms/next` package is responsible for the Admin Panel and the entire HTTP layer (REST, GraphQL) that Payload exposes. +Whereas Payload itself is responsible for direct database access, and control over Payload business logic, the `@payloadcms/next` package is responsible for the Admin Panel and the entire HTTP layer that Payload exposes, including the [REST API](../rest-api/overview) and [GraphQL API](../graphql/overview). `@payloadcms/graphql` diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx index 1c0058eed..76a6ae802 100644 --- a/docs/getting-started/installation.mdx +++ b/docs/getting-started/installation.mdx @@ -48,9 +48,9 @@ pnpm i payload@beta @payloadcms/next@beta @payloadcms/richtext-lexical@beta shar Swap out `pnpm` for your package manager. If you are using NPM, you might need to install using legacy peer deps: `npm i --legacy-peer-deps`.
-**Install a Database Adapter** +Next, install a [Database Adapter](/docs/database/overview). Payload requires a Database Adapter to establish a database connection. Payload works with all types of databases, but the most common are MongoDB and Postgres. -Payload requires a [Database Adapter](/docs/database/overview) to connect to your database of choice. Payload works with all types of databases, but the most common are MongoDB and Postgres. +To install a Database Adapter, you can run **one** of the following commands: - To install the [MongoDB Adapter](../database/mongodb), run: ```bash @@ -64,7 +64,7 @@ Payload requires a [Database Adapter](/docs/database/overview) to connect to you Reminder: - New [Database Adapters](/docs/database/overview) are becoming available every day. Check the docs for the most up-to-date list. + New [Database Adapters](/docs/database/overview) are becoming available every day. Check the docs for the most up-to-date list of what's available. #### 2. Copy Payload files into your Next.js app folder diff --git a/docs/getting-started/what-is-payload.mdx b/docs/getting-started/what-is-payload.mdx index 1947b8a76..40d03f83d 100644 --- a/docs/getting-started/what-is-payload.mdx +++ b/docs/getting-started/what-is-payload.mdx @@ -36,7 +36,7 @@ Even in spite of how much you get out of the box, you still have full control ov In Payload, there are no "click ops" - as in clicking around in an Admin Panel to define your schema. In Payload, everything is done the right way—code-first and version controlled like a proper backend. But once developers define how Payload should work, non-technical users can independently make use of its Admin Panel to manage whatever they need to without having to know code whatsoever. -## Use cases +## Use Cases Payload started as a headless Content Management System (CMS), but since, we've seen our community leverage Payload in ways far outside of simply managing pages and blog posts. It's grown into a full-stack TypeScript app framework. diff --git a/docs/hooks/collections.mdx b/docs/hooks/collections.mdx index b3e878c9b..dc83e57cd 100644 --- a/docs/hooks/collections.mdx +++ b/docs/hooks/collections.mdx @@ -6,43 +6,54 @@ desc: You can add hooks to any Collection, several hook types are available incl keywords: hooks, collections, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- -Collections feature the ability to define the following hooks: +Collection Hooks are [Hooks](./overview) that run on Documents within a specific [Collection](../configuration/collections). They allow you to execute your own logic during specific events of the Document lifecycle. -- [beforeOperation](#beforeoperation) -- [beforeValidate](#beforevalidate) -- [beforeChange](#beforechange) -- [afterChange](#afterchange) -- [beforeRead](#beforeread) -- [afterRead](#afterread) -- [beforeDelete](#beforedelete) -- [afterDelete](#afterdelete) -- [afterOperation](#afteroperation) - -Additionally, `auth`-enabled collections feature the following hooks: - -- [beforeLogin](#beforelogin) -- [afterLogin](#afterlogin) -- [afterLogout](#afterlogout) -- [afterRefresh](#afterrefresh) -- [afterMe](#afterme) -- [afterForgotPassword](#afterforgotpassword) -- [refresh](#refresh) -- [me](#me) - -## Config - -All collection Hook properties accept arrays of synchronous or asynchronous functions. Each Hook type receives specific arguments and has the ability to modify specific outputs. - -`collections/exampleHooks.js` +To add hooks to a Collection, use the `hooks` property in your [Collection Config](../configuration/collections): ```ts import type { CollectionConfig } from 'payload'; -export const ExampleHooks: CollectionConfig = { - slug: 'example-hooks', - fields: [ - { name: 'name', type: 'text'}, - ], +export const CollectionWithHooks: CollectionConfig = { + // ... + hooks: { // highlight-line + // ... + }, +} +``` + +The following Collection Hooks are available: + +- [`beforeOperation`](#beforeoperation) +- [`beforeValidate`](#beforevalidate) +- [`beforeChange`](#beforechange) +- [`afterChange`](#afterchange) +- [`beforeRead`](#beforeread) +- [`afterRead`](#afterread) +- [`beforeDelete`](#beforedelete) +- [`afterDelete`](#afterdelete) +- [`afterOperation`](#afteroperation) + +Additionally, all [Auth-enabled Collections](../authentication/overview) feature the following auth-related Hooks: + +- [`beforeLogin`](#beforelogin) +- [`afterLogin`](#afterlogin) +- [`afterLogout`](#afterlogout) +- [`afterRefresh`](#afterrefresh) +- [`afterMe`](#afterme) +- [`afterForgotPassword`](#afterforgotpassword) +- [`refresh`](#refresh) +- [`me`](#me) + +## Config Options + +All Collection Hooks accept an array of [synchronous or asynchronous functions](./overview#async-vs-synchronous). Each Collection Hook receives specific arguments based on its own type, and has the ability to modify specific outputs. + +```ts +import type { CollectionConfig } from 'payload'; + +export const CollectionWithHooks: CollectionConfig = { + // ... + // highlight-start hooks: { beforeOperation: [(args) => {...}], beforeValidate: [(args) => {...}], @@ -54,7 +65,7 @@ export const ExampleHooks: CollectionConfig = { afterDelete: [(args) => {...}], afterOperation: [(args) => {...}], - // Auth-enabled hooks + // Auth-enabled Hooks beforeLogin: [(args) => {...}], afterLogin: [(args) => {...}], afterLogout: [(args) => {...}], @@ -64,6 +75,7 @@ export const ExampleHooks: CollectionConfig = { refresh: [(args) => {...}], me: [(args) => {...}], }, + // highlight-end } ``` @@ -85,6 +97,15 @@ const beforeOperationHook: CollectionBeforeOperationHook = async ({ } ``` +The following arguments are passed to the `beforeOperation` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between Hooks. [More details](./context). | +| **`operation`** | The name of the operation that this hook is running within. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### beforeValidate Runs before the `create` and `update` operations. This hook allows you to add or format data before the incoming data is validated server-side. @@ -99,15 +120,23 @@ Please do note that this does not run before the client-side validation. If you import type { CollectionBeforeValidateHook } from 'payload' const beforeValidateHook: CollectionBeforeValidateHook = async ({ - data, // incoming data to update or create with - req, // full Request object - operation, // name of the operation ie. 'create', 'update' - originalDoc, // original document + data, }) => { - return data // Return data to either create or update a document with + return data } ``` +The following arguments are passed to the `beforeValidate` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between Hooks. [More details](./context). | +| **`data`** | The incoming data passed through the operation. | +| **`operation`** | The name of the operation that this hook is running within. | +| **`originalDoc`** | The Document before changes are applied. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### beforeChange Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved. @@ -116,15 +145,23 @@ Immediately following validation, `beforeChange` hooks will run within `create` import type { CollectionBeforeChangeHook } from 'payload' const beforeChangeHook: CollectionBeforeChangeHook = async ({ - data, // incoming data to update or create with - req, // full Request object - operation, // name of the operation ie. 'create', 'update' - originalDoc, // original document + data, }) => { - return data // Return data to either create or update a document with + return data } ``` +The following arguments are passed to the `beforeChange` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`data`** | The incoming data passed through the operation. | +| **`operation`** | The name of the operation that this hook is running within. | +| **`originalDoc`** | The Document before changes are applied. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### afterChange After a document is created or updated, the `afterChange` hook runs. This hook is helpful to recalculate statistics such as total sales within a global, syncing user profile changes to a CRM, and more. @@ -133,15 +170,23 @@ After a document is created or updated, the `afterChange` hook runs. This hook i import type { CollectionAfterChangeHook } from 'payload' const afterChangeHook: CollectionAfterChangeHook = async ({ - doc, // full document data - req, // full Request object - previousDoc, // document data before updating the collection - operation, // name of the operation ie. 'create', 'update' + doc, }) => { return doc } ``` +The following arguments are passed to the `afterChange` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`doc`** | The resulting Document after changes are applied. | +| **`operation`** | The name of the operation that this hook is running within. | +| **`previousDoc`** | The Document before changes were applied. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### beforeRead Runs before `find` and `findByID` operations are transformed for output by `afterRead`. This hook fires before hidden fields are removed and before localized fields are flattened into the requested locale. Using this Hook will provide you with all locales and all hidden fields via the `doc` argument. @@ -150,14 +195,22 @@ Runs before `find` and `findByID` operations are transformed for output by `afte import type { CollectionBeforeReadHook } from 'payload' const beforeReadHook: CollectionBeforeReadHook = async ({ - doc, // full document data - req, // full Request object - query, // JSON formatted query + doc, }) => { return doc } ``` +The following arguments are passed to the `beforeRead` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`doc`** | The resulting Document after changes are applied. | +| **`query`** | The [Query](../queries/overview) of the request. +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### afterRead Runs as the last step before documents are returned. Flattens locales, hides protected fields, and removes fields that users do not have access to. @@ -166,15 +219,22 @@ Runs as the last step before documents are returned. Flattens locales, hides pro import type { CollectionAfterReadHook } from 'payload' const afterReadHook: CollectionAfterReadHook = async ({ - doc, // full document data - req, // full Request object - query, // JSON formatted query - findMany, // boolean to denote if this hook is running against finding one, or finding many + doc, }) => { return doc } ``` +The following arguments are passed to the `afterRead` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`doc`** | The resulting Document after changes are applied. | +| **`query`** | The [Query](../queries/overview) of the request. +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### beforeDelete Runs before the `delete` operation. Returned values are discarded. @@ -183,11 +243,20 @@ Runs before the `delete` operation. Returned values are discarded. import type { CollectionBeforeDeleteHook } from 'payload'; const beforeDeleteHook: CollectionBeforeDeleteHook = async ({ - req, // full Request object - id, // id of document to delete + req, + id, }) => {...} ``` +The following arguments are passed to the `beforeDelete` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`id`** | The ID of the Document being deleted. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### afterDelete Runs immediately after the `delete` operation removes records from the database. Returned values are discarded. @@ -196,12 +265,22 @@ Runs immediately after the `delete` operation removes records from the database. import type { CollectionAfterDeleteHook } from 'payload'; const afterDeleteHook: CollectionAfterDeleteHook = async ({ - req, // full Request object - id, // id of document to delete - doc, // deleted document + req, + id, + doc, }) => {...} ``` +The following arguments are passed to the `afterDelete` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`doc`** | The resulting Document after changes are applied. | +| **`id`** | The ID of the Document that was deleted. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### afterOperation The `afterOperation` hook can be used to modify the result of operations or execute side-effects that run after an operation has completed. @@ -212,123 +291,194 @@ Available Collection operations include `create`, `find`, `findByID`, `update`, import type { CollectionAfterOperationHook } from 'payload' const afterOperationHook: CollectionAfterOperationHook = async ({ - args, // arguments passed into the operation - operation, // name of the operation - req, // full Request object - result, // the result of the operation, before modifications + result, }) => { - return result // return modified result as necessary + return result } ``` +The following arguments are passed to the `afterOperation` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`args`** | The arguments passed into the operation. | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | +| **`operation`** | The name of the operation that this hook is running within. | +| **`result`** | The result of the operation, before modifications. | + ### beforeLogin -For auth-enabled Collections, this hook runs during `login` operations where a user with the provided credentials exist, but before a token is generated and added to the response. You can optionally modify the user that is returned, or throw an error in order to deny the login operation. +For [Auth-enabled Collections](../authentication/overview), this hook runs during `login` operations where a user with the provided credentials exist, but before a token is generated and added to the response. You can optionally modify the user that is returned, or throw an error in order to deny the login operation. ```ts import type { CollectionBeforeLoginHook } from 'payload' const beforeLoginHook: CollectionBeforeLoginHook = async ({ - req, // full Request object - user, // user being logged in + user, }) => { return user } ``` +The following arguments are passed to the `beforeLogin` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | +| **`user`** | The user being logged in. | + ### afterLogin -For auth-enabled Collections, this hook runs after successful `login` operations. You can optionally modify the user that is returned. +For [Auth-enabled Collections](../authentication/overview), this hook runs after successful `login` operations. You can optionally modify the user that is returned. ```ts import type { CollectionAfterLoginHook } from 'payload'; const afterLoginHook: CollectionAfterLoginHook = async ({ - req, // full Request object - user, // user that was logged in - token, // user token + user, + token, }) => {...} ``` +The following arguments are passed to the `afterLogin` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | +| **`token`** | The token generated for the user. | +| **`user`** | The user being logged in. | + ### afterLogout -For auth-enabled Collections, this hook runs after `logout` operations. +For [Auth-enabled Collections](../authentication/overview), this hook runs after `logout` operations. ```ts import type { CollectionAfterLogoutHook } from 'payload'; const afterLogoutHook: CollectionAfterLogoutHook = async ({ - req, // full Request object + req, }) => {...} ``` -### afterRefresh +The following arguments are passed to the `afterLogout` hook: -For auth-enabled Collections, this hook runs after `refresh` operations. - -```ts -import type { CollectionAfterRefreshHook } from 'payload'; - -const afterRefreshHook: CollectionAfterRefreshHook = async ({ - req, // full Request object - res, // full Response object - token, // newly refreshed user token -}) => {...} -``` +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | ### afterMe -For auth-enabled Collections, this hook runs after `me` operations. +For [Auth-enabled Collections](../authentication/overview), this hook runs after `me` operations. ```ts import type { CollectionAfterMeHook } from 'payload'; const afterMeHook: CollectionAfterMeHook = async ({ - req, // full Request object - response, // response to return + req, + response, }) => {...} ``` +The following arguments are passed to the `afterMe` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | +| **`response`** | The response to return. | + +### afterRefresh + +For [Auth-enabled Collections](../authentication/overview), this hook runs after `refresh` operations. + +```ts +import type { CollectionAfterRefreshHook } from 'payload'; + +const afterRefreshHook: CollectionAfterRefreshHook = async ({ + token, +}) => {...} +``` + +The following arguments are passed to the `afterRefresh` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`exp`** | The expiration time of the token. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | +| **`token`** | The newly refreshed user token. | + ### afterForgotPassword -For auth-enabled Collections, this hook runs after successful `forgotPassword` operations. Returned values are discarded. +For [Auth-enabled Collections](../authentication/overview), this hook runs after successful `forgotPassword` operations. Returned values are discarded. ```ts import type { CollectionAfterForgotPasswordHook } from 'payload' const afterForgotPasswordHook: CollectionAfterForgotPasswordHook = async ({ - args, // arguments passed into the operation + args, context, - collection, // The collection which this hook is being run on + collection, }) => {...} ``` +The following arguments are passed to the `afterForgotPassword` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`args`** | The arguments passed into the operation. | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | + ### refresh -For auth-enabled Collections, this hook allows you to optionally replace the default behavior of the `refresh` operation with your own. If you optionally return a value from your hook, the operation will not perform its own logic and continue. +For [Auth-enabled Collections](../authentication/overview), this hook allows you to optionally replace the default behavior of the `refresh` operation with your own. If you optionally return a value from your hook, the operation will not perform its own logic and continue. ```ts import type { CollectionRefreshHook } from 'payload' const myRefreshHook: CollectionRefreshHook = async ({ - args, // arguments passed into the `refresh` operation - user, // the user as queried from the database + args, + user, }) => {...} ``` +The following arguments are passed to the `afterRefresh` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`args`** | The arguments passed into the operation. | +| **`user`** | The user being logged in. | + ### me -For auth-enabled Collections, this hook allows you to optionally replace the default behavior of the `me` operation with your own. If you optionally return a value from your hook, the operation will not perform its own logic and continue. +For [Auth-enabled Collections](../authentication/overview), this hook allows you to optionally replace the default behavior of the `me` operation with your own. If you optionally return a value from your hook, the operation will not perform its own logic and continue. ```ts import type { CollectionMeHook } from 'payload' const meHook: CollectionMeHook = async ({ - args, // arguments passed into the `me` operation - user, // the user as queried from the database + args, + user, }) => {...} ``` +The following arguments are passed to the `me` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`args`** | The arguments passed into the operation. | +| **`user`** | The user being logged in. | + ## TypeScript Payload exports a type for each Collection hook which can be accessed as follows: diff --git a/docs/hooks/context.mdx b/docs/hooks/context.mdx index ed13616c3..d7f7d737d 100644 --- a/docs/hooks/context.mdx +++ b/docs/hooks/context.mdx @@ -120,11 +120,11 @@ const MyCollection: CollectionConfig = { } ``` -## Typing context +## TypeScript -The default typescript interface for `context` is `{ [key: string]: unknown }`. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the `declare` syntax. +The default TypeScript interface for `context` is `{ [key: string]: unknown }`. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the `declare` syntax. -This is known as "type augmentation" - a TypeScript feature which allows us to add types to existing objects. Simply put this in any .ts or .d.ts file: +This is known as "type augmentation", a TypeScript feature which allows us to add types to existing objects. Simply put this in any `.ts` or `.d.ts` file: ```ts import { RequestContext as OriginalRequestContext } from 'payload' diff --git a/docs/hooks/fields.mdx b/docs/hooks/fields.mdx index 455ad78ec..ca9b749e5 100644 --- a/docs/hooks/fields.mdx +++ b/docs/hooks/fields.mdx @@ -1,38 +1,49 @@ --- title: Field Hooks label: Fields -order: 30 +order: 40 desc: Hooks can be added to any fields, and optionally modify the return value of the field before the operation continues. keywords: hooks, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- -Field-level hooks offer incredible potential for encapsulating your logic. They help to isolate concerns and package up -functionalities to be easily reusable across your projects. +Field Hooks are [Hooks](./overview) that run on Documents on a per-field basis. They allow you to execute your own logic during specific events of the Document lifecycle. Field Hooks offer incredible potential for isolating your logic from the rest of your [Collection Hooks](./collections) and [Global Hooks](./globals). -**Example use cases include:** - -- Automatically add an `owner` relationship to a Document based on the `req.user.id` -- Encrypt / decrypt a sensitive field using `beforeValidate` and `afterRead` hooks -- Auto-generate field data using a `beforeValidate` hook -- Format incoming data such as kebab-casing a document `slug` with `beforeValidate` -- Restrict updating a document to only once every X hours using the `beforeChange` hook - -**All field types provide the following hooks:** - -- [beforeValidate](#beforevalidate) -- [beforeChange](#beforechange) -- beforeDuplicate(#beforeduplicate) -- [afterChange](#afterchange) -- [afterRead](#afterread) - -## Config - -Example field configuration: +To add hooks to a Field, use the `hooks` property in your [Field Config](../fields/overview): ```ts import type { Field } from 'payload'; -const ExampleField: Field = { +export const FieldWithHooks: Field = { + // ... + hooks: { // highlight-line + // ... + }, +} +``` + +The following Field Hooks are available: + +- [`beforeValidate`](#beforevalidate) +- [`beforeChange`](#beforechange) +- [`beforeDuplicate`](#beforeduplicate) +- [`afterChange`](#afterchange) +- [`afterRead`](#afterread) + +## Config Options + +All Field Hooks accept an array of synchronous or asynchronous functions. These functions can optionally modify the return value of the field before the operation continues. All Field Hooks are formatted to accept the same arguments, although some arguments may be `undefined` based the specific hook type. + + + Important: + Due to GraphQL's typed nature, changing the type of data that you return from a field will produce errors in the [GraphQL API](../graphql/overview). If you need to change the shape or type of data, consider [Collection Hooks](./collections) or [Global Hooks](./hooks) instead. + + +To add hooks to a Field, use the `hooks` property in your [Field Config](../fields/overview): + +```ts +import type { Field } from 'payload'; + +const FieldWithHooks: Field = { name: 'name', type: 'text', // highlight-start @@ -47,64 +58,37 @@ const ExampleField: Field = { } ``` -## Arguments and return values - -All field-level hooks are formatted to accept the same arguments, although some arguments may be `undefined` based on -which field hook you are utilizing. - - - Tip: -
- It's a good idea to conditionally scope your logic based on which operation is executing. For - example, if you are writing a beforeChange hook, you may want to perform - different logic based on if the current operation is create or{' '} - update. -
- -#### Arguments - -Field Hooks receive one `args` argument that contains the following properties: +The following arguments are available to all Field Hooks: | Option | Description | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`data`** | The data passed to update the document within `create` and `update` operations, and the full document itself in the `afterRead` hook. | -| **`siblingData`** | The sibling data passed to a field that the hook is running against. | +| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. If the field belongs to a Global, this will be `null`. | +| **`context`** | Custom context passed between Hooks. [More details](./context). | +| **`data`** | In the `afterRead` hook this is the full Document. In the `create` and `update` operations, this is the incoming data passed through the operation. | +| **`field`** | The [Field](../fields/overview) which the Hook is running against. | | **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. | -| **`operation`** | A string relating to which operation the field type is currently executing within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. | -| **`originalDoc`** | The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. | -| **`previousDoc`** | The document before changes were applied, only in `afterChange` hooks. | -| **`previousSiblingDoc`** | The sibling data of the document before changes being applied, only in `beforeChange` and `afterChange` hook. | -| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. It is mocked for Local API operations. | -| **`value`** | The value of the field. | +| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. If the field belongs to a Collection, this will be `null`. | +| **`operation`** | The name of the operation that this hook is running within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. | +| **`originalDoc`** | In the `update` operation, this is the Document before changes were applied. In the `afterChange` hook, this is the resulting Document. | +| **`overrideAccess`** | A boolean to denote if the current operation is overriding [Access Control](../access-control/overview). | +| **`path`** | The path to the [Field](../fields/overview) in the schema. | +| **`previousDoc`** | In the `afterChange` Hook, this is the Document before changes were applied. | +| **`previousSiblingDoc`** | The sibling data of the Document before changes being applied, only in `beforeChange` and `afterChange` hook. | | **`previousValue`** | The previous value of the field, before changes, only in `beforeChange` and `afterChange` hooks. | -| **`context`** | Context passed to this hook. More info can be found under [Context](/docs/hooks/context) | -| **`field`** | The field which the hook is running against. | -| **`collection`** | The collection which the field belongs to. If the field belongs to a global, this will be null. | -| **`global`** | The global which the field belongs to. If the field belongs to a collection, this will be null. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | +| **`schemaPath`** | The path of the [Field](../fields/overview) in the schema. | +| **`siblingData`** | The data of sibling fields adjacent to the field that the Hook is running against. | +| **`siblingDocWithLocales`** | The sibling data of the Document with all [Locales](../configuration/localization). | +| **`value`** | The value of the [Field](../fields/overview). | -#### Return value - -All field hooks can optionally modify the return value of the field before the operation continues. Field Hooks may -optionally return the value that should be used within the field. - - - Important -
- Due to GraphQL's typed nature, you should never change the type of data that you return from a - field, otherwise GraphQL will produce errors. If you need to change the shape or type of data, - reconsider Field Hooks and instead evaluate if Collection / Global hooks might suit you better. + + Tip: + It's a good idea to conditionally scope your logic based on which operation is executing. For example, if you are writing a `beforeChange` hook, you may want to perform different logic based on if the current `operation` is `create` or `update`. -## Examples of Field Hooks - -To better illustrate how field-level hooks can be applied, here are some specific examples. These demonstrate the -flexibility and potential of field hooks in different contexts. Remember, these examples are just a starting point - the -true potential of field-level hooks lies in their adaptability to a wide array of use cases. - ### beforeValidate -Runs before the `update` operation. This hook allows you to pre-process or format field data before it undergoes -validation. +Runs before the `update` operation. This hook allows you to pre-process or format field data before it undergoes validation. ```ts import type { Field } from 'payload' diff --git a/docs/hooks/globals.mdx b/docs/hooks/globals.mdx index 15da2a6df..16c4d832f 100644 --- a/docs/hooks/globals.mdx +++ b/docs/hooks/globals.mdx @@ -1,33 +1,44 @@ --- title: Global Hooks label: Globals -order: 40 +order: 30 desc: Hooks can be added to any Global and allow you to validate data, flatten locales, hide protected fields, remove fields and more. keywords: hooks, globals, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- -Globals feature the ability to define the following hooks: +Global Hooks are [Hooks](./overview) that run on [Global](../configuration/globals) Documents. They allow you to execute your own logic during specific events of the Document lifecycle. -- [beforeValidate](#beforevalidate) -- [beforeChange](#beforechange) -- [afterChange](#afterchange) -- [beforeRead](#beforeread) -- [afterRead](#afterread) - -## Config - -All Global Hook properties accept arrays of synchronous or asynchronous functions. Each Hook type receives specific arguments and has the ability to modify specific outputs. - -`globals/example-hooks.js` +To add hooks to a Global, use the `hooks` property in your [Global Config](../configuration/globals): ```ts import type { GlobalConfig } from 'payload'; -const ExampleHooks: GlobalConfig = { - slug: 'header', - fields: [ - { name: 'title', type: 'text'}, - ] +export const GlobalWithHooks: GlobalConfig = { + // ... + hooks: { // highlight-line + // ... + }, +} +``` + +The following Global Hooks are available: + +- [`beforeValidate`](#beforevalidate) +- [`beforeChange`](#beforechange) +- [`afterChange`](#afterchange) +- [`beforeRead`](#beforeread) +- [`afterRead`](#afterread) + +## Config Options + +All Global Hooks accept an array of [synchronous or asynchronous functions](./overview#async-vs-synchronous). Each Global Hook receives specific arguments based on its own type, and has the ability to modify specific outputs. + +```ts +import type { GlobalConfig } from 'payload'; + +const GlobalWithHooks: GlobalConfig = { + // ... + // highlight-start hooks: { beforeValidate: [(args) => {...}], beforeChange: [(args) => {...}], @@ -35,6 +46,7 @@ const ExampleHooks: GlobalConfig = { afterChange: [(args) => {...}], afterRead: [(args) => {...}], } + // highlight-end } ``` @@ -54,6 +66,16 @@ const beforeValidateHook: GlobalBeforeValidateHook = async ({ } ``` +The following arguments are passed to the `beforeValidate` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. | +| **`context`** | Custom context passed between Hooks. [More details](./context). | +| **`data`** | The incoming data passed through the operation. | +| **`originalDoc`** | The Document before changes are applied. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### beforeChange Immediately following validation, `beforeChange` hooks will run within the `update` operation. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved. @@ -70,6 +92,16 @@ const beforeChangeHook: GlobalBeforeChangeHook = async ({ } ``` +The following arguments are passed to the `beforeChange` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`data`** | The incoming data passed through the operation. | +| **`originalDoc`** | The Document before changes are applied. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### afterChange After a global is updated, the `afterChange` hook runs. Use this hook to purge caches of your applications, sync site data to CRMs, and more. @@ -86,6 +118,16 @@ const afterChangeHook: GlobalAfterChangeHook = async ({ } ``` +The following arguments are passed to the `afterChange` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`doc`** | The resulting Document after changes are applied. | +| **`previousDoc`** | The Document before changes were applied. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### beforeRead Runs before `findOne` global operation is transformed for output by `afterRead`. This hook fires before hidden fields are removed and before localized fields are flattened into the requested locale. Using this Hook will provide you with all locales and all hidden fields via the `doc` argument. @@ -99,6 +141,15 @@ const beforeReadHook: GlobalBeforeReadHook = async ({ }) => {...} ``` +The following arguments are passed to the `beforeRead` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`doc`** | The resulting Document after changes are applied. | +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ### afterRead Runs as the last step before a global is returned. Flattens locales, hides protected fields, and removes fields that users do not have access to. @@ -113,6 +164,17 @@ const afterReadHook: GlobalAfterReadHook = async ({ }) => {...} ``` +The following arguments are passed to the `beforeRead` hook: + +| Option | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. | +| **`context`** | Custom context passed between hooks. [More details](./context). | +| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many (useful in versions). | +| **`doc`** | The resulting Document after changes are applied. | +| **`query`** | The [Query](../queries/overview) of the request. +| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. | + ## TypeScript Payload exports a type for each Global hook which can be accessed as follows: diff --git a/docs/hooks/overview.mdx b/docs/hooks/overview.mdx index 5caf28a68..c2422388c 100644 --- a/docs/hooks/overview.mdx +++ b/docs/hooks/overview.mdx @@ -6,42 +6,35 @@ desc: Hooks allow you to add your own logic to Payload, including integrating wi keywords: hooks, overview, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- - - Hooks are powerful ways to tie into existing Payload actions in order to add your own logic like - integrating with third-party APIs, adding auto-generated data, or modifing Payload's base - functionality. - +Hooks allow you to execute your own logic during specific events of the Document lifecycle. With Hooks, you can transform Payload from a traditional CMS into a fully-fledged application framework. They allow you to perform business tasks, third-party integrations, etc. during precise moments within the data lifecycle. -**With Hooks, you can transform Payload from a traditional CMS into a fully-fledged application framework.** +There are many use cases for Hooks, including: -Example uses: - -- Integrate user profiles with a third-party CRM such as Salesforce or Hubspot +- Modify data before it is read or updated +- Encrypt and decrypt sensitive data +- Integrate with a third-party CRM like HubSpot or Salesforce - Send a copy of uploaded files to Amazon S3 or similar -- Automatically add `lastModifiedBy` data to a document to track who changed what over time -- Encrypt a field's data when it's saved and decrypt it when it's read -- Send emails when `ContactSubmission`s are created from a public website -- Integrate with a payment provider like Stripe to automatically process payments when an `Order` is created -- Securely recalculate order prices on the backend to ensure that the total price for `Order`s that users submit is accurate and valid -- Generate and store a `lastLoggedIn` date on a user by adding an `afterLogin` hook -- Add extra data to documents before they are read such as "average scores" or similar data that needs to be calculated on the fly +- Process orders through a payment provider like Stripe +- Send emails when contact forms are submitted +- Track data ownership or changes over time -There are many more use cases for Hooks and the sky is the limit. +There are three main types of Hooks in Payload: + +- [Collection Hooks](/docs/hooks/collections) +- [Global Hooks](/docs/hooks/globals) +- [Field Hooks](/docs/hooks/fields) + + + Reminder: + Payload also ships a set of _React_ hooks that you can use in your frontend application. Although they share a common name, these are very different things and should not be confused. [More details](../admin.hooks). + ## Async vs. synchronous -All hooks can be written as either synchronous or asynchronous functions. If the Hook should modify data before a document is updated or created, and it relies on asynchronous actions such as fetching data from a third party, it might make sense to define your Hook as an asynchronous function, so you can be sure that your Hook completes before the operation's lifecycle continues. Async hooks are run in series - so if you have two async hooks defined, the second hook will wait for the first to complete before it starts. +All Hooks can be written as either synchronous or asynchronous functions. If the Hook should modify data before a document is updated or created, and it relies on asynchronous actions such as fetching data from a third party, it might make sense to define your Hook as an asynchronous function. This way you can be sure that your Hook completes before the operation's lifecycle continues. Async hooks are run in series - so if you have two async hooks defined, the second hook will wait for the first to complete before it starts. If your Hook simply performs a side-effect, such as updating a CRM, it might be okay to define it synchronously, so the Payload operation does not have to wait for your hook to complete. ## Server-only execution -Payload Hooks are only triggered on the server and are automatically excluded from the Payload Admin bundle. - -## Hook Types - -You can specify hooks in the following contexts: - -- [Collection Hooks](/docs/hooks/collections) -- [Field Hooks](/docs/hooks/fields) -- [Global Hooks](/docs/hooks/globals) +Hooks are only triggered on the server and are automatically excluded from the client-side bundle. This means that you can safely use sensitive business logic in your Hooks without worrying about exposing it to the client. diff --git a/docs/live-preview/client.mdx b/docs/live-preview/client.mdx index e4116281c..4142d2a81 100644 --- a/docs/live-preview/client.mdx +++ b/docs/live-preview/client.mdx @@ -246,7 +246,7 @@ For a working demonstration of this, check out the official [Live Preview Exampl ## Troubleshooting -### Relationships and/or uploads are not populating +#### Relationships and/or uploads are not populating If you are using relationships or uploads in your front-end application, and your front-end application runs on a different domain than your Payload server, you may need to configure [CORS](../configuration/overview) to allow requests to be made between the two domains. This includes sites that are running on a different port or subdomain. Similarly, if you are protecting resources behind user authentication, you may also need to configure [CSRF](../authentication/overview#csrf-protection) to allow cookies to be sent between the two domains. For example: @@ -271,7 +271,7 @@ If you are using relationships or uploads in your front-end application, and you } ``` -### Relationships and/or uploads disappear after editing a document +#### Relationships and/or uploads disappear after editing a document It is possible that either you are setting an improper [`depth`](../queries/depth) in your initial request and/or your `useLivePreview` hook, or they're mismatched. Ensure that the `depth` parameter is set to the correct value, and that it matches exactly in both places. For example: @@ -297,7 +297,7 @@ const { data } = useLivePreview({ }) ``` -### Iframe refuses to connect +#### Iframe refuses to connect If your front-end application has set a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) that blocks the Admin Panel from loading your front-end application, the iframe will not be able to load your site. To resolve this, you can whitelist the Admin Panel's domain in your CSP by setting the `frame-ancestors` directive: diff --git a/docs/live-preview/overview.mdx b/docs/live-preview/overview.mdx index 06cfb40f1..49d746d9e 100644 --- a/docs/live-preview/overview.mdx +++ b/docs/live-preview/overview.mdx @@ -10,12 +10,6 @@ With Live Preview you can render your front-end application directly within the Live Preview works by rendering an iframe on the page that loads your front-end application. The Admin Panel communicates with your app through [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) events. These events are emitted every time a change is made to the document. Your app then listens for these events and re-renders itself with the data it receives. -{/* IMAGE OF LIVE PREVIEW HERE */} - -## Setup - -Setting up Live Preview is easy. This can be done either globally through the [Root Config](../configuration/overview), or on individual [Collection](../configuration/collections) and [Global](../configuration/globals) configs. Once configured, a new "Live Preview" tab will appear at the top of enabled documents. Navigating to this tab opens the preview window and loads your front-end application. - To add Live Preview, use the `admin.livePreview` property in your [Payload Config](../configuration/overview): ```ts @@ -27,14 +21,20 @@ const config = buildConfig({ // ... // highlight-start livePreview: { - url: 'http://localhost:3000', // The URL to your front-end, this can also be a function (see below) - collections: ['pages'], // The Collection(s) to enable Live Preview on (Globals are also possible) + url: 'http://localhost:3000', + collections: ['pages'] }, // highlight-end } }) ``` +{/* IMAGE OF LIVE PREVIEW HERE */} + +## Options + +Setting up Live Preview is easy. This can be done either globally through the [Root Config](../configuration/overview), or on individual [Collection](../configuration/collections) and [Global](../configuration/globals) configs. Once configured, a new "Live Preview" tab will appear at the top of enabled documents. Navigating to this tab opens the preview window and loads your front-end application. + The following options are available: | Path | Description | diff --git a/docs/live-preview/server.mdx b/docs/live-preview/server.mdx index ba5599533..bbe5dcf46 100644 --- a/docs/live-preview/server.mdx +++ b/docs/live-preview/server.mdx @@ -164,7 +164,7 @@ For a working demonstration of this, check out the official [Live Preview Exampl ## Troubleshooting -### Updates do not appear as fast as client-side Live Preview +#### Updates do not appear as fast as client-side Live Preview If you are noticing that updates feel less snappy than client-side Live Preview (i.e. the `useLivePreview` hook), this is because of how the two differ in how they work—instead of emitting events against _form state_, server-side Live Preview refreshes the route after a new document is _saved_. @@ -183,7 +183,7 @@ Use [Autosave](../versions/autosave) to mimic this effect server-side. Try decre } ``` -### Iframe refuses to connect +#### Iframe refuses to connect If your front-end application has set a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) that blocks the Admin Panel from loading your front-end application, the iframe will not be able to load your site. To resolve this, you can whitelist the Admin Panel's domain in your CSP by setting the `frame-ancestors` directive: diff --git a/docs/queries/depth.mdx b/docs/queries/depth.mdx index d665806e6..8e425dfbd 100644 --- a/docs/queries/depth.mdx +++ b/docs/queries/depth.mdx @@ -6,11 +6,11 @@ desc: Payload depth determines how many levels down related documents should be keywords: query, documents, pagination, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- -Documents in Payload can have relationships to other Documents. This is true for both [Collections](../configuration/collections) as well as [Globals](../configuration/globals). When you query a Document, you can specify the depth at which to populate any of its related Documents as full objects, or simply return their Document IDs. +Documents in Payload can have relationships to other Documents. This is true for both [Collections](../configuration/collections) as well as [Globals](../configuration/globals). When you query a Document, you can specify the depth at which to populate any of its related Documents either as full objects, or only their IDs. -Depth will optimize the performance of your application by limiting the number of queries made to the database, and significantly reducing the amount of data returned. Since Documents can be infinitely nested or recursively related, it's important to be able to control how deep your API populate. +Depth will optimize the performance of your application by limiting the amount of processing made in the database and significantly reducing the amount of data returned. Since Documents can be infinitely nested or recursively related, it's important to be able to control how deep your API populates. -When you specify a `depth` of `0`, for example, the API response might look like this: +For example, when you specify a `depth` of `0`, the API response might look like this: ```json { @@ -64,7 +64,7 @@ To specify depth in the [REST API](../rest-api/overview), you can use the `depth ```ts fetch('https://localhost:3000/api/posts?depth=2') // highlight-line - .then((response) => response.json()) + .then((res) => res.json()) .then((data) => console.log(data)) ``` @@ -75,7 +75,7 @@ fetch('https://localhost:3000/api/posts?depth=2') // highlight-line ## Max Depth -Fields like the [Relationship Field](../fields/relationship) or the [Upload Field](../fields/upload) can also set a maximum depth. If exceeded, this will limit the population depth regardless of the depth of the request. +Fields like the [Relationship Field](../fields/relationship) or the [Upload Field](../fields/upload) can also set a maximum depth. If exceeded, this will limit the population depth regardless of what the depth might be on the request. To set a max depth for a field, use the `maxDepth` property in your field configuration: diff --git a/docs/queries/sort.mdx b/docs/queries/sort.mdx index 7db1f6183..3340d21f9 100644 --- a/docs/queries/sort.mdx +++ b/docs/queries/sort.mdx @@ -6,13 +6,13 @@ desc: Payload sort allows you to order your documents by a field in ascending or keywords: query, documents, pagination, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- -Payload `find` queries support a `sort` parameter through all APIs. Pass the `name` of a top-level field to sort by that field in ascending order. Prefix the name of the field with a minus symbol ("-") to sort in descending order. +Documents in Payload can be easily sorted by a specific [Field](../fields/overview). When querying Documents, you can pass the name of any top-level field, and the response will sort the Documents by that field in _ascending_ order. If prefixed with a minus symbol ("-"), they will be sorted in _descending_ order. -Because sorting is handled by the database, the field you wish to sort on must be stored in the database to work; not a [virtual field](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges). It is recommended to enable indexing for the fields where sorting is used. +Because sorting is handled by the database, the field cannot be a [Virtual Field](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges). It must be stored in the database to be searchable. Tip: - A minus symbol ("-") before the field name will sort in descending order. Omitting the minus symbol will sort in ascending order. + For performance reasons, it is recommended to enable `index: true` for the fields that will be sorted upon. [More details](../fields/overview). ## Local API