the_category() imprime âncoras HTML. Se o cartão do arquivo, o slider ou a badge do tema precisam de texto puro, essa função está no caminho errado. A alternativa correta é get_the_category() (ou get_the_terms() para taxonomias genéricas): devolve objetos PHP, e decide se o nome transforma-se em <span>, classe CSS ou string para um atributo aria-label.
Este tutorial cobre o contrato de saída de the_category(), a forma do array de get_the_category(), escape com esc_html(), listas com implode(), o antipadrão strip_tags( the_category() ), get_the_terms(), categoria primária (Yoast / Rank Math), Full Site Editing e o papel da object cache.
Porque the_category mostra a categoria com link
No Codex e no Developer Reference, the_category() é uma template tag de saída: escreve HTML no buffer. O separador entre categorias é um argumento de string, mas cada nome continua dentro de <a href="...">. Não existe parâmetro oficial para «só o nome».
<?php
// No Loop: imprime HTML com links para os arquivos de categoria.
the_category( ', ' );
?>Exemplo típico de markup gerado (simplificado):
<a href="https://exemplo.pt/categoria/noticias/" rel="category tag">Notícias</a>,
<a href="https://exemplo.pt/categoria/eventos/" rel="category tag">Eventos</a>Para um badge visual no content-card.php, esse markup traz problemas concretos:
- o clique na badge compete com o clique no título do post;
- estilos de
ado tema (underline, cor de foco) herdam-se para a label; - leitores de ecrã anunciam «link» quando a intenção era só um rótulo;
- em JSON-LD ou meta tags, o HTML fica exposto se alguém copiar a saída sem sanitizar.
Quando o desenho pede texto, leia dados. Quando o desenho pede navegação para o arquivo, aí sim the_category() ou um <a> construído à mão com get_category_link() fazem sentido.
O que devolve get_the_category no WordPress
get_the_category( $post_id = false ) devolve um array de objetos WP_Term (historicamente documentados como objetos de categoria). Sem argumentos, usa o post global do Loop. Com ID, funciona fora do Loop, por exemplo num shortcode ou num callback REST.
Propriedades usadas na prática do tema:
| Propriedade | Uso típico |
|---|---|
name | Texto visível da badge |
slug | Classe CSS (badge-noticias) |
term_id | Chaves de cache, comparações |
term_taxonomy_id | Identidade na tabela term_taxonomy |
description | Tooltips raros; costuma ser HTML |
count | Contagem de posts na categoria |
parent | Hierarquia; 0 = raiz |
cat_ID | Alias legado de term_id |
Snippet mínimo para a primeira categoria:
<?php
$categories = get_the_category();
if ( ! empty( $categories ) ) {
echo esc_html( $categories[0]->name );
}
?>O índice [0] é a primeira categoria na ordem em que o WordPress as devolve para aquele post. Essa ordem não é «categoria primária SEO». Se o editor atribuiu três categorias e Yoast marcou outra como primary, $categories[0] pode ser a errada para o breadcrumb comercial. A secção sobre categoria primária cobre esse caso.
Posts sem categorias (ou tipos de conteúdo onde a taxonomia category não está registada) devolvem array vazio. O ! empty() evita avisos de índice indefinido em PHP 8+.
O que faz get_the_category dentro do núcleo do WordPress
No código-fonte do wordpress-develop, get_the_category() é um invólucro fino. Chama get_the_terms( $post_id, 'category' ), transforma um resultado false ou WP_Error num array vazio, reindexa-o com array_values(), passa cada termo pela função privada _make_cat_compat() e devolve o resultado através do filtro get_the_categories. Destas poucas linhas resultam três consequências práticas.
As propriedades legadas só existem na saída desta função. _make_cat_compat() copia os campos modernos para nomes antigos: cat_ID, cat_name, category_nicename, category_count, category_description e category_parent. Os objetos de get_the_terms() não as recebem. Um template que lê $cat->cat_name funciona hoje e deixa de mostrar o nome no dia em que alguém troca a chamada por get_the_terms() para suportar uma taxonomia personalizada. Leia name, slug e term_id, que as duas funções partilham.
A ordem é alfabética, não editorial. get_the_terms() consulta primeiro a cache de relações de termos do post e, se não houver dados, chama wp_get_object_terms(), que executa um WP_Term_Query cujo orderby por omissão é name. Por isso $categories[0] é a primeira categoria por ordem alfabética, a não ser que um filtro reordene o array.
Os plugins podem reescrever o array. Qualquer função ligada a get_the_categories (com o argumento $post_id desde o WordPress 4.4) ou a get_the_terms pode acrescentar, retirar ou reordenar termos antes de o template os ver. Se uma badge mostra uma categoria que o editor nunca atribuiu, verifique as funções ligadas a esses dois hooks antes de depurar o template.
Como escapar o nome da categoria com esc_html
Nomes de categoria são conteúdo editorial: aspas, & e outros caracteres especiais chegam da base de dados tal como foram guardados, e importações diretas na base de dados contornam os filtros de gravação. Em HTML de texto, use esc_html(). Em atributos (class, data-*, aria-label), use esc_attr().
<?php
$categories = get_the_category();
$first = ! empty( $categories ) ? $categories[0] : null;
if ( $first ) :
?>
<span
class="badge badge-<?php echo esc_attr( $first->slug ); ?>"
aria-label="<?php echo esc_attr( sprintf( 'Categoria: %s', $first->name ) ); ?>"
>
<?php echo esc_html( $first->name ); ?>
</span>
<?php
endif;Diferença prática:
esc_html()converte<,>,&, aspas para entidades adequadas a nós de texto;esc_attr()trata o contexto de atributo (aspas e caracteres que partem o markup);sanitize_html_class( $first->slug )é útil se o slug puder ter caracteres estranhos após migrações antigas.
Não escape duas vezes. esc_html( esc_html( $name ) ) não «segura mais»; só dificulta a leitura e pode alterar entidades em cenários edge.
Porque o nome da categoria aparece com & fora do HTML
Antes de gravar um termo, o default-filters.php passa o nome por sanitize_text_field(), wp_filter_kses() e, com prioridade 30, por _wp_specialchars() no hook pre_term_name. As tags HTML são removidas na gravação, e uma categoria chamada «Dicas & Truques» fica na base de dados como Dicas & Truques.
Dentro de HTML isso não causa problemas. esc_html() chama _wp_specialchars() com a dupla codificação desligada, por isso um & existente continua & e o browser mostra um «&» normal. O & literal aparece fora do HTML: num JSON-LD construído com wp_json_encode(), num campo REST que o JavaScript escreve com textContent, no assunto de um email em texto simples ou numa exportação CSV. Uma chamada a htmlspecialchars() do PHP, que codifica duas vezes por omissão, produz o &amp; visível. Nesses contextos descodifique primeiro e aplique depois o escape do formato de destino:
<?php
$categories = get_the_category();
if ( ! empty( $categories ) ) {
// Converte o "&" guardado em "&" para um contexto que não é HTML.
$nome = wp_specialchars_decode( $categories[0]->name, ENT_QUOTES );
$schema = array( 'articleSection' => $nome );
echo wp_json_encode( $schema );
}
?>Como listar várias categorias sem link com implode
Vários nomes sem âncoras: acumule strings escapadas e junte com implode().
<?php
$categories = get_the_category();
$names = array();
if ( ! empty( $categories ) ) {
foreach ( $categories as $category ) {
$names[] = esc_html( $category->name );
}
echo implode( ', ', $names );
}
?>Vantagens face a concatenar à mão com $out .= $name . ', ':
- o separador fica num sítio só;
- não sobra vírgula final;
- pode trocar
', 'por' · 'ou por'</span><span class="sep">'sem reescrever o loop.
Se precisar de markup por item (cada nome num <span>), faça o escape dentro do fragmento e junte os fragmentos:
<?php
$categories = get_the_category();
$chips = array();
foreach ( (array) $categories as $category ) {
$chips[] = sprintf(
'<span class="chip chip-%1$s">%2$s</span>',
esc_attr( $category->slug ),
esc_html( $category->name )
);
}
echo implode( ' ', $chips );
?>Aqui implode junta HTML já escapado por peça. Não passe o HTML completo por esc_html() no fim - isso tornaria as tags visíveis como texto.
Como ordenar e excluir categorias de um post
O ciclo com implode acima cobre o caso básico. Em templates reais costuma precisar de mais duas coisas: controlo sobre a ordem e uma forma de saltar categorias que nunca quer mostrar (o exemplo clássico é “Sem categoria” a aparecer num design de cartão).
Para a ordem, wp_list_pluck() reduz o ciclo a uma linha e um simples sort() trata da ordem alfabética. O WordPress devolve as categorias ordenadas pelo nome por defeito, mas os plugins podem filtrar isso, por isso ordenar explicitamente não custa nada e elimina uma surpresa:
<?php
$categories = get_the_category();
if ( ! empty( $categories ) ) {
// Extrair só os nomes dos objetos de termo
$names = wp_list_pluck( $categories, 'name' );
// Ordem alfabética explícita, independente de filtros
sort( $names );
echo esc_html( implode( ' / ', $names ) );
}
?>Para excluir categorias específicas, compare pelo slug e não pelo nome. Os slugs são estáveis, enquanto os nomes são editados pelos clientes sem aviso:
<?php
$categories = get_the_category();
$skip = array( 'uncategorized', 'featured' );
$names = array();
foreach ( $categories as $category ) {
if ( in_array( $category->slug, $skip, true ) ) {
continue;
}
$names[] = $category->name;
}
if ( ! empty( $names ) ) {
echo esc_html( implode( ', ', $names ) );
}
?>O escape acontece uma vez, na string final já unida. Escapar cada nome e depois unir também funciona, mas manter um único esc_html() no ponto de saída torna evidente em code review que nada sai do template sem escape.
Porque não usar strip_tags em the_category
Ainda aparece em temas antigos e em respostas de fóruns:
<?php
// Antipadrão: não faças isto.
echo strip_tags( get_the_category_list( ', ' ) );
// ou:
ob_start();
the_category( ', ' );
echo strip_tags( ob_get_clean() );
?>Problemas:
- Custo desnecessário. Constrói HTML completo (links,
rel, URLs) para deitar fora as tags. - Separadores frágeis. Se o tema ou um filtro em
the_categoryinjeta wrappers,strip_tagspode colar nomes sem espaço (NotíciasEventos). - Escape errado. Depois de
strip_tags, ainda precisa deesc_html(); muita gente esquece e imprime texto cru. - Filtros laterais. Plugins que alteram o HTML de
the_categorypassam a afetar «texto puro» sem o programador perceber. - Testes. Unit tests sobre strings HTML são mais ruidosos do que asserts sobre
$term->name.
O caminho curto e estável é get_the_category() + propriedade name. Reserve strip_tags para conteúdo rico onde realmente recebeu HTML de origem desconhecida - não para remover âncoras que o próprio pediu à API.
Como obter termos de taxonomia personalizada com get_the_terms
get_the_category() é um atalho para a taxonomia category em posts. Em custom post types com taxonomias próprias (setor, tipo_projeto, mercado), use get_the_terms( $post, $taxonomy ).
<?php
$terms = get_the_terms( get_the_ID(), 'setor' );
if ( empty( $terms ) || is_wp_error( $terms ) ) {
return;
}
$names = array();
foreach ( $terms as $term ) {
$names[] = esc_html( $term->name );
}
echo implode( ', ', $names );
?>Notas de contrato:
- retorno pode ser
false, array deWP_Term, ouWP_Errorse a taxonomia não existir; - verifique sempre
is_wp_error()antes de iterar; - a forma do objeto é a mesma família (
name,slug,term_id); - para um único termo «como categoria principal do CPT», a mesma lógica de primary (meta do SEO plugin) aplica-se se o plugin a suportar nessa taxonomia.
wp_get_post_terms() é irmão próximo com argumentos de WP_Term_Query. Para «nomes no template», get_the_terms() costuma bastar e beneficia da cache de termos do post.
Obter os nomes das categorias como array com wp_get_post_terms
wp_get_post_terms() passa o terceiro argumento a wp_get_object_terms(), por isso aceita as opções de WP_Term_Query que get_the_terms() não aceita. Com 'fields' => 'names' devolve um array de strings e o ciclo desaparece:
<?php
// Só os nomes, primeiro as categorias mais usadas.
$nomes = wp_get_post_terms(
get_the_ID(),
'category',
array(
'fields' => 'names',
'orderby' => 'count',
'order' => 'DESC',
)
);
if ( ! is_wp_error( $nomes ) && ! empty( $nomes ) ) {
echo esc_html( implode( ', ', $nomes ) );
}
?>O preço é a cache. get_the_terms() lê primeiro a cache de relações de cada post, e o WP_Query principal preenche-a para todos os posts da página porque update_post_term_cache vale true por omissão. wp_get_post_terms() ignora essa cache e chama sempre wp_get_object_terms(), pelo que num arquivo com vinte cartões faz vinte consultas de termos para dados que o WordPress já tinha carregado. Dentro do Loop, wp_list_pluck( get_the_category(), 'name' ) devolve o mesmo array de nomes a partir da cache. Guarde wp_get_post_terms() para os sítios onde precisa dos argumentos, como ordenar por count numa barra lateral ou um callback REST que trata um post de cada vez.
Obter a categoria primária do Yoast e Rank Math
Em arquivos e cartões de blog corporativo, o negócio quer uma label: a categoria comercial, não a primeira da lista alfabética ou a ordem de atribuição no editor.
Yoast SEO grava o ID em post meta _yoast_wpseo_primary_category. Rank Math usa rank_math_primary_category. Sem plugin, defina a sua própria meta ou assuma $categories[0] de forma documentada no README do tema.
<?php
/**
* Devolve o WP_Term da categoria primária, ou o primeiro de get_the_category().
*/
function wppoland_get_primary_category( $post_id = 0 ) {
$post_id = $post_id ? (int) $post_id : get_the_ID();
$primary_id = (int) get_post_meta( $post_id, '_yoast_wpseo_primary_category', true );
if ( ! $primary_id ) {
$primary_id = (int) get_post_meta( $post_id, 'rank_math_primary_category', true );
}
if ( $primary_id ) {
$term = get_term( $primary_id, 'category' );
if ( $term && ! is_wp_error( $term ) ) {
return $term;
}
}
$categories = get_the_category( $post_id );
return ! empty( $categories ) ? $categories[0] : null;
}
$primary = wppoland_get_primary_category();
if ( $primary ) {
echo esc_html( $primary->name );
}
?>Regras de robustez:
- se o termo primary foi apagado mas a meta ficou, faça fallback para
get_the_category(); - não faça cache do nome em transient sem invalidar quando o post ou o termo mudam;
- no breadcrumb, a primary é a que deve alimentar o item intermédio entre Home e o post.
Nome da categoria sem link no bloco Post Terms
Em temas de blocos (FSE), o bloco Post Categories / Post Terms renderiza sempre links, alinhado com the_category(): o block.json do bloco só define os atributos term, separator, prefix e suffix, e o render em PHP constrói a saída com get_the_term_list(). Não existe opção para desligar os links. Para texto puro no editor de site dispõe de três vias honestas:
- CSS only (limitado).
pointer-events: noneetext-decoration: noneno bloco de categorias do cartão. Remove o gesto de clique visualmente, mas o markup continua a ser<a>- mau para acessibilidade e para «copiar como texto». - Bloco personalizado ou variação. Um bloco dinâmico PHP que chama
get_the_category()e imprime<span>. Registe comblock.jsonerender_callback. - Filtro no HTML do bloco.
render_blockpode reescrever o HTML do core, mas é frágil entre versões do WordPress. Prefira um bloco seu se o cartão for peça central do design system.
Exemplo mínimo de render_callback para um bloco de tema:
<?php
function wppoland_render_category_label_block( $attributes, $content, $block ) {
$post_id = isset( $block->context['postId'] )
? (int) $block->context['postId']
: get_the_ID();
$categories = get_the_category( $post_id );
if ( empty( $categories ) ) {
return '';
}
$term = $categories[0];
return sprintf(
'<span class="wp-block-wppoland-category-label">%s</span>',
esc_html( $term->name )
);
}
?>No Query Loop, o contexto postId do bloco é o que garante a categoria do item certo - não dependa só do Loop global se o callback correr em contextos aninhados.
Como fazer cache do nome da categoria no WordPress
get_the_category() e get_the_terms() leem a relação post-termo e os objetos WP_Term. Com object cache persistente (Redis, Memcached via drop-in), termos e meta entram em cache de forma transparente. Na prática:
- Não reinvente um transient por cartão só para o nome da categoria no Loop principal do arquivo. O custo de
get_the_category()no Loop é baixo face ao resto do template. - Caches de fragmento HTML (cartões inteiros em transient ou em edge) devem incluir o nome da categoria dentro do HTML cacheado, e invalidar quando o post é guardado (
save_post) ou quando o termo é editado (edited_term). - Se guarda só o
term_idno seu índice custom (Elasticsearch, tabela própria), resolva onamena renderização comget_term()- assim renomear a categoria atualiza o arquivo sem rebuild do índice de IDs.
Exemplo de invalidação ao guardar o post, se mantém um fragmento por ID:
<?php
add_action(
'save_post_post',
function ( $post_id ) {
delete_transient( 'wppoland_card_html_' . (int) $post_id );
}
);
?>Evite cachear a string já passada por esc_html() durante horas se editores alteram nomes com frequência em sites de notícias - ou aceite um TTL curto (5-15 minutos) e documente o compromisso.
Checklist para mostrar categorias sem link no tema
Antes de fechar o PR do partial template-parts/card-category.php:
- Confirmou que o design quer texto, não navegação para o arquivo.
- Usa
get_the_category()ouget_the_terms(), nuncastrip_tagsem cima dethe_category(). - Todo o
namevisível passa poresc_html(); slugs em classes poresc_attr()ousanitize_html_class(). - Decidiu explicitamente entre «primeira do array» e «categoria primária SEO».
- No FSE, o cartão não depende de
pointer-events: nonecomo solução permanente. - Fragmentos HTML cacheados invalidam em
save_post/ edição de termo.
Código PHP da categoria sem link no cartão de arquivo
<?php
/**
* template-parts/card-category-label.php
*
* @package WPPoland_Theme
*/
defined( 'ABSPATH' ) || exit;
$post_id = get_the_ID();
$primary = function_exists( 'wppoland_get_primary_category' )
? wppoland_get_primary_category( $post_id )
: null;
if ( ! $primary ) {
$categories = get_the_category( $post_id );
$primary = ! empty( $categories ) ? $categories[0] : null;
}
if ( ! $primary ) {
return;
}
?>
<span class="card__category card__category--<?php echo esc_attr( $primary->slug ); ?>">
<?php echo esc_html( $primary->name ); ?>
</span>Este partial separa dados de apresentação: o CSS pinta a badge; o PHP não emite âncoras. Se mais tarde o produto pedir link, envolva o esc_html( $primary->name ) num <a href="<?php echo esc_url( get_category_link( $primary->term_id ) ); ?>"> - sem reintroduzir the_category().
Nome da categoria no arquivo de categoria com single_cat_title
Em category.php a categoria não pertence a um post: é o objeto consultado da página inteira. Ali get_the_category() devolve as categorias do primeiro post do Loop, não a categoria do arquivo. A função certa é single_cat_title(), e com false no segundo argumento devolve o valor em vez de o imprimir:
<?php
if ( is_category() ) {
$nome = single_cat_title( '', false );
echo esc_html( $nome );
}
?>No general-template.php, single_cat_title() delega em single_term_title(), que lê get_queried_object(), aplica o filtro single_cat_title em arquivos de categoria e devolve null fora de arquivos de categoria, etiqueta ou taxonomia. O default-filters.php liga a esse filtro wptexturize e strip_tags, que não substituem o escape: mantenha esc_html() na saída. Se precisar também do slug ou do term_id para uma classe CSS, leia o objeto diretamente com get_queried_object() e verifique que é uma instância de WP_Term.
Quando usar the_category com links
Há ecrãs em que o HTML com links é o produto:
- rodapé do single clássico «Publicado em Notícias, Eventos»;
- widgets de «meta do post» onde cada categoria deve ser clicável;
- temas minimalistas sem cartões, onde o arquivo por categoria é a navegação secundária.
Nesses casos chame the_category( ', ' ) (ou o bloco Post Terms) e não reinvente o markup. O erro é misturar os dois modos no mesmo componente sem regra: badge do cartão com texto, meta do single com links - documente a regra no tema para o próximo programador não «unificar» tudo com strip_tags.
Funções WordPress para obter categorias
| Função | Papel | Saída |
|---|---|---|
the_category() | Template tag de impressão | HTML com <a> |
get_the_category() | Dados da taxonomia category | WP_Term[] |
get_the_category_list() | Lista HTML configurável | string HTML |
get_the_terms() | Qualquer taxonomia | WP_Term[]|false|WP_Error |
wp_get_post_terms() | Qualquer taxonomia, com argumentos de WP_Term_Query | WP_Term[], string[] ou WP_Error |
single_cat_title() | Nome da categoria no arquivo de categoria | string ou null |
get_category_link() | URL do arquivo | string URL |
Documentação oficial (Developer Reference):
Porque get_the_category devolve categorias vazias ou erradas
Esquecer wp_reset_postdata() em ciclos próprios. get_the_category() lê o $post global. Num ciclo WP_Query próprio que chama the_post(), a variável global continua a apontar para o último item desse ciclo se não a repuser, por isso qualquer chamada de categoria depois do ciclo devolve as categorias do post errado:
<?php
$related = new WP_Query( array( 'posts_per_page' => 3 ) );
while ( $related->have_posts() ) {
$related->the_post();
$cats = get_the_category();
// ... saída ...
}
// Sem esta linha, get_the_category() abaixo refere-se ao último post relacionado
wp_reset_postdata();
?>Assumir que um post tem sempre categoria. Os posts normais recebem “Sem categoria” por defeito, mas tipos de conteúdo personalizados com suporte de categorias podem mesmo não ter nenhuma, e o código que lê $categories[0] às cegas gera então um aviso de índice indefinido. A verificação ! empty( $categories ) usada nos exemplos deste artigo está lá por uma razão: mantenha-a, mesmo que hoje “todos os posts têm categoria” pareça garantido.
Erros comuns ao mostrar categorias no tema
Em code review, procure estes sinais de regressão:
echo $category->namesem escape;the_category()dentro de atributos HTML (title="");- dependência da ordem do array quando o negócio fala em «categoria principal»;
- testes E2E que assertam href na badge do cartão (o teste fica a vermelho quando passa a texto puro: atualize o seletor para
.card__categorysema).
Em sites multilingues (WPML, Polylang), o name já vem na língua do termo traduzido se o post estiver na língua certa. Não concatene nomes de outro idioma a partir de IDs sem passar pela API do plugin de tradução.
Se precisar de quem implemente estes partials e a regra de primary no seu tema, fale com a equipa de desenvolvimento WordPress.
Última verificação editorial deste guia: 21 de setembro de 2026. As funções citadas mantêm o mesmo contrato público no Developer Handbook; valide sempre contra a versão do WordPress do projeto antes de copiar snippets para produção.







