A exibição correta da hierarquia de categorias é fundamental para a navegação do utilizador e para SEO. Quando um visitante acede a um post, saber em que categoria e subcategoria ele se encontra ajuda na orientação e melhora a experiência do utilizador. Além disso, URLs de categorias bem estruturadas contribuem para o posicionamento nos motores de busca.
O WordPress utiliza um sistema de taxonomia flexível que permite criar hierarquias de categorias com múltiplos níveis. Contudo, extrair e exibir essas relações hierárquicas requer conhecimento das funções adequadas e da estrutura de dados do WordPress.
Este guia técnico ensina todas as técnicas para exibir categorias atuais, pais e hierarquias completas nos seus temas e plugins WordPress.
O que são os fundamentos da taxonomia WordPress
Categorias e tags no WordPress
Categorias são uma das duas taxonomias nativas do WordPress (a outra sendo tags). A principal diferença é que categorias são hierárquicas, o que significa que podem ter categorias “pai” e “filho”, enquanto tags são planas e não hierárquicas.
Características das categorias:
- Podem ser organizadas em múltiplos níveis de profundidade
- Cada post pode pertencer a múltiplas categorias
- Categorias não utilizadas são mantidas no sistema
- A estrutura hierárquica é armazenada no campo
parentda tabelawp_term_taxonomy
Em que tabelas o WordPress guarda as categorias
O WordPress armazena informações de categorias em várias tabelas da base de dados:
Tabela wp_terms Armazena os termos (categorias, tags, etc.) com os campos principais:
term_id: Identificador úniconame: Nome da categoriaslug: Versão URL-friendly do nometerm_group: Agrupamento de termos sinónimos
Tabela wp_term_taxonomy Armazena as relações taxonómicas:
term_taxonomy_id: Identificador únicoterm_id: Referência para wp_termstaxonomy: Tipo de taxonomia (category, post_tag, etc.)parent: ID da categoria pai (0 se não houver pai)count: Número de posts nessa categoria
Propriedades do objeto de categoria
Quando obtém uma categoria no WordPress, recebe um objeto com as seguintes propriedades:
$category = get_category( 5 );
// Propriedades disponíveis:
echo $category->term_id; // ID da categoria
echo $category->name; // Nome (ex: "Tecnologia")
echo $category->slug; // Slug (ex: "tecnologia")
echo $category->parent; // ID do pai (0 se for categoria raiz)
echo $category->count; // Número de posts
echo $category->description; // Descrição da categoria
echo $category->cat_ID; // Mesmo que term_id
echo $category->category_count; // Mesmo que count
echo $category->category_description; // Mesmo que description
echo $category->cat_name; // Mesmo que name
echo $category->category_nicename; // Mesmo que slug
echo $category->category_parent; // Mesmo que parentPorque é que get_the_category() devolve a categoria errada
get_the_category() é prática e, ao mesmo tempo, a fonte de erro mais comum nos breadcrumbs. A função chama get_the_terms( $post_id, 'category' ) e devolve um array de objetos WP_Term ordenado por nome (o núcleo preenche a cache de termos com orderby => name), não pela hierarquia nem pela categoria que a redação considera principal. Se um post estiver em «Tecnologia», «WordPress» e «Desempenho», o primeiro elemento é o primeiro por ordem alfabética, não o nível mais profundo da árvore.
Fora do Loop, ou com um objeto de post diferente, passe sempre o ID: get_the_category( $post_id ). Caso contrário, a função usa o $post global e um widget ou um callback REST mostra a hierarquia do post errado.
$term->parent é apenas um ID inteiro. Sem get_category() ou get_term() não tem nome nem link. E sem esc_html() e esc_url() na saída, um nome de categoria com aspas ou «&» parte o HTML ou abre uma porta a XSS.
Há ainda a categoria predefinida. Uma instalação nova associa os posts à categoria «Sem categoria», e quando a redação mais tarde move o post para uma categoria real, a predefinida fica muitas vezes como segunda atribuição. Como o array vem ordenado por nome, um breadcrumb baseado em $categories[0] pode voltar a «Sem categoria». Filtre o termo guardado em get_option( 'default_category' ) antes de escolher a folha, ou exija uma categoria principal no processo editorial.
Como usar get_the_category() com verificação de pai
Função simples para mostrar a categoria pai e a atual
Esta é a solução mais direta para exibir apenas um nível de hierarquia (pai e filho):
/**
* Obtém a hierarquia de categoria (pai é atual) para o post atual
*
* @return string|false HTML da hierarquia ou false se não houver categorias
*/
function wppoland_get_category_hierarchy() {
// Obter todas as categorias do post atual
$categories = get_the_category();
// Se não houver categorias, retornar false
if ( empty( $categories ) ) {
return false;
}
// Usar a primeira categoria como base
$category = $categories[0];
$output = '';
// Se a categoria tem um pai, exibir primeiro
if ( $category->parent ) {
$parent = get_category( $category->parent );
$parent_link = get_category_link( $parent->term_id );
$output .= '<a href="' . esc_url( $parent_link ) . '" class="category-parent">';
$output .= esc_html( $parent->name ) . '</a>';
$output .= '<span class="breadcrumb-separator"> » </span>';
}
// Categoria atual
$current_link = get_category_link( $category->term_id );
$output .= '<a href="' . esc_url( $current_link ) . '" class="category-current">';
$output .= esc_html( $category->name ) . '</a>';
return $output;
}Como usar a função no template do tema
<?php
// No seu template (single.php, archive.php, etc.)
if ( function_exists( 'wppoland_get_category_hierarchy' ) ) {
$hierarchy = wppoland_get_category_hierarchy();
if ( $hierarchy ) {
echo '<nav class="category-breadcrumb" aria-label="Breadcrumb">';
echo $hierarchy;
echo '</nav>';
}
}
?>CSS para estilizar o breadcrumb de categorias
.category-breadcrumb {
font-size: 14px;
color: #666;
margin-bottom: 20px;
padding: 10px 15px;
background: #f9f9f9;
border-radius: 4px;
}
.category-breadcrumb a {
color: #0073aa;
text-decoration: none;
transition: color 0.2s ease;
}
.category-breadcrumb a:hover {
color: #00a0d2;
text-decoration: underline;
}
.breadcrumb-separator {
margin: 0 8px;
color: #999;
}
.category-parent {
font-weight: 500;
}
.category-current {
}Como mostrar a categoria atual na página de arquivo de categoria
Numa página de arquivo de categoria, get_the_category() não ajuda: cada post da lista pode ter várias categorias e nenhuma delas tem de ser o arquivo que está a ser mostrado. A categoria do arquivo é o objeto consultado. is_category() confirma o contexto, get_queried_object() devolve o WP_Term do arquivo e get_queried_object_id() apenas o seu 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>';
}Aqui o caminho por cima do título nasce do próprio termo do arquivo, por isso um post classificado em três ramos não o consegue desviar. Para o título sozinho também serve single_cat_title(), que imprime o nome do arquivo de categoria atual.
Como listar as subcategorias da categoria atual
A direção oposta ao pai é a lista de filhos. wp_list_categories() aceita child_of (todos os descendentes de um termo) e depth (quantos níveis imprime). Um title_li vazio remove o elemento de lista com o título predefinido «Categorias», e assim é o seu código que controla o ul.
if ( is_category() ) {
echo '<ul class="subcategories">';
wp_list_categories(
array(
'child_of' => get_queried_object_id(),
'depth' => 1,
'title_li' => '',
)
);
echo '</ul>';
}Dois pormenores dos valores predefinidos: hide_empty vale 1, pelo que as subcategorias vazias não aparecem até passar 'hide_empty' => 0. E child_of sem depth imprime toda a subárvore, não só o nível direto. Se precisar de dados em vez de HTML, get_categories( array( 'parent' => $id ) ) devolve apenas os filhos diretos, enquanto get_term_children( $id, 'category' ) devolve os IDs de todos os descendentes em qualquer nível.
Como verificar se um post está numa categoria filha
in_category() passa por has_category() até is_object_in_term(), e essa função compara apenas os termos atribuídos diretamente ao post. Um post classificado só em «WordPress» devolve false para in_category( 'tecnologia' ), mesmo que «WordPress» seja filha de «Tecnologia». Para condições do tipo «em qualquer ponto deste ramo», verifique também os antepassados:
/**
* 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() chama term_is_ancestor_of() com a taxonomia category e sobe pelos pais de forma recursiva. Em taxonomias personalizadas, chame diretamente term_is_ancestor_of( $pai, $filho, $taxonomia ). A mesma verificação evita que um item pai do menu lateral perca o estado ativo quando o leitor está no arquivo de uma neta.
Como criar uma trilha breadcrumb completa
Para exibir toda a hierarquia (múltiplos níveis de ancestrais), precisamos de uma função recursiva:
/**
* Obtém a trilha completa de categorias do post
* Inclui todos os níveis: avô, pai, filho, neto
*
* @param int|null $post_id ID do post (opcional)
* @return string HTML da trilha breadcrumb
*/
function wppoland_get_full_category_trail( $post_id = null ) {
// Se não for específicado, usar o post atual
if ( ! $post_id ) {
$post_id = get_the_ID();
}
// Obter categorias do post
$categories = get_the_category( $post_id );
if ( empty( $categories ) ) {
return '';
}
// Usar a primeira categoria
$category = $categories[0];
// Array para armazenar a trilha
$trail = array();
// Percorrer hierarquia de baixo para cima
while ( $category->parent ) {
$category = get_category( $category->parent );
array_unshift( $trail, $category );
}
// Adicionar a categoria atual ao início
array_unshift( $trail, get_category( $categories[0]->term_id ) );
// Se a trilha só tem um elemento (categoria raiz), retornar apenas ela
if ( count( $trail ) === 1 ) {
$cat = $trail[0];
return '<a href="' . esc_url( get_category_link( $cat->term_id ) ) . '">'
. esc_html( $cat->name ) . '</a>';
}
// Construir HTML da trilha
$output = '<nav class="breadcrumb-trail" aria-label="Breadcrumb">';
$output .= '<ol class="breadcrumb-list">';
foreach ( $trail as $index => $cat ) {
$is_last = ( $index === count( $trail ) - 1 );
$output .= '<li class="breadcrumb-item' . ( $is_last ? ' active' : '' ) . '">';
if ( ! $is_last ) {
$output .= '<a href="' . esc_url( get_category_link( $cat->term_id ) ) . '">';
$output .= esc_html( $cat->name ) . '</a>';
$output .= '<span class="breadcrumb-separator" aria-hidden="true">/</span>';
} else {
$output .= '<span class="breadcrumb-current">' . esc_html( $cat->name ) . '</span>';
}
$output .= '</li>';
}
$output .= '</ol>';
$output .= '</nav>';
return $output;
}Breadcrumb de categorias com marcação Schema.org
Para SEO avançado, adicione dados estruturados:
/**
* Gera trilha breadcrumb com markup Schema.org JSON-LD
*
* @param int|null $post_id ID do post
* @return string HTML + Script JSON-LD
*/
function wppoland_get_breadcrumb_with_schema( $post_id = null ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$categories = get_the_category( $post_id );
if ( empty( $categories ) ) {
return '';
}
$category = $categories[0];
$trail = array();
// Construir trilha de categorias
while ( $category->parent ) {
$category = get_category( $category->parent );
array_unshift( $trail, $category );
}
array_unshift( $trail, get_category( $categories[0]->term_id ) );
// Construir array para Schema.org
$schema_items = array();
// Homepage como primeiro item
$schema_items[] = array(
'@type' => 'ListItem',
'position' => 1,
'name' => 'Início',
'item' => home_url( '/' )
);
// Categorias
$position = 2;
foreach ( $trail as $cat ) {
$schema_items[] = array(
'@type' => 'ListItem',
'position' => $position,
'name' => $cat->name,
'item' => get_category_link( $cat->term_id )
);
$position++;
}
// Post atual (último item)
$schema_items[] = array(
'@type' => 'ListItem',
'position' => $position,
'name' => get_the_title( $post_id ),
'item' => get_permalink( $post_id )
);
// Gerar JSON-LD
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => $schema_items
);
// Gerar HTML
$html = wppoland_get_full_category_trail( $post_id );
// Adicionar JSON-LD
$html .= '';
return $html;
}Como usar get_category_parents() no WordPress
O WordPress oferece uma função nativa para obter a hierarquia de categorias:
/**
* Usa get_category_parents() para obter hierarquia
*
* @param int $category_id ID da categoria
* @param bool $link Se true, inclui links HTML
* @param string $separator Separador entre categorias
* @param bool $nicename Se true, usa nice name
* @param array $deprecated Não usado desde o WordPress 4.8.0
* @return string Hierarquia de categorias
*/
$parents = get_category_parents( $category_id, true, ' » ' );
// Exemplo completo com wrapper:
function wppoland_get_parents_wrapper() {
$categories = get_the_category();
if ( ! empty( $categories ) ) {
$category = $categories[0];
$parents = get_category_parents( $category->term_id, true, ' » ' );
// Adicionar classes CSS
$parents = str_replace(
'<a href=',
'<a class="breadcrumb-link" href=',
$parents
);
return '<div class="category-parents">' . $parents . '</div>';
}
return '';
}Como usar get_term_parents_list() em vez de get_category_parents()
Desde o WordPress 4.8.0, get_category_parents() é apenas um invólucro: chama get_term_parents_list() com a taxonomia category. A mesma função trabalha com qualquer taxonomia hierárquica, incluindo product_cat do WooCommerce. Os argumentos são format (name ou slug), separator, link (por omissão true) e inclusive (por omissão true, que acrescenta o próprio termo no fim). O quinto argumento de get_category_parents() é $deprecated: não é usado desde a versão 4.8.0 e gera um aviso de descontinuação se lhe passar algum valor. O quarto, $nicename, mostra o slug em vez do nome, o que raramente interessa numa interface para leitores.
$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, ' /' ) );
}O código-fonte de category-template.php mostra duas particularidades. O nome entra no link sem esc_html(); só o URL passa por esc_url(). E o separador é acrescentado depois de cada elemento, incluindo o último, daí o rtrim() no exemplo. Com 'inclusive' => false omite o termo atual, por exemplo quando já aparece como título por baixo. A proteção contra uma cadeia de pais corrompida (um termo que, depois de uma importação defeituosa, aponta para um dos seus próprios descendentes) vem de get_ancestors(), que para assim que um ID de pai se repete.
Obter posts por categoria com WP_Query
Quando precisa exibir posts de uma categoria e suas subcategorias:
/**
* Obtém posts de uma categoria e todas suas subcategorias
*
* @param int|string $category Categoria por ID ou slug
* @param int $posts_per_page Número de posts
* @return WP_Query Resultado da query
*/
function wppoland_get_posts_in_category_and_children( $category, $posts_per_page = 10 ) {
// Se for slug, obter ID
if ( is_string( $category ) ) {
$cat = get_category_by_slug( $category );
if ( $cat ) {
$category = $cat->term_id;
}
}
// Obter todas as subcategorias
$subcategories = get_categories( array(
'child_of' => $category,
'fields' => 'ids',
'hide_empty' => false
) );
// Incluir a categoria principal
$all_categories = array_merge( array( $category ), $subcategories );
// Query de posts
$query = new WP_Query( array(
'category__in' => $all_categories,
'posts_per_page' => $posts_per_page,
'orderby' => 'date',
'order' => 'DESC',
'ignore_sticky_posts' => true
) );
return $query;
}
// Uso:
$query = wppoland_get_posts_in_category_and_children( 'tecnologia', 5 );
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
// Exibir post
}
wp_reset_postdata();
}Exibir apenas categorias com posts
Para verificar se uma categoria tem posts antes de exibi-la:
/**
* Verifica se categoria tem posts (incluindo subcategorias)
*
* @param int $category_id ID da categoria
* @return bool True se tem posts
*/
function wppoland_category_has_posts( $category_id ) {
$category = get_category( $category_id );
// Verificar posts na categoria
if ( $category->count > 0 ) {
return true;
}
// Verificar posts nas subcategorias
$subcategories = get_categories( array(
'child_of' => $category_id,
'hide_empty' => false,
'fields' => 'ids'
) );
foreach ( $subcategories as $sub_id ) {
$sub = get_category( $sub_id );
if ( $sub->count > 0 ) {
return true;
}
}
return false;
}
/**
* Lista categorias com posts visíveis
*
* @return string HTML da lista
*/
function wppoland_list_active_categories() {
$categories = get_categories( array(
'hide_empty' => true,
'parent' => 0, // Apenas categorias raiz
'orderby' => 'name',
'order' => 'ASC'
) );
if ( empty( $categories ) ) {
return '';
}
$output = '<ul class="active-categories">';
foreach ( $categories as $cat ) {
if ( wppoland_category_has_posts( $cat->term_id ) ) {
$link = get_category_link( $cat->term_id );
$output .= '<li><a href="' . esc_url( $link ) . '">';
$output .= esc_html( $cat->name );
$output .= '<span class="post-count">(' . intval( $cat->count ) . ')</span>';
$output .= '</a></li>';
}
}
$output .= '</ul>';
return $output;
}Como trabalhar com categorias personalizadas e taxonomias
O sistema funciona da mesma forma para taxonomias personalizadas:
/**
* Obtém hierarquia para qualquer taxonomia
*
* @param string $taxonomy Nome da taxonomia
* @param int $term_id ID do termo
* @return string HTML da hierarquia
*/
function wppoland_get_term_hierarchy( $taxonomy, $term_id ) {
$term = get_term( $term_id, $taxonomy );
if ( is_wp_error( $term ) || ! $term ) {
return '';
}
$trail = array();
$current = $term;
// Percorrer hierarquia
while ( $current->parent ) {
$current = get_term( $current->parent, $taxonomy );
array_unshift( $trail, $current );
}
// Adicionar termo atual
array_unshift( $trail, $term );
// Construir HTML
$output = '<nav class="term-breadcrumb">';
foreach ( $trail as $index => $t ) {
$is_last = ( $index === count( $trail ) - 1 );
if ( ! $is_last ) {
$link = get_term_link( $t, $taxonomy );
$output .= '<a href="' . esc_url( $link ) . '">' . esc_html( $t->name ) . '</a>';
$output .= ' » ';
} else {
$output .= '<span class="current">' . esc_html( $t->name ) . '</span>';
}
}
$output .= '</nav>';
return $output;
}
// Uso para taxonomia personalizada:
$hierarchy = wppoland_get_term_hierarchy( 'produto_categoria', $term_id );Como ler a categoria principal do Yoast SEO e do Rank Math
Os editores atribuem muitas vezes várias categorias a um post, mas o breadcrumb deve seguir um único ramo. Os plugins SEO guardam essa escolha como post meta: o Yoast SEO em _yoast_wpseo_primary_category (o prefixo _yoast_wpseo_ mais primary_ e o nome da taxonomia, como mostra a classe WPSEO_Primary_Term) e o Rank Math em rank_math_primary_category. Leia esse valor antes de recorrer ao primeiro elemento do array:
/**
* Devolve o ID da categoria folha para o breadcrumb.
*
* @param int $post_id ID do post.
* @return int 0 se o post não tiver categorias.
*/
function wppoland_primary_category_id( $post_id ) {
$post_id = (int) $post_id;
foreach ( array( '_yoast_wpseo_primary_category', 'rank_math_primary_category' ) as $key ) {
$term_id = (int) get_post_meta( $post_id, $key, true );
if ( $term_id > 0 && term_exists( $term_id, 'category' ) ) {
return $term_id;
}
}
$categories = get_the_category( $post_id );
return empty( $categories ) ? 0 : (int) $categories[0]->term_id;
}O term_exists() protege contra um ID que ficou no meta depois de a categoria ter sido apagada. Sem plugin SEO, o núcleo do WordPress não tem o conceito de categoria principal: ou guarda o seu próprio post meta, ou aceita uma regra como «a categoria com mais antepassados». Quando dois plugins SEO convivem durante uma migração, decida qual deles é dono do campo e desative o módulo de breadcrumbs do outro.
Como escolher a categoria quando o post tem várias
Quando um post pertence a múltiplas categorias, pode permitir que o utilizador escolha qual exibir:
/**
* Exibe seletor de categorias para posts com múltiplas categorias
*
* @param int|null $post_id ID do post
*/
function wppoland_category_selector( $post_id = null ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$categories = get_the_category( $post_id );
if ( count( $categories ) <= 1 ) {
// Se só tem uma categoria, exibir normalmente
echo wppoland_get_full_category_trail( $post_id );
return;
}
// Múltiplas categorias - exibir seletor
?>
<div class="multi-category-selector">
<label for="category-select">Ver em:</label>
<select id="category-select" onchange="window.location.href=this.value">
<?php foreach ( $categories as $cat ) : ?>
<option value="<?php echo esc_url( get_category_link( $cat->term_id ) ); ?>">
<?php echo esc_html( $cat->name ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php
}O que a Google mostra a partir do BreadcrumbList
A documentação da Google sobre breadcrumbs define o mínimo: um BreadcrumbList com pelo menos dois ListItem, position a começar em 1 e um URL em item em cada elemento. A exceção é o último: se aí faltar item, a Google usa o URL da própria página. Para um post, a página inicial mais uma categoria já cumpre o mínimo; o título do post como último elemento é permitido, mas não obrigatório.
A Google aceita vários caminhos para a mesma página e recomenda um caminho que reflita o percurso típico do utilizador, em vez de copiar a estrutura do URL. Para breadcrumbs de categorias, isto é um argumento a favor da categoria principal: descreve o caminho pretendido pela redação, mesmo quando o permalink não contém nenhuma categoria.
O caminho só é visível na pesquisa Google em computador. Desde 23 de janeiro de 2025, os resultados em dispositivos móveis mostram apenas o domínio na linha do URL, como anunciou o blog Search Central. A marcação continua suportada e o relatório na Search Console mantém-se. Os breadcrumbs visíveis no tema servem, antes de mais, quem lê. Se o tema ou o plugin SEO já emitem um BreadcrumbList, não acrescente um segundo: escolha uma única fonte por modelo.
Como usar o bloco Breadcrumbs do WordPress 7.0 com a categoria principal
Desde o WordPress 7.0, o núcleo inclui o seu próprio bloco core/breadcrumbs (a versão atual, segundo a API de versões do WordPress.org, é a 7.1.3). O block.json indica os atributos separator (por omissão /), showHomeItem, showCurrentItem, showOnHomePage e prefersTaxonomy. Num post sem post pai, o bloco constrói o caminho com termos de taxonomia: usa a primeira taxonomia registada para o tipo de conteúdo que tenha termos e, dentro dela, o primeiro termo devolvido por get_the_terms(), através de reset( $terms ). É a mesma armadilha de $categories[0]: sem intervenção, ganha o primeiro nome por ordem alfabética.
O código-fonte do bloco oferece para isso o filtro block_core_breadcrumbs_post_type_settings. Espera um array com taxonomy e term, e term é um slug, não um ID. Assim passa ao bloco a categoria principal do Yoast SEO ou do 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
);Um segundo filtro, block_core_breadcrumbs_items, permite alterar a lista final de pares label e url antes da renderização. A marcação do bloco já cobre o básico de acessibilidade: um nav com aria-label, um ol e o último elemento como span com aria-current="page". O bloco não imprime JSON-LD. O BreadcrumbList continua a vir do plugin SEO ou de uma função como a apresentada acima, e ambos devem usar o mesmo termo que entrega ao bloco através do filtro.
Como tornar o breadcrumb de categorias acessível
Use <nav aria-label="..."> com um nome traduzido e marque o último elemento com aria-current="page". Os separadores visíveis (/, ») devem ficar em aria-hidden="true", para que os leitores de ecrã não leiam o símbolo entre cada link. Uma lista ordenada (ol e li) comunica a ordem e o número de passos melhor do que uma cadeia de links em linha. Não dependa só da cor para indicar o elemento atual.
Como gerir breadcrumbs de categorias com Polylang e WPML
Os IDs dos termos dependem do idioma. Um caminho construído num idioma e mostrado noutro apresenta as etiquetas erradas. Resolva primeiro o ID do termo traduzido através da API do plugin e só depois chame get_ancestors(). Os permalinks seguem a estrutura de URL de cada idioma: get_term_link() respeita-a, um caminho escrito à mão não. A chave de cache do breadcrumb tem de incluir o idioma, e convém confirmar que o meta da categoria principal do Yoast ou do Rank Math aponta, depois da tradução, para um termo do mesmo idioma.
Como otimizar performance de consultas de categorias
Como fazer cache das categorias
Para sites com muito tráfego, implemente cache:
/**
* Obtém hierarquia com cache transient
*
* @param int $post_id ID do post
* @param int $cache_time Tempo de cache em segundos (padrão: 1 hora)
* @return string HTML da hierarquia
*/
function wppoland_get_cached_category_hierarchy( $post_id = null, $cache_time = 3600 ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
// Gerar chave de cache única
$cache_key = 'cat_hierarchy_' . $post_id . '_' . get_current_blog_id();
// Tentar obter do cache
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
// Gerar hierarquia
$hierarchy = wppoland_get_full_category_trail( $post_id );
// Salvar no cache
set_transient( $cache_key, $hierarchy, $cache_time );
return $hierarchy;
}
// Uso - substitua a chamada original
$hierarchy = wppoland_get_cached_category_hierarchy();Como evitar consultas excessivas a categorias
Quando precisar de múltiplas categorias, use uma única consulta:
/**
* Otimizado: Obtém hierarquias para múltiplos posts
* Evita N+1 queries
*
* @param array $post_ids Array de IDs de posts
* @return array Array com post_id => hierarquia HTML
*/
function wppoland_get_multiple_category_hierarchies( $post_ids ) {
if ( empty( $post_ids ) ) {
return array();
}
// Obter todas as categorias de uma vez
$all_categories = get_the_category( $post_ids );
// Organizar por post
$categories_by_post = array();
foreach ( $all_categories as $cat ) {
if ( ! isset( $categories_by_post[ $cat->post_id ] ) ) {
$categories_by_post[ $cat->post_id ] = array();
}
$categories_by_post[ $cat->post_id ][] = $cat;
}
// Gerar hierarquias
$hierarchies = array();
foreach ( $post_ids as $post_id ) {
if ( isset( $categories_by_post[ $post_id ] ) ) {
$categories = $categories_by_post[ $post_id ];
$category = $categories[0];
// Gerar hierarquia (código simplificado)
$trail = array();
while ( $category->parent ) {
$category = get_category( $category->parent );
array_unshift( $trail, $category );
}
array_unshift( $trail, get_category( $categories[0]->term_id ) );
// Construir HTML
$html = '';
foreach ( $trail as $cat ) {
$html .= '<a href="' . esc_url( get_category_link( $cat->term_id ) ) . '">';
$html .= esc_html( $cat->name ) . '</a> / ';
}
$hierarchies[ $post_id ] = rtrim( $html, ' / ' );
}
}
return $hierarchies;
}Como criar breadcrumbs para páginas e categorias de produtos WooCommerce
Hierarquias de páginas (post_parent), categorias de loja do WooCommerce e tipos de conteúdo de documentação costumam precisar de caminhos próprios. Não misture no mesmo breadcrumb o pai do post e o pai da categoria, a menos que a arquitetura de informação o preveja. Um erro frequente em lojas: os breadcrumbs do tema leem category, enquanto os produtos vivem em product_cat. O caminho visível mostra secções do blogue, o schema mostra categorias de produto, e a Search Console assinala breadcrumbs inconsistentes. Uma taxonomia por tipo de conteúdo, um construtor de caminho, um bloco de schema.
Como testar breadcrumbs de categorias antes de publicar
- Post sem categoria: a função devolve uma string vazia ou
false, sem avisos de PHP. - Só categoria de topo: sem link para o pai, mas com um
BreadcrumbListválido com a página inicial e o termo. - Hierarquia alterada depois da publicação: limpe a cache do caminho antigo e do novo.
- ID de categoria principal apagado no meta do Yoast ou do Rank Math: recurso a
get_the_category(). - Nome de categoria com aspas ou «&»: sempre
esc_html(). - Importação de CSV ou de um CMS antigo: o ID do pai pode apontar para um termo apagado, por isso verifique o resultado de
get_term()e interrompa o caminho. - Tema de blocos: o shortcode ou a parte de template tem de ver o
get_the_ID()correto no contexto do bloco.
Se o tema tiver CI, escreva estes casos como testes PHPUnit ou de integração. Pelo menos uma vez à mão: um post com duas categorias, categoria principal definida, um pai três níveis acima e a object cache vazia.
Checklist para implementar a hierarquia de categorias
Implementação básica
- Adicionar função
wppoland_get_category_hierarchy()ao functions.php - Inserir chamada no template onde deseja exibir
- Adicionar CSS para estilização
Implementação avançada
- Usar
wppoland_get_full_category_trail()para hierarquias completas - Adicionar markup Schema.org para SEO
- Implementar cache com transients
Otimização
- Para sites com muito tráfego, usar cache agressivo
- Em loops, usar consultas otimizadas
- Minimizar chamadas get_category() em loops
Boas práticas
- Sempre usar
esc_url()eesc_html()para segurança - Verificar se categorias existem antes de usar
- Testar com posts em múltiplos níveis de hierarquia
- Validar markup HTML
Com estas técnicas, pode criar sistemas de navegação por categorias robustos e otimizados para qualquer projeto WordPress.
Navegação por categorias hierárquicas parece resolvida até surgir o nível seguinte da árvore. Se quiser esta parte do tema construída para aguentar hierarquias reais, veja o nosso desenvolvimento de temas WordPress.







