Skip to main content
skill CX Codex

wordpress-advanced-architecture — Claude Code skill: REST API

A skill for advanced WordPress work: custom REST routes, WP-CLI commands, transient and object caching.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add bobmatnyc/claude-mpm-skills --skill wordpress-advanced-architecture -a claude-code
Open on GitHub

What wordpress-advanced-architecture does

The skill activates while you write plugin or theme code and want the agent to follow the right WordPress patterns. It covers registering custom REST routes under a namespace like myplugin/v1, argument validation and sanitization callbacks, permission checks in permission_callback, and full CRUD built on WP_Query with pagination. Responses go through rest_ensure_response and get HATEOAS self, prev and next links attached.

The second half is automation and performance: WP-CLI commands plus caching with transients and the object cache. That matters once queries start slowing down page output. The skill ships as user-invocable: false and disable-model-invocation: true, so it is pulled in by task context rather than typed by hand.

One catch: this is WordPress on PHP, and the automation commands need WP-CLI installed. On a different stack there is nothing here for you.

What it helps with

  • register a custom REST route with permission checks
  • build a paginated CRUD endpoint with next-page links
  • write a WP-CLI command for a repetitive task
  • cache expensive queries with transients
  • add validation and sanitization to request arguments

Example prompts for the agent

  • register a REST route myplugin/v1/books with pagination and a permission_callback
  • build CRUD for the book post type over the REST API
  • cache the book list with transients

How to install wordpress-advanced-architecture

1Skills CLI (Vercel)

npx skills add bobmatnyc/claude-mpm-skills --skill wordpress-advanced-architecture -a claude-code

2Manually: put the files in place

~/.claude/skills/wordpress-advanced-architecture/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/wordpress-advanced-architecture/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about wordpress-advanced-architecture

How do I install wordpress-advanced-architecture in Claude?

Run npx skills add bobmatnyc/claude-mpm-skills --skill wordpress-advanced-architecture -a claude-code. The skill file then lands at ~/.claude/skills/wordpress-advanced-architecture/SKILL.md.

What does wordpress-advanced-architecture do?

It gives the agent advanced WordPress patterns: custom REST endpoints, WP-CLI commands, performance work and caching.

Which platforms does the skill support?

Claude, Codex, Cursor, GitHub Copilot, Gemini CLI and OpenCode, each with its own install command in the repository.

Source text

SKILL.md · MIT
---
name: wordpress-advanced-architecture
description: Advanced WordPress development with REST API endpoints, WP-CLI commands, performance optimization, and caching strategies for scalable applications.
user-invocable: false
disable-model-invocation: true
progressive_disclosure:
  entry_point:
    summary: "Advanced WordPress development with REST API, WP-CLI, performance optimization, and caching strategies"
    when_to_use:
      - "Building custom REST API endpoints"
      - "Automating WordPress tasks with WP-CLI"
      - "Optimizing performance with caching and transients"
    quick_start:
      - "Register custom REST endpoints with permissions"
      - "Create WP-CLI commands for automation"
      - "Implement caching with transients and object cache"
---

# Advanced WordPress Architecture

Master advanced WordPress development patterns including REST API endpoints, WP-CLI commands, performance optimization, and caching strategies for scalable WordPress applications.

## 1. REST API Development

The WordPress REST API provides a powerful interface for creating custom endpoints with proper authentication, validation, and response formatting.

### Endpoint Registration with Namespacing

