Adicionar a Abilities API a um plugin existente não começa com wp_register_ability(). O primeiro trabalho é entender o que o plugin já faz, separar política de implementação e decidir qual operação é segura para agentes descobrirem ou executarem.
Neste estudo prático, migramos o plugin WP24 — Links externos em nova aba da versão 1.0.0 para a 1.1.0. Ele já percorria links do site público e aplicava target="_blank", noopener e noreferrer a destinos externos. A nova versão preserva esse comportamento e expõe a política como a Ability wp24/external-links-policy.
O exemplo foi validado no WordPress 6.8.3, 7.0.2 e 7.1-RC1 com PHP 8.3. Isso permite demonstrar tanto a integração com a Abilities API quanto um fallback seguro para versões anteriores.
Comece pelo inventário, não pelo registro
O plugin original tinha uma operação principal: imprimir um JavaScript no rodapé e alterar links externos no navegador. Dentro desse fluxo havia decisões de negócio escondidas como valores fixos:
- protocolos aceitos: HTTP e HTTPS;
- destino: nova aba;
- relações obrigatórias:
noopenerenoreferrer; - seletor ignorado: barra administrativa;
- observação de links adicionados dinamicamente.
Esses valores formavam uma política, mas estavam acoplados à implementação JavaScript. Copiá-los para uma Ability criaria duas fontes de verdade. A primeira mudança, portanto, foi extrair a política para uma função PHP.

