DesenvolvedorWordPress WhatsApp

Settings API: páginas de opções no WordPress

A Settings API é a forma oficial de criar telas de configuração no admin do WordPress: ela desenha o formulário, processa o envio, chama a sua sanitização e grava — sem você reinventar nonces nem salvamento. Veja como montar uma página de opções robusta, do menu ao sanitize_callback.

Precisa de um painel de opções sob medida?

Atualizado em 19/06/2026

Quase todo plugin sério precisa de uma tela de configuração no admin. A tentação é montar um <form> à mão, processar o $_POST num hook qualquer e chamar update_option(). Funciona — e é por aí que entram XSS, CSRF e campos que "às vezes salvam". O WordPress já resolveu esse problema com a Settings API: um conjunto de funções que orquestra o ciclo inteiro (renderização, nonce, capability, sanitização e salvamento) de forma padronizada e auditável. Este guia mostra a arquitetura completa, com o porquê de cada peça.

Options API × Settings API: camadas distintas

Confundir as duas é o equívoco conceitual mais comum. São coisas diferentes, em níveis diferentes:

Dito de outro modo: a Settings API orquestra o formulário; a Options API guarda o dado. Você pode usar a Options API sem a Settings API (para um simples update_option() num hook próprio), mas dificilmente o contrário compensa — a Settings API só faz sentido junto da Options API.

Por que não fazer "na mão"? Porque o options.php centraliza três proteções que você teria de reimplementar (e provavelmente esquecer alguma): verificação de nonce contra CSRF, checagem de capability do usuário, e a chamada do seu sanitize_callback antes de gravar. Padronizar isso é o que torna o código previsível e seguro.

O mapa das funções

Antes do código, fixe o papel de cada função. Há dois grupos: o que você registra (no admin_init) e o que você imprime (no HTML da página).

FunçãoOndePapel
register_setting()admin_initDeclara a opção, o grupo a que pertence e o sanitize_callback que limpa o valor antes de salvar.
add_settings_section()admin_initCria um agrupamento visual de campos (título + descrição) dentro de uma página.
add_settings_field()admin_initRegistra um campo dentro de uma seção e indica o callback que desenha o <input>.
settings_fields()HTML do formImprime os campos ocultos do grupo: nonce, action e option_page. Liga o form ao options.php.
do_settings_sections()HTML do formRenderiza todas as seções e campos registrados para aquela página, na ordem.

1. A página no menu (hook admin_menu)

Primeiro registramos a página de admin. Para configurações, o lugar canônico é o submenu Configurações, via add_options_page(); para algo de primeiro nível, add_menu_page(). Em ambos, o quarto argumento é a capability exigida — use manage_options para restringir a administradores. O registro acontece no hook admin_menu:

<?php
add_action( 'admin_menu', 'dwp_registrar_pagina_opcoes' );

function dwp_registrar_pagina_opcoes() {
    add_options_page(
        'Configurações do Meu Plugin',  // <title> da página
        'Meu Plugin',                    // rótulo no menu Configurações
        'manage_options',                // capability exigida
        'meu-plugin',                    // slug (vira ?page=meu-plugin)
        'dwp_render_pagina_opcoes'       // callback que imprime o HTML
    );
}

O slug (meu-plugin) identifica a página na URL e — atenção — é o mesmo identificador que usaremos em do_settings_sections() e como contexto em add_settings_section()/add_settings_field(). A capability não é decoração: ela é a autorização. Mesmo que alguém descubra a URL, o WordPress nega o acesso a quem não tem manage_options.

2. O HTML da página (o callback de render)

O callback informado acima imprime a tela. A estrutura é sempre a mesma e cada linha tem função: o <form> aponta para options.php via POST; settings_fields() injeta o nonce e o grupo; do_settings_sections() desenha os campos; submit_button() imprime o botão "Salvar alterações".

<?php
function dwp_render_pagina_opcoes() {
    // Defesa em profundidade: nega quem não tem a capability.
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>

        <?php settings_errors(); // exibe mensagens add_settings_error() ?>

        <form action="options.php" method="post">
            <?php
            // Imprime nonce + option_page do grupo (CSRF + roteamento).
            settings_fields( 'meu_plugin_grupo' );

            // Renderiza seções e campos registrados para esta página.
            do_settings_sections( 'meu-plugin' );

            // Botão "Salvar alterações".
            submit_button();
            ?>
        </form>
    </div>
    <?php
}

Repare que o formulário não tem action de salvamento próprio: ele posta para options.php, que é o handler do núcleo. É ele quem lê o option_page oculto (impresso por settings_fields()), valida o nonce, confere a capability, chama o sanitize_callback e grava. Por isso o grupo (meu_plugin_grupo) tem que ser idêntico ao do register_setting() — é a chave que liga o envio ao registro.

3. O registro (hook admin_init)

Agora a peça central. Tudo que a Settings API precisa conhecer — a opção, suas seções e campos — é registrado no hook admin_init. Adotamos a boa prática de guardar tudo num único array de opção (meu_plugin_opcoes):

