Aktuelle und übergeordnete Kategorie in WordPress anzeigen

Aktuelle und übergeordnete Kategorie in WordPress anzeigen

Zuletzt überprüft: 21. September 2026
12 Min. Lesezeit
Tutorial
Full-Stack-Entwickler

In deutschen Redaktionen landet die Kategoriehierarchie oft erst dann auf dem Tisch, wenn das Theme schon live ist: Newsroom unter Regional, Produkt unter Dokumentation, oder ein Meetup-Bericht unter Community. Auf dem WordCamp Germany und in lokalen WordPress-Meetups höre ich dieselbe Frage: Wie hole ich Parent und Child sauber ins Template, ohne die Taxonomy-Tabelle manuell zu parsen?

WordPress speichert Kategorien hierarchisch (parent in wp_term_taxonomy), aber die Template-API gibt Ihnen zuerst ein flaches Array. Dieser Beitrag zeigt die Fallstricke von get_the_category(), die Kurzform get_category_parents(), den skalierbaren Weg über get_ancestors(), die Primary Category aus Yoast SEO DE, ein DSGVO-neutrales BreadcrumbList, Custom Taxonomies und Caching.

#Was WordPress in der Datenbank wirklich speichert

Jeder Term hat eine Zeile in wp_terms und eine Taxonomy-Zeile mit parent. Wert 0 bedeutet Root. Mehrere Ebenen sind normale Integer-Ketten, keine Nested Sets. Deshalb reichen Schleifen und get_ancestors() aus; Sie brauchen kein Graph-Framework.

In der Praxis sehen deutsche News- und Magazin-Setups oft drei bis fünf Ebenen: Ressort, Region, Rubrik, Serie. Das wächst organisch nach dem ersten WordCamp-Talk über strukturierte Archive und wird selten zurückgebaut. Wer den Trail nur für eine Ebene schreibt, baut später stillschweigend kaputte Navigation.

Wichtig für Themes: Permalinks und Pretty URLs entstehen über get_term_link() bzw. get_category_link(). Hardcodierte Pfade wie /kategorie/foo/ brechen, sobald die Rewrite-Struktur oder die Sprache wechselt. Prüfen Sie in den Permalink-Einstellungen, ob Kategorie-Basis umbenannt wurde; der Link-Helper folgt der Basis, Ihr Hardcode nicht.

#Fallstricke von get_the_category()

get_the_category() ist bequem und gleichzeitig die häufigste Fehlerquelle.

#Reihenfolge ist nicht redaktionell

Das Array folgt der Term-ID-Reihenfolge bzw. dem Query-Ergebnis, nicht der Hierarchie und nicht der „Hauptkategorie“ im Redaktionskopf. Wer $categories[0] blind als Breadcrumb-Basis nimmt, zeigt auf Magazin-Sites oft die falsche Ebene.

#Ohne Loop-Kontext leer oder falsch

Außerhalb des Loops oder mit einem fremden Post-Objekt brauchen Sie get_the_category( $post_id ). Sonst greift die globale $post und Sie rendern die Hierarchie des vorherigen Beitrags in Widgets oder REST-Callbacks.

#Parent ist eine ID, kein Objekt

$term->parent ist eine Integer-ID. Ohne get_category() oder get_term() haben Sie keinen Namen und keinen Link. Vergessen Sie esc_html() und esc_url(), riskieren Sie XSS über Kategorie-Namen aus dem Backend. Redakteure tippen gerne Anführungszeichen und Ampersands in Namen; Escaping ist Pflicht, nicht Nice-to-have.

#Uncategorized und Default-Term

Neue Installationen hängen Beiträge an die Standardkategorie. Wenn Redaktionen später umsortieren, bleibt der Default oft als zweite Zuweisung stehen. Breadcrumbs, die categories[0] nehmen, springen dann zurück auf „Allgemein“ oder „Uncategorized“. Filtern Sie den Default-Term (get_option( 'default_category' )) heraus, bevor Sie den Leaf wählen, oder erzwingen Sie Primary Category im Editorial-Prozess.

