Advanced Custom Fields (ACF) é um dos plugins mais instalados da história do WordPress — e por boas razões. A interface é impecável, o suporte a tipos de campo é imenso, e a curva de aprendizado é zero. Mas instalar um plugin de 400KB+ com dezenas de arquivos PHP para adicionar três campos de metadado a um post customizado é, na maioria dos projetos, um canhão para matar uma mosca.

O WordPress tem suporte nativo a metadados desde a versão 1.2, e desde a Block Editor ficou substancialmente mais poderoso. Este artigo mostra como usar a API nativa para 95% dos casos de uso sem depender de nenhum plugin externo.

A base: register_meta() com tipagem e sanitização

O ponto de partida é a função register_meta(), que registra um campo de metadado com tipo, valor padrão, sanitização e, crucialmente, exposição via REST API:

add_action('init', function () {
  register_meta('post', 'projeto_url', [
    'object_subtype'    => 'projeto', // só para o CPT 'projeto'
    'type'              => 'string',
    'single'            => true,
    'default'           => '',
    'show_in_rest'      => true,      // expõe na REST API
    'sanitize_callback' => 'esc_url_raw',
    'auth_callback'     => fn() => current_user_can('edit_posts'),
  ]);

  register_meta('post', 'projeto_destaque', [
    'object_subtype' => 'projeto',
    'type'           => 'boolean',
    'single'         => true,
    'default'        => false,
    'show_in_rest'   => true,
    'auth_callback'  => fn() => current_user_can('edit_posts'),
  ]);
});

Com isso, get_post_meta($id, 'projeto_url', true) já funciona, o campo aparece na REST API em meta.projeto_url, e a sanitização é automática.

Meta boxes no editor clássico e no Gutenberg

Para exibir um campo de edição no admin, adicione uma meta box com add_meta_box(). Funciona tanto no editor clássico quanto no Gutenberg (como painel lateral):

add_action('add_meta_boxes', function () {
  add_meta_box(
    'projeto_detalhes',
    'Detalhes do Projeto',
    'render_meta_box_projeto',
    'projeto',
    'side',
    'high'
  );
});

function render_meta_box_projeto(WP_Post $post) {
  wp_nonce_field('salvar_projeto_meta', 'projeto_meta_nonce');
  $url       = get_post_meta($post->ID, 'projeto_url', true);
  $destaque  = get_post_meta($post->ID, 'projeto_destaque', true);
  ?>
  <label>URL do projeto</label>
  <input type="url" name="projeto_url" value="<?= esc_attr($url) ?>" style="width:100%">
  <label>
    <input type="checkbox" name="projeto_destaque" value="1" <?= checked($destaque, true, false) ?>>
    Projeto em destaque
  </label>
  <?php
}

add_action('save_post_projeto', function ($post_id) {
  if (!wp_verify_nonce($_POST['projeto_meta_nonce'] ?? '', 'salvar_projeto_meta')) return;
  if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
  update_post_meta($post_id, 'projeto_url', esc_url_raw($_POST['projeto_url'] ?? ''));
  update_post_meta($post_id, 'projeto_destaque', isset($_POST['projeto_destaque']));
});

Block Bindings API — o futuro no Gutenberg (WP 6.5+)

A Block Bindings API, introduzida no WordPress 6.5, permite conectar campos de metadado diretamente a atributos de blocos no editor — sem nenhum código JavaScript personalizado:

add_action('init', function () {
  register_block_bindings_source('meu-plugin/meta', [
    'label'              => 'Metadados do Post',
    'get_value_callback' => function (array $source_args, WP_Block $block) {
      return get_post_meta(
        get_the_ID(),
        $source_args['key'] ?? '',
        true
      );
    },
  ]);
});

No editor, você conecta um bloco Parágrafo ao campo assim: bloco selecionado → Painel lateral → Atributos → Vincular → selecione a fonte. O conteúdo do parágrafo passa a vir dinamicamente do metadado — sem shortcodes, sem PHP inline no template.

Quando o ACF ainda faz sentido

A API nativa resolve a maioria dos casos. Mas o ACF (ou o CMB2, alternativa open source) ainda é justificado quando você precisa de:

Para tudo fora desse escopo — URL, texto simples, número, booleano, data, taxonomia — a API nativa é mais rápida, mais segura e não adiciona dependência externa ao projeto.

Consultando campos customizados em WP_Query

Com os metadados registrados, você pode filtrá-los em queries com meta_query:

$projetos_destaque = new WP_Query([
  'post_type'  => 'projeto',
  'meta_query' => [[
    'key'     => 'projeto_destaque',
    'value'   => '1',
    'compare' => '=',
  ]],
  'orderby'    => 'meta_value',
  'meta_key'   => 'projeto_url',
  'order'      => 'ASC',
]);

Use meta_query com parcimônia em tabelas grandes — queries em wp_postmeta sem índice adequado são um dos principais culpados de lentidão em sites WordPress de médio e grande porte.