🚧 Under development — suggestions for new items are always welcome! ✨

Virtual Field

A tiny helper that lets you define read‑only, computed properties inside any Payload collection without rewriting the same boilerplate hooks over and over.

Install

pnpm dlx [email protected] add https://p.livog.com/r/virtual-field.json

Code

import type { Field, FieldHook, TextField } from 'payload'

type HookArgs = Parameters<FieldHook>[0]
type Setter = (args: HookArgs) => unknown

export function virtualField(name: string, setter: Setter, overrides: Partial<TextField> = {}): Field {
  const hookOverrides = overrides.hooks ?? {}
  const adminOverrides = overrides.admin ?? {}

  const afterRead = [
    (args: HookArgs) => {
      args.siblingData[name] = setter(args)
    },
    ...(hookOverrides.afterRead ?? [])
  ]

  const beforeChange = [
    (args: HookArgs) => {
      args.siblingData[name] = undefined
    },
    ...(hookOverrides.beforeChange ?? [])
  ]

  return {
    name,
    type: 'text',
    virtual: true,
    admin: { readOnly: true, ...adminOverrides },
    hooks: { afterRead, beforeChange, ...hookOverrides },
    ...overrides
  } as Field
}

Usage

import { virtualField } from '@/payload/fields/virtual'

export const User: CollectionConfig = {
  slug: 'users',
  fields: [
    // Combines first and last name into a single virtual value
    virtualField(
      'name',
      ({ siblingData }) => `${siblingData.firstName} ${siblingData.lastName}`
    ),
    { name: 'firstName', type: 'text', required: true },
    { name: 'lastName', type: 'text', required: true }
    // …other real fields
  ]
}

When to use

When you need a computed value that should be returned in the API or Admin UI, but should never be persisted to the database.