Atualizado em 19/06/2026
Cedo ou tarde, todo projeto WordPress de porte topa com a mesma parede: você precisa processar muita coisa — reindexar 50 mil produtos, enviar 10 mil e-mails, sincronizar um catálogo inteiro com um ERP — e tentar fazer isso dentro de uma única requisição HTTP é receita para timeout, memória esgotada e trabalho pela metade. A resposta madura para esse problema é uma fila de tarefas: você enfileira o trabalho, e ele é processado aos poucos, em segundo plano, com garantia de execução. No ecossistema WordPress, essa fila tem nome: Action Scheduler.
O que é o Action Scheduler
O Action Scheduler é uma biblioteca de fila de tarefas (não um plugin que você instala isoladamente) criada e mantida pela equipe do WooCommerce. Ela é embarcada dentro de plugins — o WooCommerce, o WooCommerce Subscriptions, o Jetpack e centenas de outros já a incluem — então, num site de e-commerce típico, ela já está rodando mesmo que você nunca tenha ouvido falar dela. Quando múltiplos plugins trazem cópias diferentes, a própria biblioteca resolve o conflito carregando a versão mais recente disponível para todo o site.
O que ela faz, em uma frase: grava cada tarefa (uma "ação") em tabelas próprias no banco de dados e as processa em segundo plano, em lotes, com concorrência controlada, retentativa automática em caso de falha e um log de cada execução. As tabelas principais são actionscheduler_actions (a fila em si), actionscheduler_logs (o histórico) e actionscheduler_groups (os grupos de organização).
O "hook" de uma ação é um action comum do WordPress. Você não passa uma função diretamente para a fila: passa o nome de um hook. Quem faz o trabalho de verdade é a função que você liga a esse hook com add_action() — exatamente como em qualquer outro hook do WordPress. Esse callback precisa estar registrado em toda requisição, não só quando você enfileira.
Por que não usar só o WP-Cron?
O WP-Cron é ótimo para o que foi feito: alguns eventos esporádicos e recorrentes. Mas ele guarda toda a fila dentro de uma única option (cron) serializada, sem concorrência, sem retentativa real e sem visibilidade. Coloque milhares de eventos ali e essa option vira um gargalo que é lido e reescrito a cada requisição. O Action Scheduler nasceu justamente para resolver isso em escala. A tabela abaixo resume quando usar cada um.
| Critério | WP-Cron | Action Scheduler |
|---|---|---|
| Armazenamento | 1 option serializada | Tabelas dedicadas, indexadas |
| Escala (nº de jobs) | Dezenas | Milhares / dezenas de milhares |
| Retentativa em falha | Não (o evento some) | Sim, com reagendamento |
| Concorrência | Sem controle | Lotes com limite configurável |
| Execução assíncrona ("o quanto antes") | Não nativamente | Sim (as_enqueue_async_action) |
| Log / auditoria | Não | Sim, por ação |
| UI de monitoramento | Plugin externo | Sim (via WooCommerce) |
| Quando usar | 1–2 eventos recorrentes simples | Lotes grandes, async, garantia de execução |
A API principal
A interface pública do Action Scheduler é um punhado de funções as_*. As que você vai usar no dia a dia:
as_enqueue_async_action( $hook, $args, $group )— enfileira uma ação para rodar assim que possível, em segundo plano. É o coração do processamento assíncrono.as_schedule_single_action( $timestamp, $hook, $args, $group )— agenda uma ação única para um momento futuro.as_schedule_recurring_action( $timestamp, $interval, $hook, $args, $group )— agenda uma ação recorrente a cada$intervalsegundos.as_unschedule_action( $hook, $args, $group )— cancela a ação agendada (pendente) correspondente a hook + args + group. Numa ação recorrente, isso encerra o agendamento (não pula só uma ocorrência e continua). Para remover várias ações pendentes do mesmo hook de uma vez, useas_unschedule_all_actions().as_next_scheduled_action( $hook, $args, $group )— devolve o timestamp da próxima ação agendada, oufalse.as_has_scheduled_action( $hook, $args, $group )— versão mais barata para apenas checar a existência (recomendada a partir do Action Scheduler 3.3).
Sempre verifique function_exists() antes de chamar. O Action Scheduler é uma biblioteca de terceiros; se o site não tiver WooCommerce nem nenhum plugin que a embarque, essas funções simplesmente não existem e o seu código quebra com um fatal error. A guarda if ( function_exists( 'as_enqueue_async_action' ) ) é obrigatória em código que será distribuído.
1. Enfileirar uma ação assíncrona
O caso mais comum: algo aconteceu (um pedido foi pago, um formulário foi enviado) e você quer fazer um trabalho potencialmente lento sem segurar a resposta para o usuário. Enfileire a ação e devolva a página na hora; o trabalho roda em background.
<?php
// Em algum ponto do fluxo (ex.: ao concluir um pedido), enfileiramos o trabalho.
// Note o GROUP: agrupar facilita o monitoramento e a limpeza.
function dwp_apos_pedido_pago( $order_id ) {
if ( ! function_exists( 'as_enqueue_async_action' ) ) {
return; // Action Scheduler ausente: faça um fallback ou apenas saia.
}
// Passamos só o ID — nunca o objeto inteiro do pedido.
// Array POSICIONAL: cada item vira um parâmetro separado no handler.
as_enqueue_async_action(
'dwp_sincroniza_pedido_erp', // hook (action) a disparar
array( (int) $order_id ), // args leves (posicionais)
'dwp-sync' // group
);
}
add_action( 'woocommerce_order_status_processing', 'dwp_apos_pedido_pago' );
// O HANDLER: ligado ao hook com add_action, registrado SEMPRE.
// Aridade 1: recebe 1 argumento (o único item do array de args).
add_action( 'dwp_sincroniza_pedido_erp', 'dwp_handler_sincroniza_pedido', 10, 1 );
function dwp_handler_sincroniza_pedido( $order_id ) {
// O Action Scheduler espalha os itens do array como parâmetros posicionais.
$order_id = (int) $order_id;
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id ); // recarregamos o objeto pelo ID
if ( ! $order ) {
return;
}
// Idempotência: se já sincronizamos, não repita (evita duplicar na retentativa).
if ( $order->get_meta( '_dwp_sincronizado' ) === 'sim' ) {
return;
}
// ... aqui o trabalho lento de verdade (chamada HTTP ao ERP etc.) ...
$order->update_meta_data( '_dwp_sincronizado', 'sim' );
$order->save();
}
Repare em dois detalhes que separam o código amador do profissional: o argumento é apenas o order_id (referência leve, recarregada dentro do handler) e a verificação de idempotência logo no início. Como veremos, o Action Scheduler pode reexecutar a sua ação — então ela precisa tolerar isso.
Como o handler recebe os argumentos (armadilha comum). Ao executar a ação, o Action Scheduler chama internamente do_action_ref_array( $hook, array_values( $args ) ) — ou seja, ele descarta as chaves do array de args e espalha os valores como parâmetros posicionais para o callback. Por isso, enfileirar com array associativo (array( 'order_id' => 5 )) e ler no handler via $args['order_id'] não funciona: o callback recebe 5 como primeiro parâmetro, e $args['order_id'] vira null. Use sempre arrays posicionais, declare a aridade certa no add_action( 'hook', 'handler', 10, $n ) (igual ao número de itens) e receba cada item como um argumento separado — nunca dependa de chaves associativas.
2. Agendar uma ação recorrente (com guarda)
Para tarefas periódicas — uma varredura de manutenção, um resumo diário —, use as_schedule_recurring_action(). O padrão correto é registrar a recorrência apenas se ela ainda não existir, para não criar duplicatas a cada carregamento de plugin:
<?php
add_action( 'init', 'dwp_agenda_recorrentes' );
function dwp_agenda_recorrentes() {
// Guarda dupla: a biblioteca pode não existir...
if ( ! function_exists( 'as_has_scheduled_action' ) ) {
return;
}
// ...e a ação pode já estar agendada de execuções anteriores.
if ( as_has_scheduled_action( 'dwp_limpeza_diaria', array(), 'dwp-manutencao' ) ) {
return;
}
as_schedule_recurring_action(
time(), // primeiro disparo: agora
DAY_IN_SECONDS, // intervalo em SEGUNDOS (não use strings tipo 'daily')
'dwp_limpeza_diaria', // hook
array(), // sem args
'dwp-manutencao' // group
);
}
// Handler da recorrente
add_action( 'dwp_limpeza_diaria', 'dwp_executa_limpeza' );
function dwp_executa_limpeza() {
// ... limpeza de logs antigos, caches órfãos etc. ...
}
Diferentemente do WP-Cron, o intervalo aqui é um número de segundos, não uma string de recorrência ('hourly', 'daily'). Passar uma string não vai funcionar como você espera. Use as constantes do núcleo — HOUR_IN_SECONDS, DAY_IN_SECONDS, WEEK_IN_SECONDS — para manter o código legível.
3. O padrão de lote: processar N e re-enfileirar o próximo
Este é o padrão mais importante do artigo e o motivo número um para se usar o Action Scheduler. Você nunca tenta processar 50 mil itens em uma única ação — isso estouraria a memória e o tempo de execução. Em vez disso, cada ação processa um lote pequeno (por exemplo, 50 itens) e, se ainda houver trabalho, enfileira a próxima ação para continuar de onde parou. A fila se auto-alimenta até terminar.
<?php
// Disparo inicial (uma vez, ex.: ao clicar num botão no admin):
// as_enqueue_async_action( 'dwp_reindexa_lote', array( 0 ), 'dwp-reindex' );
// Aridade 1: o offset chega como parâmetro posicional (não como array de chaves).
add_action( 'dwp_reindexa_lote', 'dwp_processa_lote', 10, 1 );
function dwp_processa_lote( $offset ) {
$offset = (int) $offset; // 1º (e único) item do array de args
$por_lote = 50; // tamanho do lote: ajuste conforme o peso de cada item
$ids = get_posts( array(
'post_type' => 'product',
'post_status' => 'publish',
'fields' => 'ids',
'posts_per_page' => $por_lote,
'offset' => $offset,
'orderby' => 'ID',
'order' => 'ASC',
'no_found_rows' => true, // não precisamos do total: mais leve
) );
// Sem mais itens: o trabalho acabou. NÃO re-enfileira.
if ( empty( $ids ) ) {
return;
}
foreach ( $ids as $id ) {
// ... reindexa o produto $id ...
clean_post_cache( $id ); // higiene de memória em loops longos
}
// Ainda pode haver mais: enfileira o PRÓXIMO lote, avançando o offset.
if ( function_exists( 'as_enqueue_async_action' ) ) {
as_enqueue_async_action(
'dwp_reindexa_lote',
array( $offset + $por_lote ), // próximo offset (posicional)
'dwp-reindex'
);
}
}
Esse desenho tem propriedades excelentes: cada execução é curta (não dá timeout), o consumo de memória é constante, e se uma ação falhar o Action Scheduler a repete — sem perder o lugar na fila, porque o offset está gravado nos argumentos. É exatamente assim que ferramentas sérias importam e reindexam grandes catálogos no WooCommerce.
Cuidado ao usar offset com dados que mudam. Se itens são apagados durante o processamento, o offset pode "pular" registros. Em jobs onde isso importa, prefira paginar por uma âncora estável — por exemplo, WHERE ID > $ultimo_id ORDER BY ID — guardando o último ID processado em vez de um deslocamento numérico.
4. Cancelar e consultar ações
Para desfazer um agendamento (recorrente ou único), use as_unschedule_action() com a mesma assinatura usada no agendamento — mesmo hook, mesmos args, mesmo group. Ele cancela a ação pendente correspondente; numa ação recorrente, isso encerra o agendamento por completo (não apenas pula a próxima ocorrência). Se houver várias pendentes do mesmo hook, as_unschedule_all_actions() remove todas. Para apenas saber se há algo agendado e quando, use as_next_scheduled_action():
<?php
// Ao desativar o plugin, é boa prática limpar as recorrências.
register_deactivation_hook( __FILE__, 'dwp_ao_desativar' );
function dwp_ao_desativar() {
if ( function_exists( 'as_unschedule_action' ) ) {
// Cancela a ação pendente (assinatura idêntica ao agendamento).
// Numa recorrente, isto ENCERRA o agendamento — não pula só uma vez.
as_unschedule_action( 'dwp_limpeza_diaria', array(), 'dwp-manutencao' );
// Para remover VÁRIAS ações pendentes do mesmo hook de uma vez:
// as_unschedule_all_actions( 'dwp_limpeza_diaria', array(), 'dwp-manutencao' );
}
}
// Consultar a próxima execução (ex.: para exibir no admin)
if ( function_exists( 'as_next_scheduled_action' ) ) {
$ts = as_next_scheduled_action( 'dwp_limpeza_diaria', array(), 'dwp-manutencao' );
if ( $ts ) {
// $ts é um timestamp Unix em UTC; converta para o fuso do site ao exibir.
$quando = wp_date( 'd/m/Y H:i', $ts );
// ... mostre "Próxima limpeza: $quando" ...
}
}
Monitoramento e disparo em produção
Com o WooCommerce ativo, a tela WooCommerce › Status › Scheduled Actions lista cada ação por status — pending, in-progress, complete, failed, canceled — com o log completo de cada uma. Filtre pelo group do seu plugin (no exemplo, dwp-sync ou dwp-reindex) para isolar as suas ações no meio das dezenas que o WooCommerce gera. Pela linha de comando, os comandos wp action-scheduler list e wp action-scheduler run permitem inspecionar e forçar o processamento da fila:
# Listar ações de um grupo, mais recentes primeiro:
wp action-scheduler list --group=dwp-reindex --per-page=20
# Forçar o processamento da fila AGORA (ideal no cron de sistema):
wp action-scheduler run
# Quantas ações há em cada status?
wp action-scheduler status
E no wp-config.php, para tirar o processamento das mãos do tráfego e colocá-lo num cron real:
<?php
// wp-config.php — desliga o disparo por loopback a cada visita.
define( 'DISABLE_WP_CRON', true );
// No crontab do servidor, dispare a cada minuto, por exemplo:
// * * * * * cd /var/www/seusite && wp action-scheduler run --quiet
A fila ainda depende de um gatilho. O Action Scheduler resolve a escala e a confiabilidade, mas se apoia no WP-Cron para "acordar" e processar — logo, herda a dependência de tráfego. Num site de baixo movimento, isso significa atrasos. Em produção, o padrão é definir DISABLE_WP_CRON como true no wp-config.php e disparar wp-cron.php (ou wp action-scheduler run) por um cron de sistema a cada minuto. Veja o detalhamento no artigo de WP-Cron.
Armadilhas comuns
- Chamar as funções
as_*sem guarda. Semfunction_exists(), um site sem WooCommerce derruba com fatal error. - Esquecer o
add_action()do handler. Enfileirar só cria o registro; quem executa é o hook. Se o callback não estiver registrado em toda requisição, a ação roda, não encontra ninguém ouvindo e é marcada como concluída sem fazer nada. - Payloads gigantes nos argumentos. Args são serializados e indexados por hash. Passe IDs, não objetos.
- Handlers não idempotentes. Como há retentativa, uma ação que cobra um cliente sem checar se já cobrou pode cobrar duas vezes.
- Não agrupar (
group). Sem grupo, suas ações se perdem no meio das do WooCommerce e a limpeza/monitoramento vira um pesadelo. - Confiar no horário sem cron real. Em site de baixo tráfego, "assim que possível" pode virar "daqui a algumas horas".
- Não tratar o caso "lote vazio" no padrão de lote. Se você sempre re-enfileira, cria um loop infinito de ações. Pare quando não houver mais itens.
Boas práticas
- Sempre defina um
groupcom o prefixo do seu plugin (ex.:dwp-sync). - Mantenha cada ação curta. Prefira muitos lotes pequenos a uma ação longa. Isso evita timeout e mantém a memória estável.
- Torne todo handler idempotente. Grave um marcador de "já feito" e cheque-o no início.
- Recarregue objetos pelo ID dentro do handler, em vez de carregá-los no payload.
- Limpe a memória em loops com
clean_post_cache()e, se necessário,wp_cache_flush()entre fatias. - Cancele recorrências na desativação do plugin, e considere apagar ações concluídas antigas para não inchar as tabelas (o Action Scheduler já faz uma limpeza periódica padrão).
- Dispare por cron de sistema em produção, exatamente como recomendado para o WP-Cron.
Se o seu projeto envolve WooCommerce, a boa notícia é que você não precisa instalar nada: a fila já está lá, esperando para ser usada com responsabilidade. E se você está empacotando isso num plugin próprio, vale revisar como organizar o registro dos hooks no artigo sobre criar um plugin.
Perguntas frequentes
Preciso instalar o Action Scheduler separadamente?
Quase nunca. O Action Scheduler é uma biblioteca embarcada em muitos plugins populares — o WooCommerce, o WooCommerce Subscriptions, o Jetpack e dezenas de outros já o trazem. Quando vários plugins incluem cópias diferentes, a própria biblioteca decide qual versão (a mais recente) é carregada para todo o site. Se você desenvolve um plugin que não depende do WooCommerce, pode incluir a biblioteca via Composer (woocommerce/action-scheduler) ou baixá-la do site oficial. Sempre proteja as chamadas com function_exists( 'as_enqueue_async_action' ), porque a função pode não existir.
Qual a diferença entre Action Scheduler e WP-Cron?
O WP-Cron é um agendador simples: guarda eventos em uma única option e dispara o hook na próxima visita após o horário. O Action Scheduler é uma fila de tarefas: grava cada ação em tabelas próprias do banco, processa em lotes com concorrência controlada, repete ações que falham e mantém um log de cada execução. Para alguns eventos esporádicos, WP-Cron basta; para milhares de jobs, processamento assíncrono ou lotes grandes, o Action Scheduler é a escolha certa.
O Action Scheduler executa tarefas sem ninguém visitar o site?
Por padrão, não. Ele se ancora no WP-Cron para acordar e processar a fila, então herda a mesma limitação: depende de tráfego ou de um cron real do sistema. Em produção séria, desligue o DISABLE_WP_CRON e dispare wp-cron.php (ou wp action-scheduler run via WP-CLI) por um cron de sistema a cada minuto. Sem isso, em sites de baixo movimento, as ações podem atrasar horas.
O que significa uma ação ser idempotente e por que isso importa?
Idempotente significa que rodar a ação duas vezes produz o mesmo resultado de rodá-la uma vez. Como o Action Scheduler pode repetir uma ação que falhou no meio (ou que o processo perdeu por timeout), o seu handler precisa tolerar reexecução: verifique se o trabalho já foi feito antes de fazê-lo de novo (por exemplo, cheque um meta de "já processado" antes de cobrar um cliente). Sem idempotência, uma retentativa pode duplicar e-mails, pedidos ou cobranças.
Posso passar objetos grandes nos argumentos da ação?
Não passe. Os argumentos são serializados e gravados na tabela actionscheduler_actions, e o Action Scheduler usa uma hash deles para evitar duplicatas. Passar um array enorme (um pedido inteiro, um post completo) incha o banco, pode estourar limites de coluna e torna a deduplicação imprevisível. Passe sempre uma referência leve — um ID — e recarregue o objeto dentro do handler com get_post(), wc_get_order() etc.
Como monitoro as ações em produção?
Com o WooCommerce ativo, vá em WooCommerce › Status › Scheduled Actions (ou Ferramentas › Ações agendadas, dependendo da versão). Lá você vê ações pendentes, em execução, concluídas e com falha, com o log de cada uma. Pela linha de comando, wp action-scheduler list e wp action-scheduler run ajudam a inspecionar e forçar o processamento. Filtre pelo group do seu plugin para não se perder no meio das ações do WooCommerce.