Conditional Tags entscheiden in Themes und Plugins, welche Assets geladen werden, welcher Disclaimer erscheint und welche Capability nötig ist. In 2026 bleiben sie relevant: Full Site Editing verschiebt Layout in Blöcke, aber Query-Kontext, Capabilities und Cache-Grenzen liegen weiter in PHP. Wer diese Trennung ignoriert, baut Templates, die auf der Staging-Site mit Admin-Session korrekt wirken und im anonymen CDN-Cache das falsche Markup ausliefern.
Dieser Guide trennt Seitenkontext (is_*) von Postdaten (has_term, in_category), zeigt typische Fallstricke um is_front_page und is_singular, erklärt in_the_loop und current_user_can, und endet bei testbaren Helpern statt verstreuter If-Ketten. Die Codebeispiele sind bewusst klein; die Regeln dahinter sollen in echten Projekten als Module landen, nicht als Copy-Paste in zehn Template Parts.
Wofür WordPress Conditional Tags 2026 gebraucht werden
Die Template Hierarchy wählt die Datei. Conditional Tags steuern Verhalten innerhalb dieser Datei und in Hooks wie wp_enqueue_scripts, the_content oder body_class. Beide Ebenen zu vermischen erzeugt Bugs, die erst auf Staging sichtbar werden.
Typische Einsatzorte:
- schwere Skripte nur auf
is_singular( 'product' )oder einer bestimmten Taxonomie laden - Hinweise nur an Beiträge mit dem Term
rechtlichesanhängen - Admin-UI oder REST-Callbacks hinter
current_user_can( 'edit_posts' )absichern - Cache-Keys so bauen, dass eingeloggte Nutzer keinen öffentlichen HTML-Cache bekommen
Die offizielle Referenz bleibt Conditional Tags im Theme Handbook; Hooks und Filter sind im Plugin Handbook dokumentiert.
Woher Conditional Tags ihren Zustand lesen
Conditional Tags werten die URL nicht selbst aus. Nach parse_request und WP_Query hält das globale Objekt $wp_query Flags wie is_single, is_page, is_front_page und is_category. Funktionen wie is_singular() und is_front_page() sind Wrapper um diese Flags.
Praktische Folge: Wer ein Conditional Tag zu früh aufruft, vor wp / template_redirect, bekommt ein undefiniertes oder falsches Ergebnis. Das Handbuch sagt es klar: Ein Conditional Tag direkt in der functions.php funktioniert nicht, es muss in einer Funktion stehen, die an eine spätere Action oder einen späteren Filter gehängt ist.
Zweite Folge: Ein sekundäres WP_Query (Widget, Block mit neuesten Beiträgen, Shortcode) ändert die Flags der Hauptabfrage nicht. is_singular() beschreibt weiter die angefragte Seite, nicht den Beitrag, über den das Widget gerade iteriert. Für Daten eines bestimmten Beitrags übergeben Sie die ID oder das WP_Post-Objekt an die Post- und Term-APIs.
Seitenkontext: is_singular, is_front_page und Verwandte
Unterschied zwischen is_singular und is_single
is_singular() ist wahr für jeden Einzelansicht-Request: Post, Page, Attachment und Custom Post Type. Wer nur Blogbeiträge meint, braucht is_singular( 'post' ) oder is_single().
if ( is_singular( 'post' ) ) {
// Nur native Posts, keine Pages und kein CPT.
}
if ( is_singular( array( 'post', 'case_study' ) ) ) {
// Mehrere Typen in einem Check.
}Häufiger Fehler: In einem Template-Part für Archive is_singular() erwarten. Archive setzen den Query-Zustand anders; dort sind is_archive(), is_category() oder is_tax() die richtigen Tags.
is_front_page() und is_home() trennen
WordPress unterscheidet Startseite und Blog-Index:
is_front_page()- die Seite, die unter Einstellungen > Lesen als Startseite gilt (statische Seite oder Posts)is_home()- der Blog-Index (Posts-Seite oder Startseite, wenn dort Posts angezeigt werden)
Wenn die Startseite eine statische Page ist und Beiträge auf /blog/ liegen, gilt auf /:
is_front_page()ist trueis_home()ist falseis_page()ist true (die zugewiesene Page)
Auf /blog/ gilt:
is_home()ist trueis_front_page()ist false
Ein Check if ( is_home() ) für „nur auf der Startseite“ ist deshalb falsch, sobald eine separate Posts-Seite existiert. Für Marketing-Module auf der echten Startseite: is_front_page(). Für Blog-Listen-Layout: is_home().
get_queried_object außerhalb des Loops nutzen
Außerhalb des Hauptloops liefert get_queried_object() den Kontext (Term, Page, Post). Conditional Tags lesen denselben Zustand, aber Helper, die Term-IDs brauchen, sollten das Objekt explizit nehmen statt global $post zu raten.
$object = get_queried_object();
if ( is_category() && $object instanceof WP_Term ) {
$parent_id = (int) $object->parent;
}in_the_loop und Post-ID außerhalb des Loops
in_the_loop() ist true, während der Hauptloop läuft (have_posts() / the_post()). Viele Conditional Tags und Template-Tags erwarten diesen Zustand oder zumindest einen gesetzten globalen Post.
Probleme entstehen, wenn Sie:
- in
wp_headoderwp_enqueue_scriptsauf$postzugreifen, bevor der Loop startet - in Widgets oder Shortcodes den Hauptloop und einen Nested Loop vermischen
- nach
wp_reset_postdata()weiterhin Annahmen aus dem Nested Loop treffen
Muster für sichere Post-Checks außerhalb des Loops:
function wppoland_current_post_id(): int {
if ( in_the_loop() ) {
return (int) get_the_ID();
}
$object = get_queried_object();
if ( $object instanceof WP_Post ) {
return (int) $object->ID;
}
return 0;
}Taxonomie-Checks gehören an die Post-ID, nicht an den Seitenkontext:
$post_id = wppoland_current_post_id();
if ( $post_id && has_term( 'news', 'category', $post_id ) ) {
// Term am konkreten Post, unabhängig von is_category().
}is_category() sagt „diese Request ist ein Kategorie-Archiv“. in_category() / has_term() sagen „dieser Post trägt den Term“. Beide zu verwechseln ist der Klassiker in Theme-Forks.
Kategorien und Taxonomien: in_category vs has_term
in_category() nur für die native Kategorie
if ( in_category( 'news' ) ) {
// Nur taxonomy = category.
}in_category() deckt Custom Taxonomies nicht ab. WooCommerce product_cat, Project-Terms oder Themen-Taxonomien brauchen has_term().
has_term() für Custom Taxonomies
if ( has_term( 'jeans', 'product_cat' ) ) {
// Term in product_cat am aktuellen Post.
}
if ( has_term( array( 'jeans', 'hemden' ), 'product_cat', $post_id ) ) {
// Mehrere Terms, explizite Post-ID.
}In Plugins und Mu-Plugins die Post-ID immer übergeben. So bleiben Unit-Tests und CLI-Kontexte stabil, auch wenn kein globaler Post gesetzt ist.
Unterkategorien mit has_term prüfen
Native Checks sind exakt. Ein Post nur in apfel erfüllt has_term( 'obst', 'category' ) nicht, auch wenn apfel Kind von obst ist.
/**
* Prüft, ob ein Post in einem Term oder einem seiner Nachkommen liegt.
*
* @param int|string|array $term Term-ID, Slug oder Array davon.
* @param string $taxonomy Taxonomy-Slug.
* @param int|null $post_id Optional Post-ID.
*/
function wppoland_post_in_term_or_descendant( $term, string $taxonomy, ?int $post_id = null ): bool {
$post_id = $post_id ?: wppoland_current_post_id();
if ( ! $post_id ) {
return false;
}
if ( has_term( $term, $taxonomy, $post_id ) ) {
return true;
}
$term_obj = get_term_by( is_numeric( $term ) ? 'id' : 'slug', $term, $taxonomy );
if ( ! $term_obj || is_wp_error( $term_obj ) ) {
return false;
}
$descendants = get_term_children( (int) $term_obj->term_id, $taxonomy );
if ( empty( $descendants ) || is_wp_error( $descendants ) ) {
return false;
}
return has_term( $descendants, $taxonomy, $post_id );
}get_term_children() kann bei sehr großen Bäumen teuer sein. Ergebnis pro Request cachen (statische Variable oder Transient mit klarer Invalidierung bei edited_term).
Benutzerrechte mit current_user_can prüfen
Rollen ändern sich zwischen Projekten. Capabilities bleiben die API-Grenze.
// Fragil: Rollennamen sind nicht stabil.
if ( in_array( 'editor', (array) wp_get_current_user()->roles, true ) ) {
// Vermeiden.
}
// Robust: Capability prüfen.
if ( current_user_can( 'edit_others_posts' ) ) {
// Editor-ähnliche Rechte ohne Rollenstring.
}
if ( current_user_can( 'edit_post', $post_id ) ) {
// Meta-Capability mit Objektkontext.
}In REST-Callbacks und Admin-AJAX gehört der Capability-Check vor Datenmutationen, zusammen mit Nonce-Prüfung. Conditional Tags wie is_admin() ersetzen das nicht: is_admin() sagt nur, dass der Request im Admin-Kontext läuft, nicht dass der Nutzer berechtigt ist.
Für Front-End-Varianten (Mitgliedsbereich, Preview):
add_action( 'template_redirect', function (): void {
if ( ! is_singular( 'member_doc' ) ) {
return;
}
if ( current_user_can( 'read_member_doc' ) ) {
return;
}
wp_safe_redirect( wp_login_url( get_permalink() ) );
exit;
} );Template Hierarchy versus PHP-Bedingungen
Die Hierarchy wählt Dateien nach Query-Typ: single-{post-type}.php, taxonomy-{taxonomy}-{term}.php, front-page.php, home.php, page-{slug}.php. PHP-if in einer generischen single.php ist oft ein Zeichen, dass eine spezifischere Template-Datei fehlen würde.
Faustregel:
- Strukturelle Unterschiede (anderes Markup, andere Sidebar): eigene Template-Datei laut Hierarchy
- Kleine Variationen (ein Banner, ein Script, eine CSS-Klasse): Conditional Tag oder
body_class-Filter - Wiederkehrende Regeln: Helper-Funktion, die von Template und Hook genutzt wird
add_filter( 'body_class', function ( array $classes ): array {
if ( is_singular( 'post' ) && has_term( 'longform', 'category' ) ) {
$classes[] = 'is-longform';
}
return $classes;
} );So bleibt die Template-Datei lesbar und die Regel testbar.
Block Themes ändern die Dateiauswahl (HTML-Templates im Theme), nicht die Query-Conditional-Tags. is_front_page() funktioniert weiterhin; nur der Ort, an dem Sie Markup setzen, wandert teilweise in Template Parts und Patterns.
Conditional Tags in Block Themes und Full Site Editing
Full Site Editing deckt Layout, Template Parts und viele Conditional Displays über Block-Variationen ab. Es ersetzt nicht:
- Capability-Checks vor Mutationen
- Query-abhängiges Enqueue von Drittanbieter-Skripten mit komplexer Logik
- Server-seitige Redirects und HTTP-Status
- Object-Cache- und Transient-Strategien um teure Term-Bäume
- CLI- und Cron-Kontexte ohne „aktuellen“ Besucher
Block-Sichtbarkeit im Editor (etwa „nur auf Beiträgen“) ist eine UI-Schicht über demselben Query-Zustand. Sobald die Regel Capabilities, Term-Hierarchien oder Cache-Keys berührt, gehört sie in PHP. Sonst debuggen Sie divergierende Header- und Single-Templates, die scheinbar dieselbe Bedingung meinen.
Server-Side-Rendering in Blöcken kann Conditional Tags nutzen, aber die Logik sollte in PHP-Helpern leben, nicht in kopierten Snippets in zehn Template Parts. Sonst driftet das Verhalten zwischen Header, Footer und Single-Template. Ein gemeinsamer Helper, den Block-Callbacks und klassische Templates importieren, hält die Regel eine Quelle.
Praktischer Split:
- FSE: Struktur, Spacing, wiederkehrende Parts
- PHP-Helper + Hooks: Berechtigungen, Taxonomie-Regeln, Cache-Bypass, Asset-Loading
Page Cache für eingeloggte Nutzer in WordPress
Full-Page-Caches (Plugin, Reverse Proxy, CDN) speichern HTML. Conditional Output für eingeloggte Nutzer in demselben Cache-Key wie für anonyme Besucher ist ein Sicherheits- und UX-Fehler.
Regeln:
- Varianten für
is_user_logged_in()vom öffentlichen Cache ausschließen oder separat keyen current_user_can()-abhängiges Markup niemals in den anonymen Page Cache legen- Edge-Caches: Cookie-basierte Bypass-Regeln für WordPress-Login-Cookies respektieren
- Fragment-Cache nur für wirklich anonyme Teile (Footer, Archivlisten ohne User-State)
add_action( 'wp_enqueue_scripts', function (): void {
if ( is_user_logged_in() && is_singular( 'post' ) ) {
wp_enqueue_script( 'wppoland-editor-hint' );
// Dieses Script darf nicht im öffentlichen HTML-Cache landen.
}
} );Object Cache bleibt für has_term() und Term-Meta meist der richtige Layer. Page Cache und Object Cache erfüllen unterschiedliche Aufgaben; Conditional Logic muss wissen, welcher Layer den Request sieht.
Bei WooCommerce und Mitgliedsbereichen: Shop-Seiten mit Warenkorb-Fragmenten und Account-Seiten standardmäßig aus dem Full-Page-Cache nehmen. Conditional Tags helfen bei der Diagnose (is_cart(), is_account_page()), ersetzen aber keine Cache-Konfiguration am Edge. Prüfen Sie nach Deploy mit einem anonymen und einem eingeloggten Browserprofil denselben URL-Pfad und vergleichen Sie Response-Header sowie sichtbare Fragmente. Weichen die HTML-Bodies trotz unterschiedlicher Session nicht ab, liegt oft ein zu aggressiver Shared Cache vor dem PHP-Kontext.
Conditional Tags in WordPress mit PHPUnit testen
Streuen Sie Conditional Tags nicht über Templates. Kapseln Sie Regeln:
final class Wppoland_View_Context {
public static function should_load_analytics(): bool {
if ( is_admin() || wp_doing_cron() || wp_doing_ajax() ) {
return false;
}
if ( current_user_can( 'manage_options' ) ) {
return false; // Optional: Admins von Tracking ausnehmen.
}
return is_front_page() || is_singular();
}
public static function post_shows_legal_banner( ?int $post_id = null ): bool {
$post_id = $post_id ?: wppoland_current_post_id();
return $post_id > 0 && has_term( 'rechtliches', 'category', $post_id );
}
}Vorteile:
- PHPUnit kann die Klasse mit gesetztem Query mocken oder über Wrapper testen
- Templates bleiben deklarativ:
if ( Wppoland_View_Context::post_shows_legal_banner() ) - Regeländerungen passieren an einer Stelle
Für Integrationstests den Query setzen ($wp_query->is_page = true und Verwandte) oder go_to() in der WordPress-Testsuite nutzen. Reine Unit-Tests kapseln die Conditional-Aufrufe hinter einem schmalen Interface, das Sie im Test stubben.
Performance von has_term und get_term_children
Einzelne has_term()-Aufrufe nutzen geladene Term-Caches und sind in normalen Themes unkritisch. Teuer wird es durch:
- rekursive
get_term_children()auf großen Taxonomien in jedem Loop-Durchlauf - wiederholte
get_term_by()ohne Memoization inthe_content-Filtern - Conditional Tags in verschachtelten Loops ohne
wp_reset_postdata()
Memoization pro Request:
function wppoland_memo_term_children( int $term_id, string $taxonomy ): array {
static $cache = array();
$key = $taxonomy . ':' . $term_id;
if ( isset( $cache[ $key ] ) ) {
return $cache[ $key ];
}
$children = get_term_children( $term_id, $taxonomy );
$cache[ $key ] = is_wp_error( $children ) ? array() : $children;
return $cache[ $key ];
}Assets konditional enqueuen statt global und per CSS zu verstecken: weniger JS auf Archiven, klarere Cache-Profile.
WooCommerce Produktkategorie und Custom Post Types prüfen
is_product_category() gilt für das Produktkategorie-Archiv, nicht für einen einzelnen Produkt-Post im Loop. Am Produkt:
if ( has_term( $slug, 'product_cat', $product_id ) ) {
// Korrekt im Produktkontext.
}Für CPT-Archive: is_post_type_archive( 'case_study' ). Für Einzelansicht: is_singular( 'case_study' ). Dieselbe Trennung wie bei Posts und Kategorien.
is_main_query und pre_get_posts: nur die Hauptabfrage ändern
Wer ändern will, was ein Archiv zeigt (Anzahl Beiträge, eine Kategorie ausschließen, Sortierung), nutzt pre_get_posts, nicht ein neues WP_Query im Template. Der Hook bekommt das WP_Query-Objekt per Referenz und läuft für jede Abfrage: die Hauptabfrage, sekundäre Abfragen aus Widgets und Blöcken und die Abfragen im Admin.
Zwei Regeln aus der Referenz zu pre_get_posts verhindern die meisten Fehler:
- Nutzen Sie die Methoden des übergebenen Objekts (
$query->is_main_query(),$query->is_category()), nicht die globalen Funktionen. Die Dokumentation erklärt, dassis_main_query()die globale Instanz$wp_queryprüft, nicht die vom Hook übergebene. Die Seite zu is_main_query() nennt diesen Aufruf „zirkulär“. - Schützen Sie den Admin mit
is_admin(). Ohne diese Prüfung ändert dieselbe Regel auch die Beitragsliste in 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( 'news' ) ) {
$query->set( 'posts_per_page', 20 );
}
}
);Dieselbe Referenz warnt, dass einige Conditional Tags in pre_get_posts noch nicht funktionieren: is_front_page() versagt, is_home() funktioniert. Soll eine Regel „nur auf der statischen Startseite“ greifen, vergleichen Sie $query->get( 'page_id' ) mit get_option( 'page_on_front' ) oder verschieben die Logik nach template_redirect, wo die Abfrage bereits aufgelöst ist.
Welche Seiten is_archive abdeckt und wann is_tax passt
is_archive() ist ein Sammelbegriff. Im Core (class-wp-query.php) wird das Flag is_archive wahr, wenn die Abfrage ein Post-Type-Archiv, ein Datums-, Autoren-, Kategorie- oder Schlagwortarchiv oder ein Archiv einer Custom Taxonomy ist. Das Handbuch zu Conditional Tags fasst es so zusammen: Kategorie-, Schlagwort-, Autoren- und Datumsseiten sind alle Archive.
Das hat direkte Folgen:
is_archive()allein lädt dasselbe CSS im Autorenarchiv und improduct_cat-Archiv. Wenn nur Blog-Kategoriearchive gemeint sind, nehmen Sieis_category().is_tax()gibt in Kategorie- und Schlagwortarchiven false zurück. Die Referenz zu is_tax() sagt: wahr für Archive von Custom Taxonomies, falsch für die eingebauten Taxonomien. Für alle drei schreiben Sieis_category() || is_tag() || is_tax().is_post_type_archive( 'produkt' )ist nur wahr, wenn der Post Type mithas_archive => trueregistriert wurde, wie dasselbe Handbuch erinnert.
Keine dieser Funktionen sagt etwas über einen einzelnen Beitrag. In einer Einzelansicht ist is_tax() false, auch wenn der Beitrag den Term trägt; dort ist has_term() die richtige Frage.
is_page_template in Block Themes
is_page_template() hat mit Block Themes seine Bedeutung geändert, ohne die Signatur zu ändern. Im Core (post-template.php) gibt die Funktion außerhalb von is_singular() false zurück und vergleicht dann das Argument mit get_page_template_slug() des abgefragten Objekts, also mit dem am Beitrag gespeicherten Wert.
In einem klassischen Theme ist dieser Wert der PHP-Dateiname, deshalb funktioniert is_page_template( 'landing.php' ). In einem Block Theme verwenden eigene Templates, die in theme.json unter customTemplates registriert sind, als name den HTML-Dateinamen ohne Endung, laut Handbuch zu Custom Templates. Ein Template /templates/landing.html prüfen Sie mit is_page_template( 'landing' ). Wer Bedingungen aus einem klassischen Theme ungeprüft in ein Block Theme kopiert, bekommt ein if, das nie wahr wird, ohne Warnung und ohne Fehler.
Checkliste für Conditional Tags im Theme-Code
- Seitenkontext (
is_*) und Postdaten (has_term/ Meta) getrennt? is_front_pagevsis_homean den Leseeinstellungen geprüft?- Post-ID außerhalb des Loops über
get_queried_object()oder Helper? - Capabilities statt Rollennamen?
- Strukturelle Unterschiede in der Hierarchy, kleine Varianten in PHP?
- Eingeloggte Varianten vom öffentlichen Page Cache ausgeschlossen?
- Regeln in testbaren Helpern, nicht nur in Template-Ifs?
pre_get_posts-Callbacks mit$query->is_main_query()und ohne Wirkung im Admin (is_admin())?
Kurzfassung
has_term() statt blindem in_category(), Seitenkontext sauber von Postdaten getrennt, Capabilities statt Rollenstrings, Hierarchy für Struktur und PHP für Verhalten: an diesen Grenzen entscheidet sich, ob Theme-Logik den nächsten Taxonomie-Umbau und FSE-Migration überlebt. Dokumentieren Sie Leseeinstellungen und Cache-Bypass neben dem Code; ohne diesen Kontext sind Conditional Tags allein nicht reproduzierbar. Wenn die Conditional-Ketten bereits unübersichtlich sind, räumen wir sie in der Theme- und Template-Arbeit strukturiert auf.







