At a high level, you can create virtual fields by using
afterRead field hooks to attach and remove data from your document. The field itself can be either visible or hidden in the Admin UI.
When you need to access or manipulate data that doesn't need to be stored in your database, virtual fields are the way to go. This is an incredibly useful feature that has countless possibilities. Here are a few common use cases:
A full name field dynamically joining the user's first and last name
A dynamic title field that takes the values of several text fields and combines them
Users count field which runs a fetch request on the users collection and returns the total number of users
Calculations where the field reads values from other number fields, computes the calculation and returns the output
Reference field which fetches and returns data from another document
To understand how virtual fields can improve CMS usability, imagine you are working with nested pages, you have two parent pages—let's say Sales and Technical. Both of these pages have a child Contact page.
At a glance, both child pages are just labeled as "Contact". To determine which is which, you need to look at the parent page it's associated with.
This is where virtual fields can help. You can generate a
fullTitle field that takes the page title with the parent title and return it as a combined string i.e.
contact - sales and then set your
useAsTitle property to this field. Boom, across the CMS and in your frontend, this page will display the new dynamic title.
Of course, you can achieve this with any regular text field, but since the data it stores is duplicative, it doesn't make as much sense to store the data. Just compute it virtually.
To follow along and see virtual fields in action, let's boot up a quick virtual fields demo.
In the Location collection, we have three standard text fields defined: City, State and Country.
For every document in a collection, the admin UI will display the
id as its title by default, which is shown at the top of the document view, in the collection list view and in relationship fields.
We can set
useAsTitle on the Collection to assign our own field as the title, but what field should we use? State or Country will be the same in many cases, and if we use the City field we can still run into cases where this City exists in another Country i.e. London, Canada and London, UK.
We can solve this scenario with a virtual field to read the values of City, State and Country and return them as one combined string. And since this data is duplicative, it really doesn't need to get stored in the database.
Here are our three standard text fields:
Now let's take a look at the virtual location field itself
The essential properties here are
afterRead . Setting
admin.hidden: true is optional but considered best practice. Being that an admin can't really edit this field, the only reason to show it in the admin UI is if it's relevant to your editors. In this case, they are already seeing the City, State, and Country fields, so no need to add additional clutter.
afterRead hook is responsible for retrieving and combining the data that will be returned as the field's value. This hook runs as the last step before a document gets returned from Payload.
Below, we've written a quick
formatLocation hook that uses the
data arg to access the values of City, State and Country, returning them in one combined string.
FieldHook type can be imported directly from Payload.
update access controls to false denies the ability to set or update a field's value and will discard any values passed to the field during these operations.
Some virtual fields can function without restricting permissions however it is best practice to prevent unintended changes to the field.
As mentioned above, you might want to hide the field from the admin UI if it's duplicative or unnecessary. That's as easy as setting
true. The functionality will remain unaffected but the field itself will not be visible in the admin panel.
To test out the virtual field, create a document in the Location collection and enter a City, State and Country. When you access the response directly in the browser at
/api/locations/[id] we can see the location field has formatted these fields into a single string.
In MongoDB, after navigating to the database for this repo, you will see the data shape is unchanged and the location field is not present keeping the data shape clean and easier to work with.
All hooks can be written as either synchronous or asynchronous functions. Using async and await will suspend any action until the promise is fulfilled. This is super valuable if your virtual field relies on asynchronous actions such as fetching data.
Let’s take a look at the virtual field
staff which uses the async
In this demo, the Staff collection has a relationship to Locations.
getLocationStaff hook, we are using
payload.find to fetch all documents in the staff collection where the location is equal to the current location. By using async and await, this function will not return a value until the
payload.find request has run. This virtual field then functions as an inverse relationship field to the staff collection.
Virtual fields unlock an extra level of control across your data and CMS, allowing you to perform and streamline complex logic while keeping your code clean and simple. With this article, hopefully you have gained a basic understanding of virtual fields along with the ability to setup and configure own.
If you have any questions or feedback, reach out on GitHub or Discord.
For more information about topics we touched on, checkout the following:
Getting started with Payload is easy—and free forever. Just fire up a new terminal window and run the following command:
We're trying to change the CMS status quo by delivering editors with a great experience, but first and foremost, giving developers a CMS that they don't absolutely despise working with. All of our new features are meant to be extensible and work simply and sanely.