Como mostrar la categoría actual y padre de una entrada (fragmento WordPress)

Como mostrar la categoría actual y padre de una entrada (fragmento WordPress)

Última verificación: 15 de octubre de 2026
20 min de lectura
Tutorial
Desarrollador full-stack

En temas WordPress a medida, casi siempre acabas mostrando más que el nombre de una sola categoría. Las migas de pan del single, el encabezado del archivo y los bloques de “entradas relacionadas” necesitan el término hoja y también sus padres. WordPress guarda la jerarquía en term_taxonomy.parent, pero get_the_category() te entrega un array plano. Tú decides qué término es la hoja y cómo subes por los ancestros.

Este tutorial cubre los fallos habituales de get_the_category(), el atajo get_category_parents(), el control fino con get_ancestors(), la categoría primaria (Yoast y Rank Math), el JSON-LD de BreadcrumbList, taxonomías personalizadas jerárquicas y cache. Todo el HTML de ejemplo va escapado.

Si trabajas en proyectos de tema o plugin a escala: desarrollador WordPress.

#Por qué get_the_category() no basta sola

get_the_category() (y su hermano get_the_category( $post_id )) devuelve un array de objetos WP_Term asociados a la entrada. Cada término trae term_id, name, slug y parent. El array viene ordenado por nombre (el núcleo llena la caché de términos con orderby => name), no por jerarquía: un hijo puede aparecer antes que su padre. Si una entrada vive en Tecnología / WordPress / Rendimiento, puedes recibir tres términos y aún así no saber cuál es la hoja más profunda para la miga de pan.

Otro fallo clásico: asumir que la entrada tiene exactamente una categoría. El editor permite muchas. Si tomas $categories[0] sin más, el orden es alfabético, no el de la intención editorial. En sitios españoles con redacción grande (medios, revistas sectoriales, blogs de producto) eso se nota enseguida: la miga salta entre ramas distintas según la entrada.

Tercer fallo: llamar a get_the_category() fuera del Loop sin pasar el ID. En widgets, REST callbacks o bloques renderizados en el pie, get_the_ID() puede ser cero o apuntar a otra entrada. Pasa siempre el $post_id explícito cuando no estés en single.php / the_post().

Cuarto fallo: olvidar que un término con parent = 0 es raíz. No es un error. Si construyes el rastro con un while ( $current->parent ) y el término ya es raíz, el bucle no entra y eso es correcto. El bug aparece cuando alguien fuerza un padre inventado o concatena separadores vacíos.

Quinto fallo: no escapar la salida. esc_html() en el nombre y esc_url() en el enlace no son opcionales. Un nombre de categoría malicioso (poco habitual, pero posible con usuarios colaboradores) rompe el markup o abre XSS en el front.

Documentación de referencia: get_the_category().

#Mostrar la categoría actual y su categoría padre

Para un rastro corto (padre » hoja) basta con leer el primer término (o el primario, ver más abajo) y, si parent no es cero, cargar el padre con get_category().

function wppoland_get_category_hierarchy( $post_id = null ) {
    $post_id    = $post_id ?: get_the_ID();
    $categories = get_the_category( $post_id );

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

    $category = $categories[0];
    $parts    = array();

    if ( $category->parent ) {
        $parent = get_category( $category->parent );
        if ( $parent && ! is_wp_error( $parent ) ) {
            $parts[] = sprintf(
                '<a href="%s">%s</a>',
                esc_url( get_category_link( $parent->term_id ) ),
                esc_html( $parent->name )
            );
        }
    }

    $parts[] = sprintf(
        '<a href="%s">%s</a>',
        esc_url( get_category_link( $category->term_id ) ),
        esc_html( $category->name )
    );

    return implode( ' <span class="separator" aria-hidden="true">&raquo;</span> ', $parts );
}

Uso en plantilla:

$hierarchy = wppoland_get_category_hierarchy();
if ( $hierarchy ) {
    echo '<nav class="category-breadcrumb" aria-label="Migas de pan">' . $hierarchy . '</nav>';
}

Esto produce algo como: Tecnología » WordPress. Sirve para cabeceras simples. No cubre abuelos ni bisabuelos; para eso pasan a los métodos siguientes.

#Mostrar la categoría actual en la página de archivo de categoría

En un archivo de categoría, get_the_category() no sirve: cada entrada del listado puede tener varias categorías y ninguna tiene por qué ser el archivo que se está mostrando. La categoría del archivo es el objeto consultado. is_category() confirma el contexto, get_queried_object() devuelve el WP_Term del archivo y get_queried_object_id() solo su ID.

if ( is_category() ) {
	$term = get_queried_object();

	foreach ( array_reverse( get_ancestors( $term->term_id, 'category', 'taxonomy' ) ) as $ancestor_id ) {
		$ancestor = get_term( $ancestor_id, 'category' );
		if ( $ancestor && ! is_wp_error( $ancestor ) ) {
			printf(
				'<a href="%s">%s</a> <span aria-hidden="true">/</span> ',
				esc_url( get_term_link( $ancestor ) ),
				esc_html( $ancestor->name )
			);
		}
	}

	echo '<h1>' . esc_html( $term->name ) . '</h1>';
}

Aquí el rastro sobre el título sale del propio término del archivo, así que una entrada clasificada en tres ramas no puede desplazarlo. Para el título por sí solo también sirve single_cat_title(), que imprime el nombre del archivo de categoría actual.

#Listar las subcategorías de la categoría actual

La dirección contraria al padre es la lista de hijos. wp_list_categories() acepta child_of (todos los descendientes de un término) y depth (cuántos niveles imprime). Un title_li vacío elimina el elemento de lista con el título por defecto “Categorías”, y así controlas tú el ul.

if ( is_category() ) {
	echo '<ul class="subcategories">';
	wp_list_categories(
		array(
			'child_of' => get_queried_object_id(),
			'depth'    => 1,
			'title_li' => '',
		)
	);
	echo '</ul>';
}

Dos detalles de los valores por defecto: hide_empty vale 1, de modo que las subcategorías vacías no aparecen hasta que pasas 'hide_empty' => 0. Y child_of sin depth imprime todo el subárbol, no solo el nivel directo. Si necesitas datos en vez de HTML, get_categories( array( 'parent' => $id ) ) devuelve solo los hijos directos, mientras que get_term_children( $id, 'category' ) devuelve los IDs de todos los descendientes en cualquier nivel.

#Comprobar si una entrada está en una categoría hija

in_category() pasa por has_category() hasta is_object_in_term(), y esa función solo compara los términos asignados directamente a la entrada. Una entrada clasificada solo en WordPress devuelve false para in_category( 'tecnologia' ) aunque WordPress sea hija de Tecnología. Para condiciones del tipo “en cualquier punto de esta rama”, comprueba también los ancestros:

/**
 * True if the post is in $parent_id or in any of its descendants.
 */
function wppoland_in_category_branch( $parent_id, $post_id = false ) {
	$parent_id = (int) $parent_id;

	foreach ( get_the_category( $post_id ) as $term ) {
		if ( $term->term_id === $parent_id || cat_is_ancestor_of( $parent_id, $term ) ) {
			return true;
		}
	}

	return false;
}

cat_is_ancestor_of() llama a term_is_ancestor_of() con la taxonomía category y sube por los padres de forma recursiva. En taxonomías custom, llama directamente a term_is_ancestor_of( $padre, $hijo, $taxonomia ). La misma comprobación evita que un elemento padre del menú lateral pierda su estado activo cuando el lector está en el archivo de una nieta.

#Ruta de categorías con get_category_parents()

get_category_parents() es la función de núcleo pensada exactamente para esto: dada una categoría, devuelve una cadena con todos los ancestros hasta la raíz, opcionalmente con enlaces y un separador. Ideal para prototipos y temas clásicos donde el HTML del rastro no necesita Schema ni clases por nivel.

function wppoland_quick_category_trail( $post_id = null ) {
    $post_id    = $post_id ?: get_the_ID();
    $categories = get_the_category( $post_id );

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

    $leaf_id = (int) $categories[0]->term_id;

    // true = enlaces, ' » ' = separador, false = no incluir el propio término al final si quieres solo padres
    return get_category_parents( $leaf_id, true, ' &raquo; ', false );
}