Classifique a operação antes de expô-la
Nem toda função do plugin deve virar Ability. Para este caso, avaliamos três candidatas:
| Candidata | Risco | Decisão |
|---|---|---|
| retornar a política pública | leitura, sem dado sensível | implementar |
| classificar uma URL enviada pelo agente | entrada externa e diferenças de parser | adiar |
| reescrever conteúdo armazenado | escrita e efeito colateral | não implementar neste ciclo |
A operação escolhida não aceita URL, não faz requisição de rede e não grava nada. Ela apenas descreve um comportamento que qualquer visitante já consegue observar no HTML e no JavaScript públicos.
Extraia uma fonte única de verdade
A política passou a ser retornada por uma função pura:
function wp24_external_links_policy(): array {
return array(
'enabled_public_site' => true,
'allowed_protocols' => array( 'http:', 'https:' ),
'external_target' => '_blank',
'rel_tokens' => array( 'noopener', 'noreferrer' ),
'ignored_selectors' => array( '#wpadminbar' ),
'observes_dynamic_content' => true,
);
}
O front-end recebe essa estrutura com wp_json_encode():
$policy = wp24_external_links_policy();
?>
<script id="wp24-external-links">
(() => {
const policy = <?php echo wp_json_encode(
$policy,
JSON_UNESCAPED_SLASHES
); ?>;
const allowedProtocols = new Set(policy.allowed_protocols);
// A rotina existente usa os valores da política.
})();
</script>
<?php
Agora o JavaScript e a Ability descrevem exatamente a mesma regra. Alterar rel_tokens em PHP modifica os dois consumidores.
Registre uma categoria compatível
O plugin continua funcionando em versões anteriores à Abilities API. Por isso, os callbacks verificam se as funções existem:
function wp24_external_links_register_ability_category(): void {
if ( ! function_exists( 'wp_register_ability_category' ) ) {
return;
}
wp_register_ability_category(
'wp24-site-behavior',
array(
'label' => 'Comportamento do site',
'description' => 'Políticas públicas aplicadas ao site.',
)
);
}
add_action(
'wp_abilities_api_categories_init',
'wp24_external_links_register_ability_category'
);
Adicionar callbacks a hooks inexistentes não causa erro no WordPress. Quando a versão não dispara esses hooks, o comportamento antigo permanece ativo.
Registre a Ability com contrato restrito
A Ability não recebe entrada e declara toda a forma da saída:
wp_register_ability(
'wp24/external-links-policy',
array(
'label' => 'Política de links externos',
'description' => 'Retorna a política pública usada pelo site para abrir links externos com segurança.',
'category' => 'wp24-site-behavior',
'input_schema' => array(),
'output_schema' => array(
'type' => 'object',
'additionalProperties' => false,
'required' => array(
'enabled_public_site',
'allowed_protocols',
'external_target',
'rel_tokens',
'ignored_selectors',
'observes_dynamic_content',
),
'properties' => array(
'enabled_public_site' => array( 'type' => 'boolean' ),
'allowed_protocols' => array(
'type' => 'array',
'items' => array( 'type' => 'string' ),
),
'external_target' => array( 'type' => 'string' ),
'rel_tokens' => array(
'type' => 'array',
'items' => array( 'type' => 'string' ),
),
'ignored_selectors' => array(
'type' => 'array',
'items' => array( 'type' => 'string' ),
),
'observes_dynamic_content' => array(
'type' => 'boolean',
),
),
),
'execute_callback' => 'wp24_external_links_policy',
'permission_callback' => '__return_true',
'meta' => array(
'show_in_rest' => true,
'annotations' => array(
'readonly' => true,
'destructive' => false,
'idempotent' => true,
),
),
)
);
additionalProperties => false impede que o callback acrescente campos não documentados sem que o contrato seja revisado.
Por que a permissão é pública neste caso
Usar __return_true exige uma justificativa, não conveniência. Aqui a saída reproduz uma política já entregue a visitantes anônimos no JavaScript do site. Não há opções privadas, lista de plugins, dados de usuários ou conteúdo editorial.
Se a Ability retornasse configurações administrativas, domínios internos ou regras ainda não publicadas, a permissão deveria exigir uma capability apropriada.
O erro que os testes encontraram
Na primeira implementação, show_in_rest foi colocado no nível principal dos argumentos. A Ability existia e executava em PHP, mas o endpoint REST respondia rest_ability_not_found.
O local correto é dentro de meta:
'meta' => array(
'show_in_rest' => true,
'annotations' => array(
'readonly' => true,
'destructive' => false,
'idempotent' => true,
),
),
Esse teste demonstra por que verificar apenas wp_get_ability() é insuficiente quando a integração depende de REST. Registro, execução PHP e exposição externa são critérios separados.
Matriz de compatibilidade executada
| WordPress | Resultado |
|---|---|
| 6.8.3 | plugin ativo, política e JavaScript preservados, Abilities API ausente sem fatal |
| 7.0.2 | Ability registrada, saída aprovada pelo schema e REST HTTP 200 |
| 7.1-RC1 | Ability registrada, saída aprovada pelo schema e REST HTTP 200 |
Saída observada no 7.0.2 e no 7.1-RC1:
{
"enabled_public_site": true,
"allowed_protocols": ["http:", "https:"],
"external_target": "_blank",
"rel_tokens": ["noopener", "noreferrer"],
"ignored_selectors": ["#wpadminbar"],
"observes_dynamic_content": true
}
Os testes também capturaram o script renderizado e confirmaram a presença da política serializada e de noopener. Todos os containers, redes e volumes temporários foram removidos depois da validação.
Checklist para adaptar seu plugin
- inventarie funções e efeitos colaterais;
- separe política de implementação;
- comece por uma operação pequena e somente leitura;
- rejeite candidatos que ampliem o acesso sem benefício claro;
- defina namespace, nome e descrição orientados a tarefa;
- use schemas fechados e tipos específicos;
- justifique a
permission_callbackcom base nos dados; - coloque
show_in_restdentro demetaquando necessário; - trate Abilities ausentes sem quebrar versões anteriores;
- teste registro PHP, execução, REST e regressão do comportamento antigo;
- valide também a negação quando a operação for protegida;
- versione e documente a mudança.
Download do plugin do estudo
Baixe o WP24 External Links 1.1.0. O pacote contém o plugin e o README, sem ambiente Docker, credenciais ou arquivos de teste.
SHA-256 do pacote validado:
FCE2B994B1BE11B6D8723765320F6AC4623F445083AD748F7CC6D8D201611C5A
Próximo passo
Depois de migrar uma operação real e conectá-la a um agente, o próximo nível é desenhar a arquitetura de produção: isolamento do bridge, gestão de secrets, allowlists por ambiente, observabilidade, limites de execução e resposta a incidentes.



