Describe the Bug
Virtual fields (virtual: true or virtual: "path") render as normal editable inputs in the admin panel. Users can type into them, but the input is never persisted — it's silently discarded on save and overwritten on the next read (by hooks, built-in virtual population, or plugin logic).
This is confusing because nothing in the UI indicates the field is virtual or that edits won't be saved.
Reproduction Steps
- Add a virtual field to a collection:
{
name: 'computedTitle',
type: 'text',
virtual: true,
hooks: {
afterRead: [({ siblingData }) => `Generated: ${siblingData.title}`],
},
}
- Open the admin panel, edit a document in that collection
- The
computedTitle field is a normal editable text input
- Type something into it and save
- On reload, the user's input is gone — replaced by the hook value
Expected behavior
Virtual fields should default to admin.readOnly: true since they are not user-editable. Users who explicitly set admin.readOnly: false could override this if they have a reason to (e.g. custom component that handles the input differently).
Root cause
In packages/payload/src/fields/config/sanitize.ts and packages/ui/src/forms/RenderFields/index.tsx, there is no logic that checks virtual when determining readOnly. Virtual fields are treated identically to regular persisted fields.
Workaround
{
name: 'computedTitle',
type: 'text',
virtual: true,
admin: { readOnly: true },
// ...
}
Affected areas
Describe the Bug
Virtual fields (
virtual: trueorvirtual: "path") render as normal editable inputs in the admin panel. Users can type into them, but the input is never persisted — it's silently discarded on save and overwritten on the next read (by hooks, built-in virtual population, or plugin logic).This is confusing because nothing in the UI indicates the field is virtual or that edits won't be saved.
Reproduction Steps
computedTitlefield is a normal editable text inputExpected behavior
Virtual fields should default to
admin.readOnly: truesince they are not user-editable. Users who explicitly setadmin.readOnly: falsecould override this if they have a reason to (e.g. custom component that handles the input differently).Root cause
In
packages/payload/src/fields/config/sanitize.tsandpackages/ui/src/forms/RenderFields/index.tsx, there is no logic that checksvirtualwhen determiningreadOnly. Virtual fields are treated identically to regular persisted fields.Workaround
Affected areas