Atualizado em 19/06/2026
O WordPress nasceu como blog, mas há tempos é um CMS completo. A peça que viabiliza isso são os Custom Post Types (CPTs) — tipos de conteúdo personalizados além de "post" e "página" — e as taxonomias, que classificam esse conteúdo. Com eles você modela imóveis, eventos, produtos ou portfólio com menus, URLs e campos próprios. Este guia mostra como registrar tudo no momento certo (init), o que cada argumento faz e como evitar o erro 404 que pega quase todo iniciante.
CPT e taxonomia: o que é cada um
- Custom Post Type: o conteúdo em si. Um
imovel, umevento, umdepoimento. Tem menu próprio no painel, listagem, edição e (opcionalmente) arquivo público. - Taxonomia: a forma de agrupar esse conteúdo.
categoriaetagsão taxonomias nativas dos posts; você cria as suas (ex.:tipo_imovel,cidade) e as associa ao CPT.
Taxonomias podem ser hierárquicas (como categorias, com pai/filho) ou não hierárquicas (como tags, lista plana). A escolha define como o usuário seleciona os termos no editor.
Registrando um CPT no hook init
O registro acontece no hook init. Registrar tarde demais (ou no hook errado) faz o WordPress não reconhecer o tipo. Veja um CPT livro completo:
<?php
add_action( 'init', 'meu_registrar_cpt_livro' );
function meu_registrar_cpt_livro() {
$labels = array(
'name' => 'Livros',
'singular_name' => 'Livro',
'add_new_item' => 'Adicionar novo livro',
'edit_item' => 'Editar livro',
'search_items' => 'Buscar livros',
'not_found' => 'Nenhum livro encontrado',
);
$args = array(
'labels' => $labels,
'public' => true, // visível no front e no admin
'has_archive' => true, // habilita /livro/ (arquivo)
'rewrite' => array( 'slug' => 'livros' ), // URLs /livros/...
'menu_icon' => 'dashicons-book', // ícone no menu do admin
'show_in_rest' => true, // editor de blocos + REST API
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
);
register_post_type( 'livro', $args );
}
Use uma chave (primeiro parâmetro de register_post_type()) curta, em minúsculas, sem acentos e com até 20 caracteres. Prefira um prefixo do projeto para evitar colisão com outros plugins — por exemplo acme_livro. Não use post, page, attachment e outros nomes reservados pelo núcleo.
Principais argumentos de register_post_type()
| Argumento | Efeito |
|---|---|
public | Atalho que define visibilidade no front e no admin. Quase sempre true. |
has_archive | true cria a página de arquivo do tipo (ex.: /livros/). |
rewrite | Controla a URL. ['slug' => 'livros'] define o prefixo das URLs. |
supports | Recursos do editor: title, editor, thumbnail, excerpt, custom-fields, page-attributes... |
show_in_rest | true habilita o editor de blocos (Gutenberg) e expõe o tipo na API REST. |
menu_icon | Ícone do menu no admin (um Dashicon, ex.: dashicons-book, ou uma URL). |
menu_position | Posição do item no menu lateral do admin. |
hierarchical | true faz o CPT se comportar como página (com pai/filho). |
labels | Textos da interface (botões, títulos), traduzidos para o seu idioma. |
Registrando uma taxonomia e associando ao CPT
A taxonomia também é registrada no init. Importante: registre a taxonomia depois do CPT (ou simplesmente no mesmo hook), e informe no terceiro parâmetro a qual(is) post type(s) ela se aplica. Aqui criamos genero_livro, hierárquica, ligada ao CPT livro:
<?php
add_action( 'init', 'meu_registrar_taxonomia_genero' );
function meu_registrar_taxonomia_genero() {
$labels = array(
'name' => 'Gêneros',
'singular_name' => 'Gênero',
'add_new_item' => 'Adicionar novo gênero',
'search_items' => 'Buscar gêneros',
);
$args = array(
'labels' => $labels,
'hierarchical' => true, // true = como categorias; false = como tags
'public' => true,
'show_in_rest' => true, // necessário no editor de blocos
'show_admin_column' => true, // mostra a coluna na listagem do CPT
'rewrite' => array( 'slug' => 'genero' ),
);
// 3º parâmetro: o(s) post type(s) ao(s) qual(is) a taxonomia se aplica
register_taxonomy( 'genero_livro', array( 'livro' ), $args );
}
Você também pode associar uma taxonomia já existente a um CPT com register_taxonomy_for_object_type( 'genero_livro', 'livro' ) — útil quando a taxonomia foi registrada por outro plugin. Mas, ao criar a sua, prefira declarar os post types direto no register_taxonomy(), como acima.
O erro clássico: URLs do CPT dando 404
Você registra o CPT, acessa /livros/algum-livro/ e leva um 404. O conteúdo existe, mas a URL "não existe". O motivo: as regras de reescrita (rewrite rules) que mapeiam essas URLs bonitas para a consulta interna só passam a valer depois de serem gravadas. Registrar o CPT no init não regrava essas regras sozinho.
A solução depende do contexto:
- Em desenvolvimento, o jeito mais rápido: vá em Configurações → Links permanentes e clique em Salvar. Isso dispara um flush das regras.
- Em um plugin, automatize: rode
flush_rewrite_rules()uma única vez, na ativação do plugin — não a cada requisição.
<?php
// Arquivo principal do plugin
// 1) O CPT é registrado normalmente no init (em toda requisição)
add_action( 'init', 'meu_registrar_cpt_livro' );
// 2) Na ATIVAÇÃO: registra o CPT e DEPOIS regrava as regras, uma vez só
register_activation_hook( __FILE__, 'meu_plugin_na_ativacao' );
function meu_plugin_na_ativacao() {
// Importante: o CPT precisa existir antes do flush,
// por isso chamamos o registro aqui também.
meu_registrar_cpt_livro();
flush_rewrite_rules();
}
// 3) Boa prática: limpar as regras também na desativação
register_deactivation_hook( __FILE__, 'flush_rewrite_rules' );
Nunca chame flush_rewrite_rules() dentro do init ou em todo carregamento de página. É uma operação cara — recalcula e grava todas as regras de reescrita do site — e, repetida a cada requisição, derruba a performance. O lugar certo é a ativação (e, opcionalmente, a desativação) do plugin. Note também que, na ativação, o CPT ainda não foi registrado pelo init daquela requisição; por isso chamamos meu_registrar_cpt_livro() antes do flush.
Registre cedo, não tarde demais
O init é o hook correto para CPTs e taxonomias: ele dispara após o WordPress carregar, mas antes de processar a requisição. Registrar em hooks tardios (como wp ou template_redirect) faz o tipo "não existir" para a consulta da página, gerando comportamentos inconsistentes. Se você usa uma classe, conecte o método ao init normalmente:
<?php
class Meu_CPT_Livro {
public function __construct() {
add_action( 'init', array( $this, 'registrar' ) );
}
public function registrar() {
register_post_type( 'livro', array(
'public' => true,
'has_archive' => true,
'show_in_rest' => true,
'supports' => array( 'title', 'editor', 'thumbnail' ),
'rewrite' => array( 'slug' => 'livros' ),
'label' => 'Livros',
) );
}
}
new Meu_CPT_Livro();
Depois de criados, você consulta esses tipos com a WP_Query usando o argumento post_type, e filtra por termos com tax_query. O registro de CPTs e taxonomias normalmente vive em um plugin próprio, para que o conteúdo sobreviva à troca de tema.
Perguntas frequentes
O que é um Custom Post Type (CPT)?
É um tipo de conteúdo personalizado, além dos nativos (post e page). Serve para modelar dados que não são "post de blog" nem "página": imóveis, produtos, eventos, depoimentos, portfólio. Cada CPT tem seu próprio menu no painel, suas URLs e pode ter campos e taxonomias próprios. Você o registra com register_post_type() no hook init.
Qual a diferença entre Custom Post Type e taxonomia?
O CPT é o conteúdo em si (um imóvel). A taxonomia é a forma de classificar/agrupar esse conteúdo (o tipo do imóvel, o bairro). Categorias e tags são taxonomias nativas dos posts. Você cria as suas com register_taxonomy() e as associa a um ou mais CPTs. Regra prática: se é uma "coisa", é CPT; se serve para agrupar várias coisas, é taxonomia.
Por que as URLs do meu CPT dão erro 404?
Porque o WordPress só conhece as novas regras de reescrita (permalinks) depois que elas são gravadas. Ao registrar um CPT ou taxonomia com rewrite, as URLs novas só funcionam após regravar os links permanentes. Em desenvolvimento, basta abrir Configurações → Links permanentes e salvar. Em um plugin, chame flush_rewrite_rules() uma única vez, na ativação (register_activation_hook) — nunca a cada requisição.
Preciso de show_in_rest para quê?
show_in_rest => true expõe o CPT (ou taxonomia) na API REST e, com isso, habilita o editor de blocos (Gutenberg) para esse tipo. Sem ele, o CPT cai no editor clássico e não aparece na REST. Para qualquer CPT novo que deva usar o editor moderno e/ou ser consumido por aplicações externas, defina show_in_rest => true.
Posso chamar flush_rewrite_rules() em todo carregamento?
Não. flush_rewrite_rules() é uma operação cara: ela recalcula e regrava todas as regras de reescrita. Rodá-la a cada requisição (por exemplo, dentro do init) degrada a performance do site inteiro. O lugar correto é a ativação do plugin (ou troca de tema), uma vez só — e sempre depois de o CPT já ter sido registrado.
Onde devo registrar o CPT: no tema ou em um plugin?
Em um plugin, na grande maioria dos casos. CPTs representam dados do negócio; se você os registra no tema e o cliente troca de tema, o conteúdo "desaparece" das telas (os registros continuam no banco, mas sem onde editá-los). Plugin garante que o tipo de conteúdo sobreviva à troca de aparência.