DesenvolvedorWordPress WhatsApp

WP-CLI: criar comandos customizados no WordPress

Operações em massa, importações, rotinas de manutenção: tudo isso é mais rápido, mais seguro e automatizável pela linha de comando. Veja como expor a sua própria lógica como um comando wp de primeira classe.

Precisa automatizar tarefas no seu WordPress?

Atualizado em 19/06/2026

Há tarefas que simplesmente não cabem no navegador: reprocessar 80 mil pedidos, importar um catálogo a partir de um CSV, corrigir metadados em massa, rodar uma limpeza noturna agendada no cron do sistema. Tentar fazer isso por uma tela de admin esbarra em timeout, em limite de memória e na falta de automação. A ferramenta certa para esse tipo de trabalho é o WP-CLI — e o que poucos desenvolvedores exploram é que você pode estender o WP-CLI com os seus próprios comandos, tão integrados quanto os nativos, com ajuda automática, validação de argumentos e barra de progresso. Este guia mostra como.

O que é o WP-CLI

O WP-CLI é a interface de linha de comando oficial do WordPress. Em vez de clicar no painel, você digita comandos como wp plugin update --all, wp user create ou wp db export direto no terminal do servidor. O detalhe que torna tudo isso possível: antes de executar qualquer comando, o WP-CLI carrega o WordPress por completo — o mesmo wp-load.php de uma requisição web. Isso significa que, dentro do seu comando, você tem à disposição todas as funções do núcleo, dos plugins ativos e do tema: get_posts(), wc_get_orders(), wp_insert_user() funcionam exatamente como funcionariam num plugin. O seu comando é, na prática, código WordPress rodando fora do ciclo HTTP.

Registrando um comando: WP_CLI::add_command()

A porta de entrada é o método estático WP_CLI::add_command( $nome, $callable_ou_classe ). O primeiro argumento é o nome que você digitará após wp; o segundo pode ser o nome de uma classe (o padrão recomendado — cada método público vira um subcomando), uma instância, uma função nomeada ou uma closure.

Registre o comando SOMENTE no contexto CLI. A constante WP_CLI só existe quando o WordPress foi carregado pelo WP-CLI. Se você chamar WP_CLI::add_command() numa requisição web normal, a classe WP_CLI não existe e o site inteiro cai com fatal error. A guarda if ( defined( 'WP_CLI' ) && WP_CLI ) é obrigatória — não opcional.

<?php
// Em um plugin ou mu-plugin. Este bloco SÓ roda quando o WP-CLI carrega o WP.
if ( defined( 'WP_CLI' ) && WP_CLI ) {
    // Registramos uma CLASSE: cada método público vira um subcomando.
    // Uso: wp dwp-loja <subcomando> ...
    WP_CLI::add_command( 'dwp-loja', 'DWP_Loja_CLI_Command' );
}

Com isso, wp dwp-loja passa a existir, e cada método público da classe DWP_Loja_CLI_Command vira um subcomando — por exemplo, wp dwp-loja reindexar ou wp dwp-loja exportar. Esse modelo de "uma classe, vários subcomandos" é o que o próprio núcleo usa (compare com wp post list, wp post create...).

O comando como classe, com synopsis via PHPDoc

Cada método público que aceita a assinatura ( $args, $assoc_args ) torna-se um subcomando. O grande recurso do WP-CLI é o synopsis automático: você documenta os parâmetros no PHPDoc do método, e o WP-CLI lê esse comentário para gerar a ajuda (wp help dwp-loja exportar) e validar a entrada antes de o seu código rodar.

<?php
/**
 * Comandos da loja para o WP-CLI.
 */
class DWP_Loja_CLI_Command {

    /**
     * Exporta produtos para um arquivo CSV.
     *
     * ## OPTIONS
     *
     * <arquivo>
     * : Caminho do CSV de saída.
     *
     * [--status=<status>]
     * : Filtra por status do produto.
     * ---
     * default: publish
     * options:
     *   - publish
     *   - draft
     *   - any
     * ---
     *
     * [--limit=<numero>]
     * : Quantidade máxima de produtos. Padrão: todos.
     *
     * [--dry-run]
     * : Simula a operação sem gravar nada no arquivo.
     *
     * ## EXAMPLES
     *
     *     wp dwp-loja exportar produtos.csv --status=publish --limit=500
     *     wp dwp-loja exportar teste.csv --dry-run
     *
     * @when after_wp_load
     */
    public function exportar( $args, $assoc_args ) {
        // ... corpo do comando (veja o próximo bloco) ...
    }
}

Repare em alguns detalhes idiomáticos: <arquivo> entre <> declara um posicional obrigatório; os colchetes em [--status=<status>] indicam que a flag é opcional; o bloco --- com default e options faz o WP-CLI aplicar um valor padrão e recusar valores fora da lista. A anotação @when after_wp_load garante que o comando só execute após o WordPress estar totalmente carregado — o padrão para comandos que usam funções do core.

Lendo argumentos e produzindo saída

