Skip to content

Users: Make wp_dropdown_users() scalable via autocomplete on large sites#11447

Open
sanketio wants to merge 2 commits intoWordPress:trunkfrom
sanketio:fix/19867
Open

Users: Make wp_dropdown_users() scalable via autocomplete on large sites#11447
sanketio wants to merge 2 commits intoWordPress:trunkfrom
sanketio:fix/19867

Conversation

@sanketio
Copy link
Copy Markdown

@sanketio sanketio commented Apr 6, 2026

Summary

wp_dropdown_users() loads every user in the database unconditionally. On sites with tens of thousands of users this causes fatal memory exhaustion and browser hangs. This ticket has been open since 2012. This patch implements the approach originally proposed by Helen Hou-Sandi in 19867.3.diff, modernised for the current codebase.


What changed

wp_dropdown_users()wp-includes/user.php

  • Added 'autocomplete' => false argument. When true, renders a jQuery UI autocomplete text input backed by an AJAX user search instead of a <select>.
  • In wp-admin, autocomplete is automatically enabled when wp_is_large_user_count() returns true (> 10,000 users), unless the caller passes an explicit 'include' list or 'show_option_all'.
  • Two inputs are rendered in autocomplete mode:
    • Visible <input class="wp-suggest-user"> — the user types into this; jQuery UI shows suggestions.
    • Hidden <input class="wp-suggest-user-helper" name="..."> — stores the selected user ID for form submission.
  • Pre-selected user is resolved server-side and pre-populated into the visible input.
  • The data-autocomplete-label attribute is derived from the 'show' argument so suggestion items match what <option> text would have shown (e.g. display_name_with_login{{display_name}} ({{user_login}})).
  • New filter: wp_dropdown_users_autocomplete — allows overriding the autocomplete decision.
  • Existing filter wp_dropdown_users_args now fires unconditionally, before the autocomplete/select branch, so existing hooks continue to work regardless of mode.

wp_ajax_autocomplete_user()wp-admin/includes/ajax-actions.php

  • Extended to support single-site installs. Previously bailed with -1 on any non-multisite install.
  • Single-site search requires list_users capability. Email search column is only included for users with edit_users.
  • Added user_id as a supported autocomplete_field value, returning the numeric user ID as the suggestion value.
  • Added label template token resolution: {{user_login}}, {{user_email}}, {{display_name}}, {{user_id}}. The {{user_email}} token is silently stripped for users without edit_users.
  • Results capped at 20 via 'number' => 20 in get_users().
  • Response uses wp_send_json() (correct Content-Type: application/json header).
  • New filter: autocomplete_term_length — minimum search term length (default 2).
  • New filter: autocomplete_user_results — allows modifying the final results array.
  • Existing filter autocomplete_users_for_site_admins preserved.

user-suggest.jsjs/_enqueues/lib/user-suggest.js

  • Removed "multisite only" restriction from the file description.
  • Reads data-autocomplete-label attribute and passes it to the AJAX source URL as autocomplete_label.
  • Detects hasHelper pattern (user_id field + sibling .wp-suggest-user-helper input).
  • focus handler: shows ui.item.label (the display text) while keyboard-navigating, not the raw ID.
  • select handler: writes the user ID to the hidden helper input, the label to the visible input.

users.phpwp-admin/users.php

  • Hardened the delete/reassign handler: $_REQUEST['reassign_user'] is now passed through absint() and validated (> 0 and !== $id) before being forwarded to wp_delete_user(). Previously, if the autocomplete visible input was partially typed and the hidden helper remained 0, content would be silently deleted instead of reassigned.

Tests

  • tests/phpunit/tests/user/wpDropdownUsers.php — 10 new test methods:

    • Autocomplete mode renders <input> elements, not <select>
    • Hidden input carries the correct name attribute
    • Pre-selected user is pre-populated
    • wp_dropdown_users_autocomplete filter can force-enable or force-disable
    • wp_dropdown_users_args fires in both modes
    • wp_dropdown_users HTML filter fires in both modes
    • Auto-enable is suppressed when include or show_option_all is set
    • data-autocomplete-label matches the show argument (data-provider: 5 cases)
  • tests/phpunit/tests/ajax/wpAjaxAutocompleteUser.php — new file, 14 test methods:

    • Capability checks (subscriber rejected, editor/admin allowed)
    • Term length validation and autocomplete_term_length filter
    • autocomplete_field selection (user_login, user_id, user_email)
    • Label token resolution and {{user_email}} stripping for non-edit_users
    • autocomplete_user_results filter fires and can modify results
    • Response format (every result has label + value)
    • Result cap (≤ 20)

Backward compatibility

All existing filters fire unconditionally. The <select> path is fully preserved and is the default on small sites. No existing callers are broken: export.php calls with include + show_option_all suppress auto-enable; the Quick Edit path has its own wp_is_large_user_count() guard.


Trac ticket: https://core.trac.wordpress.org/ticket/19867


Use of AI Tools

AI assistance: Yes
Tool(s): GitHub Copilot
Model(s): Claude Sonnet 4.6
Used for: Initial code skeleton, test suggestions, and bug diagnosis; final implementation, logic, and all tests were reviewed and edited by me.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props sanketparmar.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

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.

1 participant