toolchains/php/frameworks/wordpress/advanced-architecture/SKILL.md
Advanced WordPress development with REST API endpoints, WP-CLI commands, performance optimization, and caching strategies for scalable applications.
npx skillsauth add bobmatnyc/claude-mpm-skills wordpress-advanced-architectureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Master advanced WordPress development patterns including REST API endpoints, WP-CLI commands, performance optimization, and caching strategies for scalable WordPress applications.
The WordPress REST API provides a powerful interface for creating custom endpoints with proper authentication, validation, and response formatting.
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',
],
],
]);
}
// 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',
]);
}
For complex REST endpoints, use a controller class to organize logic:
<?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();
});
// 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 ) );
*/
WP-CLI enables automation of WordPress tasks through custom commands.
<?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' );
}
/**
* 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 ) );
}
# 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
// 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_%'" );
// 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' );
}
// 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 );
// BAD: Multiple queries in loop
$posts = get_posts([ 'post_type' => 'book', 'posts_per_page' => -1 ]);
foreach ( $posts as $post ) {
$author = get_user_by( 'id', $post->post_author ); // N+1 query problem
$meta = get_post_meta( $post->ID, '_isbn', true ); // Another query per iteration
}
// GOOD: Pre-fetch data
$posts = get_posts([ 'post_type' => 'book', 'posts_per_page' => -1 ]);
$author_ids = wp_list_pluck( $posts, 'post_author' );
$authors = get_users([ 'include' => $author_ids ]); // Single query
$authors_by_id = [];
foreach ( $authors as $author ) {
$authors_by_id[ $author->ID ] = $author;
}
// Pre-load all meta with update_meta_cache()
update_post_caches( $posts, 'book' );
foreach ( $posts as $post ) {
$author = $authors_by_id[ $post->post_author ];
$isbn = get_post_meta( $post->ID, '_isbn', true ); // Uses cache
}
// Custom queries with JOIN
global $wpdb;
$results = $wpdb->get_results("
SELECT p.*, pm.meta_value as isbn
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_isbn'
WHERE p.post_type = 'book'
AND p.post_status = 'publish'
ORDER BY p.post_date DESC
LIMIT 10
");
// Add database indexes for frequently queried meta
global $wpdb;
$wpdb->query("
CREATE INDEX meta_key_value ON {$wpdb->postmeta} (meta_key, meta_value(20))
");
// Paginate large queries
function get_books_paginated( $page = 1, $per_page = 20 ) {
$args = [
'post_type' => 'book',
'posts_per_page' => $per_page,
'paged' => $page,
];
return new WP_Query( $args );
}
// Infinite scroll with AJAX
add_action( 'wp_ajax_load_more_books', 'ajax_load_more_books' );
add_action( 'wp_ajax_nopriv_load_more_books', 'ajax_load_more_books' );
function ajax_load_more_books() {
check_ajax_referer( 'load_more_nonce', 'nonce' );
$page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
$query = get_books_paginated( $page, 10 );
if ( $query->have_posts() ) {
ob_start();
while ( $query->have_posts() ) {
$query->the_post();
get_template_part( 'template-parts/content', 'book' );
}
$html = ob_get_clean();
wp_send_json_success([
'html' => $html,
'has_more' => $query->max_num_pages > $page,
]);
} else {
wp_send_json_error( 'No more books' );
}
}
// Lazy load images (native HTML)
<img src="book-cover.jpg" loading="lazy" alt="Book Cover">
// Install Query Monitor plugin
// https://wordpress.org/plugins/query-monitor/
// Add custom timing measurements
do_action( 'qm/start', 'my_expensive_operation' );
// ... expensive code ...
do_action( 'qm/stop', 'my_expensive_operation' );
// Log custom data
do_action( 'qm/debug', 'Custom debug message' );
do_action( 'qm/info', $data_to_inspect );
// Benchmark queries
Query Monitor shows:
// - SQL queries (count, time, duplicates)
// - HTTP requests
// - Hooks and actions
// - PHP errors and notices
// - Template file hierarchy
// - Enqueued scripts/styles
// Cache HTML fragments
function render_book_grid() {
$cache_key = 'book_grid_html';
$html = get_transient( $cache_key );
if ( false === $html ) {
ob_start();
$books = new WP_Query([
'post_type' => 'book',
'posts_per_page' => 12,
]);
if ( $books->have_posts() ) {
echo '<div class="book-grid">';
while ( $books->have_posts() ) {
$books->the_post();
?>
<div class="book-item">
<h3><?php the_title(); ?></h3>
<?php the_post_thumbnail( 'medium' ); ?>
</div>
<?php
}
echo '</div>';
}
wp_reset_postdata();
$html = ob_get_clean();
set_transient( $cache_key, $html, HOUR_IN_SECONDS );
}
echo $html;
}
/**
* Page Caching:
* - Caches entire HTML pages
* - Fastest (serves static HTML)
* - Plugins: WP Super Cache, W3 Total Cache, WP Rocket
* - Bypassed for logged-in users
*
* Object Caching:
* - Caches database queries and computed values
* - Works for all users (logged-in and anonymous)
* - Requires persistent cache backend (Redis, Memcached)
* - Granular cache control
*/
// Example: Bypass page cache for dynamic content
header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
// Cache-Control headers for static assets
function add_cache_headers() {
if ( is_admin() || is_user_logged_in() ) {
return;
}
// Cache static pages for 1 hour
if ( is_page() || is_single() ) {
header( 'Cache-Control: public, max-age=3600' );
}
// Cache archives for 30 minutes
if ( is_archive() || is_home() ) {
header( 'Cache-Control: public, max-age=1800' );
}
}
add_action( 'send_headers', 'add_cache_headers' );
// Pattern 1: Time-based expiration
set_transient( 'data', $value, 12 * HOUR_IN_SECONDS );
// Pattern 2: Event-based invalidation
add_action( 'save_post_book', 'clear_book_caches' );
function clear_book_caches( $post_id ) {
// Clear specific caches
delete_transient( 'popular_books' );
delete_transient( 'recent_books' );
wp_cache_delete( "book_{$post_id}", 'books' );
// Clear author cache
$post = get_post( $post_id );
wp_cache_delete( "user_{$post->post_author}_books", 'user_books' );
}
// Pattern 3: Versioned cache keys
function get_cache_version() {
$version = wp_cache_get( 'cache_version', 'global' );
if ( false === $version ) {
$version = time();
wp_cache_set( 'cache_version', $version, 'global' );
}
return $version;
}
function get_cached_data( $key ) {
$version = get_cache_version();
$versioned_key = "{$key}_v{$version}";
return wp_cache_get( $versioned_key, 'my_group' );
}
function set_cached_data( $key, $data ) {
$version = get_cache_version();
$versioned_key = "{$key}_v{$version}";
wp_cache_set( $versioned_key, $data, 'my_group', HOUR_IN_SECONDS );
}
function invalidate_all_caches() {
// Increment version to invalidate all caches
$new_version = time();
wp_cache_set( 'cache_version', $new_version, 'global' );
}
// Pattern 4: Cache warming
add_action( 'save_post_book', 'warm_book_caches', 20 );
function warm_book_caches( $post_id ) {
// Rebuild cache immediately after invalidation
get_popular_books(); // Rebuilds transient
}
// Rewrite asset URLs to CDN
add_filter( 'wp_get_attachment_url', 'cdn_rewrite_url' );
add_filter( 'wp_calculate_image_srcset', 'cdn_rewrite_srcset' );
function cdn_rewrite_url( $url ) {
$cdn_domain = 'https://cdn.example.com';
$site_url = site_url();
// Only rewrite uploads directory
if ( strpos( $url, '/wp-content/uploads/' ) !== false ) {
return str_replace( $site_url, $cdn_domain, $url );
}
return $url;
}
function cdn_rewrite_srcset( $sources ) {
if ( ! is_array( $sources ) ) {
return $sources;
}
foreach ( $sources as &$source ) {
$source['url'] = cdn_rewrite_url( $source['url'] );
}
return $sources;
}
// Purge CDN cache on content update
add_action( 'save_post', 'purge_cdn_cache' );
function purge_cdn_cache( $post_id ) {
// Example: Cloudflare API
$zone_id = 'your_zone_id';
$api_key = 'your_api_key';
$email = '[email protected]';
$url = get_permalink( $post_id );
wp_remote_post( "https://api.cloudflare.com/client/v4/zones/{$zone_id}/purge_cache", [
'headers' => [
'X-Auth-Email' => $email,
'X-Auth-Key' => $api_key,
'Content-Type' => 'application/json',
],
'body' => json_encode([
'files' => [ $url ],
]),
]);
}
// Use 'fields' parameter to limit data
$query = new WP_Query([
'post_type' => 'book',
'fields' => 'ids', // Only return IDs (faster)
]);
// Count queries
$count = new WP_Query([
'post_type' => 'book',
'posts_per_page' => -1,
'fields' => 'ids',
'no_found_rows' => true, // Skip SQL_CALC_FOUND_ROWS
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
]);
// Complex meta queries with performance in mind
$query = new WP_Query([
'post_type' => 'book',
'meta_query' => [
'relation' => 'AND',
[
'key' => '_pages',
'value' => 200,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_rating',
'value' => 4,
'compare' => '>=',
'type' => 'DECIMAL',
],
],
'orderby' => 'meta_value_num',
'order' => 'DESC',
]);
// When WP_Query is too slow, use direct SQL
global $wpdb;
// Get books with JOIN on multiple meta keys
$results = $wpdb->get_results("
SELECT
p.ID,
p.post_title,
pm1.meta_value as isbn,
pm2.meta_value as pages,
pm3.meta_value as rating
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm1 ON p.ID = pm1.post_id AND pm1.meta_key = '_isbn'
LEFT JOIN {$wpdb->postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_pages'
LEFT JOIN {$wpdb->postmeta} pm3 ON p.ID = pm3.post_id AND pm3.meta_key = '_rating'
WHERE p.post_type = 'book'
AND p.post_status = 'publish'
AND CAST(pm2.meta_value AS UNSIGNED) > 200
ORDER BY CAST(pm3.meta_value AS DECIMAL(3,2)) DESC
LIMIT 20
");
// Use $wpdb->prepare() for dynamic values
$min_pages = 200;
$results = $wpdb->get_results( $wpdb->prepare("
SELECT p.ID, p.post_title, pm.meta_value as pages
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
WHERE p.post_type = 'book'
AND pm.meta_key = '_pages'
AND CAST(pm.meta_value AS UNSIGNED) > %d
ORDER BY p.post_date DESC
", $min_pages ) );
// Add custom indexes in plugin activation
register_activation_hook( __FILE__, 'create_custom_indexes' );
function create_custom_indexes() {
global $wpdb;
// Index on post_type and post_status (common filter)
$wpdb->query("
CREATE INDEX idx_post_type_status
ON {$wpdb->posts} (post_type, post_status)
");
// Index on meta_key and meta_value for frequent lookups
$wpdb->query("
CREATE INDEX idx_meta_key_value
ON {$wpdb->postmeta} (meta_key, meta_value(191))
");
// Composite index for date-based queries
$wpdb->query("
CREATE INDEX idx_post_type_date
ON {$wpdb->posts} (post_type, post_date)
");
}
// Remove indexes on deactivation
register_deactivation_hook( __FILE__, 'remove_custom_indexes' );
function remove_custom_indexes() {
global $wpdb;
$wpdb->query( "DROP INDEX idx_post_type_status ON {$wpdb->posts}" );
$wpdb->query( "DROP INDEX idx_meta_key_value ON {$wpdb->postmeta}" );
$wpdb->query( "DROP INDEX idx_post_type_date ON {$wpdb->posts}" );
}
// Detect multisite
if ( is_multisite() ) {
// Multisite-specific code
}
// Network-wide hooks
add_action( 'network_admin_menu', 'add_network_admin_page' );
function add_network_admin_page() {
add_menu_page(
'Network Settings',
'My Plugin',
'manage_network_options',
'my-network-settings',
'render_network_settings_page'
);
}
// Network options (not site-specific)
add_site_option( 'my_network_setting', 'value' );
$value = get_site_option( 'my_network_setting' );
update_site_option( 'my_network_setting', 'new_value' );
delete_site_option( 'my_network_setting' );
// Switch to another site
$current_blog_id = get_current_blog_id();
switch_to_blog( 2 ); // Switch to site ID 2
// Perform operations on site 2
$posts = get_posts([ 'post_type' => 'book' ]);
update_option( 'my_option', 'value' );
// Always restore
restore_current_blog();
// Iterate all sites
$sites = get_sites([ 'number' => 999 ]);
foreach ( $sites as $site ) {
switch_to_blog( $site->blog_id );
// Do something on each site
$count = wp_count_posts( 'book' );
error_log( "Site {$site->blog_id} has {$count->publish} books" );
restore_current_blog();
}
<?php
namespace MyPlugin\Services;
class BookService {
private $cache;
private $validator;
public function __construct( CacheService $cache, ValidationService $validator ) {
$this->cache = $cache;
$this->validator = $validator;
}
public function get_book( $book_id ) {
// Check cache first
$cache_key = "book_{$book_id}";
$book = $this->cache->get( $cache_key );
if ( false === $book ) {
$book = get_post( $book_id );
if ( $book && 'book' === $book->post_type ) {
$this->cache->set( $cache_key, $book, HOUR_IN_SECONDS );
}
}
return $book;
}
public function create_book( $data ) {
// Validate input
$errors = $this->validator->validate_book_data( $data );
if ( ! empty( $errors ) ) {
return new \WP_Error( 'validation_failed', 'Validation failed', $errors );
}
// Create book
$book_id = wp_insert_post([
'post_type' => 'book',
'post_title' => $data['title'],
'post_content' => $data['content'],
'post_status' => $data['status'],
]);
if ( is_wp_error( $book_id ) ) {
return $book_id;
}
// Clear related caches
$this->cache->invalidate_group( 'books' );
return $book_id;
}
}
// Dependency injection container
class Container {
private $services = [];
public function register( $name, $service ) {
$this->services[ $name ] = $service;
}
public function get( $name ) {
if ( ! isset( $this->services[ $name ] ) ) {
throw new \Exception( "Service not found: {$name}" );
}
return $this->services[ $name ];
}
}
// Bootstrap
$container = new Container();
$container->register( 'cache', new CacheService() );
$container->register( 'validator', new ValidationService() );
$container->register( 'books', new BookService(
$container->get( 'cache' ),
$container->get( 'validator' )
) );
// Fire custom events
do_action( 'myplugin_book_created', $book_id, $book_data );
do_action( 'myplugin_book_updated', $book_id, $old_data, $new_data );
do_action( 'myplugin_book_deleted', $book_id );
// Other plugins can listen
add_action( 'myplugin_book_created', function( $book_id, $book_data ) {
// Send email notification
// Update analytics
// Sync to external service
}, 10, 2 );
// Use filters for modifiable data
$book_data = apply_filters( 'myplugin_before_book_save', $book_data, $book_id );
$notification_recipients = apply_filters( 'myplugin_notification_recipients', [ '[email protected]' ], $book_id );
/**
* Performance Checklist:
*
* 1. Database:
* - Use indexes on frequently queried columns
* - Avoid SELECT * queries (use specific fields)
* - Batch operations instead of loops
* - Use LIMIT for large datasets
*
* 2. Caching:
* - Implement object caching (Redis/Memcached)
* - Use transients for expensive operations
* - Fragment caching for HTML blocks
* - CDN for static assets
*
* 3. Queries:
* - Use 'fields' => 'ids' when possible
* - Set 'no_found_rows' => true if not paginating
* - Disable update_post_meta_cache and update_post_term_cache when not needed
*
* 4. Assets:
* - Minify and concatenate CSS/JS
* - Lazy load images
* - Use responsive images (srcset)
* - Defer non-critical JavaScript
*
* 5. Monitoring:
* - Use Query Monitor for development
* - New Relic or Application Insights for production
* - Monitor slow queries
* - Track cache hit rates
*/
// Example: Batch processing with WP-CLI
function process_books_batch() {
$offset = 0;
$batch_size = 100;
do {
$books = get_posts([
'post_type' => 'book',
'posts_per_page' => $batch_size,
'offset' => $offset,
'fields' => 'ids',
]);
foreach ( $books as $book_id ) {
// Process each book
update_post_meta( $book_id, '_processed', time() );
}
$offset += $batch_size;
// Prevent memory leaks
wp_cache_flush();
} while ( count( $books ) === $batch_size );
}
When building advanced WordPress applications, consider these complementary skills (available in the skill library):
development
Optimize web performance using Core Web Vitals, modern patterns (View Transitions, Speculation Rules), and framework-specific techniques
development
Best practices for documenting APIs and code interfaces, eliminating redundant documentation guidance per agent.
development
Comprehensive API design patterns covering REST, GraphQL, gRPC, versioning, authentication, and modern API best practices
development
Visual verification workflow for UI changes to accelerate code review and catch ...