No primeiro artigo da série, vimos como a Abilities API transforma funções de plugins em capacidades que ferramentas e agentes conseguem descobrir. Agora vamos construir uma ability com entrada parametrizada, validação automática e permissão vinculada ao objeto consultado.
O exemplo recebe o ID de um post e devolve título, status, tipo, URL e resumo. A ação é somente de leitura, mas não fica aberta para qualquer usuário: só pode executá-la quem tiver permissão para editar o post solicitado.
O contrato que vamos construir
Nossa ability se chamará wp24/get-post-summary e receberá:
{
"post_id": 123
}
A resposta terá este formato:
{
"id": 123,
"title": "Título do post",
"status": "publish",
"post_type": "post",
"url": "https://example.com/titulo-do-post/",
"excerpt": "Resumo do conteúdo"
}
Esse contrato é importante para integrações e agentes. A ferramenta sabe que post_id é obrigatório e inteiro; também sabe exatamente quais propriedades encontrará no resultado.

Estrutura mínima do plugin
Crie uma pasta chamada wp24-abilities-demo dentro de wp-content/plugins e adicione o arquivo wp24-abilities-demo.php:
<?php
/**
* Plugin Name: WP24Horas Abilities Demo
* Version: 0.2.0
* Requires at least: 6.9
* Requires PHP: 7.4
*/
defined( 'ABSPATH' ) || exit;
O requisito mínimo é WordPress 6.9 porque foi nessa versão que a Abilities API entrou no Core.
Registrando a categoria
Toda ability pertence a uma categoria. O registro deve acontecer no hook específico da API:
add_action( 'wp_abilities_api_categories_init', 'wp24_register_category' );
function wp24_register_category(): void {
wp_register_ability_category(
'wp24-site',
array(
'label' => __( 'Informações do site', 'wp24-abilities-demo' ),
'description' => __( 'Ações de consulta sobre o site WordPress.', 'wp24-abilities-demo' ),
)
);
}
O slug aceita letras minúsculas, números e hífens. A categoria ajuda clientes a filtrar e organizar as capacidades descobertas.
Definindo o schema de entrada
O input_schema usa a sintaxe de JSON Schema suportada pela REST API do WordPress:
'input_schema' => array(
'type' => 'object',
'additionalProperties' => false,
'required' => array( 'post_id' ),
'properties' => array(
'post_id' => array(
'type' => 'integer',
'minimum' => 1,
'description' => __( 'ID do post que será consultado.', 'wp24-abilities-demo' ),
),
),
),
Três decisões merecem atenção:
requiredimpede a execução sempost_id;minimumrejeita zero e números negativos;additionalProperties: falserejeita parâmetros que não fazem parte do contrato.
O schema valida a forma dos dados. Ele não substitui autorização nem confirma que o post realmente existe.
Definindo o schema de saída
A resposta também deve ser validada:
'output_schema' => array(
'type' => 'object',
'additionalProperties' => false,
'required' => array( 'id', 'title', 'status', 'post_type', 'url', 'excerpt' ),
'properties' => array(
'id' => array( 'type' => 'integer' ),
'title' => array( 'type' => 'string' ),
'status' => array( 'type' => 'string' ),
'post_type' => array( 'type' => 'string' ),
'url' => array( 'type' => 'string', 'format' => 'uri' ),
'excerpt' => array( 'type' => 'string' ),
),
),
Validar a saída evita que uma alteração futura no callback quebre silenciosamente os consumidores da ability.
Permissão vinculada ao post
Uma checagem genérica como current_user_can( 'edit_posts' ) não é suficiente. Um autor pode editar posts próprios e não ter acesso aos posts de outra pessoa. Por isso, verificamos a permissão meta edit_post com o ID concreto:
function wp24_can_get_post_summary( $input ) {
if ( ! is_array( $input ) || empty( $input['post_id'] ) ) {
return new WP_Error(
'wp24_missing_post_id',
__( 'Informe um post_id válido.', 'wp24-abilities-demo' )
);
}
$post_id = (int) $input['post_id'];
if ( ! get_post( $post_id ) ) {
return new WP_Error(
'wp24_post_not_found',
__( 'O post solicitado não existe.', 'wp24-abilities-demo' )
);
}
return current_user_can( 'edit_post', $post_id );
}
A permission_callback recebe a mesma entrada do callback de execução. Ela pode retornar true, false ou um WP_Error com uma mensagem específica.
Implementando a execução
Depois da validação e da autorização, o callback pode montar a resposta:
function wp24_get_post_summary( array $input ) {
$post = get_post( (int) $input['post_id'] );
if ( ! $post ) {
return new WP_Error(
'wp24_post_not_found',
__( 'O post solicitado não existe.', 'wp24-abilities-demo' )
);
}
return array(
'id' => (int) $post->ID,
'title' => get_the_title( $post ),
'status' => get_post_status( $post ),
'post_type' => get_post_type( $post ),
'url' => get_permalink( $post ),
'excerpt' => wp_strip_all_tags( get_the_excerpt( $post ) ),
);
}
Mesmo com a verificação anterior, o callback trata a ausência do post. Essa defesa evita depender de estado que pode mudar entre autorização e execução.
Registrando a ability completa
Juntando os componentes:
add_action( 'wp_abilities_api_init', 'wp24_register_post_summary_ability' );
function wp24_register_post_summary_ability(): void {
wp_register_ability(
'wp24/get-post-summary',
array(
'label' => __( 'Resumo de um post', 'wp24-abilities-demo' ),
'description' => __( 'Retorna um resumo estruturado de um post que o usuário autenticado pode editar.', 'wp24-abilities-demo' ),
'category' => 'wp24-site',
'input_schema' => array( /* schema de entrada */ ),
'output_schema' => array( /* schema de saída */ ),
'execute_callback' => 'wp24_get_post_summary',
'permission_callback' => 'wp24_can_get_post_summary',
'meta' => array(
'show_in_rest' => true,
'annotations' => array(
'readonly' => true,
'destructive' => false,
'idempotent' => true,
),
),
)
);
}
As anotações informam a ferramentas que repetir a chamada não modifica o site. Elas são metadados semânticos; a segurança continua dependendo da autenticação e da permission_callback.
Testando em PHP
Com o plugin ativo e um usuário autenticado:
$ability = wp_get_ability( 'wp24/get-post-summary' );
$result = $ability->execute(
array(
'post_id' => 123,
)
);
if ( is_wp_error( $result ) ) {
error_log( $result->get_error_message() );
}
Teste pelo menos quatro casos:
- post existente e autorizado;
- post existente sem permissão;
post_idinexistente;- entrada inválida ou com campos extras.
Testando pela REST API
Use uma Application Password própria para a integração, não a senha principal da conta:
curl --user "USUARIO:SENHA_DE_APLICATIVO" \
--request GET \
--get \
--data-urlencode 'input={"post_id":123}' \
"https://example.com/wp-json/wp-abilities/v1/abilities/wp24/get-post-summary/run"
A requisição passa pelo mesmo pipeline: normalização, validação de entrada, permissão, execução e validação de saída.
Por que a permissão usa a entrada
Para abilities parametrizadas, autorização frequentemente depende do objeto. Alguns exemplos:
- editar um post específico;
- consultar um pedido concreto;
- reembolsar uma compra determinada;
- acessar dados de um membro;
- alterar uma configuração de uma filial.
Verificar apenas uma capability genérica pode autorizar mais dados do que o necessário. Prefira as meta capabilities do WordPress sempre que a decisão depender de um objeto.
Checklist antes de expor pela REST
- recuse propriedades não documentadas;
- declare todos os campos obrigatórios;
- use limites como
minimum,enumemaxLengthquando fizer sentido; - autorize o objeto, não apenas o tipo de ação;
- retorne somente os dados necessários;
- remova HTML quando o consumidor espera texto;
- use
WP_Errorpara falhas previsíveis; - marque corretamente efeitos colaterais;
- teste com usuários de papéis diferentes;
- revogue credenciais usadas apenas durante o desenvolvimento.
Próximo passo: auditoria e políticas no WordPress 7.1
Agora temos uma ability parametrizada com um contrato verificável e autorização por objeto. No próximo artigo, usaremos os filtros do WordPress 7.1 para adicionar auditoria e uma política adicional sem modificar o callback original.
Baixe o plugin de demonstração 0.2.0 e compare a nova ability com wp24/site-summary, criada no primeiro artigo da série.