$categories = get_the_category();
if ( empty( $categories ) ) {
	return;
}

// Unsicher: erste ID ist selten die redaktionelle Primärkategorie.
$leaf = $categories[0];

#Methode 1: aktueller Term plus direkter Parent

Für flache Hierarchien (eine Ebene Parent) reicht die direkte Parent-Prüfung. Das ist der klassische Snippet für single.php oder ein Pattern in Block Themes.

/**
 * Gibt Parent » Child als HTML zurück, oder false.
 *
 * @return string|false
 */
function wppoland_category_parent_child() {
	$categories = get_the_category();

	if ( empty( $categories ) ) {
		return false;
	}

	$category = $categories[0];
	$output   = '';

	if ( $category->parent ) {
		$parent = get_category( (int) $category->parent );
		if ( $parent && ! is_wp_error( $parent ) ) {
			$output .= sprintf(
				'<a href="%s">%s</a> <span aria-hidden="true">&raquo;</span> ',
				esc_url( get_category_link( $parent->term_id ) ),
				esc_html( $parent->name )
			);
		}
	}

	$output .= sprintf(
		'<a href="%s" aria-current="page">%s</a>',
		esc_url( get_category_link( $category->term_id ) ),
		esc_html( $category->name )
	);

	return $output;
}

Im Template:

$hierarchy = wppoland_category_parent_child();
if ( $hierarchy ) {
	echo '<nav class="category-breadcrumb" aria-label="Kategorie">' . $hierarchy . '</nav>';
}

Ausgabebeispiel: Technologie » WordPress. Sobald Redaktionen drei oder vier Ebenen anlegen, reicht diese Methode nicht mehr. Auf Agentur-Themes, die ich nach Meetups in Köln oder München reviewt habe, war genau dieser Ein-Parent-Snippet der Grund für abgeschnittene Pfade in der mobilen Navigation.

Nutzen Sie cat_is_ancestor_of( $parent_id, $child_id ), wenn Sie prüfen müssen, ob ein Term wirklich unter einem Ressort hängt, bevor Sie Filter oder Conditional Tags setzen. Das ersetzt keinen Trail-Builder, verhindert aber falsche Highlight-Zustände in Sidebar-Menüs.

#Methode 2: get_category_parents() für schnelles Markup

Core liefert bereits einen Pfad-Builder. get_category_parents( $id, $link, $separator, $nicename, $visited ) schreibt von Root bis Leaf. Die Parameter dokumentiert der Developer Handbook; der fünfte Argument $visited schützt vor Endlosschleifen bei korrupten Parent-Ketten, die nach Importen aus alten CMS gelegentlich auftreten.

$categories = get_the_category();
if ( ! empty( $categories ) ) {
	$leaf_id = (int) $categories[0]->term_id;
	echo '<nav class="category-trail" aria-label="Kategoriepfad">';
	echo get_category_parents( $leaf_id, true, ' <span aria-hidden="true">/</span> ' );
	echo '</nav>';
}

Vorteile: wenig Code, korrekte Link-Generierung, Umgang mit Zyklen über $visited. Nachteile: wenig Kontrolle über CSS-Klassen pro Ebene, schwieriger JSON-LD-Bau, und Sie stecken weiter an categories[0] fest. Für Prototypen und interne Tools ist das oft genug; für Produktions-Themes wollen Redaktionen meist Primary Category plus eigene Markup-Semantik.

#Methode 3: get_ancestors() für volle Kontrolle

get_ancestors( $object_id, $object_type, $resource_type ) gibt ein Array von Vorfahren-IDs zurück (nächster Parent zuerst). Sie drehen die Liste um und hängen den Leaf-Term an. Derselbe Aufruf funktioniert später für Custom Taxonomies, wenn Sie den Taxonomy-Slug übergeben.

