The Power of Virtual Fields in Payload CMS

Payload CMS

Written by Jens Becker

Last updated on June 21, 2025

Grafic titled 'The Power of Virtual Fields in Payload CMS', with a dark background and diagrams illustrating code functions.

When working with Payload CMS , sometimes you want to expose values in your API that are not manually entered by a user, nor stored persistently in your database. Instead, these values are computed on the fly, derived from other data. This is where virtual fields truly shine.

In this article, we'll cover what virtual fields are, how and when to use them, and provide practical real-world examples. Let's dive in.

What Are Virtual Fields?

Virtual fields in Payload CMS are dynamically computed fields that are not persisted in the database. Instead, they are populated during the document read process using the afterRead hook . This hook runs after the document is read from the database but before it's returned to the caller, making it the perfect place to inject derived or calculated data.

Because virtual fields don’t exist in the database, they are read-only, not queryable, cannot be used for sorting, and are not eligible for use as useAsTitle.

How to make a field virtual in Payload CMS?

Since Payload v3.0.0 , you can define a virtual field by simply adding the virtual: true property to the field configuration. This tells Payload not to store the field in the database. Its value can then be populated dynamically, typically using an afterRead hook.

How to Set the Value of Virtual Fields

There are two common ways to populate virtual fields in Payload CMS, depending on your use case:

Via a Field Hook

This is the most common and straightforward approach, ideal when you're setting the value of a single virtual field. The logic stays encapsulated in the afterRead hook within the field definition.

{
  name: 'virtual-field',
  type: 'text',
  virtual: true,
  hooks: {
    afterRead: [
      ({ siblingData, req }) {
        // Compute the virtual field data. 
        // Here you have access to the sibling field data, Payload request and more.
      },
    ],
  },
}

A simple virtual text field whose value is dynamically computed after the document data has been read from the database.

Via a Collection Hook

This method is useful when you need to set multiple virtual fields that depend on shared logic or related data. For example, if you have a slug and parent field stored on the document, and want to derive a virtual path and alternatePaths field, it makes sense to use a beforeRead or afterRead Collection Hook and compute them together.

⚠️ Note: In collection-level hooks like afterRead, you should first check if a given virtual field was requested (selected). This ensures that you're not computing unnecessary values when the field isn’t needed.

Use Cases

Virtual fields are especially useful when you want to augment your document with values that can be dynamically calculated or derived, without storing them in your database. Below are some of the most common and practical scenarios:

Combining Data from Non-Virtual Fields

The most simple and common use case is combining existing fields within the same document.

Example: Combine First and Last Name

Here’s a simple example that generates a full name based on two existing fields: firstName and lastName.

{
  name: 'fullName',
  type: 'text',
  virtual: true,
  hooks: {
    afterRead: [
      ({ siblingData }) => {
        return `${siblingData.firstName} ${siblingData.lastName}`;
      },
    ],
  },
}

This is useful in many real-world scenarios, such as user profiles, author displays, or contact lists, where it’s beneficial to have the full name computed automatically.

Calculations Based on Existing Fields

Another common pattern is performing simple calculations from field values from a sibling field data.

Example: Estimated Reading Time for Blog Posts

Suppose you're building a blog and want to show readers how long it will take to read each post. Instead of manually entering this value, you can calculate it from the post's content using a virtual field.

{
  name: 'estimatedReadingTime',
  type: 'number',
  virtual: true,
  hooks: {
    afterRead: [
      ({ siblingData }) => {
        const contentString = siblingData.content
          ? convertLexicalToPlaintext({ data: siblingData.content })
          : '';

        const wordCount = contentString.trim().split(/\s+/).length;
        const wordsPerMinute = 238;
        const readingTime = Math.ceil(wordCount / wordsPerMinute);

        return readingTime;
      },
    ],
  },
}
💡 In this example, convertLexicalToPlaintext is a utility function offered by Payload that converts your rich text or lexical editor content into plain text for analysis.

Merging or Injecting Internal or External Data (Async Virtual Fields)

More advanced use cases include asynchronously enriching documents with data from other collections or external APIs.

{
  name: 'weatherData',
  type: 'json',
  virtual: true,
  hooks: {
    afterRead: [
      async ({ siblingData }) => {
        const res = await fetch(`https://weatherapi.com/forecast?city=${siblingData.city}`)
        const weather = await res.json()
        return weather
      },
    ],
  },
}

