payloadcms / payloadcms/payload
richtext-lexical: nested beforeValidate hooks never run for fields inside Blocks, Link, and Upload features
@paulpopus is already working on this.
Since Aug 26, 2026.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Describe the Bug
A hooks.beforeValidate function defined on a field nested inside a Lexical richtext field's Blocks, Link, or Upload feature never runs, for any operation (create, update, or the new on-demand validate).
This is caused by a stricter, second gate inside the RichText field's own beforeValidate hook:
https://github.com/payloadcms/payload/blob/main/packages/richtext-lexical/src/hooks.ts#L553-L555
if (!editorConfig.features.nodeHooks?.beforeValidate?.size) {
return value
}
This only checks nodeHooks.beforeValidate and ignores getSubFields. No built-in feature (Blocks, Link, Upload) ever registers a nodeHooks.beforeValidate entry — they only register getSubFields/getSubFieldsData, which is what's used to recurse into a block/link/upload's own field schema. So this condition is always true for editors using these features, and the hook returns before it reaches the loop that calls beforeValidateTraverseFields for the block/link/upload's sub-fields (hooks.ts:604-622), which is where a nested field's hooks.beforeValidate would actually run.
For comparison, the equivalent beforeChange hook gets this right — its gate (hooks.ts:307-312) checks both conditions:
if (
!editorConfig.features.nodeHooks?.beforeChange?.size &&
!editorConfig.features.getSubFields?.size
) {
return value
}
So beforeChange hooks on nested block/link/upload fields run correctly today; beforeValidate hooks on the same fields silently never run.
Suggested fix
Match the beforeChange gate's condition at hooks.ts:553:
if (
!editorConfig.features.nodeHooks?.beforeValidate?.size &&
!editorConfig.features.getSubFields?.size
) {
return value
}
Link to the code that reproduces this issue
Found via code review on main (packages/richtext-lexical/src/hooks.ts); reproducible directly in this repository, no external reproduction needed.
Reproduction Steps
-
Add a collection with a
richTextfield usinglexicalEditorwithBlocksFeature(orLinkFeature/UploadFeature), where a nested field defineshooks.beforeValidate:import { BlocksFeature, lexicalEditor } from '@payloadcms/richtext-lexical' const MyBlock = { slug: 'myBlock', fields: [ { name: 'value', type: 'text', hooks: { beforeValidate: [ ({ value }) => { console.log('this never logs') return value }, ], }, }, ], } const field = { name: 'content', type: 'richText', editor: lexicalEditor({ features: [BlocksFeature({ blocks: [MyBlock] })], }), } -
Create or update a document with a block node populated in that field.
-
Observe the nested field's
beforeValidatehook never runs. -
Compare with the same field using
hooks.beforeChangeinstead, which does run correctly.
Which area(s) are affected?
plugin: richtext-lexical
Environment Info
Found via code review while working on the on-demand validation feature (payload.validate()); not tied to a specific dependency version.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.