WordPress-Bedingungslogik für Kategorien und Taxonomien

WordPress-Bedingungslogik für Kategorien und Taxonomien

Zuletzt überprüft: 21. September 2026
10 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.

#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 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.

#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() → true
  • is_home() → false
  • is_page() → true (die zugewiesene Page)

Auf /blog/ gilt:

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

  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() 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:

  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.

#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:

  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.

#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 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 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

  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?

#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