| name | wordpress-patterns |
| description | This skill should be used when the user asks to "register a custom post type", "create a taxonomy", "add a meta box", "build a REST API endpoint", "use the Settings API", "schedule a cron event", "use transients", or mentions "custom post type", "taxonomy", "meta box", "REST API", "Settings API", "wp_cron", "transients", "enqueue scripts", "admin notices". Provides common WordPress development patterns including Custom Post Types, taxonomies, meta boxes, REST API endpoints, Settings API, cron, transients, and enqueue patterns. |
| license | MIT |
| metadata | {"author":"Chris Kelley (hello@iwritecode.io)","version":"1.0.0"} |
WordPress Development Patterns
This skill covers common WordPress development patterns including Custom Post Types, taxonomies, meta boxes, REST API endpoints, Settings API, cron, transients, and enqueue patterns.
Custom Post Types
add_action( 'init', 'myplugin_register_post_types' );
function myplugin_register_post_types(): void {
$labels = array(
'name' => _x( 'Events', 'post type general name', 'myplugin' ),
'singular_name' => _x( 'Event', 'post type singular name', 'myplugin' ),
'menu_name' => _x( 'Events', 'admin menu', 'myplugin' ),
'add_new' => _x( 'Add New', 'event', 'myplugin' ),
'add_new_item' => __( 'Add New Event', 'myplugin' ),
'edit_item' => __( 'Edit Event', 'myplugin' ),
'new_item' => __( 'New Event', 'myplugin' ),
'view_item' => __( 'View Event', 'myplugin' ),
'search_items' => __( 'Search Events', 'myplugin' ),
'not_found' => __( 'No events found.', 'myplugin' ),
'not_found_in_trash' => __( 'No events found in Trash.', 'myplugin' ),
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_rest' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'events' ),
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 20,
'menu_icon' => 'dashicons-calendar-alt',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ),
);
register_post_type( 'myplugin_event', $args );
}
Custom Taxonomies
add_action( 'init', 'myplugin_register_taxonomies' );
function myplugin_register_taxonomies(): void {
$labels = array(
'name' => _x( 'Event Types', 'taxonomy general name', 'myplugin' ),
'singular_name' => _x( 'Event Type', 'taxonomy singular name', 'myplugin' ),
'search_items' => __( 'Search Event Types', 'myplugin' ),
'all_items' => __( 'All Event Types', 'myplugin' ),
'edit_item' => __( 'Edit Event Type', 'myplugin' ),
'update_item' => __( 'Update Event Type', 'myplugin' ),
'add_new_item' => __( 'Add New Event Type', 'myplugin' ),
'new_item_name' => __( 'New Event Type Name', 'myplugin' ),
'menu_name' => __( 'Event Types', 'myplugin' ),
);
register_taxonomy( 'myplugin_event_type', array( 'myplugin_event' ), array(
'hierarchical' => true,
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'show_in_rest' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'event-type' ),
) );
}
Meta Boxes
add_action( 'add_meta_boxes', 'myplugin_add_meta_boxes' );
add_action( 'save_post_myplugin_event', 'myplugin_save_meta_box' );
function myplugin_add_meta_boxes(): void {
add_meta_box(
'myplugin_event_details',
__( 'Event Details', 'myplugin' ),
'myplugin_render_meta_box',
'myplugin_event',
'normal',
'high'
);
}
function myplugin_render_meta_box( WP_Post $post ): void {
wp_nonce_field( 'myplugin_save_event_details', 'myplugin_event_nonce' );
$date = get_post_meta( $post->ID, '_myplugin_event_date', true );
$location = get_post_meta( $post->ID, '_myplugin_event_location', true );
?>
<p>
<label for="myplugin_event_date"><?php esc_html_e( 'Date:', 'myplugin' ); ?></label>
<input type="date" id="myplugin_event_date" name="myplugin_event_date"
value="<?php echo esc_attr( $date ); ?>">
</p>
<p>
<label for="myplugin_event_location"><?php esc_html_e( 'Location:', 'myplugin' ); ?></label>
<input type="text" id="myplugin_event_location" name="myplugin_event_location"
value="<?php echo esc_attr( $location ); ?>" class="widefat">
</p>
<?php
}
function myplugin_save_meta_box( int $post_id ): void {
if ( ! isset( $_POST['myplugin_event_nonce'] ) ||
! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['myplugin_event_nonce'] ) ), 'myplugin_save_event_details' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
if ( isset( $_POST['myplugin_event_date'] ) ) {
update_post_meta( $post_id, '_myplugin_event_date',
sanitize_text_field( wp_unslash( $_POST['myplugin_event_date'] ) ) );
}
if ( isset( $_POST['myplugin_event_location'] ) ) {
update_post_meta( $post_id, '_myplugin_event_location',
sanitize_text_field( wp_unslash( $_POST['myplugin_event_location'] ) ) );
}
}
Settings API
add_action( 'admin_menu', 'myplugin_add_settings_page' );
add_action( 'admin_init', 'myplugin_register_settings' );
function myplugin_add_settings_page(): void {
add_options_page(
__( 'My Plugin Settings', 'myplugin' ),
__( 'My Plugin', 'myplugin' ),
'manage_options',
'myplugin-settings',
'myplugin_render_settings_page'
);
}
function myplugin_register_settings(): void {
register_setting( 'myplugin_settings_group', 'myplugin_api_key', array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '',
) );
add_settings_section(
'myplugin_general_section',
__( 'General Settings', 'myplugin' ),
'myplugin_general_section_cb',
'myplugin-settings'
);
add_settings_field(
'myplugin_api_key',
__( 'API Key', 'myplugin' ),
'myplugin_api_key_field_cb',
'myplugin-settings',
'myplugin_general_section'
);
}
function myplugin_general_section_cb(): void {
echo '<p>' . esc_html__( 'Configure your plugin settings.', 'myplugin' ) . '</p>';
}
function myplugin_api_key_field_cb(): void {
$value = get_option( 'myplugin_api_key', '' );
echo '<input type="text" name="myplugin_api_key" value="' . esc_attr( $value ) . '" class="regular-text">';
}
function myplugin_render_settings_page(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<form action="options.php" method="post">
<?php
settings_fields( 'myplugin_settings_group' );
do_settings_sections( 'myplugin-settings' );
submit_button();
?>
</form>
</div>
<?php
}
REST API Custom Endpoints
add_action( 'rest_api_init', 'myplugin_register_rest_routes' );
function myplugin_register_rest_routes(): void {
register_rest_route( 'myplugin/v1', '/events', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'myplugin_rest_get_events',
'permission_callback' => '__return_true', // Public endpoint
'args' => array(
'per_page' => array(
'default' => 10,
'sanitize_callback' => 'absint',
'validate_callback' => function ( $value ): bool {
return $value > 0 && $value <= 100;
},
),
),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => 'myplugin_rest_create_event',
'permission_callback' => function (): bool {
return current_user_can( 'publish_posts' );
},
'args' => array(
'title' => array(
'required' => true,
'sanitize_callback' => 'sanitize_text_field',
),
),
),
) );
register_rest_route( 'myplugin/v1', '/events/(?P<id>\d+)', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'myplugin_rest_get_event',
'permission_callback' => '__return_true',
'args' => array(
'id' => array(
'sanitize_callback' => 'absint',
),
),
) );
}
function myplugin_rest_get_events( WP_REST_Request $request ): WP_REST_Response {
$per_page = $request->get_param( 'per_page' );
$query = new WP_Query( array(
'post_type' => 'myplugin_event',
'posts_per_page' => $per_page,
'post_status' => 'publish',
) );
$events = array_map( function ( WP_Post $post ): array {
return array(
'id' => $post->ID,
'title' => $post->post_title,
'date' => get_post_meta( $post->ID, '_myplugin_event_date', true ),
);
}, $query->posts );
return new WP_REST_Response( $events, 200 );
}
Cron / Scheduled Events
register_activation_hook( __FILE__, 'myplugin_activate_cron' );
register_deactivation_hook( __FILE__, 'myplugin_deactivate_cron' );
function myplugin_activate_cron(): void {
if ( ! wp_next_scheduled( 'myplugin_daily_cleanup' ) ) {
wp_schedule_event( time(), 'daily', 'myplugin_daily_cleanup' );
}
}
function myplugin_deactivate_cron(): void {
wp_clear_scheduled_hook( 'myplugin_daily_cleanup' );
}
add_action( 'myplugin_daily_cleanup', 'myplugin_run_cleanup' );
function myplugin_run_cleanup(): void {
}
add_filter( 'cron_schedules', 'myplugin_add_cron_interval' );
function myplugin_add_cron_interval( array $schedules ): array {
$schedules['myplugin_fifteen_minutes'] = array(
'interval' => 900,
'display' => esc_html__( 'Every 15 Minutes', 'myplugin' ),
);
return $schedules;
}
Transients (Caching)
function myplugin_get_api_data(): array {
$data = get_transient( 'myplugin_api_data' );
if ( false === $data ) {
$response = wp_remote_get( 'https://api.example.com/data' );
if ( is_wp_error( $response ) ) {
return array();
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
set_transient( 'myplugin_api_data', $data, HOUR_IN_SECONDS );
}
return $data;
}
add_action( 'update_option_myplugin_api_key', function (): void {
delete_transient( 'myplugin_api_data' );
} );
Enqueue Scripts & Styles
add_action( 'wp_enqueue_scripts', 'myplugin_enqueue_public' );
add_action( 'admin_enqueue_scripts', 'myplugin_enqueue_admin' );
function myplugin_enqueue_public(): void {
wp_enqueue_style(
'myplugin-public',
plugin_dir_url( __FILE__ ) . 'public/css/style.css',
array(),
MYPLUGIN_VERSION
);
wp_enqueue_script(
'myplugin-public',
plugin_dir_url( __FILE__ ) . 'public/js/script.js',
array( 'jquery' ),
MYPLUGIN_VERSION,
true
);
wp_localize_script( 'myplugin-public', 'myPluginData', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'myplugin_public_nonce' ),
) );
}
function myplugin_enqueue_admin( string $hook ): void {
if ( 'settings_page_myplugin-settings' !== $hook ) {
return;
}
wp_enqueue_style(
'myplugin-admin',
plugin_dir_url( __FILE__ ) . 'admin/css/admin.css',
array(),
MYPLUGIN_VERSION
);
}
Admin Notices
add_action( 'admin_notices', 'myplugin_admin_notice' );
function myplugin_admin_notice(): void {
if ( get_option( 'myplugin_api_key' ) ) {
return;
}
$settings_url = admin_url( 'options-general.php?page=myplugin-settings' );
?>
<div class="notice notice-warning is-dismissible">
<p>
<?php
printf(
/* translators: %s: settings page URL */
wp_kses(
__( 'My Plugin requires an API key. <a href="%s">Configure it here</a>.', 'myplugin' ),
array( 'a' => array( 'href' => array() ) )
),
esc_url( $settings_url )
);
?>
</p>
</div>
<?php
}
AJAX Handlers
Admin AJAX (Logged-in Users)
add_action( 'wp_ajax_myplugin_save_data', 'myplugin_ajax_save_data' );
function myplugin_ajax_save_data(): void {
check_ajax_referer( 'myplugin_nonce', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( __( 'Unauthorized.', 'myplugin' ), 403 );
}
$title = sanitize_text_field( wp_unslash( $_POST['title'] ?? '' ) );
if ( empty( $title ) ) {
wp_send_json_error( __( 'Title is required.', 'myplugin' ) );
}
$post_id = wp_insert_post( [
'post_title' => $title,
'post_type' => 'myplugin_event',
'post_status' => 'draft',
] );
if ( is_wp_error( $post_id ) ) {
wp_send_json_error( $post_id->get_error_message() );
}
wp_send_json_success( [ 'post_id' => $post_id ] );
}
Public AJAX (No Login Required)
add_action( 'wp_ajax_myplugin_search', 'myplugin_ajax_search' );
add_action( 'wp_ajax_nopriv_myplugin_search', 'myplugin_ajax_search' );
function myplugin_ajax_search(): void {
check_ajax_referer( 'myplugin_public_nonce', 'nonce' );
$query = sanitize_text_field( wp_unslash( $_GET['q'] ?? '' ) );
$results = new WP_Query( [
'post_type' => 'myplugin_event',
'posts_per_page' => 10,
's' => $query,
'post_status' => 'publish',
] );
$items = array_map( fn( WP_Post $post ): array => [
'id' => $post->ID,
'title' => $post->post_title,
'url' => get_permalink( $post ),
], $results->posts );
wp_send_json_success( $items );
}
Enqueuing with AJAX Support
add_action( 'wp_enqueue_scripts', 'myplugin_enqueue_ajax' );
function myplugin_enqueue_ajax(): void {
wp_enqueue_script(
'myplugin-ajax',
plugin_dir_url( __FILE__ ) . 'public/js/ajax.js',
[],
MYPLUGIN_VERSION,
true
);
wp_localize_script( 'myplugin-ajax', 'myPluginAjax', [
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'myplugin_public_nonce' ),
] );
}
JavaScript AJAX Call
async function saveData( title ) {
const formData = new FormData();
formData.append( 'action', 'myplugin_save_data' );
formData.append( 'nonce', myPluginAjax.nonce );
formData.append( 'title', title );
const response = await fetch( myPluginAjax.url, {
method: 'POST',
body: formData,
} );
const result = await response.json();
if ( result.success ) {
console.log( 'Saved:', result.data.post_id );
} else {
console.error( 'Error:', result.data );
}
}
AJAX Security Checklist
| Requirement | Implementation |
|---|
| Nonce | wp_create_nonce() + check_ajax_referer() |
| Capability check | current_user_can() after nonce verification |
| Input sanitization | sanitize_text_field( wp_unslash( ... ) ) |
| Response format | wp_send_json_success() / wp_send_json_error() |
| Public endpoint | Register both wp_ajax_ and wp_ajax_nopriv_ |
| Admin-only endpoint | Register only wp_ajax_ (no nopriv) |
For Gutenberg block development patterns, see references/gutenberg-patterns.md.