While working on #2438017: Entity reference does not validate auto-created entities and #2800875: Autocreate for Entity Reference selection handlers that extend ViewsSelection, I encountered a few UX issues with entity reference validation. This is the first one, addressing the wording and presentation of entity reference error messages.
Problem/Motivation
Entity reference validation produces inconsistent messages depending on which validation path reports the failure. Some messages suggest that no matching entity exists when the actual problem is that it cannot be referenced. Others reveal unnecessary information about the existence or status of entities the user is not permitted to access, even though those details do not help the user resolve the problem in this form.
Broader improvements to form-error handling have been discussed for years, including how validation errors should be formatted, grouped and presented consistently across forms. Alongside these architectural questions, specific bugs also remain unresolved, such as #2818437: FormState::setErrorByName does not set multiple errors on the same element. This issue leaves that behavior unchanged, but improving entity reference messages need not wait for its resolution.
Core already provides a useful precedent: #3364506: Add optional validation constraint support to ConfigFormBase introduced constraint-based validation in ConfigFormBase, including formatMultipleViolationsMessage(), a protected formatting step that can use knowledge of the form to improve violation messages. #3481323: Enable ConfigTarget to format error messages proposes extending this approach to support contextual formatting consistently for both single and multiple violations.
Proposed resolution
Introduce protected WidgetBase::prepareViolations(), returning the original violations by default. EntityReferenceAutocompleteWidget overrides it to adapt standard messages to the original submitted input.
- Use consistent wording and the human-readable entity type label for autocomplete rejections.
- Consolidate ambiguity instructions, using the same input normalization as autocomplete suggestions. Show concise guidance when autocomplete is working, with detailed examples available without JavaScript and to screen readers.
- Retain the existing diagnostic messages for invalid target IDs outside the autocomplete presentation path, and preserve explicitly customized messages.
Keep the rejection-message templates together in ValidReferenceConstraint.
Shared formatting code serves both the standalone EntityAutocomplete element and EntityReferenceAutocompleteWidget, including its tags-style subclass.
WidgetBase::extractFormValues() also preserves original item deltas for widgets that handle multiple values themselves, before empty items are removed. Existing widget-supplied mappings remain unchanged. This replaces the tags widget's additional delta bookkeeping without requiring an opt-in.
The shared mapping also provides a basis for contextual, per-entry validation messages in other widgets, such as Tagify, Entity Browser, and DER's selection widgets. These modules would need to adapt their error presentation to use it.
User interface changes
The following table compares the current and proposed messages.
| # | Input | 1. Target exists |
2. Reference-able |
3. Auto-create |
4. Create-able |
Current Message
Proposed Message
|
|---|---|---|---|---|---|---|
| Autocomplete: input with explicit ID | ||||||
| 1 |
“Example (42)” |
🟢 Yes | 🟢 Allowed | — | — |
✅ No error
No error
|
| 2a |
“Example (42)” |
🟢 Yes | 🔴 Rejected | Either | — |
⚠️ This entity (node: 42)
cannot be referenced. No content item matching
Example (42) can be referenced. |
| 2b |
“Example (42)” |
🟢 Yes | 🔴 Rejected | Either | — |
❌ The referenced entity (node: 42)
does not exist. No content item matching
Example (42) can be referenced. |
| 3 |
“Example (42)” |
🔴 No | — | Either | — |
⚠️ The referenced entity (node: 42)
does not exist. No content item matching
Example (42) can be referenced. |
| Autocomplete: with a referenceable existing target | ||||||
| 4 |
“Example” |
🟢 Yes | 🟢 Allowed | — |
✅ No error
No error
|
|
| Autocomplete: no existing target with this label | ||||||
| 5 |
“Example” |
🔴 No | — | 🔴 Off | — |
✅ There are no content items
matching Example. No content item matching
Example can be referenced. |
| 6 |
“Example” |
🔴 No | — | 🟢 On | 🟢 Allowed |
✅ No error
No error
|
| 7 |
“Example” |
🔴 No | — | 🟢 On | 🔴 Rejected by handler |
⚠️ This entity (node: Example)
cannot be referenced. No content item matching
Example can be referenced. |
| 8 |
“Example” |
🔴 No | — | 🟢 On | 🔴 Entity constraints violated |
❌ No error²;
No content item matching
Example can be referenced. |
| Autocomplete: existing targets with this label, none referenceable | ||||||
| 9 |
“Example” |
🟢 Yes | 🔴 Rejected | 🔴 Off | — |
❌ There are no content items
matching Example. No content item matching
Example can be referenced. |
| 10 |
“Example” |
🟢 Yes | 🔴 Rejected | 🟢 On | 🟢 Allowed |
✅ No error
No error
|
| 11 |
“Example” |
🟢 Yes | 🔴 Rejected | 🟢 On | 🔴 Rejected by handler |
⚠️ This entity (node: Example)
cannot be referenced. No content item matching
Example can be referenced. |
| 12 |
“Example” |
🟢 Yes | 🔴 Rejected | 🟢 On | 🔴 Entity constraints violated |
❌ No error²
No content item matching
Example can be referenced. |
| Autocomplete: multiple referenceable matches | ||||||
| 13 |
“Example” |
🟢 Yes | 🟢 2–5 matches | — | — |
✅ Multiple content items match this reference: Example (42), Example (43).
Specify the one you want by appending the id in parentheses, like “Example (42)”.
with JavaScript: Multiple content items match Example. Select a match from the autocomplete suggestions. w/o JavaScript: Multiple content items match Example. To select a specific match, append its ID in parentheses, as in: • “Example (42)” • “Example (43)”. |
| 14 |
“Example” |
🟢 Yes | 🟢 More than 5 matches | — | — |
✅ Many content items are called Example.
Specify the one you want by appending the id in parentheses, like “Example (42)”.
with JavaScript:
Multiple content items match Example. Select a match from the autocomplete suggestions. w/o JavaScript: Multiple content items match Example. To select a specific match, append its ID in parentheses, as in: • “Example (42)” • “Example (43)”. |
| API; incorrect checkboxes, radios and select lists³ | ||||||
| 15 | Selected ID: 42 | 🟢 Yes | 🟢 Allowed | — | — |
✅ No error
No error
|
| 16 | Selected ID: 42 | 🟢 Yes | 🔴 Rejected³ | — | — |
✅ This entity (node: 42)
cannot be referenced. This entity (node: 42) cannot be referenced. |
| 17 | Selected ID: 42 | 🔴 No³ | — | — | — |
✅ The referenced entity (node: 42)
does not exist. The referenced entity (node: 42)
does not exist. |
| 🟢 Yes / On / Allowed 🔴 No / Off / Rejected |
✅ Suitable ⚠️ Unnecessary disclosure of unhelpful details. ❌ Outright false or misleading. |
|||||
¹) 2a: the field widget uses ValidReference. 2b: the standalone element validates references itself, #validate_reference = TRUE.²) New targets' entity constraints currently aren't checked at all; the proposed message assumes the fix from #2438017: Entity reference does not validate auto-created entities. ³) Cases 16 & 17 assume custom code offers an invalid target; Core methods do or should exclude it. An explicit ID bypasses label-based autocreate. Excluded existing label matches do not prevent autocreate. Other widgets and API callers reaching ValidReference use the generic selected-target message; earlier API errors and custom messages are separate. |
||||||
Validation rules, selection results, error-element resolution and reporting order remain unchanged.
Separating validation from presentation provides a foundation for later message consolidation without depending on, or prescribing, a general Form API solution.
With the Views selection handler, autocomplete suggestions can include additional identifying information from the configured display fields. With JavaScript enabled, the concise error directs users to these suggestions. Without JavaScript, the full error includes instructions and a list of example matches.
The detailed message also remains available to assistive technology when JavaScript is enabled; the concise alternative is excluded from the accessibility tree to avoid duplicate output.
![]() with JavaScript enabled |
![]() without JavaScript enabled |
API changes
- Add protected
WidgetBase::prepareViolations(), allowing widgets to prepare violation messages before the existing error-element resolution and reporting steps. - Add
EntityReferenceMessageFormatter::formatMessage()and::formatViolations(), sharing rejection-message formatting between the standalone autocomplete element and the autocomplete field widgets. - Add public static
EntityAutocomplete::formatAutocompleteInput(), extracting the existing normalization fromEntityAutocompleteMatcher::getMatches(). Both the matcher andEntityAutocomplete::matchEntityByTitle()use it, so suggestions and ambiguity-message examples use the same input syntax. - Add named violation codes and the
noReferenceableMatchMessageandnoReferenceableSelectionMessageoptions toValidReferenceConstraint. These allow presentation code to identify standard failures and obtain their message templates without inspecting translated text. - Extend the existing original-delta mapping in
WidgetBase::extractFormValues()to multiple-value widgets using that extraction path.
Existing constructor argument positions and explicitly customized validation messages are preserved. Existing widgets do not need to implement the new preparation method.
Data model changes
None.
Remaining tasks
- Review the proposed wording and implementation.
- Verify keyboard operation and screen-reader output, including access to the detailed ambiguity guidance without duplicate announcements. JavaScript and no-JavaScript presentation, and accessibility-tree exposure, have been checked.
- Draft and review the change record.
| Comment | File | Size | Author |
|---|---|---|---|
| #3 | error_messages_without_javascript.png | 102.79 KB | pancho |
| #3 | error_messages_with_javascript.png | 74.14 KB | pancho |
Issue fork drupal-3632280
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 #3
panchoadded screenshots for the disambiguation case, both with and without JavaScript
added API extension and additional potential use cases: Tagify, Entity Browser, and DER's widgets
With all tests being green, this should be ready for review now.
Comment #4
smustgrave commentedBy chance was the summary written by AI?
Comment #5
pancho@smustgrave: Yes and no. Not written, but I did ask an AI to copyedit my issue summary and assist me with formatting the HTML table. Some of my previous summaries used to be a hindrance for review, so I figured some third party copyediting would be an improvement. Nevertheless, if this is frowned upon, sry for that. I'll trust my own English skills a bit more and present my summaries as I'm able to word them.
Comment #6
smustgrave commentedNot at all just was curious. If you're ever iffy about it just disclose at the bottom :)