payloadcms / payloadcms/payload

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

Open
#15,959 0 comments 0 reactions 1 assignee View on GitHub

@GermanJablo is already working on this.

Since Mar 16, 2026.

area: core
Dominant language
TypeScript
Stars
44.8k
Forks
4.2k
Avg merge
2d 21h
Merged PRs (30d)
53

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.