Skip to content

Virtual field fails to resolve when referencing a related field populated via afterRead hook #15959

@max-atkins

Description

@max-atkins

Describe the Bug

A virtual field using dot-notation (e.g., virtual: "relation.field") fails to populate with data if the target field in the related collection is itself a virtual/hook-populated field generated during afterRead.

In this scenario, the parent collection's virtual field returns undefined or an empty string, likely because the virtual field resolution occurs before the afterRead hooks of the populated relationship have finished executing.

Expected Behaviour
The virtual field authorName should correctly display the value generated by the afterRead hook of the related author document.

Actual Behaviour
The field remains empty. If the fullName field in the Users collection is changed to a standard stored text field (no hooks), the virtual field works as expected.

Link to the code that reproduces this issue

https://github.com/max-atkins/payload-virtual-field-blank

Reproduction Steps

  1. Create a Users collection with a fullName field that uses an afterRead hook to combine firstName and lastName.
  2. Create a Posts collection with a relationship field to Users.
  3. Add a virtual field to Posts named authorName with virtual: "author.fullName".
  4. Create a User, then create a Post linked to that User.
  5. Observe that authorName in the Admin UI or API response is empty/null, despite author.fullName being visible when inspecting the expanded relationship.
{
  slug: "users",
  fields: [
    { name: "firstName", type: "text", required: true },
    { name: "lastName", type: "text", required: true },
    {
      name: "fullName",
      type: "text",
      admin: { readOnly: true },
      hooks: {
        afterRead: [({ data }) => `${data.firstName} ${data.lastName}`], // Simplified for example
      },
    },
  ],
}
{
  slug: "posts",
  fields: [
    {
      name: "author",
      type: "relationship",
      relationTo: "users",
    },
    {
      name: "authorName",
      type: "text",
      virtual: "author.fullName",
      admin: { readOnly: true },
    }
  ]
}

Which area(s) are affected?

area: core

Environment Info

Binaries:
  Node: 24.11.0
  npm: 11.6.1
  Yarn: 1.22.22
  pnpm: 10.28.0
Relevant Packages:
  payload: 3.79.0
  next: 16.1.6
  @payloadcms/db-postgres: 3.79.0
  @payloadcms/drizzle: 3.79.0
  @payloadcms/graphql: 3.79.0
  @payloadcms/live-preview: 3.79.0
  @payloadcms/live-preview-react: 3.79.0
  @payloadcms/next/utilities: 3.79.0
  @payloadcms/plugin-cloud-storage: 3.79.0
  @payloadcms/plugin-multi-tenant: 3.79.0
  @payloadcms/richtext-lexical: 3.79.0
  @payloadcms/storage-s3: 3.79.0
  @payloadcms/translations: 3.79.0
  @payloadcms/ui/shared: 3.79.0
  react: 19.2.3
  react-dom: 19.2.3
Operating System:
  Platform: linux
  Arch: x64
  Version: #14~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Jan 15 15:52:10 UTC 2
  Available memory (MB): 31776
  Available CPU cores: 32

Metadata

Metadata

Assignees

Labels

area: coreCore Payload functionality

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions