wp-rest-api

📁 peixotorms/odinlayer-skills 📅 8 days ago
4
总安装量
2
周安装量
#53326
全站排名
安装命令
npx skills add https://github.com/peixotorms/odinlayer-skills --skill wp-rest-api

Agent 安装分布

amp 2
opencode 2
kimi-cli 2
codex 2
github-copilot 2
gemini-cli 2

Skill 文档

WP REST API & Abilities API

Route Registration

Register routes on the rest_api_init hook using register_rest_route(). A route is the URL pattern; an endpoint is the method + callback bound to that route. For non-trivial endpoints, extend WP_REST_Controller for a structured CRUD pattern with built-in schema wiring.

Namespacing rules

  • Always use a unique namespace: vendor/v1 (e.g. myplugin/v1).
  • Never use wp/* unless contributing to WordPress core.
  • For non-pretty permalinks, routes are accessed via ?rest_route=/namespace/route.

HTTP method constants

Constant Methods
WP_REST_Server::READABLE GET
WP_REST_Server::CREATABLE POST
WP_REST_Server::EDITABLE PUT, PATCH
WP_REST_Server::DELETABLE DELETE
WP_REST_Server::ALLMETHODS All of the above

Multiple endpoints can share a route (one per method group).

Schema & Validation

JSON Schema

WordPress REST API uses a subset of JSON Schema (draft 4) for both resource definitions and argument validation. Schema serves as documentation AND validation.

  • Provide schema via get_item_schema() on controllers or a schema callback on routes.
  • Schema is returned on OPTIONS requests, enabling API discovery.
  • Cache the schema on the controller instance ($this->schema) to avoid recomputation.

Common formats and types

Format Example
date-time 2025-01-15T10:30:00
uri https://example.com
email user@example.com
ip 192.168.1.1
uuid 550e8400-e29b-41d4-a716-446655440000
hex-color #ff0000

For array types, define items schema. For object types, define properties.

validate_callback and sanitize_callback

'args' => [
    'email' => [
        'type'              => 'string',
        'format'            => 'email',
        'required'          => true,
        'validate_callback' => 'rest_validate_request_arg', // uses schema
        'sanitize_callback' => 'sanitize_email',
    ],
    'count' => [
        'type'              => 'integer',
        'minimum'           => 1,
        'maximum'           => 100,
        'default'           => 10,
        'validate_callback' => function ( $value, $request, $param ) {
            if ( ! is_numeric( $value ) ) {
                return new WP_Error( 'invalid_param', "$param must be numeric." );
            }
            return true;
        },
        'sanitize_callback' => 'absint',
    ],
],

Important: If you override sanitize_callback, built-in schema validation will not run automatically. Use rest_validate_request_arg as validate_callback to preserve it.

The controller method get_endpoint_args_for_item_schema() wires validation automatically from the schema.

Permission & Authentication

permission_callback is REQUIRED

Every route MUST have a permission_callback. Omitting it triggers a _doing_it_wrong notice.

  • Public read endpoints: use 'permission_callback' => '__return_true'.
  • Write endpoints: NEVER use __return_true. Always check capabilities.
  • Use current_user_can() for capability checks, not just “is logged in”.
  • Hide sensitive endpoints from the REST index with 'show_in_index' => false — prevents API keys, tokens, or admin data from being discoverable via GET /wp-json/.
'permission_callback' => function ( $request ) {
    return current_user_can( 'edit_post', $request['id'] );
},

// Sensitive admin endpoint — hide from /wp-json/ discovery.
register_rest_route( 'myplugin/v1', '/admin/config', [
    'methods'             => 'POST',
    'callback'            => 'myplugin_admin_config',
    'permission_callback' => function () {
        return current_user_can( 'manage_options' );
    },
    'show_in_index'       => false,  // Not listed in /wp-json/ index.
] );

Authentication methods

Cookie + nonce (logged-in frontend JS):

// Localize in PHP:
wp_localize_script( 'my-script', 'MyPlugin', [
    'root'  => esc_url_raw( rest_url() ),
    'nonce' => wp_create_nonce( 'wp_rest' ),
] );
// In JS:
fetch( MyPlugin.root + 'myplugin/v1/items', {
    headers: { 'X-WP-Nonce': MyPlugin.nonce },
} );

If the nonce is missing, the request is treated as unauthenticated even if cookies exist.

Application Passwords (external clients, WP 5.6+):

# HTTPS required. Use Basic Auth with the application password.
curl -u "username:xxxx xxxx xxxx xxxx xxxx xxxx" \
  https://example.com/wp-json/myplugin/v1/items

OAuth/JWT (via plugins): Use only when required. Follow the specific plugin’s documentation.

rest_authentication_errors filter: Hook into this to add custom authentication or block unauthenticated access globally. Return WP_Error to deny, true to approve, or pass through existing results.

Response Shaping

WP_REST_Response

function myplugin_get_items( $request ) {
    $items = []; // fetch data
    $total = 100;
    $pages = 10;

    $response = new WP_REST_Response( $items, 200 );
    $response->header( 'X-WP-Total', $total );
    $response->header( 'X-WP-TotalPages', $pages );
    return $response;
}

Use rest_ensure_response() to wrap arrays/scalars into WP_REST_Response automatically.

Field registration with register_rest_field()

Add computed fields to existing endpoints. Do NOT remove or change core fields (breaks clients including wp-admin).

add_action( 'rest_api_init', function () {
    register_rest_field( 'post', 'word_count', [
        'get_callback' => function ( $post_arr ) {
            return str_word_count( wp_strip_all_tags( $post_arr['content']['rendered'] ) );
        },
        'update_callback' => null, // read-only
        'schema'          => [
            'description' => 'Word count of the post content.',
            'type'        => 'integer',
            'context'     => [ 'view', 'edit' ],
            'readonly'    => true,
        ],
    ] );
} );

Meta fields with register_meta()

register_post_meta( 'post', 'myplugin_rating', [
    'type'          => 'integer',
    'single'        => true,
    'show_in_rest'  => true,
    'auth_callback' => function () {
        return current_user_can( 'edit_posts' );
    },
] );

// For object/array meta, provide a schema:
register_post_meta( 'post', 'myplugin_settings', [
    'type'         => 'object',
    'single'       => true,
    'show_in_rest' => [
        'schema' => [
            'type'       => 'object',
            'properties' => [
                'color' => [ 'type' => 'string' ],
                'size'  => [ 'type' => 'integer' ],
            ],
        ],
    ],
] );

Links and embedding

$response->add_link( 'author', rest_url( 'wp/v2/users/' . $post->post_author ), [
    'embeddable' => true, // allows ?_embed to inline this
] );

Register CURIEs (compact URIs) via the rest_response_link_curies filter.

Sparse responses with _fields

Clients can request ?_fields=id,title,meta.myplugin_rating to receive only specific fields. Supports nested keys.

Pagination

Collections should return these headers:

Header Purpose
X-WP-Total Total number of items
X-WP-TotalPages Total number of pages

Standard params: page, per_page (capped at 100), offset.

Raw vs rendered content

content.rendered reflects filters (plugins may inject HTML). Use ?context=edit&_fields=content.raw (authenticated) to access raw content.

Custom Post Types & Taxonomies

Exposing a CPT

register_post_type( 'book', [
    'label'               => 'Books',
    'public'              => true,
    'show_in_rest'        => true,            // Required for REST
    'rest_base'           => 'books',          // Custom URL slug
    'rest_controller_class' => 'WP_REST_Posts_Controller', // Default
    'supports'            => [ 'title', 'editor', 'custom-fields' ],
] );

Exposing a custom taxonomy

register_taxonomy( 'genre', 'book', [
    'label'          => 'Genres',
    'show_in_rest'   => true,
    'rest_base'      => 'genres',
    // rest_controller_class defaults to WP_REST_Terms_Controller
] );

Adding REST to existing types you do not control

Use register_post_type_args or register_taxonomy_args filters to set show_in_rest => true and rest_base on types you do not own.

Custom controllers

Set rest_controller_class to a class extending WP_REST_Controller. Use rest_route_for_post or rest_route_for_term filters for discovery links.

Discovery & Features

API discovery

REST API root is discovered via the Link: <.../wp-json/>; rel="https://api.w.org/" header or the equivalent <link> element in HTML. The /wp-json/ index lists all namespaces and routes. For non-pretty permalinks use ?rest_route=/.

Global parameters

Parameter Purpose
_fields Sparse field selection
_embed Include linked resources in _embedded
_method Simulate PUT/DELETE via POST
_envelope Wrap status + headers in response body
_jsonp JSONP for legacy clients

Also: X-HTTP-Method-Override header as alternative to _method.

Batch API

Send multiple requests in one call: POST /wp-json/batch/v1 with a requests array of { "method", "path" } objects.

Abilities API (WordPress 6.9+)

The Abilities API is a declarative permission/feature system for exposing server-side capabilities to client-side code. Register categories on wp_abilities_api_categories_init (first), then abilities on wp_abilities_api_init. Set meta.show_in_rest => true to expose abilities via GET /wp-json/wp-abilities/v1/abilities. Use @wordpress/abilities (useAbility, hasAbility) on the client side.

Error Handling

WP_Error for error responses

// In a callback:
$post = get_post( $request['id'] );
if ( ! $post ) {
    return new WP_Error(
        'myplugin_not_found',
        __( 'Item not found.', 'myplugin' ),
        [ 'status' => 404 ]
    );
}

// In permission_callback (returning WP_Error gives the client a reason):
if ( ! current_user_can( 'edit_posts' ) ) {
    return new WP_Error(
        'rest_forbidden',
        __( 'You do not have permission to create items.', 'myplugin' ),
        [ 'status' => 403 ]
    );
}

Always include status in the data array. Standard codes:

HTTP Status When
400 Invalid parameters / bad request
401 Not authenticated
403 Authenticated but insufficient permissions
404 Resource not found
500 Server error

Use rest_validate_request_arg() to validate individual args against their schema (returns WP_Error on failure).

Do NOT call wp_send_json() or die() inside REST callbacks. Always return data, WP_REST_Response, or WP_Error.

Common Patterns

Custom collection parameters (filtering, sorting)

public function get_collection_params() {
    $params = parent::get_collection_params();
    $params['status'] = [
        'description' => 'Limit results to a specific status.',
        'type'        => 'string',
        'enum'        => [ 'draft', 'publish', 'archived' ],
        'default'     => 'publish',
    ];
    $params['orderby'] = [
        'description' => 'Sort collection by attribute.',
        'type'        => 'string',
        'enum'        => [ 'date', 'title', 'rating' ],
        'default'     => 'date',
    ];
    return $params;
}

Registering custom REST fields on existing endpoints

add_action( 'rest_api_init', function () {
    register_rest_field( 'comment', 'author_avatar_url', [
        'get_callback' => function ( $comment_arr ) {
            return get_avatar_url( $comment_arr['author_email'], [ 'size' => 48 ] );
        },
        'schema' => [
            'type'        => 'string',
            'format'      => 'uri',
            'description' => 'Avatar URL for the comment author.',
            'context'     => [ 'view', 'embed' ],
        ],
    ] );
} );

Common Mistakes

Mistake Consequence Fix
Omitting permission_callback _doing_it_wrong notice; route may not work Always provide it; use __return_true for public reads
Using __return_true on write endpoints Any visitor can create/update/delete data Check capabilities with current_user_can()
Reading $_GET/$_POST directly Bypasses validation and sanitization Use $request->get_param() or $request['key']
Calling wp_send_json() or die() Breaks response lifecycle, no proper headers Return data, WP_REST_Response, or WP_Error
Missing status in WP_Error data Client receives 500 instead of correct code Always pass ['status' => 4xx] in error data
Using wp/* namespace Conflicts with WordPress core routes Use your own vendor namespace
Forgetting show_in_rest on CPT Post type not exposed in REST API Set show_in_rest => true
Missing custom-fields support on CPT Meta fields not available via REST Add 'supports' => ['custom-fields']
Not sending X-WP-Nonce with cookie auth Request treated as unauthenticated Send nonce as header or _wpnonce param
Setting per_page above 100 WP rejects the request Cap at 100; use pagination
Removing core fields from default endpoints Breaks wp-admin and other clients Add new fields instead; clients use _fields to limit
Registering abilities outside proper hooks _doing_it_wrong(); registration fails Use wp_abilities_api_init / wp_abilities_api_categories_init
Missing meta.show_in_rest on abilities Ability invisible to REST clients Set meta.show_in_rest => true
Registering abilities before categories Ability has no category association Register categories on wp_abilities_api_categories_init first