Skip to content

[6.x] Restore relation fields' search input and Create entry option - #20654

Open
brandonkelly wants to merge 8 commits into
6.xfrom
bugfix/20598-relation-field-search
Open

brandonkelly wants to merge 8 commits into
6.xfrom
bugfix/20598-relation-field-search

Conversation

@brandonkelly

@brandonkelly brandonkelly commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

Description

Relation fields in the entry editor get their search input back, as Craft 5's elementselect had it. Typing finds elements in the field's one source, and an Entries field limited to one section the user can publish in offers “Create entry: {query}”, which creates the entry, opens it in a slideout, and relates it once it's saved.

 BaseRelationField::uiControl()
   selectControl()                        # Entries → EntrySelect
+    ->createSection($section)            # when the user can publish in it on the target site
+  ->search($criteria, $siteId)           # source + selection criteria, when “Show the search input” applies
+  ->disabledElementIds($selfAndOwners)  # unless self relations are allowed

 <ElementSelectControl>
   <craft-button> Add an entry
+  <RelationSearch>                       # combobox → listbox, after the command palette’s search
+    element-search/search                # results, already-related ones dimmed and skipped
+    entries/create → slideout → relate   # “Create entry: {query}”

The section gate changes. Entries::inputTemplateVariables() only offered a section when the field had no selection condition, but every new Craft 6 Entries field gets a “Viewable” rule, and the upgrade migration turns showUnpermittedEntries: false into one, so creating was off almost everywhere. A condition whose only rules are “Viewable” set to on no longer blocks it: createEntries is nested under viewEntries, so the user can view the entry they author. Any other rule still blocks it, since a new entry can't be relied on to meet it. The legacy input uses the same gate. Creation is also only offered when the user can edit the field's target site.

The search leaves out the element being edited and its owners when the field doesn't allow self relations, and so does the selection modal, which the Vue control hadn't been telling about them. On a hierarchical field with a branch limit of one, a search result replaces the related branch, as a modal choice does. The search applies the field's selection criteria over its source's, as the modal does, so ElementCriteriaResolving restrictions and the Assets field's uploaderId: null apply to it too. Asset fields search subfolders, as the legacy input did. element-search/search now returns each result's UI label, so untitled results such as users relate with their names.

The search input follows the list autocomplete combobox pattern the command palette's search already uses (its useResultsListbox() is now generic over row types), with craft-options in a top-layer Dropdown so the list isn't clipped by blocks or slideouts. Already-related elements are listed after the others as unavailable (aria-disabled, dimmed, with a not-allowed cursor), as Craft 5's menu listed them: the first available result starts active, and the arrow keys and pointer skip the unavailable ones. Focus stays in the input; Up and Down move the active option, Return or a click chooses it (Return never submits the entry form, as in Craft 5), Tab and Escape close the list, and result counts are announced through a polite status region.

Evidence

  • Before: the Field Showcase's showcaseEntries field, limited to the Posts section with “Show the search input” on, renders only “Add an entry”. The resolved control has no search props, and with the old gate the field's default “Viewable” rule blocks creation:
    RelationFieldInputTest › searches an Entries field’s one section, and creates entries in it unless its condition asks for more than viewable ones
      viewable:  createSection → null      ✗ (expected the section handle)
    
  • After: the field shows a search input. Typing lists matching posts, with already-related ones dimmed and skipped, plus “Create entry: {query}” unless an entry already matches the query exactly. Choosing a post relates it, and choosing Create opens the new entry in a slideout and relates it once it's saved (a draft save doesn't). Checked by keyboard and pointer in the workbench.
    RelationFieldInputTest › … viewable ✓  not viewable ✓
    ElementSelectControl › search › lists already related elements as unavailable, skipping them to relate another ✓
    ElementSelectControl › search › offers no entry to create when an already related one matches exactly ✓
    ElementSelectControl › search › creates an entry from the query, and relates it once it’s saved ✓
    ElementSelectControl › search › offers no entry to create without a section to create it in ✓
    ElementSelectControl › search › excludes the element being edited from the search and the selector ✓
    ElementSelectControl › search › doesn’t choose an earlier query’s result while the next search is pending ✓
    ElementSelectControl › search › replaces the related branch when the field allows only one ✓
    RelationFieldInputTest › only offers to create entries in the target site when the user can edit it ✓
    ElementSelectControl › search › keeps Return from submitting the form when nothing can be chosen ✓
    ElementSelectControl › search › relates an untitled result, such as a user, by its label ✓
    RelationFieldInputTest › searches an Assets field’s volume and its subfolders, with the field’s selection criteria ✓
    