```php
add_action( 'rest_api_init', 'register_custom_rest_routes' );
function register_custom_rest_routes() {
    // Namespace: myplugin/v1 (enables versioning)
    $namespace = 'myplugin/v1';

    // GET /wp-json/myplugin/v1/books
    register_rest_route( $namespace, '/books', [
        'methods'  => 'GET',
        'callback' => 'get_books_callback',
        'permission_callback' => '__return_true', // Public endpoint
        'args' => [
            'per_page' => [
                'default' => 10,
                'validate_callback' => function( $param ) {
                    return is_numeric( $param ) && $param > 0 && $param <= 100;
                },
                'sanitize_callback' => 'absint',
            ],
            'page' => [
                'default' => 1,
                'validate_callback' => function( $param ) {
                    return is_numeric( $param ) && $param > 0;
                },
                'sanitize_callback' => 'absint',
            ],
        ],
    ]);

    // GET /wp-json/myplugin/v1/books/(?P<id>\d+)
    register_rest_route( $namespace, '/books/(?P<id>\d+)', [
        'methods'  => 'GET',
        'callback' => 'get_book_callback',
        'permission_callback' => '__return_true',
        'args' => [
            'id' => [
                'validate_callback' => function( $param ) {
                    return is_numeric( $param );
                },
                'sanitize_callback' => 'absint',
            ],
        ],
    ]);

    // POST /wp-json/myplugin/v1/books (authenticated)
    register_rest_route( $namespace, '/books', [
        'methods'  => 'POST',
        'callback' => 'create_book_callback',
        'permission_callback' => function() {
            return current_user_can( 'edit_posts' );
        },
        'args' => [
            'title' => [
                'required' => true,
                'type' => 'string',
                'validate_callback' => function( $param ) {
                    return is_string( $param ) && strlen( $param ) > 0;
                },
                'sanitize_callback' => 'sanitize_text_field',
            ],
            'content' => [
                'required' => false,
                'type' => 'string',
                'sanitize_callback' => 'wp_kses_post',
            ],
            'status' => [
                'default' => 'draft',
                'enum' => [ 'draft', 'publish', 'private' ],
            ],
        ],
    ]);

    // PUT /wp-json/myplugin/v1/books/(?P<id>\d+)
    register_rest_route( $namespace, '/books/(?P<id>\d+)', [
        'methods'  => 'PUT',
        'callback' => 'update_book_callback',
        'permission_callback' => function( $request ) {
            $book_id = $request->get_param( 'id' );
            return current_user_can( 'edit_post', $book_id );
        },
        'args' => [
            'id' => [
                'validate_callback' => function( $param ) {
                    return is_numeric( $param );
                },
                'sanitize_callback' => 'absint',
            ],
            'title' => [
                'type' => 'string',
                'sanitize_callback' => 'sanitize_text_field',
            ],
            'content' => [
                'type' => 'string',
                'sanitize_callback' => 'wp_kses_post',
            ],
        ],
    ]);

    // DELETE /wp-json/myplugin/v1/books/(?P<id>\d+)
    register_rest_route( $namespace, '/books/(?P<id>\d+)', [
        'methods'  => 'DELETE',
        'callback' => 'delete_book_callback',
        'permission_callback' => function( $request ) {
            $book_id = $request->get_param( 'id' );
            return current_user_can( 'delete_post', $book_id );
        },
        'args' => [
            'id' => [
                'validate_callback' => function( $param ) {
                    return is_numeric( $param );
                },
                'sanitize_callback' => 'absint',
            ],
        ],
    ]);
}
```

### Complete CRUD Implementation

```php
// GET /wp-json/myplugin/v1/books
function get_books_callback( $request ) {
    $per_page = $request->get_param( 'per_page' );
    $page = $request->get_param( 'page' );
    $offset = ( $page - 1 ) * $per_page;

    $args = [
        'post_type' => 'book',
        'posts_per_page' => $per_page,
        'offset' => $offset,
        'post_status' => 'publish',
    ];

    $query = new WP_Query( $args );

    if ( ! $query->have_posts() ) {
        return rest_ensure_response([
            'books' => [],
            'total' => 0,
            'page' => $page,
            'per_page' => $per_page,
        ]);
    }

    $books = [];
    while ( $query->have_posts() ) {
        $query->the_post();
        $books[] = [
            'id' => get_the_ID(),
            'title' => get_the_title(),
            'content' => get_the_content(),
            'author' => get_the_author(),
            'date' => get_the_date( 'c' ), // ISO 8601 format
            'link' => get_permalink(),
        ];
    }
    wp_reset_postdata();

    $response = rest_ensure_response([
        'books' => $books,
        'total' => $query->found_posts,
        'page' => $page,
        'per_page' => $per_page,
        'total_pages' => ceil( $query->found_posts / $per_page ),
    ]);

    // Add HATEOAS links
    $response->add_link( 'self', rest_url( "myplugin/v1/books?page={$page}&per_page={$per_page}" ) );

    if ( $page > 1 ) {
        $prev_page = $page - 1;
        $response->add_link( 'prev', rest_url( "myplugin/v1/books?page={$prev_page}&per_page={$per_page}" ) );
    }

    if ( $page < ceil( $query->found_posts / $per_page ) ) {
        $next_page = $page + 1;
        $response->add_link( 'next', rest_url( "myplugin/v1/books?page={$next_page}&per_page={$per_page}" ) );
    }

    return $response;
}

// GET /wp-json/myplugin/v1/books/123
function get_book_callback( $request ) {
    $book_id = $request->get_param( 'id' );
    $book = get_post( $book_id );

    if ( ! $book || 'book' !== $book->post_type ) {
        return new WP_Error(
            'book_not_found',
            'Book not found',
            [ 'status' => 404 ]
        );
    }

    $data = [
        'id' => $book->ID,
        'title' => $book->post_title,
        'content' => apply_filters( 'the_content', $book->post_content ),
        'excerpt' => $book->post_excerpt,
        'author' => get_the_author_meta( 'display_name', $book->post_author ),
        'date' => get_the_date( 'c', $book ),
        'modified' => get_the_modified_date( 'c', $book ),
        'status' => $book->post_status,
        'link' => get_permalink( $book ),
        'featured_image' => get_the_post_thumbnail_url( $book, 'large' ),
        'meta' => [
            'isbn' => get_post_meta( $book->ID, '_isbn', true ),
            'pages' => (int) get_post_meta( $book->ID, '_pages', true ),
        ],
    ];

    return rest_ensure_response( $data );
}

// POST /wp-json/myplugin/v1/books
function create_book_callback( $request ) {
    $title = $request->get_param( 'title' );
    $content = $request->get_param( 'content' );
    $status = $request->get_param( 'status' );

    $post_data = [
        'post_type' => 'book',
        'post_title' => $title,
        'post_content' => $content,
        'post_status' => $status,
        'post_author' => get_current_user_id(),
    ];

    $book_id = wp_insert_post( $post_data, true );

    if ( is_wp_error( $book_id ) ) {
        return new WP_Error(
            'book_creation_failed',
            $book_id->get_error_message(),
            [ 'status' => 500 ]
        );
    }

    // Add custom metadata if provided
    if ( $request->has_param( 'isbn' ) ) {
        update_post_meta( $book_id, '_isbn', sanitize_text_field( $request->get_param( 'isbn' ) ) );
    }

    if ( $request->has_param( 'pages' ) ) {
        update_post_meta( $book_id, '_pages', absint( $request->get_param( 'pages' ) ) );
    }

    $response = rest_ensure_response([
        'id' => $book_id,
        'title' => $title,
        'message' => 'Book created successfully',
        'link' => get_permalink( $book_id ),
    ]);

    $response->set_status( 201 ); // Created
    $response->header( 'Location', rest_url( "myplugin/v1/books/{$book_id}" ) );

    return $response;
}

// PUT /wp-json/myplugin/v1/books/123
function update_book_callback( $request ) {
    $book_id = $request->get_param( 'id' );
    $book = get_post( $book_id );

    if ( ! $book || 'book' !== $book->post_type ) {
        return new WP_Error(
            'book_not_found',
            'Book not found',
            [ 'status' => 404 ]
        );
    }

    $post_data = [ 'ID' => $book_id ];

    if ( $request->has_param( 'title' ) ) {
        $post_data['post_title'] = $request->get_param( 'title' );
    }

    if ( $request->has_param( 'content' ) ) {
        $post_data['post_content'] = $request->get_param( 'content' );
    }

    if ( $request->has_param( 'status' ) ) {
        $post_data['post_status'] = $request->get_param( 'status' );
    }

    $result = wp_update_post( $post_data, true );

    if ( is_wp_error( $result ) ) {
        return new WP_Error(
            'book_update_failed',
            $result->get_error_message(),
            [ 'status' => 500 ]
        );
    }

    return rest_ensure_response([
        'id' => $book_id,
        'message' => 'Book updated successfully',
        'link' => get_permalink( $book_id ),
    ]);
}

// DELETE /wp-json/myplugin/v1/books/123
function delete_book_callback( $request ) {
    $book_id = $request->get_param( 'id' );
    $book = get_post( $book_id );

    if ( ! $book || 'book' !== $book->post_type ) {
        return new WP_Error(
            'book_not_found',
            'Book not found',
            [ 'status' => 404 ]
        );
    }

    // Soft delete (trash) or hard delete
    $force = $request->get_param( 'force' );
    $result = wp_delete_post( $book_id, $force );

    if ( ! $result ) {
        return new WP_Error(
            'book_deletion_failed',
            'Failed to delete book',
            [ 'status' => 500 ]
        );
    }

    return rest_ensure_response([
        'deleted' => true,
        'id' => $book_id,
        'message' => $force ? 'Book permanently deleted' : 'Book moved to trash',
    ]);
}
```