Limitaciones prácticas:

  • Solo funciona con la taxonomía category. Para taxonomías custom, el equivalente es get_term_parents_list() (ver la sección siguiente).
  • Devuelve HTML (o texto) ya ensamblado. Si necesitas JSON-LD, microdatos por ítem o filtrar el nivel raíz, tendrás que parsear la cadena o, mejor, no usarla.
  • El separador y el escape interno siguen las reglas de núcleo; personalizar clases CSS por cada eslabón es incómodo.

Documentación: get_category_parents().

Si el diseño debe omitir el nivel superior (algunas cabeceras empiezan en el hijo, no en “Inicio » Tecnología”), esta función tampoco es la mejor herramienta. Ahí gana get_ancestors() más un filtro explícito del primer ID.

#Usar get_term_parents_list() en lugar de get_category_parents()

Desde WordPress 4.8.0, get_category_parents() es solo un envoltorio: llama a get_term_parents_list() con la taxonomía category. La misma función trabaja con cualquier taxonomía jerárquica, también con product_cat de WooCommerce. Sus argumentos son format (name o slug), separator, link (por defecto true) e inclusive (por defecto true, que añade el propio término al final). El quinto argumento de get_category_parents() es $deprecated: no se usa desde la 4.8.0 y lanza un aviso de obsolescencia si le pasas algo.

$list = get_term_parents_list(
	$term_id,
	'product_cat',
	array(
		'inclusive' => false,
		'separator' => ' / ',
	)
);

if ( $list && ! is_wp_error( $list ) ) {
	echo wp_kses_post( rtrim( $list, ' /' ) );
}

El código fuente de category-template.php muestra dos particularidades. El nombre entra en el enlace sin esc_html(); solo la URL pasa por esc_url(). Y el separador se añade después de cada elemento, también del último, por eso el ejemplo llama a rtrim(). Con 'inclusive' => false omites el término actual, por ejemplo cuando ya aparece como título debajo. La protección frente a una cadena de padres rota (un término que apunta a uno de sus propios descendientes tras una importación defectuosa) la aporta get_ancestors(), que se detiene en cuanto un ID de padre se repite.

#Obtener todas las categorías padre con get_ancestors()

get_ancestors() es la vía genérica. Funciona con cualquier taxonomía jerárquica. Devuelve un array de IDs de ancestros, del padre más cercano hacia arriba. Tú inviertes el array, cargas cada término y decides el markup.

function wppoland_ancestor_trail( $term_id, $taxonomy = 'category' ) {
    $term_id = (int) $term_id;
    if ( ! $term_id ) {
        return array();
    }

    $ancestor_ids = get_ancestors( $term_id, $taxonomy );
    $ancestor_ids = array_reverse( $ancestor_ids ); // raíz primero
    $ancestor_ids[] = $term_id; // incluir la hoja

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

    return $trail;
}

function wppoland_render_ancestor_trail( $post_id = null ) {
    $post_id    = $post_id ?: get_the_ID();
    $categories = get_the_category( $post_id );

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

    $trail  = wppoland_ancestor_trail( $categories[0]->term_id, 'category' );
    $parts  = array();

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

    return implode( ' <span class="separator" aria-hidden="true">&raquo;</span> ', $parts );
}

Ventajas frente a un while manual sobre $term->parent:

  • Una sola llamada obtiene todos los IDs; el núcleo ya conoce el grafo.
  • El mismo patrón sirve para product_cat, documento_tipo u otras taxonomías jerárquicas.
  • Puedes calcular profundidad con count( get_ancestors( $id, $taxonomy ) ) para elegir la hoja más profunda cuando hay varias categorías asignadas.

Documentación: get_ancestors().

#Cómo elegir la categoría cuando la entrada tiene varias

Cuando la entrada tiene tres categorías en ramas distintas, $categories[0] es frágil. Una heurística razonable (no perfecta) es elegir el término con más ancestros:

function wppoland_deepest_category( $post_id = null ) {
    $post_id    = $post_id ?: get_the_ID();
    $categories = get_the_category( $post_id );

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

    $deepest = $categories[0];
    $max     = count( get_ancestors( $deepest->term_id, 'category' ) );

    foreach ( $categories as $cat ) {
        $depth = count( get_ancestors( $cat->term_id, 'category' ) );
        if ( $depth > $max ) {
            $max     = $depth;
            $deepest = $cat;
        }
    }

    return $deepest;
}

Esto no sustituye una categoría primaria editorial. Solo evita que la miga use un término raíz cuando existe un hijo más específico en la misma entrada.

#Categoría principal de Yoast y Rank Math en PHP

Los plugins SEO populares guardan qué categoría debe representar la entrada en migas, Open Graph y a veces en el permalink de categoría. Léelos antes de caer al primer elemento del array.

#Con Yoast SEO

function wppoland_get_primary_category( $post_id = null ) {
    $post_id = $post_id ?: get_the_ID();

    if ( class_exists( 'WPSEO_Primary_Term' ) ) {
        $primary_term   = new WPSEO_Primary_Term( 'category', $post_id );
        $primary_cat_id = $primary_term->get_primary_term();

        if ( $primary_cat_id && ! is_wp_error( $primary_cat_id ) ) {
            $term = get_category( (int) $primary_cat_id );
            if ( $term && ! is_wp_error( $term ) ) {
                return $term;
            }
        }
    }

    return wppoland_deepest_category( $post_id );
}

#Con Rank Math

function wppoland_get_rankmath_primary_category( $post_id = null ) {
    $post_id        = $post_id ?: get_the_ID();
    $primary_cat_id = (int) get_post_meta( $post_id, 'rank_math_primary_category', true );

    if ( $primary_cat_id ) {
        $term = get_category( $primary_cat_id );
        if ( $term && ! is_wp_error( $term ) ) {
            return $term;
        }
    }

    return wppoland_deepest_category( $post_id );
}

En redacciones que usan ambos plugins en entornos distintos (staging con Yoast, producción con Rank Math, o al revés tras una migración), encapsula la lectura detrás de una sola función que pruebe Yoast, luego Rank Math, luego la heurística de profundidad. No hardcodees el meta key en plantillas repartidas por el tema.

Sin plugin SEO, la categoría primaria no existe en el núcleo. WordPress no define “primary” nativo para category. O guardas tu propio post meta, o aceptas la heurística.

#Schema BreadcrumbList para migas de pan de categorías

Para SEO, el rastro visible y el rastro estructurado deben contar la misma historia. Schema.org define BreadcrumbList con itemListElement de tipo ListItem, cada uno con position, name e item (URL).

function wppoland_schema_breadcrumbs( $post_id = null ) {
    if ( ! is_singular( 'post' ) ) {
        return;
    }

    $post_id  = $post_id ?: get_the_ID();
    $primary  = wppoland_get_primary_category( $post_id );

    if ( ! $primary ) {
        return;
    }

    $trail = wppoland_ancestor_trail( $primary->term_id, 'category' );
    $items = array();
    $pos   = 1;

    $items[] = array(
        '@type'    => 'ListItem',
        'position' => $pos++,
        'name'     => 'Inicio',
        'item'     => home_url( '/' ),
    );

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

    $items[] = array(
        '@type'    => 'ListItem',
        'position' => $pos,
        'name'     => get_the_title( $post_id ),
        'item'     => get_permalink( $post_id ),
    );

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

    echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>' . "\n";
}
add_action( 'wp_head', 'wppoland_schema_breadcrumbs', 20 );

Notas de producción:

  • Emite el bloque solo en is_singular( 'post' ) (o el CPT que corresponda). En archivos y la portada el rastro es otro grafo.
  • Usa las mismas URLs que el HTML visible. Si el nav enlaza a /categoria/wordpress/ y el JSON-LD apunta a un permalink con ?cat=12, Google ve inconsistencia.
  • wp_json_encode evita romper el <script> con comillas en títulos. No concatenes a mano.
  • Si ya emites BreadcrumbList desde Yoast o Rank Math, no dupliques el nodo. Dos BreadcrumbList en la misma página confunden más de lo que ayudan.

