WordPress-Bedingungslogik für Kategorien und Taxonomien

WordPress-Bedingungslogik für Kategorien und Taxonomien

Zuletzt überprüft: 13. Oktober 2026
14 Min. Lesezeit
Leitfaden
Full-Stack-Entwickler

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 rechtliches anhä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 true
  • is_home() ist false
  • is_page() ist true (die zugewiesene Page)

Auf /blog/ gilt:

  • is_home() ist true
  • is_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:

  1. in wp_head oder wp_enqueue_scripts auf $post zugreifen, bevor der Loop startet
  2. in Widgets oder Shortcodes den Hauptloop und einen Nested Loop vermischen
  3. 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:

  1. Strukturelle Unterschiede (anderes Markup, andere Sidebar): eigene Template-Datei laut Hierarchy
  2. Kleine Variationen (ein Banner, ein Script, eine CSS-Klasse): Conditional Tag oder body_class-Filter
  3. 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:

  1. Varianten für is_user_logged_in() vom öffentlichen Cache ausschließen oder separat keyen
  2. current_user_can()-abhängiges Markup niemals in den anonymen Page Cache legen
  3. Edge-Caches: Cookie-basierte Bypass-Regeln für WordPress-Login-Cookies respektieren
  4. 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 in the_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, dass is_main_query() die globale Instanz $wp_query prü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 im product_cat-Archiv. Wenn nur Blog-Kategoriearchive gemeint sind, nehmen Sie is_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 Sie is_category() || is_tag() || is_tax().
  • is_post_type_archive( 'produkt' ) ist nur wahr, wenn der Post Type mit has_archive => true registriert 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

  1. Seitenkontext (is_*) und Postdaten (has_term / Meta) getrennt?
  2. is_front_page vs is_home an den Leseeinstellungen geprüft?
  3. Post-ID außerhalb des Loops über get_queried_object() oder Helper?
  4. Capabilities statt Rollennamen?
  5. Strukturelle Unterschiede in der Hierarchy, kleine Varianten in PHP?
  6. Eingeloggte Varianten vom öffentlichen Page Cache ausgeschlossen?
  7. Regeln in testbaren Helpern, nicht nur in Template-Ifs?
  8. 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.

Nächster Schritt

Machen Sie aus dem Artikel eine echte Umsetzung

Dieser Block stärkt die interne Verlinkung und führt Nutzer gezielt zum nächsten sinnvollen Schritt im Service- und Content-System.

Soll das Thema auf Ihrer Website umgesetzt werden?

Wenn Sie aus dem Artikel konkrete Maßnahmen für Website, Relaunch oder Weiterentwicklung ableiten wollen, definiere ich den Scope und setze ihn um.

Relevanter Cluster

Weitere WordPress-Dienste und Wissensbasis entdecken

Stärken Sie Ihr Unternehmen mit professionellem technischen Support in den Kernbereichen des WordPress-Ökosystems.

Artikel-FAQ

Häufig gestellte Fragen

Praktische Antworten zur Umsetzung des Themas.

SEO-readyGEO-readyAEO-ready3 Q&A
Wann sollte ich has_term() statt in_category() verwenden?#
Sobald Sie mit Custom Taxonomies arbeiten oder eine belastbarere Variante für mehrere Inhaltstypen brauchen. in_category() ist nur für die native Kategorie gedacht.
Wie prüfe ich untergeordnete Kategorien korrekt?#
Für Eltern-Kind-Beziehungen brauchen Sie meist eine Helper-Funktion mit get_term_children() oder eine rekursive Prüfung. Eine exakte Kategorieabfrage reicht dafür nicht.
Beeinflusst Bedingungslogik die Performance?#
Einzelne has_term()-Checks sind meist unkritisch. Teurer werden rekursive Prüfungen und unnötige Abfragen in großen Loops, deshalb lohnt sich saubere Struktur und Caching.

Sie brauchen ein FAQ für Branche und Zielmarkt? Wir erstellen eine Version passend zu Ihren Business-Zielen.

Kontakt aufnehmen

Ähnliche Artikel