### Controller Pattern for Complex Endpoints

For complex REST endpoints, use a controller class to organize logic:

```php
<?php
namespace MyPlugin\API;

class Books_Controller extends \WP_REST_Controller {

    protected $namespace = 'myplugin/v1';
    protected $rest_base = 'books';

    public function register_routes() {
        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 book',
                    'type' => 'integer',
                ],
            ],
            [
                'methods'  => \WP_REST_Server::READABLE,
                'callback' => [ $this, 'get_item' ],
                'permission_callback' => [ $this, 'get_item_permissions_check' ],
                'args' => [
                    'context' => $this->get_context_param( [ 'default' => 'view' ] ),
                ],
            ],
            [
                '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' ],
                'args' => [
                    'force' => [
                        'type' => 'boolean',
                        'default' => false,
                        'description' => 'Whether to bypass trash and force deletion',
                    ],
                ],
            ],
            'schema' => [ $this, 'get_public_item_schema' ],
        ]);
    }

    public function get_items( $request ) {
        // Implementation similar to get_books_callback above
    }

    public function get_item( $request ) {
        // Implementation similar to get_book_callback above
    }

    public function create_item( $request ) {
        // Implementation similar to create_book_callback above
    }

    public function update_item( $request ) {
        // Implementation similar to update_book_callback above
    }

    public function delete_item( $request ) {
        // Implementation similar to delete_book_callback above
    }

    public function get_items_permissions_check( $request ) {
        return true; // Public endpoint
    }

    public function get_item_permissions_check( $request ) {
        return true; // Public endpoint
    }

    public function create_item_permissions_check( $request ) {
        return current_user_can( 'edit_posts' );
    }

    public function update_item_permissions_check( $request ) {
        $book_id = $request->get_param( 'id' );
        return current_user_can( 'edit_post', $book_id );
    }

    public function delete_item_permissions_check( $request ) {
        $book_id = $request->get_param( 'id' );
        return current_user_can( 'delete_post', $book_id );
    }

    public function get_public_item_schema() {
        if ( $this->schema ) {
            return $this->add_additional_fields_schema( $this->schema );
        }

        $schema = [
            '$schema'    => 'http://json-schema.org/draft-04/schema#',
            'title'      => 'book',
            'type'       => 'object',
            'properties' => [
                'id' => [
                    'description' => 'Unique identifier for the book',
                    'type'        => 'integer',
                    'context'     => [ 'view', 'edit', 'embed' ],
                    'readonly'    => true,
                ],
                'title' => [
                    'description' => 'The book title',
                    'type'        => 'string',
                    'context'     => [ 'view', 'edit', 'embed' ],
                    'required'    => true,
                ],
                'content' => [
                    'description' => 'The book content',
                    'type'        => 'string',
                    'context'     => [ 'view', 'edit' ],
                ],
                'status' => [
                    'description' => 'The book status',
                    'type'        => 'string',
                    'enum'        => [ 'draft', 'publish', 'private' ],
                    'context'     => [ 'view', 'edit' ],
                ],
            ],
        ];

        $this->schema = $schema;

        return $this->add_additional_fields_schema( $this->schema );
    }
}

// Register controller
add_action( 'rest_api_init', function() {
    $controller = new \MyPlugin\API\Books_Controller();
    $controller->register_routes();
});
```