<?php
add_action( 'admin_init', 'dwp_registrar_settings' );

function dwp_registrar_settings() {

    // (a) Declara a opção, o grupo e quem sanitiza antes de salvar.
    register_setting(
        'meu_plugin_grupo',                 // grupo (= settings_fields)
        'meu_plugin_opcoes',                // nome da opção (array único)
        array(
            'type'              => 'array',
            'sanitize_callback' => 'dwp_sanitizar_opcoes',
            'default'           => array(),
        )
    );

    // (b) Uma seção visual dentro da página 'meu-plugin'.
    add_settings_section(
        'meu_plugin_secao_geral',           // id da seção
        'Configurações gerais',             // título exibido
        'dwp_descricao_secao',              // callback de descrição (pode ser null)
        'meu-plugin'                        // página (= do_settings_sections / slug)
    );

    // (c) Os campos, ligados à seção acima.
    add_settings_field(
        'campo_email',                      // id do campo
        'E-mail de notificação',            // <label>
        'dwp_render_campo_email',           // callback que imprime o input
        'meu-plugin',                       // página
        'meu_plugin_secao_geral',           // seção a que pertence
        array( 'label_for' => 'campo_email' ) // associa o <label> ao input
    );

    add_settings_field(
        'campo_limite',
        'Limite de itens',
        'dwp_render_campo_limite',
        'meu-plugin',
        'meu_plugin_secao_geral',
        array( 'label_for' => 'campo_limite' )
    );
}

function dwp_descricao_secao() {
    echo '<p>Ajustes gerais do plugin.</p>';
}

Três identificadores precisam ser consistentes ou nada funciona: o grupo (meu_plugin_grupo, igual em register_setting e settings_fields), a página (meu-plugin, igual em add_settings_section, add_settings_field e do_settings_sections) e o nome da opção (meu_plugin_opcoes). O label_for não é obrigatório, mas melhora a acessibilidade ao ligar o <label> da coluna esquerda ao id do campo.

4. O callback de um campo (ler e escapar)

Cada campo registrado precisa de um callback que imprime o <input>. Aqui valem duas regras: leia o valor atual com get_option() (lembrando que guardamos um array) e escape a saída com esc_attr(). Como o name usa a notação de array, o WordPress entrega o valor já agrupado em $_POST['meu_plugin_opcoes']:

<?php
function dwp_render_campo_email() {
    $opcoes = get_option( 'meu_plugin_opcoes', array() );
    $valor  = isset( $opcoes['email'] ) ? $opcoes['email'] : '';
    ?>
    <input type="email"
           id="campo_email"
           name="meu_plugin_opcoes[email]"
           value="<?php echo esc_attr( $valor ); ?>"
           class="regular-text">
    <p class="description">Receberá os avisos do plugin.</p>
    <?php
}

function dwp_render_campo_limite() {
    $opcoes = get_option( 'meu_plugin_opcoes', array() );
    $valor  = isset( $opcoes['limite'] ) ? (int) $opcoes['limite'] : 10;
    ?>
    <input type="number" min="1" max="100" step="1"
           id="campo_limite"
           name="meu_plugin_opcoes[limite]"
           value="<?php echo esc_attr( $valor ); ?>">
    <?php
}

O name="meu_plugin_opcoes[email]" é o que faz tudo cair sob a mesma opção: o navegador envia meu_plugin_opcoes como um array com as chaves email e limite. Por isso o sanitize_callback receberá o array inteiro. E note: mesmo num campo numérico, escapamos com esc_attr() na saída — escapar é função do contexto de impressão, não da confiança no dado.

5. O sanitize_callback (validar é aqui)

Este é o coração da segurança da tela. O sanitize_callback recebe o valor cru do $_POST e retorna o que será gravado. Como usamos um array, ele recebe o array inteiro e devolvemos uma versão limpa — campo a campo, descartando qualquer chave que não esperamos:

<?php
function dwp_sanitizar_opcoes( $entrada ) {
    $saida = array();

    // E-mail: valida e normaliza; se inválido, avisa e mantém o anterior.
    $email = isset( $entrada['email'] ) ? sanitize_email( $entrada['email'] ) : '';
    if ( '' !== $email && ! is_email( $email ) ) {
        add_settings_error(
            'meu_plugin_opcoes',
            'email_invalido',
            'O e-mail informado é inválido. O valor anterior foi mantido.',
            'error'
        );
        $antigas        = get_option( 'meu_plugin_opcoes', array() );
        $saida['email'] = isset( $antigas['email'] ) ? $antigas['email'] : '';
    } else {
        $saida['email'] = $email;
    }

    // Limite: inteiro forçado para a faixa 1..100.
    $limite           = isset( $entrada['limite'] ) ? (int) $entrada['limite'] : 10;
    $saida['limite']  = max( 1, min( 100, $limite ) );

    return $saida; // SÓ o que está aqui é salvo.
}

