agentsclimarketplace

Wordpress patterns

Skill iwritec0de/wp-dev/skills/wordpress-patterns

WordPress development plugin for Claude Code

Install
npx -y skills add iwritec0de/wp-dev --skill wordpress-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

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.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

16.6 KB, as published. Nobody here has run it

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,   // Required for Gutenberg
        '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 {
    // 1. Verify nonce
    if ( ! isset( $_POST['myplugin_event_nonce'] ) ||
         ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['myplugin_event_nonce'] ) ), 'myplugin_save_event_details' ) ) {
        return;
    }

    // 2. Check autosave
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    // 3. Verify capability
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    // 4. Sanitize and save
    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

// Schedule on activation:
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 {
    // Cleanup logic here.
}

// Custom interval:
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;
}

// Invalidate when settings change:
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 // Load in footer
    );

    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 {
    // Only load on our admin pages:
    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)

// Register for both logged-in and logged-out users
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

// Using fetch (modern)
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

RequirementImplementation
Noncewp_create_nonce() + check_ajax_referer()
Capability checkcurrent_user_can() after nonce verification
Input sanitizationsanitize_text_field( wp_unslash( ... ) )
Response formatwp_send_json_success() / wp_send_json_error()
Public endpointRegister both wp_ajax_ and wp_ajax_nopriv_
Admin-only endpointRegister only wp_ajax_ (no nopriv)

For Gutenberg block development patterns, see references/gutenberg-patterns.md.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.