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.
Warum Conditional Tags 2026 noch zählen
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.
Seitenkontext: is_singular, is_front_page und Verwandte
is_singular() ist kein Synonym für „ein Beitrag“
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()→ trueis_home()→ falseis_page()→ true (die zugewiesene Page)
Auf /blog/ gilt:
is_home()→ trueis_front_page()→ 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().
Queried Object statt Annahmen
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: wann der globale Post gilt
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() als Standard
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.
Hierarchie: Kind-Terms erfordern Helper
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).
current_user_can: Capabilities statt Rollennamen
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.
FSE-Grenzen: was Blöcke nicht lösen
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
Caching und eingeloggte Nutzer
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.
Testbare Helper statt If-Spaghetti
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 und Datenbank
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 und Custom Post Types
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.
Checkliste vor dem Merge
- 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?
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.