### REST API Authentication

```php
// Application Passwords (WordPress 5.6+)
// Users generate passwords at /wp-admin/profile.php

// Example cURL request with authentication:
// curl -X POST https://example.com/wp-json/myplugin/v1/books \
//   -u username:application_password \
//   -H "Content-Type: application/json" \
//   -d '{"title":"New Book","content":"Book content"}'

// Cookie authentication for logged-in users (requires nonce)
add_action( 'rest_api_init', function() {
    // Add nonce to wp_localize_script
    wp_localize_script( 'my-ajax-script', 'wpApiSettings', [
        'root' => esc_url_raw( rest_url() ),
        'nonce' => wp_create_nonce( 'wp_rest' ),
    ]);
});

// JavaScript REST API call with nonce:
/*
fetch( wpApiSettings.root + 'myplugin/v1/books', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-WP-Nonce': wpApiSettings.nonce
    },
    body: JSON.stringify({
        title: 'New Book',
        content: 'Book content'
    })
}).then( response => response.json() )
  .then( data => console.log( data ) );
*/
```

## 2. WP-CLI Commands

WP-CLI enables automation of WordPress tasks through custom commands.

### Custom Command Registration

```php
<?php
namespace MyPlugin\CLI;

class Books_Command {

    /**
     * List all books.
     *
     * ## OPTIONS
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * [--status=<status>]
     * : Filter by post status.
     *
     * ## EXAMPLES
     *
     *     wp books list
     *     wp books list --format=json
     *     wp books list --status=publish
     *
     * @when after_wp_load
     */
    public function list( $args, $assoc_args ) {
        $defaults = [
            'format' => 'table',
            'status' => 'any',
        ];
        $assoc_args = wp_parse_args( $assoc_args, $defaults );

        $query_args = [
            'post_type' => 'book',
            'posts_per_page' => -1,
            'post_status' => $assoc_args['status'],
        ];

        $books = get_posts( $query_args );

        if ( empty( $books ) ) {
            \WP_CLI::warning( 'No books found.' );
            return;
        }

        $items = [];
        foreach ( $books as $book ) {
            $items[] = [
                'ID' => $book->ID,
                'Title' => $book->post_title,
                'Status' => $book->post_status,
                'Author' => get_the_author_meta( 'display_name', $book->post_author ),
                'Date' => get_the_date( 'Y-m-d H:i:s', $book ),
            ];
        }

        \WP_CLI\Utils\format_items( $assoc_args['format'], $items, [ 'ID', 'Title', 'Status', 'Author', 'Date' ] );

        \WP_CLI::success( sprintf( 'Found %d books.', count( $items ) ) );
    }

    /**
     * Create a new book.
     *
     * ## OPTIONS
     *
     * <title>
     * : The book title.
     *
     * [--content=<content>]
     * : The book content.
     *
     * [--status=<status>]
     * : The book status.
     * ---
     * default: draft
     * options:
     *   - draft
     *   - publish
     *   - private
     * ---
     *
     * [--isbn=<isbn>]
     * : The book ISBN.
     *
     * [--pages=<pages>]
     * : Number of pages.
     *
     * ## EXAMPLES
     *
     *     wp books create "My Book Title"
     *     wp books create "My Book" --status=publish --isbn=978-3-16-148410-0 --pages=350
     *
     * @when after_wp_load
     */
    public function create( $args, $assoc_args ) {
        $title = $args[0];

        $defaults = [
            'content' => '',
            'status' => 'draft',
        ];
        $assoc_args = wp_parse_args( $assoc_args, $defaults );

        $post_data = [
            'post_type' => 'book',
            'post_title' => $title,
            'post_content' => $assoc_args['content'],
            'post_status' => $assoc_args['status'],
            'post_author' => get_current_user_id(),
        ];

        $book_id = wp_insert_post( $post_data, true );

        if ( is_wp_error( $book_id ) ) {
            \WP_CLI::error( 'Failed to create book: ' . $book_id->get_error_message() );
        }

        // Add custom metadata
        if ( isset( $assoc_args['isbn'] ) ) {
            update_post_meta( $book_id, '_isbn', sanitize_text_field( $assoc_args['isbn'] ) );
        }

        if ( isset( $assoc_args['pages'] ) ) {
            update_post_meta( $book_id, '_pages', absint( $assoc_args['pages'] ) );
        }

        \WP_CLI::success( sprintf( 'Created book #%d: %s', $book_id, $title ) );
    }

    /**
     * Import books from CSV file.
     *
     * ## OPTIONS
     *
     * <file>
     * : Path to CSV file.
     *
     * [--dry-run]
     * : Preview import without creating books.
     *
     * ## EXAMPLES
     *
     *     wp books import books.csv
     *     wp books import books.csv --dry-run
     *
     * @when after_wp_load
     */
    public function import( $args, $assoc_args ) {
        $file = $args[0];
        $dry_run = isset( $assoc_args['dry-run'] );

        if ( ! file_exists( $file ) ) {
            \WP_CLI::error( 'File not found: ' . $file );
        }

        $csv = array_map( 'str_getcsv', file( $file ) );
        $header = array_shift( $csv );

        $total = count( $csv );
        $created = 0;

        \WP_CLI::log( sprintf( 'Processing %d books...', $total ) );

        $progress = \WP_CLI\Utils\make_progress_bar( 'Importing books', $total );

        foreach ( $csv as $row ) {
            $book = array_combine( $header, $row );

            if ( $dry_run ) {
                \WP_CLI::log( sprintf( 'Would create: %s', $book['title'] ) );
            } else {
                $post_data = [
                    'post_type' => 'book',
                    'post_title' => $book['title'],
                    'post_content' => $book['content'] ?? '',
                    'post_status' => $book['status'] ?? 'draft',
                ];

                $book_id = wp_insert_post( $post_data, true );

                if ( ! is_wp_error( $book_id ) ) {
                    if ( isset( $book['isbn'] ) ) {
                        update_post_meta( $book_id, '_isbn', $book['isbn'] );
                    }
                    $created++;
                }
            }

            $progress->tick();
        }

        $progress->finish();

        if ( $dry_run ) {
            \WP_CLI::success( sprintf( 'Dry run complete. Would create %d books.', $total ) );
        } else {
            \WP_CLI::success( sprintf( 'Imported %d of %d books.', $created, $total ) );
        }
    }

    /**
     * Generate sample books.
     *
     * ## OPTIONS
     *
     * [--count=<count>]
     * : Number of books to generate.
     * ---
     * default: 10
     * ---
     *
     * [--status=<status>]
     * : Book status.
     * ---
     * default: publish
     * ---
     *
     * ## EXAMPLES
     *
     *     wp books generate --count=50
     *     wp books generate --count=100 --status=draft
     *
     * @when after_wp_load
     */
    public function generate( $args, $assoc_args ) {
        $count = isset( $assoc_args['count'] ) ? absint( $assoc_args['count'] ) : 10;
        $status = isset( $assoc_args['status'] ) ? $assoc_args['status'] : 'publish';

        $progress = \WP_CLI\Utils\make_progress_bar( 'Generating books', $count );

        for ( $i = 1; $i <= $count; $i++ ) {
            $post_data = [
                'post_type' => 'book',
                'post_title' => sprintf( 'Sample Book %d', $i ),
                'post_content' => sprintf( 'This is sample book number %d.', $i ),
                'post_status' => $status,
            ];

            $book_id = wp_insert_post( $post_data );

            // Add random metadata
            update_post_meta( $book_id, '_isbn', sprintf( '978-3-16-%06d-0', rand( 100000, 999999 ) ) );
            update_post_meta( $book_id, '_pages', rand( 100, 500 ) );

            $progress->tick();
        }

        $progress->finish();

        \WP_CLI::success( sprintf( 'Generated %d books.', $count ) );
    }
}

// Register WP-CLI command
if ( defined( 'WP_CLI' ) && WP_CLI ) {
    \WP_CLI::add_command( 'books', 'MyPlugin\CLI\Books_Command' );
}
```