Tudo que não retornar do sanitize_callback é descartado — essa é a defesa contra campos injetados (alguém forjando um $_POST com chaves extras). Por isso construímos um array $saida novo e copiamos apenas as chaves conhecidas, em vez de "limpar" o array de entrada e devolvê-lo. Validação no JavaScript do formulário é só conveniência; a fronteira de confiança é o servidor.

Feedback ao usuário com add_settings_error()

No menu Configurações, o WordPress já mostra automaticamente o aviso "Configurações salvas." quando o salvamento ocorre. Para mensagens próprias — sobretudo erros de validação — use add_settings_error() dentro do sanitize_callback (como no exemplo acima) e garanta a chamada de settings_errors() no topo da página. Em páginas fora do menu Configurações, o settings_errors() é obrigatório até para o aviso de sucesso aparecer.

Armadilhas comuns

SintomaCausa provávelCorreção
Nada é salvo ao enviarGrupo do settings_fields()register_setting()Use a mesma string de grupo nos dois
Campos não aparecemPágina do do_settings_sections() ≠ slug usado nos add_settings_*Unifique o identificador da página
"Options page not found"register_setting fora do admin_initRegistre tudo no hook admin_init
Valor some ao salvarChave não retornada no sanitize_callbackCopie a chave para o array de saída
Formulário recarrega sem efeitoaction do form não é options.phpUse action="options.php" method="post"
HTML quebrado/XSS no campoSaída sem esc_attr()Escape sempre ao imprimir o value

Boas práticas

Com esse esqueleto você tem uma tela de opções segura por construção: o núcleo cuida do nonce, da capability e do salvamento, e você concentra a inteligência onde ela importa — no sanitize_callback. A partir daí, adicionar campos é repetir o trio add_settings_field → callback de render → chave no sanitizador.

Precisa de uma página de opções profissional no seu plugin?

Construo painéis de configuração no WordPress com a Settings API, sanitização correta e feedback ao usuário — seguros e prontos para atualização. Me chame no WhatsApp.

WhatsApp: (43) 99932-9697

ou e-mail: [email protected]

Perguntas frequentes

Qual a diferença entre a Options API e a Settings API?

A Options API é a camada de armazenamento: get_option(), add_option() e update_option() leem e gravam valores na tabela wp_options. Você pode usá-la sozinha em qualquer lugar. A Settings API é uma camada acima: ela orquestra a tela de opções no admin — desenha as seções e campos, processa o POST para options.php, chama o seu sanitize_callback e grava com a Options API por baixo. Em resumo: a Settings API monta o formulário e a Options API guarda o dado.

Por que meus campos não salvam?

A causa nº 1 é o grupo divergente: o primeiro argumento de register_setting( $group, ... ) tem que ser exatamente o mesmo passado a settings_fields( $group ) no formulário. Se não baterem, o options.php rejeita o envio (falha na verificação de nonce/capability) e nada é salvo. A nº 2 é registrar o register_setting fora do hook admin_init — o options.php só conhece as configurações registradas ali. A nº 3 é apontar o <form> para um arquivo que não seja options.php.

Onde eu valido os dados enviados?

No sanitize_callback declarado em register_setting(). Ele recebe o valor cru vindo do $_POST e deve retornar o valor já limpo, que é o que será gravado. É o ponto central de validação e sanitização — não confie em validação só no JavaScript do formulário. Quando guardar tudo num único array de opção, o callback recebe o array inteiro e você sanitiza campo a campo, descartando chaves inesperadas.

Devo usar uma opção para cada campo ou um array só?

Prefira um único array (ex.: get_option( 'meu_plugin_opcoes' ) retornando ['email' => ..., 'limite' => ...]). Vantagens: uma única linha em wp_options (menos consultas e menos lixo), um único register_setting e um único sanitize_callback onde você valida o conjunto de uma vez. Dezenas de opções soltas poluem o banco e dificultam a limpeza na desinstalação.

Como mostro a mensagem "Configurações salvas"?

No contexto de add_options_page() (menu Configurações), o WordPress já exibe automaticamente o aviso de sucesso. Em outros menus, ou para mensagens de erro de validação, use add_settings_error() dentro do sanitize_callback e chame settings_errors() no topo da página. Assim você dá feedback preciso ("o e-mail informado é inválido") em vez de um sucesso genérico.

A Settings API escapa os valores na saída automaticamente?

Não. A Settings API cuida do salvamento; a exibição é com você. No callback de cada campo, ao imprimir o value de um <input>, sempre use esc_attr() (ou esc_textarea(), checked(), selected()). Escapar na saída é uma regra de segurança que vale mesmo para dados que você mesmo sanitizou na entrada.

Referências oficiais

  1. WordPress.org — Settings API (Plugin Handbook)
  2. WordPress.org — Custom Settings Page
  3. WordPress.org — Administration Menus
  4. WordPress.org — register_setting() (Code Reference)
  5. WordPress.org — add_settings_section() (Code Reference)
  6. WordPress.org — add_settings_field() (Code Reference)
Tem alguma dúvida? Ficarei feliz em ajudar. Clique Aqui