In this guide, we’re going to go over how to use the root and global hooks in Payload CMS. Let’s dive in.
Hooks are used to execute side effects from events performed in your Payload CMS admin panel. This allows you to do things within the platform like populate fields with data from other fields, integrate with third-parties, or perform other types of business logic.
Hooks are what make PayloadCMS truly powerful and flexible because there’s no limit to what you can pull off with these. With the right API keys and app IDs, you can integrate things like HubSpot, Mailchimp, and Salesforce with fields in your PayloadCMS instance. You can build readOnly fields using data from within your collection and even in relationships to that collection. You can even trigger revalidation for your collections and globals and accept payments using hooks.
Hooks are executed on the server only. This means you can include sensitive data in your hooks without fear of them being exposed to the client. You will, of course, still need to worry about exposing credentials if you publish your project to a git provider, but you should handle that type of data with environment variables anyway.
You also have the option to run any hook asynchronously or synchronously. Your use case will determine if you want to use async or not. If you need to fetch data from a third-party, you may want to set the hook as async so you can ensure the hook completes before the next operation begins. Async hooks run in a series, so the next hook will always wait for the previous hook to complete before running.
If you don’t need to fetch data and perhaps just need to update a CRM or other third party, you may be okay excluding async from your hook’s function. When a hook is not set to async, the payload operation will move ahead and not wait for the hook to complete before the next operation takes place.
You define each of them by creating a hooks option and assigning an empty object to it. It’s best practice to include a hooks directory at the same level you plan on applying the hook and creating a separate TypeScript file to then be exported and imported into the collection, global, or field you’d like to use the hook.
There are 4 types of hooks available to you in PayloadCMS. We’ll talk about 2 in this guide: root and global hooks. We’ll talk about field and collection Hooks in the next guide so this one doesn’t get too long.
Root hooks
Root hooks are hooks that aren’t associated with a specific Collection, Global, or Field. You assign a root hook to the payload config file, and the only hook available here is afterError, which allows you to create a side effect if there is any kind of error at the application level. Since there’s only one hook available to us in the payload config, it doesn’t make sense to create a new file just for this hook.
The afterError hook can take several arguments: error, context, graphqlResult, req, collection, and result. You can use the error argument to log the error in the console for troubleshooting or use try-catch statements to create different types of behavior when a specific error occurs. You can use req to access the currently authenticated user and a Local API instance of Payload in order to query any of your collections.
Global hooks
Global hooks run on Global documents only. You can use these hooks to execute logic during specific operations and events that occur within the GlobalConfig. Just like root hooks, you can add hooks to a collection by adding the hooks option and assigning an empty object to it.
All global hooks accept an array of asynchronous or synchronous functions, so you can have more than one function per operation. Each global hook has their own arguments based on the type of hook.
To make a global hook, we’ll create a hooks directory in the same folder of the global we want to create a hook for. In our case, we can create that hooks directory in our Header folder. Let’s then create a new TypeScript file called index, then let’s start with the beforeValidate hook.
beforeValidate
You’ll want to import the type {GlobalBeforeValidateHook} from ‘payload’ at the top of your hooks/index.ts. beforeValidate runs before the update operation of your global. This allows you to add or format data before the incoming data is validated as required or in the proper format.
You have a choice to use 5 arguments in beforeValidate: global, context, data, originalDoc, and req. Global returns the global the hook is running against. In our case, that would return the Header global. Data is the incoming data passed through the operation. This argument lets you access the fields available to you in your global document, which are all options you created. originalDoc allows you to access the document before your changes are applied. This allows you to check for changes in your hook and do some kind of custom logic depending on the result. Lastly, the req argument allows you to use the Local API in your hook. You can use this to query your other collections and return data based on what you receive.
You can also include custom context, which we won’t cover here.
You can then export a new const we’ll call beforeValidateHook and assign the GlobalBeforeValidateHook type to it. Then set it equal to an async arrow function with arguments you’d like to include and then open the curly braces and return whatever logic you’d like to return.
For the sake of this example, we’re just going to log the arguments so we can see what we’re working with. After we set this up, we can now import it into our Global in the hooks.beforeValidate option.
Your support helps me create more high-quality tutorials and keep them free for everyone. Thank you!
The next hook is beforeChange. This hook runs during the update operation, after your submission is validated. Since your form data is validated, you can be confident that your data is valid document data and will be saved in the new document. You’re also able to intercept the data being saved in this stage to change the shape of the data before it’s saved – as long as the data is valid.
To make this hook, we’ll use the same config file and import type {GlobalBeforeChangeHook} from ‘payload’. You’ll then need to export a new const that we’ll call beforeChangeHook and assign the GlobalBeforeChangeHook type to it. Set this equal to an async arrow function and open the curly braces.
The beforeChange hook takes all the same arguments as the beforeValidate hook, so we’ll add all but the context argument to the function and log the arguments so we’re able to see what we’re working with. If you skipped to this part of the guide and want to learn more about the global, data, originalDoc, and req arguments, you can go to the timestamps in the description and go to the beforeValidate section.
After we set this up, we can now import it into our Global in the hooks.beforeChange option.
afterChange
The afterChange hook runs after your global is updated. So here, you know the file has been validated and updated, and now you can use the afterChange hook to clear caches or sync site data to your CRM. This is where you would perform on-demand revalidation for your global document. We’ll come back to that in another guide soon.
The afterChange hook allows the global, context, and req arguments just like the beforeValidate and beforeChange hooks. There are two new arguments, though: doc and previousDoc. The doc argument returns the resulting document after your changes have been applied. You can use this argument to send a request to your CRM to update a contact’s record, for example. The other argument, previousDoc, can be used to show the document before any changes were applied. You can use this argument to compare to the updated doc and perform logic before returning anything.
In our same config file, we’ll import type {GlobalAfterChangeHook} from ‘payload’. Then we’ll export a new const called afterChangeHook and assign the new type to it. We’ll set that equal to an async arrow function with our doc and previousDoc arguments, and then log them just so we can see what we’re working with. After we set this up, we can now import it into our Global in the hooks.afterChange option.
beforeRead
The beforeRead hook fires before all hidden fields are removed or localized fields are localized. You can use this hook’s doc argument to work with the locales and hidden fields before you lose access to them on the frontend.
There are no new arguments here, only the global, context, doc, and req arguments. So we’ll jump right to creating this one. You can use this hook by importing type {GlobalBeforeReadHook} from ‘payload,’ exporting a new const called beforeReadHook assigned to the GlobalBeforeReadHook, and setting that equal to an async arrow function with the doc and req arguments. We’ll just log the doc argument for now.
After we set this up, we can now import it into our Global in the hooks.beforeRead option.
afterRead
The last global hook is afterRead. This is the last step before the global is returned and what you see on the admin dashboard is rendered. All locales are flattened, fields are hidden, and restricted fields are removed from unauthorized users.
Global, content, doc, and req are available to you in afterRead, as well as 2 new options: findMany and query. findMany allows you to check if this hook is running against finding one document or many, which helps when you have versioning turned on. Query returns the query of the request, which we can see when we log the result in our hook.
To use this hook, import type {GlobalAfterReadHook} from ‘payload’, export a new const called afterReadHook and assign the GlobalAfterReadHook to it, and set it equal to an async arrow function with the findMany and query arguments. In the curly braces, log these arguments to see what they look like for you.
After we set this up, we can now import it into our Global in the hooks.afterRead option.
Final Thoughts
Hooks are probably the deepest yet richest concept in Payload. As you’ve seen, they are extremely powerful and flexible and allow you to extend the functionality of your application as far as your imagination will allow you to go. We’ve just scratched the surface of hooks in this guide, and we’ll cover the field and collection hooks in the next guide, but the real power comes in applying these hooks.