### Interactive Prompts and Confirmation

```php
/**
 * Delete all books (with confirmation).
 *
 * ## OPTIONS
 *
 * [--yes]
 * : Skip confirmation prompt.
 *
 * ## EXAMPLES
 *
 *     wp books delete-all
 *     wp books delete-all --yes
 *
 * @when after_wp_load
 */
public function delete_all( $args, $assoc_args ) {
    $books = get_posts([
        'post_type' => 'book',
        'posts_per_page' => -1,
        'fields' => 'ids',
    ]);

    $count = count( $books );

    if ( 0 === $count ) {
        \WP_CLI::warning( 'No books to delete.' );
        return;
    }

    // Prompt for confirmation unless --yes flag is provided
    \WP_CLI::confirm( sprintf( 'Are you sure you want to delete %d books?', $count ), $assoc_args );

    $progress = \WP_CLI\Utils\make_progress_bar( 'Deleting books', $count );

    foreach ( $books as $book_id ) {
        wp_delete_post( $book_id, true ); // Force delete
        $progress->tick();
    }

    $progress->finish();

    \WP_CLI::success( sprintf( 'Deleted %d books.', $count ) );
}
```

### Testing WP-CLI Commands

```bash
# List all WP-CLI commands
wp cli command-list

# Get help for custom command
wp help books
wp help books create

# Test commands
wp books list
wp books list --format=json
wp books create "Test Book" --status=publish
wp books generate --count=50
wp books import books.csv --dry-run
wp books delete-all --yes
```