Related issues

brandonkelly and others added 2 commits November 13, 2026 02:21
Relation fields in the entry editor show their search input again when
"Show the search input" applies, finding elements in the field's one
source. Entries fields limited to one section the user can publish in
offer "Create entry: {query}", which opens the new entry in a slideout
and relates it once it's saved.

A selection condition whose only rules are "Viewable" no longer stops
entries from being created, since every Craft 6 Entries field has one.

Amp-Thread-ID: https://ampcode.com/threads/T-c5b638c3-0d79-4a2a-b36e-62c818663e7d
Co-authored-by: Amp <amp@ampcode.com>
@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

📚 Storybook previews

@craftcms/ui — open Storybook

No changed components detected in this Storybook.

resources/js — open Storybook

Changed components:

Already related elements stay in the search results, dimmed with a
not-allowed cursor, and the arrow keys and pointer skip over them, as
Craft 5's menu did. The first available result starts active.

Amp-Thread-ID: https://ampcode.com/threads/T-c5b638c3-0d79-4a2a-b36e-62c818663e7d
Co-authored-by: Amp <amp@ampcode.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Creation eligibility ignores target-site permissions, while search regressions allow disallowed or stale selections, omit nested assets, and can silently discard newly selected branches.

5 open issues
What changed in this PR

Restores inline search and “Create entry” suggestions in relation fields, addressing the remaining part of #20598 alongside the selection-modal changes in #20652.

Changes:

  • Adds debounced search with keyboard navigation, unavailable results, and entry creation through a slideout.
  • Passes search settings through UI controls and allows creation when the selection condition only requires viewable entries.
  • Adds regression coverage and documents the restored behavior.
File Description
tests/​Unit/​Field/​AssetsTest.php Disables search in an upload-focused unit test.
tests/​Feature/​Field/​RelationFieldInputTest.php Tests search props and the viewable-condition creation gate.
src/​Ui/​UiControlTypes.php Registers the entry-select control.
src/​Ui/​Controls/​EntrySelect.php Exposes the section available for entry creation.
src/​Ui/​Controls/​ElementSelect.php Adds search criteria and target-site props.
src/​Field/​Entries.php Configures entry creation and shares its condition gate with legacy inputs.
src/​Field/​BaseRelationField.php Supplies shared search criteria to relation controls.
resources/​js/​modules/​ui/​ElementSelectControl.vue Integrates search, selection, and focus handling.
resources/​js/​modules/​ui/​ElementSelectControl.test.ts Covers search exclusions and entry creation.
resources/​js/​modules/​ui/​element-select/​types.ts Defines search and creation props.
resources/​js/​modules/​ui/​element-select/​RelationSearch.vue Implements the search combobox and creation flow.
resources/​js/​modules/​command-palette/​useResultsListbox.ts Generalizes listbox support for reuse.
CHANGELOG.md Documents restored relation-field search and creation.

Review effort: Balanced

criteria: props.field.search.criteria,
condition: props.field.selectionCondition,
...props.field.referenceElement,
excludeIds: props.excludeIds,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2e7a6d0: the field now sends disabledElementIds (the element being edited and its owners, unless self relations are allowed, computed by the same helper the legacy input uses), and the search excludes them. The Vue selector modal had the same gap, so it now excludes them too, as Craft 5 did.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The search excludes them, as in Craft 5: disabledElementIds goes in the search's excludeIds, so the element being edited and its owners show as unavailable results that can't be chosen. Craft 5 sent sourceElementId the same way. The test is excludes the element being edited from the search and the selector, and choosing an excluded result is covered by lists already related elements as unavailable, skipping them to relate another.

