Im WordPress-Ökosystem bekommt WP_Query den ganzen Ruhm, aber WP_User_Query trägt Mitgliederseiten, Intranets und Community-Plattformen.
Egal, ob Sie eine einfache “Unser Team”-Seite oder eine komplexe “Arzt finden”-Suche mit Tausenden von Fachleuten bauen: Plugins wie Ultimate Member für die Präsentationsschicht sind oft übertrieben und ein Performance-Flaschenhals. Eine schlanke Abfrage im Template schlägt ein weiteres Mitglieder-Plugin, das bei jedem Request eigene Templates, REST-Routen und CSS lädt.
In diesem Guide umgehen wir die GUI und erstellen effiziente, sichere Benutzerabfragen direkt in PHP: Filter mit role__in, meta_query und Paginierung, ohne die Datenbank zu überlasten oder Anmeldenamen preiszugeben.
Offizielle Referenz: WP_User_Query auf developer.wordpress.org. Jeder Standardwert und jedes Verhalten weiter unten wurde am 12. Oktober 2026 gegen den Quellcode der Klasse in wordpress-develop geprüft.
get_users() vs. WP_User_Query
Genauso wie get_posts() ein Wrapper für WP_Query ist, ist get_users() ein dünner Wrapper für WP_User_Query. Die Funktion akzeptiert dasselbe Argument-Array und gibt ein Array von WP_User-Objekten zurück (oder Teilmengen der Felder, wenn Sie fields übergeben).
- Nutzen Sie
get_users()für kurze Listen und sonst nichts, zum Beispiel fünf Redakteure für ein Autoren-Widget. - Nutzen Sie
WP_User_Query, wenn Sieget_results()plusget_total()für paginierte Verzeichnisse, die Inspektion von$query->requestbeim SQL-Debugging oder einen expliziten Lebenszyklus des Abfrageobjekts brauchen.
Ein Unterschied im Verhalten ist wichtig. get_users() setzt count_total vor dem Aufbau der Abfrage auf false, führt also nie die zusätzliche Zählung aus und hat keine Gesamtzahl für Sie. Für ein Widget ist das richtig, für ein paginiertes Verzeichnis falsch.
Für beide APIs ist das Argument-Array der Vertrag. Alles Folgende funktioniert mit beiden Formen; die Beispiele nutzen WP_User_Query, weil Verzeichnisse Gesamtzahlen brauchen.
Was WP_User_Query mit Standardargumenten zurückgibt
Die Standardwerte entscheiden über den Großteil der Kosten einer nachlässigen Abfrage:
| Argument | Standard | Was das in der Praxis bedeutet |
|---|---|---|
number | kein Limit | Alle passenden Benutzer kommen zurück. Laut Docblock ist -1 (alle) auf größeren Websites mit Vorsicht einzusetzen. |
paged | 1 | Wird nur genutzt, wenn number größer als null ist. |
orderby | login | Sortierung nach user_login, selten das, was eine öffentliche Liste will. |
fields | all | Vollständige WP_User-Objekte, alle User-Meta über cache_users() vorgeladen. |
count_total | true | Fügt SQL_CALC_FOUND_ROWS hinzu und führt eine zweite Abfrage SELECT FOUND_ROWS() aus. |
blog_id | aktuelle Website | Rollen- und Mitgliedschaftsfilter gelten nur für die aktuelle Website. |
cache_results | true | Ergebnisse landen im Object Cache (seit WordPress 6.3). |
Daraus folgen zwei Dinge. Eine Abfrage ohne number lädt auf einer Website mit vielen Benutzern alle. Und eine Abfrage, die nie paginiert, sollte 'count_total' => false übergeben, denn laut Docblock verbessert das Abschalten die Performance, wenn keine Paginierung nötig ist.
Außerdem verweigert die Klasse einen zu frühen Start: Seit WordPress 6.1.1 löst eine Benutzerabfrage vor dem Hook plugins_loaded einen _doing_it_wrong()-Hinweis aus.
WP_User_Query mit role__in für eine Team-Seite
Redakteure und Autoren, sortiert nach Anzeigename, zwölf pro Seite:
$paged = max( 1, absint( $_GET['team_page'] ?? 1 ) );
$args = [
'role__in' => [ 'editor', 'author' ],
'orderby' => 'display_name',
'order' => 'ASC',
'number' => 12,
'paged' => $paged,
'fields' => [ 'ID', 'display_name' ],
];
$user_query = new WP_User_Query( $args );
$results = $user_query->get_results();
if ( ! empty( $results ) ) {
// Eine Abfrage für die Meta aller zwölf Benutzer statt zwölf Abfragen.
update_meta_cache( 'user', wp_list_pluck( $results, 'ID' ) );
echo '<div class="team-grid">';
foreach ( $results as $user ) {
$avatar = get_avatar( $user->ID, 128 );
$name = esc_html( $user->display_name );
$bio = esc_html( get_user_meta( $user->ID, 'description', true ) );
echo "<article class='team-member'>
<figure>{$avatar}</figure>
<h3>{$name}</h3>
<p>{$bio}</p>
</article>";
}
echo '</div>';
}Hinweise, die in Produktion zählen:
role__intrifft jede der aufgeführten Rollen. Ziehen Sie es mehreren Abfragen mitrolevor.fieldsliefertstdClass-Zeilen statt vollständigerWP_User-Objekte und spart Speicher, wenn Sie nur IDs und Namen brauchen.- Mit einem
fields-Array lädt WordPress die User-Meta nicht vor. Der Aufruf vonupdate_meta_cache()oben hält die Bio-Abfragen deshalb bei einer einzigen Abfrage. Mitfields => 'all'erledigt die Klasse das selbst übercache_users(). - Die Seitenzahl kommt aus einem eigenen Parameter
team_page. Auf einer statischen Seite nutzt die Hauptabfragepage, nichtpaged;get_query_var( 'paged' )liefert dort stillschweigend den Standardwert.
Wie die Filter role, role__in und capability funktionieren
Die drei Rollen-Argumente sehen ähnlich aus und verhalten sich unterschiedlich:
roleakzeptiert einen String oder ein Array, und das Array ist eine UND-Liste: Ein Benutzer muss jede genannte Rolle haben.'role' => [ 'editor', 'author' ]liefert auf einer Standard-Website fast niemanden.role__inist die ODER-Liste: mindestens eine der Rollen.role__not_inschließt Benutzer aus, die eine der genannten Rollen haben.
Intern berührt keines davon eine Rollentabelle, denn WordPress hat keine. Rollen liegen als serialisiertes Array in der User-Meta-Zeile {prefix}capabilities, und jede Rollen-Klausel wird zu einem Vergleich LIKE '%"editor"%' auf dieser Zeile. Ein Rollenfilter ist also schon ein Join auf wp_usermeta, bevor Sie eine einzige meta_query hinzufügen.
Seit WordPress 5.9 gibt es außerdem capability, capability__in und capability__not_in. Sie übersetzen Capabilities in die Rollen, die sie gewähren, plus Capabilities, die einem Benutzer direkt zugewiesen sind. Der Docblock warnt, dass sie nicht für Capabilities funktionieren, die nicht in der Datenbank stehen oder über map_meta_cap gefiltert werden. Behandeln Sie sie als Abkürzung für primitive Capabilities wie edit_posts, nicht als Berechtigungsprüfung.
Wie Sie WP_User_Query mit get_total() paginieren
Die Paginierungsargumente sind number (Seitengröße) und paged (1-basiert). Nach der Abfrage liefert get_total() die Trefferzahl über alle Seiten, nicht nur über die aktuelle.
$per_page = 24;
$paged = max( 1, absint( $_GET['udir_page'] ?? 1 ) );
$query = new WP_User_Query(
[
'role__in' => [ 'subscriber', 'contributor' ],
'number' => $per_page,
'paged' => $paged,
'fields' => 'ID',
'orderby' => 'registered',
'order' => 'DESC',
]
);
$total_users = (int) $query->get_total();
$total_pages = (int) ceil( $total_users / $per_page );
$user_ids = $query->get_results();
// Links mit add_query_arg( 'udir_page', $n ) bauen und URLs escapen.Anti-Patterns, die Sie vermeiden sollten:
'number' => -1abfragen (oder weglassen) und mitarray_slicein PHP zerschneiden- Eine zweite vollständige Abfrage nur zum Zählen, wenn
get_total()die Arbeit schon gemacht hat - Ungeprüfte
$_GET-Werte ohne strikte Allow-List inorderbyeinsetzen
Zwei Details aus dem Quellcode überraschen viele. Wenn Sie offset übergeben, gewinnt es: Die Klasse baut LIMIT offset, number und ignoriert paged. Und mit fields => 'ID' ist das Ergebnis ein flaches Array von ID-Strings aus $wpdb->get_col(), keine Objekte; casten Sie also vor strikten Vergleichen.
Nutzen Sie für öffentliche Verzeichnisse eine eigene Query-Variable (wie oben), damit Sie nicht mit der paged-Schleife des Blogs kollidieren.
Wie Sie Mitglieder mit meta_query filtern
Mit meta_query werden Verzeichnisse nützlich, und hier zeigen sich die MySQL-Kosten. Beispiel: Abonnenten in Berlin, nur öffentliche Profile, mit PHP im Metafeld für Fähigkeiten.
$args = [
'role' => 'subscriber',
'number' => 20,
'paged' => 1,
'fields' => [ 'ID', 'display_name' ],
'meta_query' => [
'relation' => 'AND',
[
'key' => 'city',
'value' => 'Berlin',
'compare' => '=',
],
[
'key' => 'is_public_profile',
'value' => '1',
'compare' => '=',
],
[
'key' => 'skills',
'value' => 'PHP',
'compare' => 'LIKE', // funktioniert bei serialisierten Arrays; teuer im großen Maßstab
],
],
];
$directory = new WP_User_Query( $args );Praktische Regeln für meta_query auf Benutzern:
- Bevorzugen Sie exakte
=-Vergleiche auf eigenen Schlüsseln (city,is_public_profile) gegenüberLIKEauf serialisierten Blobs. LIKE 'PHP'ist ein Teilstring-Vergleich und trifft deshalb auch eine Fähigkeit namensPHPUnit. Das ist ein Korrektheitsfehler, nicht nur ein Tempoproblem.- Speichern Sie mehrwertige Fähigkeiten als getrennte boolesche Metaschlüssel (
skill_php=1), wenn Sie oft danach filtern. So werden UND/ODER zu indexfreundlichen Gleichheitsprüfungen. Alternativ eineskills-Zeile pro Wert, geschrieben mitadd_user_meta( $user_id, 'skills', 'php' ), das doppelte Schlüssel erlaubt, und danach gefiltert mit'compare' => '='oder'IN'. - Kombinieren Sie
relationmit Bedacht. Verschachtelte Gruppen sind gültig, aber jede Klausel ist ein weiterer Join aufwp_usermeta. - Koppeln Sie öffentliche Filter immer an ein Sichtbarkeitsflag, damit private Profile nie in nicht angemeldeten Listen erscheinen.
Warum exakte Schlüssel zählen, zeigt das Core-Schema. Die Tabelle wp_usermeta, die wp-admin/includes/schema.php anlegt, hat einen Primärschlüssel, einen Index auf user_id und einen Präfix-Index auf meta_key. Auf meta_value, einer longtext-Spalte, gibt es keinen Index. MySQL kann den Join auf die Zeilen eines Schlüssels eingrenzen, muss aber jede dieser Zeilen lesen, um Werte zu vergleichen. Für Verzeichnisse mit sehr vielen Benutzern lohnt deshalb ein Suchindex in Elasticsearch (über ElasticPress) oder eine eigene SQL-Tabelle.
Wie search und search_columns in WP_User_Query funktionieren
Das Argument search wird leicht falsch eingesetzt, weil es sich anders verhält als ein Suchfeld.
- Ohne Platzhalter ist es ein exakter Treffer.
'search' => 'anna'findet den Benutzer, dessen Login genauannalautet. Setzen Sie*an den Anfang, das Ende oder beide Seiten ('*anna*'), um einLIKEmit%zu bekommen. search_columnsakzeptiertID,user_login,user_email,user_url,user_nicenameunddisplay_name.IDwird immer mit=verglichen.- Ist
search_columnsleer, rät WordPress: Ein Begriff mit@durchsuchtuser_email, ein numerischer Begriffuser_loginundID, eine URLuser_url, alles andere Login, URL, E-Mail, Nicename und Anzeigename zugleich.
Dieser letzte Standard ist für ein öffentliches Verzeichnis gefährlich. Wer einen Teil einer E-Mail-Adresse in Ihre Suche tippt, kann bestätigen, dass die Adresse zu einem Mitglied gehört. Legen Sie für die öffentliche Suche die Spalten immer selbst fest:
$term = sanitize_text_field( wp_unslash( $_GET['q'] ?? '' ) );
$args = [
'role__in' => [ 'subscriber' ],
'search' => '*' . $term . '*',
'search_columns' => [ 'display_name' ],
'number' => 20,
'fields' => [ 'ID', 'display_name' ],
];Über den Filter user_search_columns kann ein Plugin die Spaltenliste für jede Benutzersuche der Website ändern. Prüfen Sie ihn, wenn Ergebnisse nicht stimmen.
Wie Sie WP_User_Query für große Verzeichnisse beschleunigen
Benutzerabfragen wirken billig, bis das Verzeichnis öffentlich und gefiltert ist.
Wie Sie die Rückgabefelder (fields) begrenzen
Standardmäßig lädt WordPress mehr Daten, als ein Kartenraster braucht. Übergeben Sie fields früh:
$args = [
'role' => 'subscriber',
'number' => 100,
'fields' => [ 'ID', 'display_name', 'user_email' ], // Gibt stdClass-Objekte zurück, nicht WP_User
];Entfernen Sie user_email aus öffentlichen Templates. Behalten Sie es nur für interne Werkzeuge hinter Capability-Prüfungen.
Halten Sie die Liste noch aus einem zweiten Grund kurz: Nennt ein fields-Array mehr als drei Felder, schaltet WP_User_Query den eigenen Ergebnis-Cache für diese Abfrage ab.
Wie Sie Benutzer zählen, ohne Profile zu laden
$query = new WP_User_Query(
[
'role' => 'subscriber',
'number' => 1,
'fields' => 'ID',
]
);
$count = $query->get_total(); // nutzt SQL_CALC_FOUND_ROWSnumber => 1 hält das Ergebnis bei einer einzigen ID, während get_total() trotzdem die volle Zahl meldet.
Für Rollensummen ohne Filter gibt es count_users(). Kostenlos ist das nicht: Dahinter steht kein gespeicherter Rollenzähler. Mit der Standardstrategie time läuft eine Aggregatabfrage über alle {prefix}capabilities-Zeilen mit einem LIKE pro Rolle, und der Docblock beschreibt sie als CPU-intensiv und geeignet für etwa 10^7 Benutzer. Brauchen Sie nur die Gesamtzahl der Konten, liest get_user_count() die gespeicherte Option user_count. Auf Websites, die wp_is_large_user_count() als groß einstuft (standardmäßig mehr als 10.000 Benutzer), aktualisiert ein Cron-Ereignis diesen Wert zweimal täglich, er kann also hinterherhinken.
Wie Sie Ergebnisse von WP_User_Query cachen
Seit WordPress 6.3 cacht die Klasse selbst. Mit cache_results auf true speichert sie die Ergebnis-IDs und die Gesamtzahl in der Object-Cache-Gruppe user-queries, mit einem MD5 des erzeugten SQL als Schlüssel. Der Eintrag wird mit dem last-changed-Zeitstempel von users gesalzen, und Core aktualisiert diesen Zeitstempel bei added_user_meta, updated_user_meta, deleted_user_meta, add_user_role, set_user_role und remove_user_role. Jede Profiländerung invalidiert alle gecachten Benutzerabfragen.
Dieser Cache überlebt nur dann zwischen Requests, wenn die Website einen persistenten Object Cache hat (Redis oder Memcached). Ohne ihn hält er einen einzigen Seitenaufruf.
Wenn Sie eine eigene Schicht darüberlegen, etwa einen Transient mit dem gerenderten HTML über die Transients API, nutzen Sie einen Hash der Filter als Schlüssel und leeren Sie ihn über dieselben Hooks. Cachen Sie kein HTML mit Capability-geschützten Feldern für anonyme Besucher. Und halten Sie häufige Meta-Schreibvorgänge aus dem Request-Pfad heraus: Ein Plugin, das bei jedem Seitenaufruf ein “zuletzt gesehen”-Metafeld schreibt, verschiebt den users-Zeitstempel bei jedem Treffer und lässt den eingebauten Cache leer laufen.
Wann Sie ein Mitgliederverzeichnis zu Elasticsearch verlagern
Joins auf wp_usermeta skalieren nicht wie eine Suchmaschine. Wenn sich die Filter vervielfachen (Stadt + Fachgebiet + Sprache + Verfügbarkeit) und das Publikum groß ist, verlagern Sie die durchsuchbare Projektion nach Elasticsearch (ElasticPress oder ein eigener Index) oder in eine eigene Verzeichnistabelle mit echten Indizes. Behalten Sie WP_User_Query für das abschließende Laden einer kleinen ID-Liste.
Die Klasse hat genau dafür einen Hook. Gibt der Filter users_pre_query ein Array statt null zurück, entfällt das SQL komplett; der Docblock bittet solche Filter, die Eigenschaft total_users zu setzen, damit die Paginierung weiter funktioniert.
Wie Sie N+1-Abfragen für Avatare und User-Meta vermeiden
In der Schleife vervielfachen sich get_avatar() und wiederholte get_user_meta()-Aufrufe. Laden Sie die Meta der ganzen Seite mit update_meta_cache( 'user', $ids ) vor; die Funktion holt alle Meta-Zeilen der ID-Menge in einer Abfrage, wie im Team-Beispiel. Bei sehr breiten Profilen ist eine denormalisierte Verzeichniszeile, die beim Speichern des Profils geschrieben wird, noch günstiger.
Wer Benutzer über die WordPress REST API auflisten darf
Ihr Template kann abgesichert sein, während /wp-json/wp/v2/users weiter antwortet. Was zurückkommt, hängt vom Besucher ab. Die Regeln stehen in WP_REST_Users_Controller und zusammengefasst in der Referenz des Users-Endpoints:
- Ein Besucher ohne die Capability
list_users(standardmäßig haben sie nur Administratoren) sieht nur Benutzer, die Beiträge in einem für die REST API freigegebenen Inhaltstyp veröffentlicht haben. Mitglieder ohne Veröffentlichungen fehlen in der anonymen Liste. - Im Standardkontext
viewenthält ein Benutzerobjektid,name,url,description,link,slugundavatar_urls.username,email,rolesundregistered_dategibt es nur im Kontextedit, derlist_usersverlangt. - Filtern nach
rolesodercapabilities, Sortieren nachemailoderregistered_dateundcontext=editliefern ohnelist_userseinen Autorisierungsfehler. - Für diese Besucher ist eine Suche auf
ID,user_login,user_nicenameunddisplay_namefestgelegt. Der Parametersearch_columns, eingeführt in WordPress 6.8, kann diese Liste nicht erweitern. per_pagesteht standardmäßig auf 10 und ist auf 100 begrenzt; die Antwort trägt die HeaderX-WP-TotalundX-WP-TotalPages.
Das Feld slug ist die Schwachstelle. wp_insert_user() bildet user_nicename aus dem Login, wenn kein Nicename angegeben ist: Die ersten 50 Zeichen von user_login laufen durch sanitize_title(). Bei den meisten Konten ist der öffentliche Slug also der Login oder fast derselbe. Derselbe Nicename steht in den URLs der Autoren-Archive.
Zwei Filter geben Ihnen Kontrolle. rest_user_query erhält bei jeder Listenanfrage die Argumente für WP_User_Query, dort können Sie eine Sichtbarkeits-meta_query ergänzen. Hat die Website keinen öffentlichen Bedarf für den Endpoint, entfernen Sie ihn nur für anonyme Besucher, denn der Block-Editor liest Benutzer für angemeldete Redakteure über dieselbe Route:
add_filter(
'rest_endpoints',
static function ( $endpoints ) {
if ( ! is_user_logged_in() ) {
unset( $endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
}
return $endpoints;
}
);Wie Sie User Enumeration in einem Mitgliederverzeichnis verhindern
Ein Mitgliederverzeichnis ist eine öffentliche Liste von Konten. Behandeln Sie es so.
- Keine Anmeldenamen zeigen: Führen Sie nie
echo $user->user_loginaus. Der Login ist die halbe Information, die ein Angreifer für einen Anmeldeversuch braucht. Nutzen Siedisplay_nameoder einen eigenen öffentlichen Spitznamen.user_nicenamewird standardmäßig aus dem Login abgeleitet und ist deshalb kein sicherer Ersatz. - E-Mails verstecken: Geben Sie
user_emailnur aus, wenn der Besucher angemeldet ist und sie sehen darf. - Autoren-Archive blockieren: Autoren-Archive (
/?author=1,/?author=2) verraten auf vielen Websites weiterhin Logins. Wenn das Produkt keine öffentlichen Autoren-Archive braucht, leiten Sie sie um:
add_action(
'template_redirect',
static function () {
if ( is_author() ) {
wp_safe_redirect( home_url( '/' ), 301 );
exit;
}
}
);- Ausgaben escapen: alles, was PHP verlässt, mit
esc_html(),esc_url()undesc_attr()bei Attributen. - Admin-Verzeichnisse absichern: über die Capability
list_users, nicht über eine “geheime” URL. - Öffentliche Suche eingrenzen:
search_columnsaufdisplay_namebeschränken, wie oben gezeigt.
Mitgliederverzeichnis und personenbezogene Daten nach DSGVO
Für Mitglieder in der EU oder im EWR ja. Namen, Fotos, Städte und Fähigkeiten identifizierbarer Personen sind personenbezogene Daten nach Artikel 4 Nummer 1 der Datenschutz-Grundverordnung. Artikel 25 Absatz 2 ergänzt eine Regel, die sich direkt auf Ihre Abfrageargumente übertragen lässt: Durch Voreinstellung dürfen personenbezogene Daten nicht ohne Eingreifen der betroffenen Person einer unbestimmten Zahl von Personen zugänglich gemacht werden.
Im Code heißt das:
- Neue Konten starten mit
is_public_profile=0, bis das Mitglied aktiv zustimmt. - Eigene Flags für “Telefon zeigen” oder “E-Mail zeigen”: Ein öffentliches Profil ist keine Einwilligung zur Veröffentlichung von Kontaktdaten.
- Jede öffentliche Abfrage enthält die Sichtbarkeitsklausel in ihrer
meta_query, ebenso jederrest_user_query-Filter. - Staging-Datenbanken mit echten Mitgliedern werden anonymisiert, bevor jemand außerhalb des Teams eine Kopie bekommt.
WP_User_Query filtert nur. Die Klasse ersetzt weder Einwilligung noch Zweckbestimmung oder Löschkonzept, aber ohne Sichtbarkeitsflag verhindert keine Datenschutzerklärung, dass das Profil erscheint.
Mitglieder-Plugin oder eigenes Verzeichnis mit WP_User_Query
Mitglieder-Plugins verdienen ihren Platz bei Registrierung, Bezahlstufen und Nachrichten. Die öffentliche Liste ist das kleinere Problem, und genau sie läuft bei jedem anonymen Request. Behalten Sie das Plugin für den Kontoablauf und bauen Sie die Liste selbst, wenn sie schnell oder stark gefiltert sein muss.
Sind Sie bereits an das Raster eines Plugins gebunden, messen Sie es, bevor Sie es akzeptieren: Öffnen Sie die Liste mit Query Monitor und prüfen Sie, ob die Benutzerabfrage ein LIMIT hat, nur die Felder holt, die die Karten zeigen, und nicht pro Karte eine Meta-Abfrage auslöst.
Wie ein Template für ein Mitgliederverzeichnis aufgebaut ist
Ein belastbares öffentliches Verzeichnis hat meist vier Schichten:
- Eingabe-Bereinigung: Sortierschlüssel per Allow-List, ganzzahlige Seite, normalisierter Stadt-Slug.
- Abfrage:
role__in/meta_query/fields/number/paged. - Darstellung: Karten nur aus IDs und sicheren Anzeigefeldern.
- Cache-Invalidierung: gekoppelt an die Metaschlüssel, die die Sichtbarkeit steuern.
Behalten Sie die Plugin-Oberfläche für Registrierung und Nachrichten, wenn Sie diese Funktionen brauchen. Halten Sie Liste und Filter in Ihrem Theme oder einem kleinen mu-plugin, das Sie selbst kontrollieren. Diese Trennung hält das Verzeichnis schnell, wenn das Marketing im nächsten Quartal einen weiteren Filter wünscht.
Wie WP_User_Query Rollen auf Multisite behandelt
In einem WordPress-Multisite-Netzwerk ist die Benutzertabelle gemeinsam. WP_User_Query funktioniert weiter, aber Rollenfilter gelten pro Website. Ein Benutzer, der auf Website A Redakteur ist, hat auf Website B vielleicht keine Rolle. Für ein netzwerkweites Personenverzeichnis:
- Setzen Sie
blog_id, wenn Rollen auf eine Website begrenzt sein sollen. - Nehmen Sie nicht an, dass
role__in => array( 'author' )auf jedem Blog dieselben Personen bedeutet. - Speichern Sie für websiteübergreifende Mitarbeiterlisten ein Netzwerk-Metaflag (zum Beispiel
wpp_show_in_directory) und fragen Sie dieses ab statt nur Rollen.
Der Quellcode erklärt den Grund. Auf Multisite fügt selbst eine Abfrage ohne Rollenargumente eine EXISTS-Klausel auf den Schlüssel {prefix}capabilities der aktuellen Website hinzu, sodass nur deren Mitglieder zurückkommen. Mit 'blog_id' => 0 entfallen die Rollen- und Mitgliedschaftsklauseln, und die Abfrage läuft über das ganze Netzwerk.
Staging-Kopien von Multisite kürzen oft die Benutzertabelle. Messen Sie die Abfragekosten auf einem Dump mit der Kardinalität der Produktion, sonst sieht Ihr meta_query-Plan bis zum Launch-Tag gut aus.
Wie Sie das SQL von WP_User_Query mit Query Monitor testen
Vor dem Merge:
- Aktivieren Sie Query Monitor auf Staging.
- Öffnen Sie die Verzeichnis-URL mit der schwersten Filterkombination, die das Marketing nutzt.
- Bestätigen Sie eine primäre Benutzerabfrage, nicht eine Meta-Abfrage pro Karte.
- Speichern Sie
$query->requestin den PR-Notizen, damit die nächste Person Regressionen erkennt.
Muss das Verzeichnis eine Freitextsuche über Anzeigenamen unterstützen, nutzen Sie einen eigenen Suchindex oder ein eng begrenztes search-Argument in WP_User_Query. Kein LIKE über user_email auf einem öffentlichen Endpoint. Protokollieren Sie langsame Verzeichnis-Requests mit den Filterdaten, damit Sie sie später nachstellen können.
Soll ein eigenes WordPress-Verzeichnis oder eine Mitgliederabfrage vor dem Livegang geprüft werden? Sprechen Sie mit einem WordPress-Entwickler.
Checkliste für ein Mitgliederverzeichnis mit WP_User_Query
- Nutzen Sie
fields, damit Kartenraster keine vollständigen Benutzerobjekte laden, und bleiben Sie bei höchstens drei Feldern, um den eingebauten Cache zu behalten. - Paginieren Sie mit
number+pagedund vertrauen Sieget_total()für die Seitenrechnung; ohne Paginierungcount_total => false. - Bevorzugen Sie exakte Metaschlüssel gegenüber
LIKEauf serialisierten Fähigkeits-Arrays. - Laden Sie Meta vor mit
update_meta_cache()vor der Schleife. - Cachen Sie ID-Listen oder Markup, wenn die Filter stabil sind; invalidieren Sie beim Schreiben von Profilen.
- Schützen Sie die Privatsphäre: nur
display_namerendern, Logins, Nicenames und E-Mails aus öffentlichem HTML und öffentlicher Suche heraushalten, Ausgaben immer escapen. - Prüfen Sie
/wp/v2/usersaus Sicht eines anonymen Besuchers. - Messen Sie das SQL (
$query->request) auf Staging mit realistischem Benutzer- und Meta-Volumen vor dem Launch.
Benutzer in WordPress sind Entitäten genau wie Beiträge. Fragen Sie sie mit derselben Präzision ab wie WP_Query.
Die Umsetzung dieses Themas betreuen wir im Rahmen unseres WordPress-Sicherheitsaudits.