Dentro do método, $args traz os posicionais (na ordem) e $assoc_args traz as flags (por chave). Para extrair flags com valor padrão de forma limpa, o WP-CLI oferece \WP_CLI\Utils\get_flag_value(). E, para comunicar com o usuário, use os helpers de saída — eles cuidam de cores, do exit code correto e do comportamento em modo silencioso.

<?php
public function exportar( $args, $assoc_args ) {
    // Posicional obrigatório: o WP-CLI já garantiu que existe (está no synopsis).
    $arquivo = $args[0];

    // Flags com padrão seguro.
    $status  = \WP_CLI\Utils\get_flag_value( $assoc_args, 'status', 'publish' );
    $limit   = (int) \WP_CLI\Utils\get_flag_value( $assoc_args, 'limit', -1 );
    $dry_run = (bool) \WP_CLI\Utils\get_flag_value( $assoc_args, 'dry-run', false );

    if ( ! $dry_run && ! is_writable( dirname( $arquivo ) ) ) {
        // error() imprime em vermelho E encerra com exit code 1 (falha real).
        WP_CLI::error( "Diretório não gravável: " . dirname( $arquivo ) );
    }

    if ( $dry_run ) {
        WP_CLI::warning( 'Modo simulação: nenhum arquivo será gravado.' );
    }

    $ids = get_posts( array(
        'post_type'      => 'product',
        'post_status'    => ( 'any' === $status ) ? 'any' : $status,
        'fields'         => 'ids',
        'posts_per_page' => $limit,
        'no_found_rows'  => true,
    ) );

    WP_CLI::log( sprintf( 'Encontrados %d produtos.', count( $ids ) ) );

    // ... gravação do CSV (omitida) ...

    // success() = verde + exit code 0. É a forma certa de sinalizar "deu certo".
    WP_CLI::success( $dry_run
        ? 'Simulação concluída.'
        : sprintf( '%d produtos exportados para %s', count( $ids ), $arquivo )
    );
}

Por que isso importa para automação: WP_CLI::error() encerra com exit code diferente de zero, e WP_CLI::success() com zero. Esse código de saída é o que um cron de sistema, um pipeline de CI ou um script de deploy usam para saber se o comando falhou. Imprimir um "erro" com echo e sair com sucesso engana toda a automação a jusante.

Tabela: helpers de saída do WP_CLI

HelperUsoEfeito no exit code
WP_CLI::success()Mensagem de sucesso (verde)Não encerra; sinaliza êxito
WP_CLI::error()Erro fatal (vermelho)Encerra com código ≠ 0
WP_CLI::warning()Aviso (não interrompe)Não encerra
WP_CLI::log()Mensagem informativa (oculta em --quiet)Não encerra
WP_CLI::line()Linha crua (sempre exibida)Não encerra
WP_CLI::confirm()Pede confirmação (s/N); aborta se negadoEncerra se o usuário recusar
WP_CLI::debug()Mensagem só com --debugNão encerra

Confirmação, barra de progresso e tabelas

Para operações destrutivas, WP_CLI::confirm() pede um "sim" explícito (e respeita a flag --yes para rodar sem interação). Para operações longas, a barra de progresso de \WP_CLI\Utils\make_progress_bar() dá feedback visual; e \WP_CLI\Utils\format_items() imprime dados tabulados em table, csv, json ou yaml.

<?php
public function reindexar( $args, $assoc_args ) {
    // Operação cara: confirme antes (a flag --yes pula isto, útil em cron).
    WP_CLI::confirm( 'Reindexar TODOS os produtos? Isso pode demorar.', $assoc_args );

    $ids   = get_posts( array(
        'post_type'      => 'product',
        'post_status'    => 'any',
        'fields'         => 'ids',
        'posts_per_page' => -1,
        'no_found_rows'  => true,
    ) );
    $total = count( $ids );

    $barra = \WP_CLI\Utils\make_progress_bar( 'Reindexando', $total );

    foreach ( $ids as $id ) {
        // ... reindexa o produto $id ...
        clean_post_cache( $id ); // higiene: impede o cache de objeto de inflar
        $barra->tick();
    }
    $barra->finish();

    WP_CLI::success( sprintf( '%d produtos reindexados.', $total ) );
}

public function relatorio( $args, $assoc_args ) {
    $formato = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'table' );

    $linhas = array(
        array( 'status' => 'publish', 'total' => 1280 ),
        array( 'status' => 'draft',   'total' =>   42 ),
    );

    // Imprime em table/csv/json/yaml conforme --format. Os 3 args:
    // ( formato, itens, colunas )
    \WP_CLI\Utils\format_items( $formato, $linhas, array( 'status', 'total' ) );
}

Carregar tudo com posts_per_page => -1 é uma faca de dois gumes. Para milhares de registros pesados, isso pode estourar a memória só na consulta. Em comandos sérios, pagine em lotes (consulte 200 IDs por vez, avançando o offset) e chame clean_post_cache() dentro do loop. O cache de objeto do WordPress guarda tudo que você consulta na memória do processo — sem limpá-lo, um loop longo cresce sem parar até o out of memory.