Comment thread src/Field/BaseRelationField.php Resolved
Comment thread src/Field/Entries.php Resolved
brandonkelly and others added 2 commits November 13, 2026 04:43
- Exclude the element being edited and its owners from search results and
  the selector modal when the field doesn't allow self relations, as
  Craft 5 did.
- Replace the related branch when a hierarchical field's branch limit is
  one, as the selector modal does.
- Don't choose an earlier query's result while the next search is
  pending.
- Search asset subfolders from the Vue input too, through an
  Assets::searchCriteria() override.
- Only offer to create entries when the user can edit the target site.

Amp-Thread-ID: https://ampcode.com/threads/T-c5b638c3-0d79-4a2a-b36e-62c818663e7d
Co-authored-by: Amp <amp@ampcode.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Relation search can accidentally submit the parent form on Enter, bypass field-level selection restrictions, and display numeric IDs instead of user names.

3 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Use the user’s UI label for group-restricted search results

resources/​js/​modules/​ui/​element-select/​RelationSearch.vue:278

In a Users field limited to a group, results can have title: null, so selecting a user displays their numeric ID instead of their name. relateFound() caches that incomplete presentation, preventing the missing-presentation loader from correcting the chip. Return the element’s UI label from the search endpoint and use it in the result type and selection mapping.

4 resolved since last review

Review effort: Balanced

elementType: props.field.elementType,
siteId: props.field.search.siteId,
search: value,
criteria: props.field.search.criteria,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 18ba2f8: searchCriteria() now puts the field's selection criteria (getInputSelectionCriteria(), including ElementCriteriaResolving additions and the Assets field's uploaderId: null) over the source's, in the order the element index applies them for the selector modal. It's done server-side, so the legacy input's searchCriteria gets them too. Covered by searches an Assets field’s volume and its subfolders, with the field’s selection criteria.

- Search with the field's selection criteria over its source's, as the
  selector modal does, so ElementCriteriaResolving restrictions and the
  Assets field's uploaderId override apply to it too.
- Keep Return in the search input from submitting the entry form when
  no result can be chosen, as Craft 5 did.
- Return each result's UI label from element-search/search and relate
  results by it, so users show their names rather than their IDs.

Amp-Thread-ID: https://ampcode.com/threads/T-c5b638c3-0d79-4a2a-b36e-62c818663e7d
Co-authored-by: Amp <amp@ampcode.com>

Copy link
Copy Markdown
Member Author

On the review's previously missed issue (Users field results showing an ID): fixed in 18ba2f8. element-search/search now returns each result's label (getUiLabel()), and the search relates results by it, so a user's chip shows their name. Covered by relates an untitled result, such as a user, by its label and a label assertion in SearchControllerTest.

Keeps both sides' changelog entries and tests. The search input now
focuses a related element through elementFocusTarget(), which 6.x added
for the selector modal.

Amp-Thread-ID: https://ampcode.com/threads/T-c5b638c3-0d79-4a2a-b36e-62c818663e7d
Co-authored-by: Amp <amp@ampcode.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Search results retain stale exclusion flags after a relation is removed, leaving the element disabled instead of selectable until the query changes.

2 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Recompute result exclusions from the current excludeIds prop

resources/​js/​modules/​ui/​element-select/​RelationSearch.vue:96

In a field that is not full, search for an already-related element, remove it through its chip menu, then return to the unchanged search: the result remains disabled even though it can now be related. The rows retain the server’s exclusion flags instead of using the updated excludeIds prop. Derive each result’s excluded state from the current excludeIds so removing a relation makes it selectable without changing the query.

1 resolved since last review

Review effort: Balanced

A result for an element whose relation was removed while the results
were open stayed unavailable until the query changed, because rows kept
the search response's exclude flags. They now check the field's current
exclusions.

Copy link
Copy Markdown
Member Author

On the third review's previously missed issue (stale result exclusions): fixed in 8e45b9c. A result's availability now comes from the field's current exclusions rather than the search response's exclude flags, so removing a relation while the results are open makes that result available without searching again. Covered by makes a result available once its relation is removed, without searching again.

The two other open items, selection criteria and self relations, are the earlier threads, answered in r4560110659 and r4560111098.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants