Atualizado em 19/06/2026
Quase todo conteúdo no WordPress precisa, cedo ou tarde, de informações além do título e do corpo: um preço, uma data de evento, um link de afiliado, um campo "destaque na home". A forma idiomática de armazenar isso é o post meta — pares chave/valor associados a um post, guardados na tabela wp_postmeta. Sobre esse armazenamento, você constrói uma interface com meta boxes (no editor clássico) ou expõe o campo ao editor de blocos via REST. Este artigo cobre a API completa de metadados, a construção de uma meta box e — o ponto inegociável — o salvamento seguro. Como tudo aqui se apoia em hooks, ter esse conceito firme ajuda.
A API de post meta
Quatro funções leem e gravam metadados, e uma quinta os registra formalmente. Elas operam sobre a mesma tabela, e a diferença mais importante entre elas é o conceito de valor único (single) versus múltiplo:
| Função | Uso |
|---|---|
get_post_meta( $id, $key, $single ) | Lê o valor. Com $single = true devolve o valor direto; com false, um array de todos os valores da chave. |
update_post_meta( $id, $key, $valor ) | Grava: atualiza a chave se existir, cria se não existir. É o que você usa em 90% dos casos. |
add_post_meta( $id, $key, $valor, $unico ) | Adiciona um valor. Com $unico = true não cria duplicata; com false, permite múltiplos valores na mesma chave. |
delete_post_meta( $id, $key, $valor ) | Remove a chave. Se informar $valor, apaga só a entrada que casa com ele (útil quando há múltiplos). |
register_post_meta( $type, $key, $args ) | Registra o meta formalmente: define tipo, single, sanitização, permissão e exposição na REST/editor de blocos. |
Nuance de update_post_meta: o quarto parâmetro ($prev_value) permite atualizar apenas a entrada cujo valor antigo casa — relevante quando a chave tem múltiplos valores. Sem ele, em uma chave de valor único, a função simplesmente substitui. Para a esmagadora maioria dos campos (valor único), update_post_meta( $id, $key, $valor ) é tudo de que você precisa.
Registrando o meta com register_post_meta()
Antes de criar a interface, registre o campo. Isso não é obrigatório para o post meta funcionar, mas é a boa prática moderna: você declara o tipo, se é single, uma sanitização centralizada, um controle de permissão e — crucialmente — se ele aparece na REST API (e portanto no editor de blocos):
<?php
add_action( 'init', 'meusite_registra_meta' );
function meusite_registra_meta() {
register_post_meta( 'post', '_meusite_subtitulo', array(
'type' => 'string',
'single' => true,
'show_in_rest' => true, // expõe ao editor de blocos / REST
'sanitize_callback' => 'sanitize_text_field', // saneia ANTES de gravar
'auth_callback' => function () {
return current_user_can( 'edit_posts' ); // quem pode escrever via REST
},
) );
}
O sanitize_callback roda automaticamente sempre que o meta é gravado pela REST, o que reduz a chance de salvar dados sujos. O auth_callback controla quem pode editar o meta via REST — não substitui a checagem de capability no salvamento clássico, que veremos adiante. E o show_in_rest é a chave que liga o campo ao Gutenberg.
Criando uma meta box no editor clássico
Uma meta box é uma caixa na tela de edição do post. Você a registra no hook add_meta_boxes com add_meta_box(), cuja assinatura completa é add_meta_box( $id, $titulo, $callback, $screen, $context, $priority ). O $callback é quem renderiza os campos — e é ali que você imprime um wp_nonce_field(), peça central da segurança no salvamento:
<?php
add_action( 'add_meta_boxes', 'meusite_add_meta_box' );
function meusite_add_meta_box() {
add_meta_box(
'meusite_subtitulo_box', // id (HTML)
'Subtítulo do post', // título visível
'meusite_render_meta_box', // callback que desenha os campos
'post', // tela (post type) onde aparece
'side', // contexto: 'normal' | 'side' | 'advanced'
'default' // prioridade
);
}
// Callback: renderiza o campo + o nonce.
function meusite_render_meta_box( $post ) {
$valor = get_post_meta( $post->ID, '_meusite_subtitulo', true );
// Nonce: protege o salvamento contra CSRF.
wp_nonce_field( 'meusite_salvar_subtitulo', 'meusite_subtitulo_nonce' );
?>
<label for="meusite_subtitulo">Subtítulo</label>
<input type="text" id="meusite_subtitulo" name="meusite_subtitulo"
value="<?php echo esc_attr( $valor ); ?>" class="widefat">
<?php
}
Sempre escape na saída. Note o esc_attr() no value do input: o valor salvo é dado não confiável e precisa ser escapado ao ser impresso em um atributo HTML, exatamente como em qualquer outro lugar — o tema da segurança no código. Saneie na entrada, escape na saída.
O salvamento seguro: a tríade obrigatória
Aqui está o coração do assunto. O hook save_post dispara em situações que você precisa filtrar: ele roda para todos os post types, durante o autosave e ao salvar revisões. Ignorar isso causa desde sobrescrever dados com valores vazios até brechas de segurança. Antes de gravar qualquer coisa, passe pelas três barreiras — nonce, autosave/revisão e capability — e só então sanitize e grave:
<?php
add_action( 'save_post', 'meusite_salvar_meta' );
function meusite_salvar_meta( $post_id ) {
// (1) NONCE: o formulário veio mesmo da nossa meta box?
if ( ! isset( $_POST['meusite_subtitulo_nonce'] )
|| ! wp_verify_nonce( $_POST['meusite_subtitulo_nonce'], 'meusite_salvar_subtitulo' ) ) {
return;
}
// (2) AUTOSAVE / REVISÃO: ignore salvamentos automáticos e revisões.
if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
|| wp_is_post_autosave( $post_id )
|| wp_is_post_revision( $post_id ) ) {
return;
}
// (3) CAPABILITY: este usuário pode editar ESTE post?
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// Só agora: SANITIZE na entrada e grave.
if ( isset( $_POST['meusite_subtitulo'] ) ) {
$valor = sanitize_text_field( wp_unslash( $_POST['meusite_subtitulo'] ) );
update_post_meta( $post_id, '_meusite_subtitulo', $valor );
} else {
delete_post_meta( $post_id, '_meusite_subtitulo' );
}
}
Por que cada barreira importa: sem o nonce, um site malicioso poderia forjar a requisição (CSRF). Sem ignorar o autosave, o salvamento automático do editor (que não envia os seus campos) sobrescreveria o meta com vazio. Sem a capability, um usuário com acesso ao painel mas sem permissão sobre aquele post conseguiria alterá-lo. Note também o wp_unslash() antes de sanitizar: o WordPress adiciona barras a $_POST, e você precisa removê-las antes de gravar.
Lendo o valor no template
Para exibir o campo no front-end, leia com get_post_meta() usando $single = true e escape conforme o contexto de saída:
<?php
$subtitulo = get_post_meta( get_the_ID(), '_meusite_subtitulo', true );
if ( $subtitulo ) {
echo '<p class="subtitulo">' . esc_html( $subtitulo ) . '</p>';
}
Com true, você recebe a string direto. Se tivesse usado false (ou omitido o parâmetro), receberia um array — e o echo falharia. Essa é a origem do clássico "meu campo virou um array". Mantenha a coerência: se o meta é de valor único, sempre leia com true.
Convenções de nomenclatura das meta keys
Dois hábitos evitam dor de cabeça em produção:
- Prefixe sempre. Use um prefixo do seu projeto (
_meusite_) em todas as chaves. Sem isso, você corre o risco de colisão com chaves de outros plugins ou temas que usem o mesmo nome genérico (comoprecooucor), sobrescrevendo dados silenciosamente. - Use o underscore para metas internas. Uma chave iniciada com
_(ex.:_meusite_preco) é protegida: o WordPress a oculta do painel nativo de Custom Fields. Use isso para metadados que o seu código gerencia e que o usuário não deve editar manualmente pelo painel cru de chave/valor.
Expondo ao editor de blocos (Gutenberg)
No editor de blocos, a meta box clássica continua funcionando, mas a abordagem nativa é outra: você registra o meta com show_in_rest => true (como fizemos acima) e o edita por JavaScript, lendo e gravando o valor via useEntityProp e exibindo um controle em um painel lateral (por exemplo, um PluginDocumentSettingPanel). O ponto a guardar é o pré-requisito do lado do servidor: sem show_in_rest, o editor de blocos não enxerga o meta, por mais que ele exista no banco. O registro com sanitização e auth_callback também garante que a gravação via REST seja saneada e autorizada.
Armadilhas comuns
- Salvar sem a tríade: esquecer nonce, autosave ou capability no
save_posté o erro mais grave — gera CSRF, perda de dados no autosave ou gravação por usuário sem permissão. save_postdispara para tudo: todos os post types, revisões e autosaves. Filtre o que não deve gravar, ou você sobrescreve metas com valores vazios.- Confundir single e múltiplo: ler com
falseum meta de valor único devolve array e quebra o template. Seja consistente com o que você definiu no registro. - Colisão de meta keys: chaves genéricas sem prefixo colidem com outros plugins. Sempre prefixe.
- Não sanitizar na entrada nem escapar na saída: post meta é dado não confiável. Use
sanitize_*ao gravar eesc_*ao imprimir. - Esquecer
wp_unslash(): valores de$_POSTchegam com barras adicionadas pelo WordPress; remova-as antes de sanitizar e gravar.
Boas práticas
- Registre o meta com
register_post_meta(), definindotype,single,sanitize_callbackeauth_callback— sanitização centralizada e clareza de intenção. - Aplique a tríade de segurança em todo
save_post: nonce, ignorar autosave/revisão e checar capability, nessa ordem, antes de gravar. - Prefixe as chaves com o nome do projeto e use
_para metas internas que o usuário não deve editar pelo painel cru. - Saneie na entrada, escape na saída — escolha a função de sanitização e de escape conforme o tipo do dado e o contexto de impressão.
- Use
show_in_restquando o campo deve ser editável no editor de blocos ou consumido por aplicações via REST. - Mantenha o código em um plugin, não no tema, para não perder os campos e o salvamento ao trocar de template. Para telas de configuração global do site, considere a Settings API em vez de post meta.
Perguntas frequentes
Qual a diferença entre custom fields, post meta e meta boxes?
São camadas distintas. Post meta é o armazenamento: pares chave/valor ligados a um post, guardados na tabela wp_postmeta. Custom fields é o nome genérico desses metadados e também o nome do painel nativo, simples, que o WordPress mostra para editá-los. Meta boxes são caixas customizadas na tela de edição que você cria para oferecer uma interface amigável (campos próprios) sobre esse mesmo post meta — em vez do painel cru de chave/valor.
Por que single = true muda o que get_post_meta() retorna?
Porque o post meta suporta múltiplos valores para a mesma chave. Com $single = true, get_post_meta() retorna o valor diretamente (string, número, array salvo). Com false (o padrão), retorna um array de todos os valores daquela chave. Confundir os dois é causa frequente de "meu campo virou um array do nada". Defina o comportamento no register_post_meta() com a chave single e seja consistente.
Para que serve o prefixo "_" na meta key?
Uma meta key iniciada com underscore (ex.: _meu_preco) é considerada protegida: o WordPress não a exibe no painel nativo de Custom Fields da tela de edição. Use o prefixo _ para metadados internos, gerenciados pelo seu código/meta box, que o usuário não deve editar manualmente pelo painel cru. Mantenha também um prefixo do seu projeto (ex.: _meusite_) para evitar colisão com outros plugins.
Preciso mesmo verificar nonce, autosave e capability ao salvar?
Sim, os três, sempre. O hook save_post dispara para todos os post types, em revisões e em autosaves. Sem checar o nonce, você abre brecha de CSRF; sem ignorar o autosave, sobrescreve dados com valores vazios; sem checar a capability, permite que usuários sem permissão alterem o campo. Só depois dessas três barreiras você sanitiza e grava. É a tríade obrigatória do salvamento seguro.
Como deixo o custom field editável no editor de blocos (Gutenberg)?
Registrando o meta com register_post_meta() e 'show_in_rest' => true. Isso expõe o campo à REST API e, com isso, ao editor de blocos — onde um plugin de JavaScript pode lê-lo e gravá-lo via useEntityProp em um painel lateral (PluginDocumentSettingPanel). Sem show_in_rest, o meta existe no banco, mas o editor de blocos não o enxerga.
Custom fields servem para Custom Post Types?
Servem para qualquer post type — posts, páginas e Custom Post Types. O post meta é a forma idiomática de guardar atributos que não justificam uma taxonomia (que serve para classificar e filtrar) nem uma tabela própria. Para dados pontuais de um item (preço, SKU, data do evento), post meta é o caminho certo.