Mestring av WP_User_Query: bygg en skalerbar medlemskatalog i WordPress

Mestring av WP_User_Query: bygg en skalerbar medlemskatalog i WordPress

Sist verifisert: 20. september 2026
8 min lesetid
Guide
Full-stack-utvikler
Core Web Vitals

WP_Query får de fleste tutorialene. WP_User_Query er det medlemsportaler, ansattkataloger og fellesskapssider faktisk kjører på. Trenger du bare et «Vårt team»-rutenett, slår en lett spørring i temaet en medlemsplugin som dra med egne maler, REST-ruter og CSS på hvert request.

Denne guiden holder seg til det problemet: filtrere brukere med role__in, meta_query og paginering uten å steke databasen eller lekke påloggingsnavn. Eksemplene er skrevet for PHP-temaer og mu-plugins, ikke for page-builder-shortcodes. Offisiell referanse: WP_User_Query og get_users().

#get_users() vs WP_User_Query

get_users() er en tynn wrapper rundt WP_User_Query. Den tar samme argument-array og returnerer WP_User-objekter (eller feltutvalg når du setter fields).

Bruk get_users() når du vil ha en kort liste og ikke noe mer - for eksempel fem redaktører til en byline-widget.

Bruk WP_User_Query når du trenger:

  • get_results() pluss get_total() til paginerte kataloger
  • inspeksjon av $query->request mens du debugger SQL
  • samme args-form som get_users(), men med eksplisitt spørringsobjekt

Argument-arrayen er kontrakten. Eksemplene under bruker WP_User_Query fordi kataloger trenger totaler. Begge API-ene leser samme dokumentasjon; forskjellen er kontrollen du får over resultatet etter spørringen.

#Bygg en teamsiden med role__in

Redaktører og forfattere, sortert etter visningsnavn, tolv per side - typisk «ansatte»-side for et norsk byrå:

$paged = max( 1, (int) get_query_var( 'paged', 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 ) ) {
	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>';
}

I produksjon:

  • role__in matcher hvilken som helst av rollene. Foretrekk det fremfor flere spørringer med role.
  • fields gir stdClass-rader i stedet for fulle WP_User-objekter.
  • Bio krever fortsatt meta-lesing. Er rutenettet hot, cache HTML eller lag en denormalisert katalograd.

#Paginering uten å laste alle brukere

Args: number (sidestørrelse) og paged (1-basert). get_total() er antall treff på tvers av sider - ikke bare inneværende side.

