Betinget logikk i WordPress er ikke «litt PHP rundt malen». Det er kontrakten mellom spørringen, malhierarkiet og det du faktisk viser til brukeren. I 2026, med block themes og Full Site Editing, lever den kontrakten fortsatt i PHP for plugins, mu-plugins, enqueue-logikk og alle steder der en malfil eller et block pattern ikke strekker til.
Denne guiden er skrevet for utviklere som allerede kjenner The Loop, men som fortsatt møter rare false-positiver med is_front_page(), tomme resultater utenfor løkken, eller cache som serverer feil variant til innloggede brukere. Vi går fra taksonomisjekker til query-pitfalls, kapasitetskontroller, FSE-grenser, caching og hjelpere du kan unit-teste.
Når conditional tags i WordPress kan kalles
Conditional tags i WordPress leser hovedsakelig den globale $wp_query (eller den spørringen du sender inn). De sier ikke «hvilken fil som kjører». De svarer på spørsmål som: er denne forespørselen et enkeltinnlegg, et kategoriarkiv, forsiden, et søk?
Det har to konsekvenser:
- Kall dem for tidlig (før
wp-hooken har bygget hovedspørringen), og du får false eller uforutsigbare verdier. - Kall dem inne i en sekundær
WP_Queryuten å sende den spørringen inn, og du leser fortsatt hovedspørringen.
Dokumentasjonen for conditional tags er eksplisitt på at mange sjekker forutsetter at hovedspørringen er klar. I praksis: tema-setup i after_setup_theme er for tidlig for is_singular(). wp_enqueue_scripts, template_redirect og malfiler er trygge nok for de fleste tilfeller. For plugins som må forgrene seg tidlig, lytt på wp eller senere, eller injiser logikken via hooks der konteksten allerede finnes.
Vanlige feil med is_singular og is_front_page
is_singular uten post type
is_singular() uten argument er true for ethvert enkelt objekt i hovedspørringen: innlegg, side, CPT. Det er nyttig når du vil laste felles CSS for alle singles. Det er farlig når du egentlig mente «bare vanlige innlegg»:
// For bredt hvis du bare vil treffe blogginnlegg.
if ( is_singular() ) {
wp_enqueue_script( 'share-buttons' );
}
// Eksplisitt.
if ( is_singular( 'post' ) ) {
wp_enqueue_script( 'share-buttons' );
}is_single() er snevrere (ikke sider, ikke attachments i vanlig bruk), men for CPT er is_singular( 'product' ) mer lesbart enn en blanding av is_singular() og manuelle get_post_type()-sjekker.
is_front_page versus is_home
Norske redaksjonsnett bruker ofte «statisk forside + innleggsside». Da er:
is_front_page()true på den valgte forsidesiden (eller bloggindekset hvis «dine siste innlegg» er satt som forside).is_home()true på innleggsindeksen, som kan være en egen side, ikke forsiden.
En klassisk feil er å putte hero-logikk bak is_home() og tro at den treffer forsiden. En annen er å bruke is_front_page() || is_home() uten å vite hvilken av de to som skal ha hvilken layout. Skill dem i kode og i kommentar:
if ( is_front_page() && ! is_home() ) {
// Statisk forside.
}
if ( is_home() && ! is_front_page() ) {
// Innleggsside som ikke er forsiden.
}Feil postkontekst etter en sekundær loop
Hvis du kjører query_posts() (unngå det) eller glemmer wp_reset_postdata() etter en sekundær loop, kan conditional tags fortsatt se riktig hovedspørring, mens the_post()-baserte hjelpere ser feil post. Symptom: is_singular( 'post' ) er true, men in_category( 'nyheter' ) treffer feil innlegg. Roten er nesten alltid global postkontekst, ikke selve conditional taggen.
Bruke in_the_loop og has_term utenfor løkken
in_the_loop() forteller om du er inne i hovedløkken for den aktuelle spørringen. Mange funksjoner som the_title(), the_content() og implisitte varianter av in_category() / has_term() forventer at gjeldende post er satt via the_post().
Utenfor løkken:
// Usikkert: avhenger av hvilken post som tilfeldig ligger i global $post.
if ( in_category( 'nyheter' ) ) {
// ...
}
// Eksplisitt post-ID.
if ( has_term( 'nyheter', 'category', $post_id ) ) {
// ...
}I widgets, shortcodes, REST-callbacks og block render-callbacks er du ofte utenfor The Loop. Da skal alle taksonomi- og meta-sjekker ta et eksplisitt postobjekt eller ID. En praktisk regel på WPPoland-prosjekter: hvis funksjonen kan kalles fra mer enn én kontekst, er tredje argument på has_term() obligatorisk.
function wppoland_post_has_news_category( int $post_id ): bool {
return has_term( 'nyheter', 'category', $post_id );
}current_user_can og innloggingsgrener
Kapasitetssjekker er betinget logikk på brukeren, ikke på spørringen. Typisk mønster:
if ( current_user_can( 'edit_post', $post_id ) ) {
// Vis «rediger»-lenke, intern merknad, A/B-flagg for redaksjon.
}Tre punkter som ofte glemmes:
is_user_logged_in()er billig, men for grov. En abonnent og en redaktør er begge «innlogget».current_user_can( 'manage_options' )er ikke det samme som «er administrator på dette nettstedet» i multisite.- Aldri cache HTML som inneholder kapasitetsgrener som om den var anonym. Se caching-seksjonen under.
For plugins: hold capability-strenger i konstanter eller én policy-klasse, slik at du ikke sprer magiske strenger i malene. Det gjør også tester enklere fordi du kan mocke policyen uten å bootstrappe hele brukersystemet i hvert scenario.
Malhierarki eller conditional tags i WordPress-temaer
Template hierarchy er WordPress sin declarative routing: single-product.php, taxonomy-product_cat.php, front-page.php, home.php, og i block themes tilsvarende HTML-maler under templates/. Når hierarkiet allerede skiller kontekst, trenger du ikke å gjenskape den med if ( is_singular( 'product' ) ) inne i en generisk single.php.
Bruk hierarkiet når:
- hele layouten er annerledes (WooCommerce-produkt vs blogginnlegg),
- du vil at en juniorutvikler skal finne filen uten å lese en 200-linjers
if/else, - du jobber i et klassisk tema der filnavn fortsatt er kontrakten.
Bruk PHP-conditionals når:
- bare én seksjon endres (disclaimer, schema, enqueue),
- konteksten krysser flere maltyper (felles script på singular CPT A og B),
- logikken lever i et plugin som ikke eier malfilene.
En sunn fordeling på større codebases: hierarki for makrostruktur, små helpers for mikrobeslutninger. Hvis single.php inneholder mer enn to nivåer med nøstede conditionals for post type og taksonomi, er det et signal om å splitte mal eller flytte beslutningen til en navngitt helper.
Conditional tags i block themes og FSE
I et block theme erstattes mange PHP-maler av HTML-maler og template parts. Conditional tags forsvinner ikke. De flytter seg:
- til
functions.php/ et theme-plugin forwp_enqueue_scripts, - til block render-callbacks (
render_callback) der dynamiske blokker bygges, - til hooks som
body_class,render_block,pre_render_block.
Begrensninger du må designe rundt:
- En ren HTML-mal kan ikke kalle
has_term()direkte. Du trenger en dynamisk blokk, et pattern med PHP via plugin, eller enqueue/body_class-logikk utenfor malen. theme.jsonog style variations uttrykker design-tokens, ikke forretningsregler som «vis mva-tekst bare påproduct_cat: mat».- Site Editor lar redaktører bytte template parts visuelt. Hardkodede PHP-grener i et gammelt child theme kan da «forsvinne» fra synlig UI mens de fortsatt kjører i hooks. Dokumenter hvor beslutningen lever.
Praktisk mønster for FSE-prosjekter:
add_filter(
'render_block',
static function ( string $block_content, array $block ): string {
if ( ( $block['blockName'] ?? '' ) !== 'core/group' ) {
return $block_content;
}
if ( empty( $block['attrs']['className'] ) || ! str_contains( $block['attrs']['className'], 'is-news-only' ) ) {
return $block_content;
}
if ( ! is_singular( 'post' ) || ! has_term( 'nyheter', 'category', get_queried_object_id() ) ) {
return '';
}
return $block_content;
},
10,
2
);Klassen is-news-only blir kontrakten mellom editor og kode. Redaktøren ser gruppen; PHP bestemmer synlighet uten å duplisere hele malen.
Taksonomier: in_category versus has_term
Tilbake til kjernen i tittelen. in_category() er en tynn wrapper rundt native category. Den forstår ikke product_cat, post_tag (bruk has_tag() / has_term()), eller egne taksonomier.
if ( in_category( 'nyheter' ) ) {
// Bare category.
}
if ( has_term( 'jeans', 'product_cat' ) ) {
// Fungerer for WooCommerce og andre CPT-taksonomier.
}Foretrekk has_term() i ny kode, også for vanlige kategorier. Da er API-et likt på tvers av innholdstyper, og du kan bytte taksonominavn uten å bytte funksjon. Bruk term-ID når du har den: det unngår slug-oppslag og er mer stabilt ved rename av slug.
Sjekke underkategorier med has_term
has_term( 'frukt', 'category', $post_id ) er false hvis innlegget bare er merket eple, selv om eple er barn av frukt. Kjernen gjør eksakt match. For «innlegg i denne grenen av treet» trenger du en hjelper:
function wppoland_post_in_descendant_category( int $post_id, int $parent_term_id ): bool {
$descendants = get_term_children( $parent_term_id, 'category' );
if ( is_wp_error( $descendants ) ) {
return false;
}
$term_ids = array_map( 'intval', $descendants );
$term_ids[] = $parent_term_id;
return has_term( $term_ids, 'category', $post_id );
}get_term_children() er memoized via object cache i vanlige oppsett, men på store trær bør du ikke kalle den inne i en loop over tusenvis av poster uten å cache resultatet per parent-ID i requesten.
is_category eller in_category
Skill disse to:
| Behov | Funksjon |
|---|---|
| Er vi på kategoriarkivet «nyheter»? | is_category( 'nyheter' ) |
| Er det aktuelle innlegget i «nyheter»? | in_category() / has_term() |
| Er vi på et egendefinert taksonomiarkiv? | is_tax( 'product_cat', 'jeans' ) |
| Er produktet merket «jeans»? | has_term( 'jeans', 'product_cat', $post_id ) |
WooCommerce-fellen: is_product_category() inne i en generisk Loop over produkter på forsiden er false, fordi du ikke er på kategoriarkivet. Bruk has_term() med produkt-ID.
Page cache for innloggede brukere i WordPress
Page cache (Nginx FastCGI cache, Cloudflare Cache Everything, plugin full-page cache) lagrer HTML for en URL. Conditional tags som leser spørringen er vanligvis trygge: samme URL, samme is_singular(), samme arkiv. Conditional tags som leser brukeren er det ikke.
Regler som holder i produksjon:
- Anonym HTML skal ikke inneholde grener styrt av
is_user_logged_in()ellercurrent_user_can()med mindre du har separate cache-varianter (Cookie,Cache-Control: private, bypass forwordpress_logged_in_*). - Object cache (Redis/Memcached) er fin for
has_term()og term-trær. Den erstatter ikke page-cache-disiplin. - Fragment cache / ESI / client-side hydrate er alternativene når du trenger «rediger»-lenker på ellers cachet side.
DONOTCACHEPAGE-konstanter og plugin-hooks finnes, men bruk dem smalt. En hel side satt til uncacheable fordi én widget sjekker kapasitet er en dyr løsning.
Testmatrise før lansering: anonym, abonnent, redaktør, på forside, single, kategoriarkiv. Hvis anonym og redaktør får samme HTML på en URL som skal vise ekstra UI, har du en cache-bug, ikke en conditional-bug.
Unit-teste conditional tags i WordPress
Global state ($wp_query, $post, current user) gjør direkte kall til is_singular() vanskelig å teste. Pakk beslutningene:
final class Wppoland_Request_Context {
public function __construct(
private WP_Query $query,
) {}
public function is_news_singular(): bool {
if ( ! $this->query->is_singular( 'post' ) ) {
return false;
}
$post_id = (int) $this->query->get_queried_object_id();
return has_term( 'nyheter', 'category', $post_id );
}
}I WP_UnitTestCase (eller en integrasjonstest med Brain Monkey for isolasjon) bygger du en WP_Query, factory-poster med termer, og assert-er på hjelperen. Unngå å assert-e på HTML fra hele temaet når du egentlig tester én boolsk beslutning.
Retningslinjer:
- Ta
WP_Post|intogWP_Querysom argumenter der det er mulig. - Returner bool eller små value objects, ikke echo.
- Hold side effects (enqueue, header, redirect) i et tynt adapter-lag som kaller hjelperen.
- Én helper, én grunn.
wppoland_should_show_vat_notice( $post_id )er lettere å navngi i en feilrapport enn et 40-linjers if-tre ifunctions.php.
Ytelse for has_term og get_term_children
Én has_term() mot object cache er billig. Det som blir dyrt:
- rekursive
get_term_children()per rad i en stor admin-tabell, - N+1 der du resolver slug til term i en loop i stedet for å bruke ID,
- conditional enqueue som treffer
filemtimepå disk for filer du ikke trenger.
Mål før du optimaliserer: Query Monitor på en single, et arkiv og forsiden. Hvis conditional-grenen ikke synes i slow queries, er lesbarhet viktigere enn mikrooptimalisering.
is_main_query og pre_get_posts: endre bare hovedspørringen
Vil du endre hva et arkiv viser (antall innlegg, utelate en kategori, sortering), er pre_get_posts riktig hook, ikke en ny WP_Query i malen. Hooken får WP_Query-objektet som referanse og kjører for alle spørringer: hovedspørringen, sekundære spørringer fra widgets og blokker, og spørringene i adminpanelet.
To regler fra referansen for pre_get_posts fjerner de fleste feilene:
- Bruk metodene på objektet du fikk (
$query->is_main_query(),$query->is_category()), ikke de globale funksjonene. Dokumentasjonen forklarer atis_main_query()tester den globale instansen$wp_query, ikke den hooken sendte inn. Siden for is_main_query() kaller den bruken «sirkulær». - Beskytt adminpanelet med
is_admin(). Uten den sjekken endrer samme regel også innleggslisten i wp-admin.
add_action(
'pre_get_posts',
static function ( WP_Query $query ): void {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_category( 'nyheter' ) ) {
$query->set( 'posts_per_page', 20 );
}
}
);Samme referanse advarer om at noen conditional tags ennå ikke virker inne i pre_get_posts: is_front_page() feiler, is_home() virker. Skal regelen gjelde «bare den statiske forsiden», sammenlign $query->get( 'page_id' ) med get_option( 'page_on_front' ), eller flytt logikken til template_redirect, der spørringen allerede er løst.
Hvilke sider is_archive dekker og når is_tax passer
is_archive() er en samlesjekk. I kjernekoden (class-wp-query.php) blir flagget is_archive sant når spørringen er et post type-arkiv, et dato-, forfatter-, kategori- eller stikkordarkiv, eller et arkiv for en egendefinert taksonomi. Håndboken for conditional tags oppsummerer: kategori-, stikkord-, forfatter- og datosider er alle arkiver.
Det får direkte følger:
is_archive()alene laster samme CSS på forfatterarkivet og påproduct_cat-arkivet. Gjelder det bare kategoriarkiver for bloggen, brukis_category().is_tax()returnerer false på kategori- og stikkordarkiver. Referansen for is_tax() sier at den er sann for arkiver av egendefinerte taksonomier og usann for de innebygde. For å dekke alle tre, skrivis_category() || is_tag() || is_tax().is_post_type_archive( 'produkt' )er bare sann hvis post typen ble registrert medhas_archive => true, som samme håndbok minner om.
Ingen av disse funksjonene sier noe om et enkelt innlegg. På en enkeltvisning er is_tax() false selv om innlegget har termen; der er has_term() riktig spørsmål.
is_page_template i block themes
is_page_template() har fått ny betydning med block themes, uten at signaturen er endret. I kjernen (post-template.php) returnerer funksjonen false utenfor is_singular() og sammenligner deretter argumentet med get_page_template_slug() for det forespurte objektet, altså verdien som er lagret på innlegget.
I et klassisk tema er den verdien PHP-filnavnet, så is_page_template( 'landing.php' ) virker. I et block theme bruker egne maler registrert under customTemplates i theme.json HTML-filnavnet uten filendelse som name, ifølge håndboken for egne maler. En mal /templates/landing.html sjekkes med is_page_template( 'landing' ). Kopierer du betingelser fra et klassisk tema til et block theme uten å justere dette argumentet, får du en if som aldri blir sann, uten varsel og uten feil.
Sjekkliste for betinget logikk i WordPress
- Er beslutningen makro (malhierarki / FSE-template) eller mikro (helper / hook)?
- Kjører sjekken etter at hovedspørringen er klar?
- Er post-ID eksplisitt når koden kan leve utenfor The Loop?
- Er brukergrener holdt utenfor anonym page cache?
- Finnes det en unit-test eller minst en WP_UnitTestCase for hjelperen?
- Bruker du
has_term()(eller bevisstin_category()) konsistent? - Bruker
pre_get_posts-callbacks$query->is_main_query()og utelateris_admin()?
Oppsummering
Betinget logikk i WordPress er tre lag: query-flagg (is_singular, is_front_page, is_category), postdata (has_term, in_the_loop), og bruker (current_user_can). Malhierarki og FSE flytter hvor du skriver beslutningen, ikke om du trenger den. Cache straffer deg bare når du blander brukergrener inn i anonym HTML. Testbare hjelpere med eksplisitte argumenter er det som skiller en SOP fra enda en rotete functions.php.
Forskjellen på has_term(), is_category() og in_category() avgjør om en mal oppfører seg likt i arkivet og i enkeltinnlegget. Vi rydder gjerne opp i slik betinget logikk, se WordPress-utvikling for maler og taksonomier.