Onde registrar e como usar em produção

Você pode registrar comandos em qualquer plugin ativo, mas há dois bons lugares: dentro de um plugin próprio (quando o comando faz parte de uma funcionalidade que você distribui) ou em um mu-plugin em wp-content/mu-plugins/ (quando é uma ferramenta interna de manutenção que deve estar sempre disponível e não pode ser desativada por engano). Os casos de uso típicos:

# Exemplo de agendamento no crontab do sistema (roda 3h da manhã):
0 3 * * * cd /var/www/seusite && wp dwp-loja reindexar --yes --quiet

# Em scripts, --path evita depender do diretório atual:
wp --path=/var/www/seusite dwp-loja exportar /backups/produtos.csv

Armadilhas comuns

Boas práticas

Comandos próprios de WP-CLI são uma das ferramentas mais subestimadas do ecossistema: transformam tarefas que seriam horas de cliques (ou scripts perigosos rodando pelo navegador) em operações repetíveis, automatizáveis e seguras. Combinados com um plugin bem estruturado e bons hooks, eles elevam a qualidade de qualquer projeto WordPress sério.

Precisa de uma ferramenta de linha de comando sob medida?

Crio comandos WP-CLI para importações, manutenção e operações em massa — com dry-run, validação e processamento em lotes. Me chame no WhatsApp.

WhatsApp: (43) 99932-9697

ou e-mail: [email protected]

Perguntas frequentes

O que é o WP-CLI?

O WP-CLI é a interface de linha de comando oficial do WordPress: uma ferramenta que você roda no terminal do servidor para administrar a instalação sem abrir o navegador. Com ela você atualiza o núcleo, plugins e temas, gerencia usuários, manipula o banco, importa/exporta conteúdo, limpa caches e — o tema deste artigo — roda os seus próprios comandos. Como o WP-CLI carrega o WordPress por inteiro antes de executar, dentro do seu comando você tem acesso a todas as funções do core (get_posts(), wp_insert_user() etc.), exatamente como num plugin.

Como registro um comando customizado?

Com WP_CLI::add_command( 'meu-comando', $callable_ou_classe ). O primeiro argumento é o nome que você digitará após wp; o segundo pode ser o nome de uma classe (cada método público vira um subcomando), uma instância, uma função ou uma closure. O ponto crítico: registre o comando apenas no contexto CLI, dentro de if ( defined( 'WP_CLI' ) && WP_CLI ). Caso contrário, em requisições web normais o WordPress tentaria chamar uma classe que nem foi carregada e geraria erro fatal.

Qual a diferença entre argumentos posicionais e associativos?

Argumentos posicionais são os valores soltos depois do comando — wp meu-comando exportar arquivo.csv — e chegam ao callback no array $args, na ordem. Argumentos associativos (ou flags) têm a forma --chave=valor ou apenas --flag — e chegam no array $assoc_args, indexados pela chave. Posicionais servem para os dados principais; flags, para opções e modificadores (--dry-run, --limit=100, --format=json).

Por que meu comando some ou dá erro fora do terminal?

Quase sempre porque você esqueceu de envolver o registro em if ( defined( 'WP_CLI' ) && WP_CLI ). A constante WP_CLI só é definida quando o WordPress é carregado pelo WP-CLI; numa requisição web ela não existe. Chamar WP_CLI::add_command() fora desse contexto resulta em "classe não encontrada" e fatal error no site inteiro. A guarda resolve: o código de registro simplesmente não roda no navegador.

Como evito estourar a memória em operações longas?

O grande risco em comandos que percorrem milhares de registros é a memória crescer até o limite. Três técnicas: (1) processe em lotes, paginando os resultados em vez de carregar tudo de uma vez; (2) limpe os caches internos do WordPress entre as fatias com clean_post_cache() e, quando necessário, wp_cache_flush(), porque o cache de objeto acumula tudo que você consulta; (3) use uma barra de progresso para acompanhar e detectar travamentos. Para jobs realmente gigantes, considere uma fila como o Action Scheduler.

O que é o synopsis de um comando e como eu o defino?

O synopsis é a descrição estruturada dos parâmetros que o comando aceita — é o que aparece em wp help meu-comando e o que o WP-CLI usa para validar a entrada. A forma idiomática de declará-lo é pelo PHPDoc do método: um bloco com ## OPTIONS listando <posicional> e [--flag=<valor>], além de ## EXAMPLES. O WP-CLI lê esse comentário, monta o synopsis automaticamente e passa a recusar chamadas inválidas — sem que você escreva validação manual.

Referências oficiais

  1. WordPress.org — WP-CLI Handbook
  2. WP-CLI Handbook — Commands Cookbook
  3. WP-CLI Handbook — Internal API
  4. WordPress.org — WP-CLI Commands Reference
Tem alguma dúvida? Ficarei feliz em ajudar. Clique Aqui