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

  1. 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.
  2. Add a Paragraphs field (e.g., field_notes) to a parent content type (e.g., Article) using the standard Paragraphs widget.
  3. Create a new Node (Article), add one Paragraph item of type “Note,” and leave both ROP subfields empty.
  4. 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_1 and field_notes.0.subform.field_sub_note_2) so that $form_state doesn’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 individual ConstraintViolations via ConstraintViolationBuilderInterface::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

  1. Add failing test(s) demonstrating multiple ROP subfield violations.
  2. Adjust validation form error mapping to generate distinct paths per subfield.
  3. Verify no regressions for non-Paragraph fields and for single-violation cases.
  4. 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.

Command icon 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

jcandan created an issue. See original summary.

  • bd511780 committed on 2.1.x
    [#3550810] feat: Fix only the last of multiple Paragraphs subfields...
jcandan’s picture

Assigned: jcandan » Unassigned
Status: Active » Fixed

Now that this issue is closed, please review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, please credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.