$per_page = 24;
$paged    = max( 1, (int) ( $_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();

Unngå:

  • 'number' => -1 og array_slice i PHP
  • ekstra full spørring bare for å telle når get_total() allerede finnes
  • usikre $_GET-verdier rett inn i orderby uten allow-list

På offentlige kataloger: egen query var (udir_page) så du ikke kolliderer med bloggens paged.

#Avansert filtrering med meta_query

Tenk medlemskatalog for behandlere: abonnenter i Oslo, offentlig profil, ferdighet «PHP» (eller «fysioterapi» i en helseportal).

$args = [
	'role'       => 'subscriber',
	'number'     => 20,
	'paged'      => 1,
	'fields'     => [ 'ID', 'display_name' ],
	'meta_query' => [
		'relation' => 'AND',
		[
			'key'     => 'city',
			'value'   => 'Oslo',
			'compare' => '=',
		],
		[
			'key'     => 'is_public_profile',
			'value'   => '1',
			'compare' => '=',
		],
		[
			'key'     => 'skills',
			'value'   => 'PHP',
			'compare' => 'LIKE', // dyrt på serialiserte arrays
		],
	],
];

$directory = new WP_User_Query( $args );

Praktiske regler:

  • Eksakt = på egne nøkler (city, is_public_profile) slår LIKE på serialiserte blobs.
  • Lagre ofte brukte ferdigheter som booleans (skill_php = 1) hvis du filtrerer dem mye.
  • Hver klausul er en join på wp_usermeta - hold relation-trær korte.
  • Kombiner alltid offentlige filtre med et synlighetsflagg, slik at private profiler aldri dukker opp for anonyme.

For kataloger over ca. ti tusen aktive profiler: vurder Elasticsearch (ElasticPress) eller egen oppslagstabell. Hold WP_User_Query til å hydrere en liten ID-liste.

#Ytelse: fields, telling, cache

Når katalogen er offentlig og filtrert, blir brukerspørringer dyre fort. Et ansatt-rutenett med tolv kort er ufarlig. Et «finn behandler»-søk med by, spesialitet og språk mot titusenvis av wp_usermeta-rader er en annen historie - særlig på delt hosting der MySQL allerede deler CPU med naboene.

#Begrens feltene

$args = [
	'role'   => 'subscriber',
	'number' => 100,
	'fields' => [ 'ID', 'display_name' ],
];

Dropp user_email fra offentlige maler. Behold det bak current_user_can( 'list_users' ) i admin-verktøy.

#Tell uten å hydrere profiler

$query = new WP_User_Query(
	[
		'role'   => 'subscriber',
		'fields' => 'ID',
	]
);
$count = $query->get_total();

Uten filtre: count_users() er billigere fordi WordPress allerede holder rolletellinger. Bruk det til «Vi har X medlemmer»-statistikk, ikke til filtrerte katalogtreff.

#Cache stabile resultatsett

Endres katalogen sjelden, lagre ID-er (eller ferdig HTML) i en transient nøklet på filter-hash. Invalider når relevante meta-nøkler oppdateres (updated_user_meta). Cache aldri HTML med kapasitetsstyrte felt for anonyme. Transient-TTL på 15-60 minutter er ofte nok for medlemskataloger som oppdateres i arbeidstiden.

#Unngå N+1

I løkken multipliserer get_avatar() og gjentatte get_user_meta(). Prefetch kjente nøkler for ID-settet på siden, eller skriv en katalograd ved profil-lagring. Gravatar-kall ute kan også dominere TTFB - vurder lokal avatar-meta hvis katalogen er hot.

#Sikkerhet: user enumeration

En medlemskatalog er en offentlig kontoliste. Behandle den deretter - særlig med personvernregler i Norge/EU. Botene som treffer /?author=1 bryr seg ikke om at katalogen din «bare er for medlemmer» hvis HTML-en allerede lekker login-navn.

  1. Aldri echo $user->user_login i frontend. Bruk display_name eller et offentlig kallenavn.
  2. Aldri user_email uten innlogging og kapasitet.
  3. Forfatterarkiv (/?author=1) lekker ofte login. Trenger du dem ikke:
add_action(
	'template_redirect',
	static function () {
		if ( is_author() ) {
			wp_safe_redirect( home_url( '/' ), 301 );
			exit;
		}
	}
);
  1. Escape alt: esc_html(), esc_url(), esc_attr().
  2. Admin-kataloger bak list_users, ikke bak «hemmelig» URL.

#Sette sammen en katalogmal

En offentlig katalog som tåler drift har vanligvis fire lag:

  1. Input - allow-list for sortering, heltall for side, normalisert by-slug (oslo, ikke fri tekst rett i SQL).
  2. Spørring - role__in / meta_query / fields / number / paged.
  3. Presentasjon - kort fra ID-er og trygge visningsfelt.
  4. Cache-invalidering - knyttet til meta-nøklene som styrer synlighet.

La plugin håndtere påmelding og meldinger hvis du trenger det. La listing og filtrering ligge i temaet eller en liten mu-plugin du eier. Det skillet holder katalogen rask når markedet ber om «ett filter til» neste kvartal.

På norske medlemsportaler (idrettslag, fagforeninger, behandlerkataloger) ser vi ofte at BuddyBoss eller Ultimate Member eier profilskjemaet, mens den offentlige søkesiden er custom PHP. Det er en sunn deling: tung UI for kontoen, lett spørring for listen.

#Personvern og synlighet (Norge / GDPR)

En katalog over personer er personopplysninger. Praktisk i kode:

  • Standard for nye kontoer: is_public_profile = 0 til brukeren aktivt krysser av.
  • Egne felt for «vis telefon» / «vis e-post» - ikke anta at profil = offentlig kontaktinfo.
  • Logg hvem som eksporterer medlemslister fra wp-admin (capability + audit hvis volumet er stort).
  • Staging-databaser med ekte medlemmer skal anonymiseres før de deles med frilansere.

WP_User_Query filtrerer teknisk. Den erstatter ikke samtykke, formål eller slettpolicy. Men uten synlighetsflagg i meta_query lekker du profiler uansett hvor fin personvernerklæringen er.

#Når du bør droppe plugin-katalogen

Tunge medlemsplugins løser påmelding, betalingsnivåer og meldinger. De er sjeldent optimale som den offentlige søkesiden:

  • ekstra HTTP-assets på forsiden
  • egne shortcodes som kjører brede spørringer uten fields
  • REST-endepunkter som eksponerer mer enn kort-gridet trenger

Behold plugin for kontoflyt. Bygg listen selv med WP_User_Query. Hvis du allerede er låst til pluginens grid, mål SQL med Query Monitor før du godtar «det er greit nok» - ofte er det én SELECT uten LIMIT gjemt bak et fancy filter-UI.

#Multisite og delte brukertabeller

På multisite er brukere delt, roller er per site. For nettverksomfattende ansatte-lister: nettverksmeta (wpp_show_in_directory) i stedet for bare role__in. Sett blog_id når roller skal scopes til ett nettsted.

Staging-kopier av multisite kutter ofte brukertabellen. Mål spørringskostnad på et dump som matcher produksjonsvolum - ellers ser meta_query-planen fin ut til lanseringsdagen, og så tar forsiden 800 ms med 40 000 rader i wp_usermeta.

#Test SQL-en du faktisk shipper

  1. Query Monitor på staging.
  2. Åpne katalog-URL med tyngste filterkombinasjon markedet bruker (by + fag + språk er en klassiker).
  3. Bekreft én primær brukerspørring, ikke én meta-spørring per kort.
  4. Lagre $query->request i PR-notater så neste person ser regresjoner.

Fritekst-søk på tvers av navn: dedikert søkeindeks eller stramt search-arg på WP_User_Query. Ikke LIKE mot user_email på offentlig endepunkt. Logg trege katalog-requests med filter-payload så du kan gjenskape dem.

#Sjekkliste før lansering

  1. fields satt - ikke fulle objekter til kort-grid.
  2. Paginering med number + paged og get_total().
  3. Eksakte meta-nøkler fremfor LIKE på serialiserte arrays.
  4. Cache av ID-lister der filtrene er stabile; invalider ved profilskriving.
  5. Bare display_name i offentlig HTML; e-post bak kapasitet.
  6. Synlighetsflagg i meta_query for alle offentlige lister.
  7. Mål $query->request med realistisk bruker- og metvolum.
  8. Forfatterarkiv redirectet hvis produktet ikke trenger dem.

Brukere er førstklassige spørringsmål i WordPress. Behandle dem med samme disiplin som WP_Query.

Trenger du en skreddersydd medlemskatalog eller ytelsesgjennomgang av brukerspørringer, ta kontakt - vi bygger og drifter slike løsninger i WordPress.

Neste steg

Gjør artikkelen om til faktisk implementering

Denne blokken styrker intern lenking og sender leseren videre til de mest relevante tjenestene og innholdet.

Vil du få dette implementert på nettstedet ditt?

Hvis problemet er Core Web Vitals, treg rendering eller tung WordPress-kjoring, kan jeg definere og gjennomfore optimaliseringen.

Når skal jeg bruke WP_User_Query i stedet for get_users?#
Bruk get_users() til enkle lister der du bare trenger en array av WP_User-objekter. Bruk WP_User_Query når du trenger get_total() til paginering, inspeksjon av $query->request, eller tydelig livssyklus for spørringsobjektet.
Hvordan paginerer jeg en medlemskatalog trygt?#
Sett number (sidestørrelse) og paged (1-basert) i args, og bruk get_total() for totalt antall treff. Last aldri alle brukere inn i minnet for så å skjære med array_slice i PHP - det kollapser under ekte trafikk.
Hvorfor er meta_query tregt på store kataloger?#
Hver meta_query-klausul joiner wp_usermeta. LIKE mot serialiserte ferdighets-arrays tvinger tabellskann. Foretrekk eksakte nøkler, lagre offentlige flagg indeksert, eller flytt søk til Elasticsearch når katalogvolumet vokser forbi det MySQL-joins takler greit.
Hva skal jeg aldri skrive ut i en offentlig medlemsliste?#
Aldri user_login eller user_email. Vis display_name (eller et eget offentlig kallenavn i usermeta) og hold e-post bak kapasitetssjekk eller innlogget intranett-visning.
Fungerer WP_User_Query likt på multisite?#
Brukertabellen er delt, men roller er per nettsted. En redaktør på site A trenger ikke rolle på site B. For nettverksomfattende kataloger: bruk blog_id der roller skal scopes, eller et nettverksmeta-flagg i stedet for bare role__in.

Trenger du FAQ tilpasset bransje og marked? Vi lager en versjon som støtter dine forretningsmål.

Ta kontakt

Relaterte artikler