Ideal for use cases like pulling the latest exchange rates, showing real-time weather conditions based on a location field, or displaying related data from another collection, essentially acting as lightweight foreign data wrappers for enriching documents at read time.

Good to Know When Using Virtual Fields

  • virtual: true alone doesn’t make a field read-only, use admin.readOnly: true to prevent users from entering unsaved values in the admin panel.
  • Virtual fields that rely solely on an afterRead hook to populate data based on another field won’t display a value when creating a new document and won’t update live in the admin panel when the source field changes. Use a custom component to enable real-time behavior.
  • If marked required, virtual fields can fail when the afterRead hook hasn't run yet, disable validation with validate: () => true.

Why Use Virtual Fields?

Virtual fields offer an elegant way to extend your documents with dynamically generated fields based on other data. They’re ideal when you want to separate static data from dynamically derived values, keeping your database lean and focused on raw, user-managed content.

Key benefits include:

  • Always in Sync: Virtual fields are generated on the fly, meaning they always reflect the current state of the source data. No manual resyncing is required.
  • Great for Presentation-Only Values: Virtual fields are ideal for values used purely for display, like labels or summaries in your frontend. This keeps all the data in one place, your CMS.
  • Clear Separation of Concerns: They maintain a clear separation between static and computed content
  • Are perfect for read-only derived data: Useful for exposing values like full names, reading time, or computed slugs that don’t require user input or database storage.

When Not to Use Virtual Fields

While powerful, virtual fields are not always the best choice. Avoid them when:

  • You Need to Query the Field: Virtual fields are not stored in the database and therefore cannot be filtered or sorted in queries.
  • The Data Should Persist Over Time: If a value should be preserved historically, virtual fields are not suitable, they will always recalculate.
  • You’re Performing Complex or Async Logic: Heavy computations or async fetches (e.g. external API calls) can slow down read performance, especially in bulk reads like list views or APIs returning many documents.

Alternatives to Virtual Fields

  • Stored Derived Fields with Sync Logic: You can store calculated values (like reading time) in the database and use hooks (beforeChange, afterChange) to update them when source fields change. This allows querying and sorting, but introduces syncing complexity.
  • Admin-Editable Fields: If a field needs to be adjusted manually or occasionally overridden, a regular stored field with optional auto-fill logic might be more appropriate.

Relationship Virtual Fields

Payload V3.35.0 introduced a powerful new capability to virtual fields: the ability to link a virtual field value directly to a specific property of a related document.

What are Relationship Virtual Fields

A relationship virtual field lets you pull in a specific field from a related document (defined via a relationship or upload field) and expose it as part of the parent document—without persisting that field’s value in the database.

The special thing is that this field  will always be populated with the corresponding value, even if the current depth is 0, Moreover, in contrast to non relationship virtual fields, they can also be queried and sorted by.

So to speak, virtual fields, pick or extract a field from a related document. They can even pick deeply nested fields like person.location.city

In the following example, each entry in the authors collection references a document from the persons collection via a person relationship field. A virtual name field is then used to dynamically mirror the title of the linked person:

{
  slug: 'authors',
  admin: {
    useAsTitle: 'name'
  },
  fields: [
    {
      type: 'relationship',
      name: 'person',
      relationTo: 'persons',
    },
    {
      type: 'text',
      name: 'name',
      virtual: 'person.title',
    },
  ],
},

Virtual name field on the authors collection which will always be set to the name of the person.

When to use Relationship Virtual Fields

Use relationship virtual fields when:

  • You want to expose a specific field from a related document (e.g. category.title) without duplicating data.
  • You want to query or sort documents based on a specific field from a related document.
  • You want to use a field from a related document as the title of the current document via useAsTitle.

⚠️ Note: The relationship field used for a virtual field must be a single, non-polymorphic relationship. Fields with hasMany: true or polymorphic relations are not supported.

Conclusion

Virtual fields are a powerful feature in Payload CMS that allow you to dynamically enrich your documents without bloating your schema or storing redundant data in the database. Whether you’re generating full names, reading times, or dynamic labels, they help keep your content model clean, efficient, and expressive.

Have a creative use case for virtual fields or questions about integrating them into your workflow? We’d love to hear from you! For feedback, suggestions, or anything else, feel free to reach out via email at info@jhb.software . Thanks for reading!