#Qué muestra Google a partir de BreadcrumbList

La documentación de Google sobre migas de pan fija el mínimo: un BreadcrumbList con al menos dos ListItem, position empezando en 1 y una URL en item en cada elemento. La excepción es el último: si ahí falta item, Google usa la URL de la propia página. Para una entrada, inicio más una categoría ya cumple el mínimo; el título de la entrada como último elemento está permitido, pero no es obligatorio.

Google admite varios rastros para una misma página y recomienda uno que refleje el recorrido típico del usuario en lugar de copiar la estructura de la URL. Para las migas de categorías es un argumento a favor de la categoría primaria: describe el camino que quiso la redacción, aunque el permalink no contenga ninguna categoría.

El rastro ya solo se ve en Google en escritorio. Desde el 23 de enero de 2025, los resultados móviles muestran solo el dominio en la línea de la URL, según anunció el blog de Search Central. El marcado sigue siendo compatible y el informe de Search Console se mantiene. Las migas visibles del tema sirven ante todo a quien lee.

#Migas de pan con taxonomías personalizadas en WordPress

La misma lógica aplica a taxonomías registradas con 'hierarchical' => true. Sustituye get_the_category / get_category / get_category_link por get_the_terms / get_term / get_term_link. get_category_parents() está fijada a category: para custom taxonomies usa get_term_parents_list() o get_ancestors().

function wppoland_get_custom_taxonomy_trail( $taxonomy, $post_id = null ) {
    $post_id = $post_id ?: get_the_ID();
    $terms   = get_the_terms( $post_id, $taxonomy );

    if ( ! $terms || is_wp_error( $terms ) ) {
        return '';
    }

    // Preferir el término más profundo si hay varios
    $leaf = $terms[0];
    $max  = count( get_ancestors( $leaf->term_id, $taxonomy ) );
    foreach ( $terms as $term ) {
        $depth = count( get_ancestors( $term->term_id, $taxonomy ) );
        if ( $depth > $max ) {
            $max  = $depth;
            $leaf = $term;
        }
    }

    $trail = wppoland_ancestor_trail( $leaf->term_id, $taxonomy );
    $parts = array();

    foreach ( $trail as $t ) {
        $link = get_term_link( $t, $taxonomy );
        if ( is_wp_error( $link ) ) {
            continue;
        }
        $parts[] = sprintf(
            '<a href="%s">%s</a>',
            esc_url( $link ),
            esc_html( $t->name )
        );
    }

    return implode( ' <span class="separator" aria-hidden="true">&raquo;</span> ', $parts );
}

Si registraste la taxonomía con 'hierarchical' => false, parent siempre es 0 y get_ancestors() devuelve un array vacío. El rastro tendrá un solo eslabón. Eso no es un bug: es el modelo de etiquetas, no de carpetas.

En proyectos WooCommerce en España, product_cat es el caso típico. El patrón anterior funciona sin tocar get_category_*. Solo pasa 'product_cat' como taxonomía y valida is_wp_error() en cada get_term_link().

#Bloque de migas de pan de WordPress 7.0 con categoría primaria

Desde WordPress 7.0, el núcleo incluye su propio bloque core/breadcrumbs (la versión actual según la API de versiones de WordPress.org es la 7.1.3). Su block.json declara los atributos separator (por defecto /), showHomeItem, showCurrentItem, showOnHomePage y prefersTaxonomy. En una entrada sin entrada padre, el bloque construye el rastro con términos de taxonomía: toma la primera taxonomía registrada para el tipo de contenido que tenga términos y, dentro de ella, el primer término que devuelve get_the_terms(), mediante reset( $terms ). Es la misma trampa que $categories[0]: si no intervienes, gana el primer nombre en orden alfabético.

El código fuente del bloque ofrece para esto el filtro block_core_breadcrumbs_post_type_settings. Espera un array con taxonomy y term, y term es un slug, no un ID. Así pasas al bloque la categoría primaria de Yoast SEO o Rank Math:

