payloadcms / payloadcms/payload

plugin-nested-docs: child validation error during resaveChildren silently rolls back the parent update while responding with success (data loss + misleading 'Document modified' modal)

Open
#17,457 3 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
44.8k
Forks
4.2k
Avg merge
2d 21h
Merged PRs (30d)
53

Description

Describe the Bug

When updating a parent page in a collection that uses @payloadcms/plugin-nested-docs, the plugin's resaveChildren hook re-saves all child documents inside the same transaction. If one of the children fails validation during this re-save (in our case: a required upload field whose media document had been deleted in the meantime), the following happens:

  1. The plugin logs the error (Nested Docs plugin encountered an error while re-saving a child document.) but does not rethrow it to the caller.
  2. The API response for the parent update reports success, including the new updatedAt of the parent.
  3. The surrounding transaction is rolled back — the parent update never reaches the database.

Result: silent data loss. Every save of the parent appears to succeed but is discarded.

In the admin UI this manifests in a particularly confusing way: after the (seemingly successful) save, the stale-data check re-arms with the updatedAt from the success response, the next form-state roundtrip compares it against the (rolled-back, old) server value, and the editor gets the “Document modified” (documentModified) modal — suggesting another user changed the document. Reloading discards their edit; repeat forever. Editors cannot save the parent page at all and never see the actual validation error, which lives on a grandchild document.

Reproduction Steps
  1. Collection pages (drafts disabled, localized fields, postgres adapter) with plugin-nested-docs (breadcrumbs).
  2. Create parent page A with child page B. Give B a block containing a required upload field, populated with a media doc.
  3. Delete the referenced media document (media delete does not re-validate referencing docs, so B now has a dangling required upload).
  4. Update any field on parent A (REST or Local API).

Expected: either the update succeeds and persists, or it fails with a validation error surfaced to the caller.

Actual: the update responds 200 with a fresh updatedAt, the error is only logged server-side, the transaction is rolled back, and the database still contains the old state. In the admin UI, the editor gets the misleading “Document modified” modal after every save attempt.

Verified via Local API: payload.update() resolves successfully with a new updatedAt, while a raw SQL read of pages.updated_at immediately afterwards still returns the old value.

Stack trace (logged, not thrown)
ERROR: Nested Docs plugin encountered an error while re-saving a child document.
ValidationError: The following field is invalid: … > File
    at beforeChange (payload/src/fields/hooks/beforeChange/index.ts:77:11)
    at async updateDocument (payload/src/collections/operations/utilities/update.ts:278:28)
    at async updateByIDOperation (payload/src/collections/operations/updateByID.ts:203:18)
    at async <anonymous> (@payloadcms/plugin-nested-docs/src/hooks/resaveChildren.ts:72:25)
    at async updateDocument (payload/src/collections/operations/utilities/update.ts:462:10)
    at async updateByIDOperation (payload/src/collections/operations/updateByID.ts:203:18)
    at async <anonymous> (@payloadcms/plugin-nested-docs/src/hooks/resaveChildren.ts:72:25)

(The double frames come from the recursive parent → child → grandchild cascade; the failing document was a grandchild.)

Suggested direction

Either of these would avoid the silent data loss:

  • resaveChildren should skip re-validation for untouched children (or run with overrideValidation), since the re-save only rewrites breadcrumbs/paths; or
  • if the child re-save fails, the error should propagate so the parent update fails loudly instead of responding success on a rolled-back transaction.
Environment Info
payload: 3.85.2
@payloadcms/db-postgres: 3.85.2
@payloadcms/plugin-nested-docs: 3.85.2
@payloadcms/next: 3.85.2
Node: 22.x
Database: PostgreSQL 17 (Neon)
next: 16.x

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.

Research direction

Start in @payloadcms/plugin-nested-docs/src/hooks/resaveChildren.ts and trace its child update calls through payload/src/collections/operations/utilities/update.ts and updateByID.ts, with beforeChange as the validation entry point. Reproduce the dangling required upload case through the Local API and verify that a parent update either persists or returns the child validation error, rather than reporting success after a rollback.

Written by the indexing model from the issue text.

Assessment

Tech stack
postgresql, typescript
Domain
api, backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.