Advanced WordPress Architecture
Master advanced WordPress development patterns including REST API endpoints, WP-CLI commands, performance optimization, and caching strategies for scalable WordPress applications.
- 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
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
// 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 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
// 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 Commands
WP-CLI enables automation of WordPress tasks through custom commands.
Custom Command Registration
<?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
/**
-
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
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
- Performance Optimization
Transients API (Expiring Cache)
// 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)
// 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)
// 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 );
Database Query Optimization
// 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)) ");
Lazy Loading and Pagination
// 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">
Profiling with Query Monitor
// 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
- Caching Strategies
Fragment Caching
// 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 vs Object Caching
/**
- 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' );
Cache Invalidation Patterns
// 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 }
CDN Integration
// 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 = 'your@email.com';
$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 ],
]),
]);
}
- Advanced Database Patterns
WP_Query Optimization
// 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', ]);
Direct SQL for Complex Queries
// 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 ) );
Database Indexing
// 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}" );
}
- Multisite Development
Network-Activated Plugins
// 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' );
Cross-Site Operations
// 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();
}
- Best Practices
Service-Oriented Architecture
<?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' ) ) );
Event-Driven Design
// 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', [ 'admin@example.com' ], $book_id );
Scalability Considerations
/**
- Performance Checklist:
-
- Database:
-
- Use indexes on frequently queried columns
-
- Avoid SELECT * queries (use specific fields)
-
- Batch operations instead of loops
-
- Use LIMIT for large datasets
-
- Caching:
-
- Implement object caching (Redis/Memcached)
-
- Use transients for expensive operations
-
- Fragment caching for HTML blocks
-
- CDN for static assets
-
- 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
-
- Assets:
-
- Minify and concatenate CSS/JS
-
- Lazy load images
-
- Use responsive images (srcset)
-
- Defer non-critical JavaScript
-
- 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 );
}
Related Skills
When building advanced WordPress applications, consider these complementary skills (available in the skill library):
-
WordPress Plugin Fundamentals: Core plugin architecture and hooks - essential foundation for custom REST endpoints and WP-CLI commands
-
WordPress Security & Data Validation: Security best practices - critical for securing REST API endpoints and validating user input
-
WordPress Testing & QA: Testing REST endpoints and WP-CLI - comprehensive testing strategies for advanced WordPress features
-
GraphQL: Alternative to REST API - consider GraphQL as a modern alternative to WordPress REST API for complex data queries
-
Docker: Development environment setup - containerize WordPress development for consistent and reproducible environments
References
-
REST API Handbook - Official REST API documentation
-
WP-CLI - Command-line interface for WordPress
-
Object Cache - WordPress object caching
-
Transients API - WordPress transients documentation
-
Query Monitor - Performance profiling plugin
-
Redis Object Cache - Redis integration plugin