Problem/Motivation
When a Paragraphs item contains more than one subfield marked as “Required on Publish” (ROP), attempting to publish the parent content with those subfields left empty results in only the last subfield displaying a validation error. Earlier subfields that are also empty are silently ignored in the UI.
We recently changed behavior to ignore Paragraph entities themselves and validate only the parent content entity. It appears that the error mapping introduced by this change is collapsing or overwriting multiple violations for the same Paragraph item, causing only the final violation to surface.
Steps to reproduce
- Create a Paragraph type (e.g., “Note”) with at least two fields (e.g.,
field_sub_note_1,field_sub_note_2) and enable ROP on both. - Add a Paragraphs field (e.g.,
field_notes) to a parent content type (e.g., Article) using the standard Paragraphs widget. - Create a new Node (Article), add one Paragraph item of type “Note,” and leave both ROP subfields empty.
- Attempt to publish (via standard node form submit, “Save and publish,” or Content Moderation transition to a published state).
Expected: Validation errors for each empty ROP subfield, both highlighted on the embedded Paragraph form.
Actual: Only the last ROP subfield shows an error; although both are highlighted on the embedded Paragraph form (likely as a result of the whole Paragraph Node field. The earlier empty ROP subfield shows no error.
Proposed resolution
- Accumulate, don’t overwrite: Ensure the validation layer collects all ROP violations for a Paragraph item and surfaces each one. Avoid returning early after the first violation.
- Distinct error paths per subfield: When “hoisting” violations to the parent entity (due to ignoring Paragraph entities), build unique error keys/paths per subfield (e.g.,
field_notes.0.subform.field_sub_note_1andfield_notes.0.subform.field_sub_note_2) so that$form_statedoesn’t overwrite earlier errors with later ones. - Avoid single-slot setters: If using helpers like
setErrorByName()or a mapping array, confirm they don’t replace prior messages for the same container. Prefer$form_state->setError($element, $message)per sub-element or build individualConstraintViolations viaConstraintViolationBuilderInterface::atPath(...)->addViolation(). - Retain parent-only validation intent: Keep the “validate parent content entities” approach, but adjust the error mapping to handle multiple subfield violations in embedded forms (Paragraphs widget simple/complex).
- Robust test coverage:
- Add a FunctionalJavascript test that creates a Paragraph with two+ ROP subfields, attempts to publish empty, and asserts both inline errors render simultaneously.
- Repeat with: multi-value Paragraphs fields (index 0,1), different widgets, and with/without Content Moderation enabled.
Remaining tasks
- Add failing test(s) demonstrating multiple ROP subfield violations.
- Adjust validation form error mapping to generate distinct paths per subfield.
- Verify no regressions for non-Paragraph fields and for single-violation cases.
- Test with:
- Multi-value Paragraphs fields (multiple items)
- Different Paragraphs widgets (simple/complex)
- Content Moderation on/off; draft → published transition; “Save and publish”
User interface changes
All applicable ROP errors will appear concurrently on embedded Paragraph subfields (no more “last error only”). We may optionally accumulate errors into a single error.
Issue fork require_on_publish-3550810
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #4
jcandan commented