/**
 * Baut einen Kategorie-Trail als Liste von WP_Term-Objekten.
 *
 * @param int $term_id Leaf-Term.
 * @return WP_Term[]
 */
function wppoland_category_trail_terms( $term_id ) {
	$term_id = (int) $term_id;
	$trail   = array();

	$ancestor_ids = get_ancestors( $term_id, 'category', 'taxonomy' );
	$ancestor_ids = array_reverse( array_map( 'intval', $ancestor_ids ) );

	foreach ( $ancestor_ids as $ancestor_id ) {
		$term = get_category( $ancestor_id );
		if ( $term && ! is_wp_error( $term ) ) {
			$trail[] = $term;
		}
	}

	$leaf = get_category( $term_id );
	if ( $leaf && ! is_wp_error( $leaf ) ) {
		$trail[] = $leaf;
	}

	return $trail;
}

/**
 * Rendert den Trail als verkettete Links.
 *
 * @param int    $term_id   Leaf-Term.
 * @param string $separator Trenner zwischen Ebenen.
 * @return string
 */
function wppoland_render_category_trail( $term_id, $separator = ' / ' ) {
	$parts = array();

	foreach ( wppoland_category_trail_terms( $term_id ) as $term ) {
		$parts[] = sprintf(
			'<a href="%s">%s</a>',
			esc_url( get_category_link( $term->term_id ) ),
			esc_html( $term->name )
		);
	}

	return implode( esc_html( $separator ), $parts );
}

Alternative ohne get_ancestors(): eine while-Schleife über $category->parent und array_unshift. Funktional gleich, aber get_ancestors() ist kürzer und einheitlich mit anderen Taxonomien. In Code-Reviews bevorzuge ich get_ancestors(), weil der Intent („Vorfahren dieser Taxonomie“) im Funktionsnamen steht und neue Entwicklerinnen den Parent-Walk nicht neu erfinden.

Achten Sie auf den Rückgabewert: die IDs kommen vom nächsten Parent zum Root. Ohne array_reverse() zeichnen Sie den Pfad rückwärts und verwirren Redaktion und Screenreader gleichermaßen.

#Primary Category: der deutsche Redaktionsalltag mit Yoast

Auf vielen DE-Sites ist Yoast SEO (deutschsprachige UI) Standard. Die Primary Category landet im Post-Meta _yoast_wpseo_primary_category. Ohne diesen Wert bleibt get_the_category()[0] ein Glücksspiel, besonders wenn Beiträge sowohl „WordPress“ als auch „Meetups“ tragen. In Schulungen nach WordCamp-Sessions taucht das Meta-Feld oft erst auf, wenn die SEO-Kollegin fragt, warum Google den falschen Kategorie-Pfad indexiert.

/**
 * Liefert die Leaf-Term-ID für Breadcrumbs.
 *
 * @param int $post_id Beitrag.
 * @return int 0 wenn keine Kategorie.
 */
function wppoland_primary_category_id( $post_id ) {
	$post_id = (int) $post_id;

	$primary = (int) get_post_meta( $post_id, '_yoast_wpseo_primary_category', true );
	if ( $primary > 0 && term_exists( $primary, 'category' ) ) {
		return $primary;
	}

	$categories = get_the_category( $post_id );
	if ( empty( $categories ) ) {
		return 0;
	}

	return (int) $categories[0]->term_id;
}

Dann im Single-Template:

$leaf_id = wppoland_primary_category_id( get_the_ID() );
if ( $leaf_id ) {
	echo '<nav class="category-breadcrumb" aria-label="Kategorie">';
	echo wppoland_render_category_trail( $leaf_id );
	echo '</nav>';
}

Rank Math und andere SEO-Plugins nutzen eigene Meta-Keys. Prüfen Sie die Plugin-Doku, bevor Sie Yoast-Meta hart verdrahten. Für Agenturprojekte lohnt ein Adapter: eine Funktion liest Primary aus mehreren bekannten Keys und fällt sonst auf die erste Kategorie zurück.

