AgentSkillsCN

Rest Api Development

REST API 开发

SKILL.md

Skill: WordPress REST API Development

Description

Expert guidance for creating custom REST API endpoints, extending existing endpoints, and working with the WordPress REST API.

When to Use

  • User wants to create custom API endpoints
  • User needs to extend existing REST responses
  • User wants to build headless WordPress
  • User needs to authenticate API requests
  • User wants to create a mobile app backend
  • User needs to integrate with external services

WordPress Boost Tools to Use

code
- rest_api_routes: List all REST routes
- list_hooks: Find REST API hooks
- wp_shell: Test API responses
- database_schema: Check data structure

Key Concepts

REST Route Structure

code
/wp-json/                      # API root
/wp-json/wp/v2/posts           # Core posts
/wp-json/my-plugin/v1/items    # Custom endpoint

HTTP Methods

  • GET - Read data
  • POST - Create data
  • PUT/PATCH - Update data
  • DELETE - Delete data

Common Tasks

1. Register Simple Endpoint

php
add_action( 'rest_api_init', function() {
    register_rest_route( 'my-plugin/v1', '/hello', array(
        'methods' => 'GET',
        'callback' => function() {
            return array( 'message' => 'Hello World!' );
        },
        'permission_callback' => '__return_true',
    ));
});

2. Full CRUD Endpoints

php
add_action( 'rest_api_init', function() {
    // GET all items
    register_rest_route( 'my-plugin/v1', '/items', array(
        'methods' => 'GET',
        'callback' => 'get_items',
        'permission_callback' => '__return_true',
    ));

    // GET single item
    register_rest_route( 'my-plugin/v1', '/items/(?P<id>\d+)', array(
        'methods' => 'GET',
        'callback' => 'get_item',
        'permission_callback' => '__return_true',
    ));

    // POST create item
    register_rest_route( 'my-plugin/v1', '/items', array(
        'methods' => 'POST',
        'callback' => 'create_item',
        'permission_callback' => function() {
            return current_user_can( 'edit_posts' );
        },
    ));

    // PUT update item
    register_rest_route( 'my-plugin/v1', '/items/(?P<id>\d+)', array(
        'methods' => 'PUT',
        'callback' => 'update_item',
        'permission_callback' => function() {
            return current_user_can( 'edit_posts' );
        },
    ));

    // DELETE item
    register_rest_route( 'my-plugin/v1', '/items/(?P<id>\d+)', array(
        'methods' => 'DELETE',
        'callback' => 'delete_item',
        'permission_callback' => function() {
            return current_user_can( 'delete_posts' );
        },
    ));
});

function get_items( $request ) {
    $posts = get_posts( array( 'post_type' => 'item' ) );
    $data = array();
    foreach ( $posts as $post ) {
        $data[] = prepare_item( $post );
    }
    return rest_ensure_response( $data );
}

function get_item( $request ) {
    $post = get_post( $request['id'] );
    if ( ! $post ) {
        return new WP_Error( 'not_found', 'Item not found', array( 'status' => 404 ) );
    }
    return rest_ensure_response( prepare_item( $post ) );
}

function create_item( $request ) {
    $post_id = wp_insert_post( array(
        'post_type' => 'item',
        'post_title' => sanitize_text_field( $request['title'] ),
        'post_content' => wp_kses_post( $request['content'] ),
        'post_status' => 'publish',
    ));
    return rest_ensure_response( prepare_item( get_post( $post_id ) ) );
}

function prepare_item( $post ) {
    return array(
        'id' => $post->ID,
        'title' => $post->post_title,
        'content' => $post->post_content,
        'link' => get_permalink( $post ),
    );
}

3. Add Custom Field to Posts API

php
add_action( 'rest_api_init', function() {
    register_rest_field( 'post', 'reading_time', array(
        'get_callback' => function( $post ) {
            $content = get_post_field( 'post_content', $post['id'] );
            $word_count = str_word_count( strip_tags( $content ) );
            return ceil( $word_count / 200 );
        },
        'schema' => array(
            'description' => 'Estimated reading time in minutes',
            'type' => 'integer',
        ),
    ));
});

4. Argument Validation

php
register_rest_route( 'my-plugin/v1', '/items', array(
    'methods' => 'GET',
    'callback' => 'get_items',
    'permission_callback' => '__return_true',
    'args' => array(
        'per_page' => array(
            'default' => 10,
            'sanitize_callback' => 'absint',
            'validate_callback' => function( $value ) {
                return $value >= 1 && $value <= 100;
            },
        ),
        'orderby' => array(
            'default' => 'date',
            'enum' => array( 'date', 'title', 'id' ),
        ),
        'search' => array(
            'sanitize_callback' => 'sanitize_text_field',
        ),
    ),
));

5. Custom Permission Check

php
'permission_callback' => function( $request ) {
    // Check nonce for cookie auth
    $nonce = $request->get_header( 'X-WP-Nonce' );
    if ( $nonce && wp_verify_nonce( $nonce, 'wp_rest' ) ) {
        return current_user_can( 'edit_posts' );
    }

    // Check for specific capability
    return current_user_can( 'manage_options' );
}

6. Return Proper Errors

php
function get_item( $request ) {
    $post = get_post( $request['id'] );

    if ( ! $post ) {
        return new WP_Error(
            'rest_not_found',
            'Item not found.',
            array( 'status' => 404 )
        );
    }

    if ( $post->post_status !== 'publish' && ! current_user_can( 'edit_post', $post->ID ) ) {
        return new WP_Error(
            'rest_forbidden',
            'You cannot view this item.',
            array( 'status' => 403 )
        );
    }

    return rest_ensure_response( prepare_item( $post ) );
}

7. Pagination Headers

php
function get_items( $request ) {
    $per_page = $request['per_page'] ?? 10;
    $page = $request['page'] ?? 1;

    $query = new WP_Query( array(
        'post_type' => 'item',
        'posts_per_page' => $per_page,
        'paged' => $page,
    ));

    $data = array();
    foreach ( $query->posts as $post ) {
        $data[] = prepare_item( $post );
    }

    $response = rest_ensure_response( $data );
    $response->header( 'X-WP-Total', $query->found_posts );
    $response->header( 'X-WP-TotalPages', $query->max_num_pages );

    return $response;
}

8. JavaScript API Fetch

js
// Using wp.apiFetch
import apiFetch from '@wordpress/api-fetch';

// GET
apiFetch( { path: '/my-plugin/v1/items' } ).then( console.log );

// POST
apiFetch( {
    path: '/my-plugin/v1/items',
    method: 'POST',
    data: { title: 'New Item' },
} );

// Vanilla fetch
fetch( '/wp-json/my-plugin/v1/items', {
    headers: {
        'Content-Type': 'application/json',
        'X-WP-Nonce': wpApiSettings.nonce,
    },
});

Authentication Methods

MethodUse Case
Cookie + NonceSame-origin requests
Application PasswordsThird-party apps
JWT (plugin)Mobile apps, SPAs
OAuth (plugin)Third-party services

Response Status Codes

  • 200 - OK
  • 201 - Created
  • 204 - No Content (delete)
  • 400 - Bad Request
  • 401 - Unauthorized
  • 403 - Forbidden
  • 404 - Not Found
  • 500 - Server Error

Checklist

  • Always include permission_callback
  • Sanitize and validate input
  • Return WP_Error for errors
  • Use rest_ensure_response()
  • Add pagination for lists
  • Version your API namespace
  • Document endpoints