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.