## 3. Performance Optimization

### Transients API (Expiring Cache)

```php
// Store data with expiration
function get_popular_books() {
    $transient_key = 'popular_books';

    // Try to get cached value
    $popular_books = get_transient( $transient_key );

    if ( false === $popular_books ) {
        // Cache miss - fetch and store
        $popular_books = new WP_Query([
            'post_type' => 'book',
            'posts_per_page' => 10,
            'meta_key' => '_view_count',
            'orderby' => 'meta_value_num',
            'order' => 'DESC',
        ]);

        // Cache for 1 hour (3600 seconds)
        set_transient( $transient_key, $popular_books, HOUR_IN_SECONDS );
    }

    return $popular_books;
}

// Invalidate cache on post update
add_action( 'save_post_book', 'invalidate_books_cache' );
function invalidate_books_cache( $post_id ) {
    delete_transient( 'popular_books' );
}

// Site-specific transients (multisite)
set_site_transient( 'network_data', $data, DAY_IN_SECONDS );
$data = get_site_transient( 'network_data' );
delete_site_transient( 'network_data' );

// Clear all transients (cleanup)
global $wpdb;
$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '%_transient_%'" );
```

### Object Caching (Redis, Memcached)

```php
// WordPress Object Cache functions (wp_cache_*)
// Works with persistent cache (Redis/Memcached) if configured

// Add to cache
wp_cache_add( 'my_key', $data, 'my_group', 3600 );

// Get from cache
$data = wp_cache_get( 'my_key', 'my_group' );
if ( false === $data ) {
    // Cache miss - fetch and cache
    $data = expensive_database_query();
    wp_cache_set( 'my_key', $data, 'my_group', 3600 );
}

// Delete from cache
wp_cache_delete( 'my_key', 'my_group' );

// Flush entire cache
wp_cache_flush();

// Example: Cache user data
function get_user_books( $user_id ) {
    $cache_key = "user_{$user_id}_books";
    $cache_group = 'user_books';

    $books = wp_cache_get( $cache_key, $cache_group );

    if ( false === $books ) {
        $books = get_posts([
            'post_type' => 'book',
            'author' => $user_id,
            'posts_per_page' => -1,
        ]);

        wp_cache_set( $cache_key, $books, $cache_group, HOUR_IN_SECONDS );
    }

    return $books;
}

// Invalidate user cache on book update
add_action( 'save_post_book', 'invalidate_user_books_cache', 10, 2 );
function invalidate_user_books_cache( $post_id, $post ) {
    wp_cache_delete( "user_{$post->post_author}_books", 'user_books' );
}
```

