payloadcms / payloadcms/payload

richtext-lexical: nested beforeValidate hooks never run for fields inside Blocks, Link, and Upload features

Open
#17,950 0 comments 0 reactions 1 assignee View on GitHub

@paulpopus is already working on this.

Since Aug 26, 2026.

created-by: Payload team plugin: richtext-lexical
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

  1. Add a collection with a richText field using lexicalEditor with BlocksFeature (or LinkFeature / UploadFeature), where a nested field defines hooks.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] })],
      }),
    }
    
  2. Create or update a document with a block node populated in that field.

  3. Observe the nested field's beforeValidate hook never runs.

  4. Compare with the same field using hooks.beforeChange instead, 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

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.