#Schema.org BreadcrumbList ohne DSGVO-Ballast

Sichtbare Breadcrumbs und JSON-LD sollten denselben Pfad beschreiben. BreadcrumbList erwartet ListItem-Einträge mit position ab 1, name und item (URL). Halten Sie die Daten öffentlich und navigationsbezogen: keine Nutzerkennungen, keine Session-IDs, keine internen Ticketsystem-URLs. So bleibt das Markup DSGVO-neutral und für Search Consoles nachvollziehbar.

/**
 * Gibt BreadcrumbList-JSON für eine Kategorie-Kette aus.
 *
 * @param int $term_id Leaf-Kategorie.
 */
function wppoland_print_category_breadcrumb_jsonld( $term_id ) {
	$terms = wppoland_category_trail_terms( $term_id );
	if ( empty( $terms ) ) {
		return;
	}

	$items    = array();
	$position = 1;

	// Optionale Startseite als Position 1.
	$items[] = array(
		'@type'    => 'ListItem',
		'position' => $position++,
		'name'     => get_bloginfo( 'name' ),
		'item'     => home_url( '/' ),
	);

	foreach ( $terms as $term ) {
		$items[] = array(
			'@type'    => 'ListItem',
			'position' => $position++,
			'name'     => $term->name,
			'item'     => get_category_link( $term->term_id ),
		);
	}

	$graph = array(
		'@context'        => 'https://schema.org',
		'@type'           => 'BreadcrumbList',
		'itemListElement' => $items,
	);

	echo '<script type="application/ld+json">' . wp_json_encode( $graph, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>';
}

Hängen Sie den Aufruf in wp_head nur auf Singular-Posts, und nur wenn eine Leaf-ID existiert. Doppelte BreadcrumbList-Blöcke von Theme und SEO-Plugin vermeiden: entweder Theme oder Plugin, nicht beide. Rich Results Test und die URL-Prüfung in der Search Console zeigen Konflikte schneller als ein Blick in den Seitenquelltext.

DSGVO-Hinweis in Klartext: Breadcrumb-Schema beschreibt öffentliche Navigation. Es ist kein Consent-Trigger und kein Ersatz für Cookie-Banner. Vermeiden Sie es trotzdem, interne Autorennamen oder hinter Login liegende Archive als item zu setzen, wenn diese URLs nicht für alle Lesenden erreichbar sind.

#Custom Taxonomies statt category

Produktlinien, Dokumentationsthemen oder Veranstaltungsorte hängen oft an eigenen hierarchischen Taxonomien. Dann entfällt get_the_category() komplett. Registrieren Sie die Taxonomie mit 'hierarchical' => true, sonst liefert get_ancestors() eine leere Liste und Ihr Trail sieht aus wie ein Bug, obwohl Core korrekt arbeitet.

/**
 * Trail für eine beliebige hierarchische Taxonomie.
 *
 * @param int    $term_id  Leaf-Term.
 * @param string $taxonomy Taxonomy-Slug, z. B. dokumentation.
 * @return WP_Term[]
 */
function wppoland_taxonomy_trail_terms( $term_id, $taxonomy ) {
	$term_id  = (int) $term_id;
	$taxonomy = sanitize_key( $taxonomy );
	$trail    = array();

	$ancestor_ids = get_ancestors( $term_id, $taxonomy, 'taxonomy' );
	$ancestor_ids = array_reverse( array_map( 'intval', $ancestor_ids ) );

	foreach ( $ancestor_ids as $ancestor_id ) {
		$term = get_term( $ancestor_id, $taxonomy );
		if ( $term && ! is_wp_error( $term ) ) {
			$trail[] = $term;
		}
	}

	$leaf = get_term( $term_id, $taxonomy );
	if ( $leaf && ! is_wp_error( $leaf ) ) {
		$trail[] = $leaf;
	}

	return $trail;
}

Links erzeugen Sie mit get_term_link( $term ). Prüfen Sie is_wp_error(), bevor Sie escapen und ausgeben. Für nicht-hierarchische Taxonomien (Tags) ergibt ein Parent-Trail keinen Sinn; dort reicht die Term-Liste ohne Vorfahren.

#Caching: Transients und Object Cache

Auf Archiv-Seiten mit vielen Beiträgen und tiefen Bäumen summieren sich Term-Lookups. Core cached Terme im Object Cache, wenn einer aktiv ist (Redis, Memcached). Für teure, selbst gebaute Trails mit Extra-Meta hilft ein Transient pro Leaf-ID.

/**
 * Cached HTML-Trail für eine Kategorie.
 *
 * @param int $term_id Leaf-Term.
 * @return string
 */
function wppoland_cached_category_trail_html( $term_id ) {
	$term_id = (int) $term_id;
	$key     = 'wppoland_cat_trail_' . $term_id;
	$cached  = get_transient( $key );

	if ( false !== $cached ) {
		return $cached;
	}

	$html = wppoland_render_category_trail( $term_id );
	set_transient( $key, $html, HOUR_IN_SECONDS );

	return $html;
}

add_action(
	'edited_category',
	static function ( $term_id ) {
		delete_transient( 'wppoland_cat_trail_' . (int) $term_id );
	}
);

add_action(
	'create_category',
	static function ( $term_id ) {
		delete_transient( 'wppoland_cat_trail_' . (int) $term_id );
	}
);

Invalidieren Sie zusätzlich Parent-Trails, wenn ein Term verschoben wird: der alte und der neue Pfad ändern sich. Für Multilingual (Polylang, WPML) müssen Cache-Keys die Sprach-ID enthalten, sonst klebt der deutsche Trail am englischen Beitrag.

Auf stark frequentierten Magazin-Frontpages genügt oft der Object Cache ohne eigene Transients: Term-Lookups sind günstig, sobald Redis läuft. Transients lohnen sich, wenn Sie Extra-Logik (Primary Meta, Filter, Markup-Varianten) pro Request wiederholen. Messen Sie mit Query Monitor, bevor Sie eine zweite Cache-Schicht einziehen.

#Arbeit im Loop und mit fremder Post-ID

Widgets, Shortcodes und Block-Callbacks laufen oft außerhalb des Hauptloops. Übergeben Sie die ID explizit:

function wppoland_category_trail_for_post( $post_id ) {
	$leaf_id = wppoland_primary_category_id( (int) $post_id );
	if ( ! $leaf_id ) {
		return '';
	}
	return wppoland_render_category_trail( $leaf_id );
}

In REST-API-Controllern gilt dasselbe: nie get_the_category() ohne Argument, immer $request- oder $post-ID.

#Semantik, Barrierefreiheit und Trenner

Nutzen Sie <nav aria-label="Kategorie"> und setzen Sie aria-current="page" auf dem letzten Link. Sichtbare Trenner (/, ») gehören in aria-hidden="true", damit Screenreader nicht „Schrägstrich Schrägstrich“ vorlesen. Eine geordnete Liste (ol/li) ist für komplexe Breadcrumbs oft klarer als eine reine Inline-Kette.

#Polylang und WPML

Term-IDs sind sprachabhängig. Ein Trail, der in de gebaut und in en gerendert wird, zeigt die falschen Labels. Lösen Sie die übersetzte Term-ID über die Plugin-API, bevor Sie get_ancestors() aufrufen. Permalinks folgen der Sprach-URL-Struktur; get_term_link() respektiert das, hardcodierte Strings nicht.

#Wann Kategorien die falsche Breadcrumb-Quelle sind

Seiten-Hierarchien (post_parent), Shop-Kategorien in WooCommerce und dokumentationsartige Custom Post Types brauchen oft eigene Trails. Mischen Sie nicht Post-Parent und Category-Parent in einem Breadcrumb, außer die Informationsarchitektur ist ausdrücklich so geplant. Redaktionen auf Meetups in Berlin oder Hamburg entscheiden das meist über IA-Workshops, nicht über den ersten PHP-Snippet.

Ein häufiger Fehler in WooCommerce-Shops: Theme-Breadcrumbs lesen category, während Produkte in product_cat leben. Der sichtbare Pfad zeigt Blog-Ressorts, das Schema zeigt Produktkategorien, und die Search Console meldet inkonsistente Breadcrumbs. Eine Taxonomie pro Content-Typ, ein Builder, ein Schema-Block.

#Edge Cases, die Sie vor dem Deploy testen

  • Beitrag ohne Kategorie: Funktion muss leeren String oder false liefern, kein PHP-Notice.
  • Nur Root-Kategorie: kein Parent-Link, trotzdem gültiges BreadcrumbList mit Startseite plus Term.
  • Verschobene Hierarchie nach Publish: Cache invalidieren.
  • Gelöschte Primary-ID in Yoast-Meta: Fallback auf get_the_category().
  • Kategorie-Name mit HTML-Sonderzeichen: immer esc_html().
  • Import aus CSV oder altem CMS: Parent-IDs können auf gelöschte Terme zeigen; get_category() dann prüfen und den Trail abbrechen, statt Notices zu loggen.
  • Block Theme mit Pattern: Shortcode oder Template-Part muss get_the_ID() im korrekten Block-Kontext sehen; sonst rendert die Startseiten-$post.

Schreiben Sie die Fälle als PHPUnit- oder Integrationstests, wenn das Theme ein CI-Setup hat. Mindestens einmal manuell: Beitrag mit zwei Kategorien, Primary gesetzt, Parent drei Ebenen tief, zweites Gerät mit leerem Object Cache.

#Kurzfassung

  1. get_the_category() allein liefert keine sortierte Vorfahrenkette.
  2. Für Ein-Ebenen-Parent reicht parent plus get_category().
  3. get_category_parents() baut schnell Markup; get_ancestors() gibt Kontrolle und skaliert auf Custom Taxonomies.
  4. Primary Category (Yoast DE Meta) verhindert den falschen Leaf bei Mehrfachzuweisung.
  5. BreadcrumbList bleibt öffentlich und ohne personenbezogene Felder.
  6. Transients und Object Cache halten Archive schnell; Invalidierung bei Term-Edits nicht vergessen.

Wenn die Taxonomy über Snippets hinausgewachsen ist und Sie die Informationsarchitektur im Theme neu schneiden müssen, strukturieren wir das in der Theme-Entwicklung für WordPress gemeinsam neu. Für verwandte Template-Arbeit siehe auch die Snippets zu Kategorienamen ohne Link und zur Extraktion von Beitragskategorien.

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.

Warum reicht get_the_category() allein nicht für Breadcrumbs?#
Die Funktion liefert nur die dem Beitrag zugewiesenen Terme als flaches Array. Eltern und Großeltern stehen nicht sortiert daneben. Für einen Pfad brauchen Sie parent, get_category_parents() oder get_ancestors().
Welche WordPress-Version und welches PHP brauche ich?#
get_the_category(), get_category_parents() und get_ancestors() sind seit Jahren in Core. Praxisnah: PHP 7.4 oder neuer, aktuellere WordPress- Releases für Object Caching und Transient-APIs.
Was passiert bei mehreren Kategorien pro Beitrag?#
Ohne Primary Category ist categories[0] oft die erste ID, nicht die redaktionell gemeinte. Yoast SEO DE speichert die Primärkategorie in _yoast_wpseo_primary_category. Lesen Sie dieses Meta vor dem Trail-Bau.
Darf BreadcrumbList personenbezogene Daten enthalten?#
Nein. Schema.org BreadcrumbList beschreibt öffentliche Navigationspfade. Keine Nutzer-IDs, E-Mails oder Tracking-IDs in name oder item. Das bleibt DSGVO-neutral und vermeidet unnötige Datenminimierungsfragen.

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

Kontakt aufnehmen

Ähnliche Artikel