### Redis Configuration (object-cache.php)

```php
// Install Redis plugin or drop-in
// Recommended: https://wordpress.org/plugins/redis-cache/

// Or manual configuration:
// wp-content/object-cache.php

<?php
// Redis configuration
global $redis_server;
$redis_server = [
    'host'     => '127.0.0.1',
    'port'     => 6379,
    'auth'     => '', // Password if required
    'database' => 0,  // Redis database number
];

// wp-config.php settings
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_DATABASE', 0 );
define( 'WP_REDIS_TIMEOUT', 1 );
define( 'WP_REDIS_READ_TIMEOUT', 1 );
```...

© bobmatnyc · MIT · bobmatnyc/claude-mpm-skills

Similar skills

Everything for Backend and APIs →

From the same set65

typescript-core A skill with modern TypeScript patterns: a strict tsconfig, runtime validation, advanced types. Useful once a TS codebase outgrows its type setup. CX Codex +1 475installs celery A Celery reference skill for background jobs, scheduled tasks and worker chains in Python. Useful when long operations should leave the request cycle. CX Codex +1 454installs golang-testing-strategies A skill for writing Go tests: table-driven cases, testify assertions, gomock mocks, benchmarks and CI coverage. Useful when a project's test suite needs structure. CX Codex +1 443installs sveltekit A skill that tells the agent how SvelteKit works: file-based routing, load functions, form actions and deployment adapters. CX Codex +1 441installs digitalocean-management A skill for Claude, Codex and Cursor that walks through monitoring, uptime checks and Projects on DigitalOcean. Useful when you need alerts and operational visibility for droplets and clusters. CX Codex +1 438installs flask A skill that hands your agent ready Flask patterns: app factory, blueprints, request and response handling. Useful when you're building a REST API or microservice in Python. CX Codex +1 437installs hono-rpc A skill that walks through Hono RPC: export AppType on the server, call hc<AppType> on the client, skip codegen entirely. CX Codex +1 437installs hono-middleware A skill covering Hono middleware: writing your own, composing them, the built-in modules and the order they run in. Useful when a Hono app needs auth, CORS or logging. CX Codex +1 431installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor