بنقرة واحدة
add-rest-endpoint
Add a REST API endpoint to an existing plugin following Dekode conventions.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Add a REST API endpoint to an existing plugin following Dekode conventions.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Add a WordPress AJAX handler to a plugin, with nonce verification, input sanitisation, and JSON response.
Register a block pattern in a theme or plugin, using the WordPress 6.0+ file-based pattern system.
Add a new Gutenberg block to an existing plugin or theme.
Register a WP-CLI command in a plugin using a command class, following Dekode conventions.
Add a WordPress action or filter hook to a plugin or theme.
Scaffold a new must-use plugin in packages/mu-plugins/ - either a simple utility or a service-class integration.
| name | add-rest-endpoint |
| description | Add a REST API endpoint to an existing plugin following Dekode conventions. |
Create a properly structured, secure REST API endpoint in an existing plugin using WP_REST_Controller.
The following inputs are required. If any are missing from the request, ask for them before writing any code.
| Input | Required | Notes |
|---|---|---|
| Plugin | Yes | Which plugin in packages/plugins/, e.g. packages/plugins/acme-blocks/ |
| Resource name | Yes | What entity does this endpoint expose? e.g. products, reviews |
| HTTP methods | Yes | GET / POST / PUT/PATCH / DELETE |
| Is public? | Yes | Does this require authentication, or is it open to all? |
| Arguments | Yes | What parameters does the endpoint accept, and are they required or optional? |
| Route namespace | No | Defaults to {plugin-slug}/v1 (kebab-case plugin slug + /v1) |
Create src/Api/<ResourceName>Controller.php in the target plugin.
<?php
/**
* REST API: {Resource} controller.
*
* @package {PhpNamespace}
*/
declare( strict_types=1 );
namespace Dekodeinteraktiv\Plugin\<PascalSlug>\Api;
use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
class <ResourceName>Controller extends WP_REST_Controller {
public function __construct() {
$this->namespace = '<plugin-slug>/v1';
$this->rest_base = '<resource-name>';
}
public function register_routes(): void {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_items' ],
'permission_callback' => [ $this, 'get_items_permissions_check' ],
'args' => $this->get_collection_params(),
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'create_item' ],
'permission_callback' => [ $this, 'create_item_permissions_check' ],
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\d]+)',
[
'args' => [
'id' => [
'description' => __( 'Unique identifier for the item.', '<plugin-slug>' ),
'type' => 'integer',
'minimum' => 1,
],
],
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_item' ],
'permission_callback' => [ $this, 'get_item_permissions_check' ],
],
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'update_item' ],
'permission_callback' => [ $this, 'update_item_permissions_check' ],
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
],
[
'methods' => WP_REST_Server::DELETABLE,
'callback' => [ $this, 'delete_item' ],
'permission_callback' => [ $this, 'delete_item_permissions_check' ],
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
}
// ── Permission callbacks ──────────────────────────────────────────────────
public function get_items_permissions_check( WP_REST_Request $request ): bool|WP_Error {
return true; // public endpoint - adjust if authentication is required
}
public function create_item_permissions_check( WP_REST_Request $request ): bool|WP_Error {
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error(
'rest_forbidden',
__( 'You do not have permission to create items.', '<plugin-slug>' ),
[ 'status' => rest_authorization_required_code() ]
);
}
return true;
}
public function get_item_permissions_check( WP_REST_Request $request ): bool|WP_Error {
return $this->get_items_permissions_check( $request );
}
public function update_item_permissions_check( WP_REST_Request $request ): bool|WP_Error {
return $this->create_item_permissions_check( $request );
}
public function delete_item_permissions_check( WP_REST_Request $request ): bool|WP_Error {
return $this->create_item_permissions_check( $request );
}
// ── Callbacks ─────────────────────────────────────────────────────────────
public function get_items( WP_REST_Request $request ): WP_REST_Response|WP_Error {
return rest_ensure_response( [] );
}
public function get_item( WP_REST_Request $request ): WP_REST_Response|WP_Error {
$id = (int) $request['id'];
return rest_ensure_response( [] );
}
public function create_item( WP_REST_Request $request ): WP_REST_Response|WP_Error {
$item = $this->prepare_item_for_database( $request );
$response = $this->prepare_item_for_response( $item, $request );
return rest_ensure_response( $response );
}
public function update_item( WP_REST_Request $request ): WP_REST_Response|WP_Error {
return rest_ensure_response( [] );
}
public function delete_item( WP_REST_Request $request ): WP_REST_Response|WP_Error {
return rest_ensure_response( [ 'deleted' => true ] );
}
// ── Schema ────────────────────────────────────────────────────────────────
public function get_item_schema(): array {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$this->schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => '<resource-name>',
'type' => 'object',
'properties' => [
'id' => [
'description' => __( 'Unique identifier.', '<plugin-slug>' ),
'type' => 'integer',
'context' => [ 'view', 'edit', 'embed' ],
'readonly' => true,
],
],
];
return $this->add_additional_fields_schema( $this->schema );
}
}
hooks()In the plugin's main class, register the controller inside the hooks() method — never in a constructor or anonymous closure:
public function hooks(): void {
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
}
public function register_rest_routes(): void {
( new \Dekodeinteraktiv\Plugin\<PascalSlug>\Api\<ResourceName>Controller() )->register_routes();
}
Always use wp_remote_*() — never curl, file_get_contents(), or Guzzle:
$response = wp_remote_post( 'https://api.example.com/endpoint', [
'headers' => [ 'Content-Type' => 'application/json' ],
'body' => wp_json_encode( [ 'param' => $param ] ),
] );
if ( is_wp_error( $response ) ) {
return new WP_REST_Response(
[ 'success' => false, 'error' => $response->get_error_message() ],
500
);
}
$code = wp_remote_retrieve_response_code( $response );
if ( ! in_array( $code, [ 200, 201 ], true ) ) {
return new WP_REST_Response(
[ 'success' => false, 'error' => __( 'Unexpected response from upstream.', '<plugin-slug>' ) ],
500
);
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
return new WP_REST_Response( [ 'success' => true, 'data' => $body ], 200 );
composer lint
composer lint-fix # if needed
composer lint # re-run to confirm clean
{plugin-slug}/v1 — kebab-case slug, never camelCase, never shared dekodeinteraktiv/v1 across plugins['success' => bool] with either 'data' on success or 'error' (i18n string) on failure200/201 success, 400 bad input, 403 forbidden, 500 server/upstream errorstrue only for genuinely public endpoints. For anything touching user data, check current_user_can() and return rest_authorization_required_code() on failurewp_remote_*() — never curl, file_get_contents(), or Guzzleis_wp_error() first: always check before reading the response bodyin_array( $code, [200, 201], true ) — never assume successecho or die() inside a REST callback — always return WP_REST_Response__() with the plugin's text domain