add_filter(
	'block_core_breadcrumbs_post_type_settings',
	static function ( $settings, $post_type, $post_id ) {
		if ( 'post' !== $post_type ) {
			return $settings;
		}

		$primary = (int) get_post_meta( $post_id, '_yoast_wpseo_primary_category', true );
		if ( ! $primary ) {
			$primary = (int) get_post_meta( $post_id, 'rank_math_primary_category', true );
		}

		$term = $primary ? get_term( $primary, 'category' ) : null;
		if ( $term && ! is_wp_error( $term ) ) {
			$settings['taxonomy'] = 'category';
			$settings['term']     = $term->slug;
		}

		return $settings;
	},
	10,
	3
);

Un segundo filtro, block_core_breadcrumbs_items, permite cambiar la lista final de pares label y url antes del renderizado. El marcado del bloque ya cubre lo básico de accesibilidad: un nav con aria-label, un ol y el último elemento como span con aria-current="page". El bloque no imprime JSON-LD. El BreadcrumbList sigue saliendo del plugin SEO o de una función como la anterior, y ambos deben usar el mismo término que entregas al bloque con el filtro.

#Cómo cachear las migas de pan de categorías

En archivos con muchas entradas, llamar a get_ancestors() y get_term() por cada post suma. El object cache de WordPress (Redis o Memcached vía drop-in) ya cachea términos individuales, pero puedes memoizar el HTML del rastro por entrada:

function wppoland_cached_breadcrumbs( $post_id = null ) {
    $post_id   = $post_id ?: get_the_ID();
    $cache_key = 'wppoland_cat_bc_' . $post_id;
    $output    = get_transient( $cache_key );

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

    $primary = wppoland_get_primary_category( $post_id );
    if ( ! $primary ) {
        return '';
    }

    $trail  = wppoland_ancestor_trail( $primary->term_id, 'category' );
    $parts  = array();
    foreach ( $trail as $term ) {
        $parts[] = sprintf(
            '<a href="%s">%s</a>',
            esc_url( get_category_link( $term->term_id ) ),
            esc_html( $term->name )
        );
    }
    $output = implode( ' <span class="separator" aria-hidden="true">&raquo;</span> ', $parts );
    set_transient( $cache_key, $output, DAY_IN_SECONDS );

    return $output;
}

add_action( 'save_post_post', function( $post_id ) {
    if ( wp_is_post_revision( $post_id ) ) {
        return;
    }
    delete_transient( 'wppoland_cat_bc_' . $post_id );
} );

add_action( 'edited_category', function() {
    // Si cambia el nombre o el padre de una categoría, los HTML cacheados quedan viejos.
    // En sitios grandes, usa un group flush o una versión en la clave de cache.
} );

Patrones que funcionan en producción:

  • Clave por post_id + versión de taxonomía (incrementas la versión al editar cualquier categoría).
  • Transient solo si no hay object cache persistente; con Redis, un wp_cache_set en el grupo wppoland_bc suele bastar.
  • Invalidar en save_post y en edited_term / delete_term para la taxonomía afectada.
  • No cachear el JSON-LD y el HTML por separado con claves distintas que puedan divergir: un solo builder, dos serializaciones.

Dentro de una misma petición (la misma entrada renderizada dos veces en la página), un static $memo[ $post_id ] evita trabajo duplicado sin tocar la base de datos.

#Categorías de una entrada fuera del Loop por ID

Todas las funciones de este artículo aceptan $post_id opcional. En single.php puedes omitirlo. En un shortcode, un bloque dinámico o un endpoint REST, pásalo siempre:

add_shortcode( 'wppoland_cat_trail', function( $atts ) {
    $atts = shortcode_atts( array( 'id' => 0 ), $atts, 'wppoland_cat_trail' );
    $id   = (int) $atts['id'] ?: get_the_ID();
    return wppoland_cached_breadcrumbs( $id );
} );

En temas de bloques, registra un bloque dinámico cuya render_callback llame a la misma función. No dupliques la lógica de ancestros en JavaScript del editor salvo que edites el rastro en tiempo real; el servidor sigue siendo la fuente de verdad para el HTML público y el JSON-LD.

#CSS y accesibilidad para migas de pan

Envuelve el rastro en <nav aria-label="Migas de pan">. Marca el separador con aria-hidden="true" para que los lectores de pantalla no lean “raquo” entre cada enlace. El último eslabón (el título de la entrada) puede ser texto plano sin enlace si ya estás en esa URL.

.category-breadcrumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    color: #6b7280;
    margin-bottom: 1rem;
}

.category-breadcrumb a {
    color: #1d4ed8;
    text-decoration: none;
}

.category-breadcrumb a:hover,
.category-breadcrumb a:focus-visible {
    text-decoration: underline;
}

.category-breadcrumb .separator {
    color: #d1d5db;
    font-size: 0.75rem;
}

Evita confiar solo en el color para el estado activo. El subrayado al foco cumple WCAG mejor que un cambio de tono mínimo.

#Migas de pan de categorías con Polylang y WPML

En sitios multilingües (comunes en agencias que publican en español y en inglés o portugués), el término padre en un idioma no es el mismo term_id que en otro. Resuelve primero el término en el idioma actual (API de Polylang / WPML), luego llama a get_ancestors() sobre ese ID. Cachear por post_id sin incluir el idioma en la clave mezcla migas entre locales.

#Cuándo no usar categorías para las migas de pan

Si la arquitectura de URLs del sitio no refleja la taxonomía (permalinks planos, hubs editoriales distintos de category), un BreadcrumbList basado en categorías miente al usuario y al buscador. En esos casos usa una jerarquía de páginas, un menú de ubicación o un campo ACF de rastro. Las categorías siguen siendo útiles para archivos y filtros; no tienen que alimentar el nav superior.

Tampoco fuerces migas de categoría en CPT que usan solo etiquetas planas. Sin jerarquía, el rastro de un solo nivel aporta poco frente a un simple enlace al archivo del CPT.

#Resumen

  • get_the_category() da términos, no un árbol ordenado: elige hoja (primaria o más profunda) y sube.
  • get_category_parents() acelera prototipos solo para category.
  • get_ancestors() es la API correcta cuando controlas markup, Schema y taxonomías custom.
  • Emite BreadcrumbList coherente con el HTML visible, o deja que el plugin SEO lo haga una sola vez.
  • Cachea el HTML por entrada e invalida al guardar el post o al editar términos.

Con eso cubres desde un tema clásico de un solo nivel hasta un single con JSON-LD, taxonomía custom y object cache. Empieza por el método 1, pasa a get_ancestors() cuando el diseño pida más de un padre, y añade Schema solo cuando el rastro visible ya sea estable.

Siguiente paso

Transforma el artículo en una implementación real

Este bloque refuerza el enlazado interno y lleva al lector al siguiente paso más útil dentro de la arquitectura del sitio.

¿Quieres implementar esto en tu sitio?

Si quieres transformar el artículo en mejoras concretas, rediseño o un plan de implementación, puedo cerrar el alcance y ejecutar.

Cluster relacionado

Explora otros servicios WordPress y base de conocimiento

Refuerza tu negocio con soporte técnico profesional en áreas clave del ecosistema WordPress.

Preguntas frecuentes

Respuestas prácticas para aplicar el tema en la ejecución real.

Cómo funciona la jerarquía de categorías en WordPress#
WordPress almacena las categorías en una estructura jerárquica usando la propiedad 'parent' en cada objeto de categoría. get_the_category() devuelve un array plano, así que necesitas recorrer los padres manualmente.
Puedo mostrar múltiples niveles de categorías padre#
Sí. Usa un bucle while que recorra los padres hasta llegar a una categoría sin padre (nivel superior), o usa la función integrada get_category_parents().
Cuál es la mejor manera de mostrar migas de pan de categorías#
Para proyectos simples usa get_category_parents(). Para control total, usa get_ancestors() y genera tu propio HTML y JSON-LD. Desde WordPress 7.0 también existe el bloque core/breadcrumbs para temas de bloques.

¿Necesitas un FAQ adaptado a tu sector y mercado? Preparamos una versión alineada con tus objetivos de negocio.

Hablemos

Artículos Relacionados