Skip to main content Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill wordpress-plugin-fundamentalsThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository LinkedIn automation via the Linked API CLI - fetch profiles, search people and companies, send messages, manage connections, create posts, react, comment, and run Sales Navigator and custom workflows. Use when the user wants to interact with LinkedIn.
Xquik X data automation API - Use REST or MCP for tweet search, user lookup, follower exports, media downloads, monitors, webhooks, giveaway draws, and confirmation-gated X actions.
MCP (Model Context Protocol) - Build AI-native servers with tools, resources, and prompts. TypeScript/Python SDKs for Claude Desktop integration.
Related occupations SOC
Based on SOC occupation classification
name wordpress-plugin-fundamentals description Modern WordPress plugin development with PHP 8.3+, OOP architecture, hooks system, database interactions, and Settings API user-invocable false disable-model-invocation true version 1.0.0 category development author Claude MPM Team license MIT progressive_disclosure {"entry_point":{"summary":"Modern WordPress plugin development with OOP, hooks system, database interactions, and Settings API","when_to_use":["Building WordPress plugins with PHP 8.3+ and WP 6.7+","Need structured architecture with actions, filters, hooks","Require database operations and settings management"],"quick_start":["Create plugin structure with OOP and Composer PSR-4","Register activation/deactivation hooks","Use actions and filters for core integration"]}} context_limit 5500 tags ["wordpress","php","plugin","hooks","wpdb","settings-api","wpcs"] requires_tools []
WordPress Plugin Fundamentals
Overview
WordPress plugin development using modern PHP 8.3+ practices, OOP architecture, Composer autoloading, and WordPress 6.7+ APIs. Build secure, maintainable plugins with proper hooks integration, database management, and settings pages.
Current Standards :
WordPress : 6.7+ (Full Site Editing stable)
PHP : 8.3 recommended (7.4 minimum)
Architecture : OOP with PSR-4 autoloading
Security : Three-layer model (sanitize, validate, escape)
Testing : PHPUnit + WPCS compliance
Installation :
composer require --dev wp-coding-standards/wpcs:"^3.0"
composer require --dev phpunit/phpunit:"^9.6"
Plugin Architecture
Directory Structure
Modern plugin organization with Composer autoloading:
my-plugin/
├── my-plugin.php # Main plugin file (metadata header)
├── composer.json # Dependency management (REQUIRED)
├── includes/ # Core business logic (PSR-4 autoloaded)
│ ├── Core.php # Plugin bootstrap/loader class
│ ├── Admin/ # Admin-specific functionality
│ │ ├── Settings.php
│ │ └── MetaBoxes.php
│ ├── Frontend/ # Public-facing functionality
│ │ └── Shortcodes.php
│ └── API/ # REST API endpoints
│ └── CustomEndpoint.php
├── assets/ # CSS, JS, images
│ ├── css/
│ ├── js/
│ └── images/
├── languages/ # Translation files
├── tests/ # PHPUnit tests
│ ├── unit/
│ ├── integration/
│ └── bootstrap.php
├── .phpcs.xml.dist # PHP_CodeSniffer config (WPCS)
└── README.md
Main Plugin File
my-plugin.php :
<?php
if ( ! defined ( 'ABSPATH' ) ) {
;
}
( , );
( , ( ) );
( , ( ) );
( , ( ) );
( ( MY_PLUGIN_PATH . ) ) {
MY_PLUGIN_PATH . ;
}
( , );
{
( ( ) ) {
= :: ();
-> ();
}
}
( , );
{
( ( ) ) {
:: ();
}
();
}
( , );
{
( ( ) ) {
:: ();
}
();
}
exit
define
'MY_PLUGIN_VERSION'
'1.0.0'
define
'MY_PLUGIN_PATH'
plugin_dir_path
__FILE__
define
'MY_PLUGIN_URL'
plugin_dir_url
__FILE__
define
'MY_PLUGIN_BASENAME'
plugin_basename
__FILE__
if
file_exists
'vendor/autoload.php'
require_once
'vendor/autoload.php'
add_action
'plugins_loaded'
'my_plugin_init'
function my_plugin_init ( )
if
class_exists
'MyPlugin\\Core'
$plugin
MyPlugin\Core
get_instance
$plugin
run
register_activation_hook
__FILE__
'my_plugin_activate'
function my_plugin_activate ( )
if
class_exists
'MyPlugin\\Activation'
MyPlugin\Activation
activate
flush_rewrite_rules
register_deactivation_hook
__FILE__
'my_plugin_deactivate'
function my_plugin_deactivate ( )
if
class_exists
'MyPlugin\\Deactivation'
MyPlugin\Deactivation
deactivate
flush_rewrite_rules
Core Plugin Class (Singleton Pattern) <?php
namespace MyPlugin ;
class Core {
private static $instance = null ;
public static function get_instance ( ) {
if ( null === self ::$instance ) {
self ::$instance = new self ();
}
return self ::$instance ;
}
private function __construct ( ) {
$this ->load_dependencies ();
$this ->define_hooks ();
$this ->load_textdomain ();
}
private function load_dependencies ( ) {
}
private function define_hooks ( ) {
add_action ( 'init' , [ $this , 'on_init' ] );
add_action ( 'admin_menu' , [ $this , 'register_admin_menu' ] );
add_action ( 'admin_enqueue_scripts' , [ $this , 'enqueue_admin_assets' ] );
add_action ( 'wp_enqueue_scripts' , [ $this , 'enqueue_frontend_assets' ] );
add_action ( 'rest_api_init' , [ $this , 'register_rest_routes' ] );
}
private function load_textdomain ( ) {
load_plugin_textdomain (
'my-plugin' ,
false ,
dirname ( MY_PLUGIN_BASENAME ) . '/languages'
);
}
public function run ( ) {
do_action ( 'my_plugin_loaded' );
}
public function on_init ( ) {
$this ->register_post_types ();
$this ->register_taxonomies ();
}
private function register_post_types ( ) {
register_post_type ( 'book' , [
'labels' => [
'name' => __ ( 'Books' , 'my-plugin' ),
'singular_name' => __ ( 'Book' , 'my-plugin' ),
],
'public' => true ,
'has_archive' => true ,
'supports' => [ 'title' , 'editor' , 'thumbnail' ],
'show_in_rest' => true , // Enable block editor
'menu_icon' => 'dashicons-book' ,
]);
}
private function register_taxonomies ( ) {
register_taxonomy ( 'genre' , 'book' , [
'labels' => [
'name' => __ ( 'Genres' , 'my-plugin' ),
'singular_name' => __ ( 'Genre' , 'my-plugin' ),
],
'hierarchical' => true ,
'show_in_rest' => true ,
]);
}
public function register_admin_menu ( ) {
add_menu_page (
__ ( 'My Plugin Settings' , 'my-plugin' ),
__ ( 'My Plugin' , 'my-plugin' ),
'manage_options' ,
'my-plugin-settings' ,
[ $this , 'render_settings_page' ],
'dashicons-admin-generic' ,
80
);
}
public function render_settings_page ( ) {
require_once MY_PLUGIN_PATH . 'includes/Admin/views/settings.php' ;
}
public function enqueue_admin_assets ( $hook ) {
if ( 'toplevel_page_my-plugin-settings' !== $hook ) {
return ;
}
wp_enqueue_style (
'my-plugin-admin' ,
MY_PLUGIN_URL . 'assets/css/admin.css' ,
[],
MY_PLUGIN_VERSION
);
wp_enqueue_script (
'my-plugin-admin' ,
MY_PLUGIN_URL . 'assets/js/admin.js' ,
[ 'jquery' ],
MY_PLUGIN_VERSION,
true
);
wp_localize_script ( 'my-plugin-admin' , 'myPluginData' , [
'ajaxurl' => admin_url ( 'admin-ajax.php' ),
'nonce' => wp_create_nonce ( 'my_plugin_nonce' ),
]);
}
public function enqueue_frontend_assets ( ) {
wp_enqueue_style (
'my-plugin-frontend' ,
MY_PLUGIN_URL . 'assets/css/frontend.css' ,
[],
MY_PLUGIN_VERSION
);
wp_enqueue_script (
'my-plugin-frontend' ,
MY_PLUGIN_URL . 'assets/js/frontend.js' ,
[ 'jquery' ],
MY_PLUGIN_VERSION,
true
);
}
public function register_rest_routes ( ) {
if ( class_exists ( 'MyPlugin\\API\\CustomEndpoint' ) ) {
$endpoint = new API\CustomEndpoint ();
$endpoint ->register_routes ();
}
}
}
Composer Configuration {
"name" : "vendor/my-plugin" ,
"description" : "Modern WordPress plugin" ,
"type" : "wordpress-plugin" ,
"require" : {
"php" : ">=8.1"
} ,
"require-dev" : {
"wp-coding-standards/wpcs" : "^3.0" ,
"phpunit/phpunit" : "^9.6" ,
"yoast/phpunit-polyfills" : "^2.0"
} ,
"autoload" : {
"psr-4" : {
"MyPlugin\\" : "includes/"
}
} ,
"config" : {
"allow-plugins" : {
"dealerdirect/phpcodesniffer-composer-installer" : true
}
} ,
"scripts" : {
"phpcs" : "phpcs" ,
"phpcbf" : "phpcbf" ,
"test" : "phpunit"
}
}
Hooks System
Actions vs. Filters Aspect Actions Filters Purpose Execute code at specific points Modify data before use/output Return Value Returns nothing (void) Must return value Example Send emails, log events, register CPTs Modify post content, filter queries Pattern do_action() / add_action()apply_filters() / add_filter()
Common WordPress Actions init - Register post types, taxonomies, rewrite rules:
add_action ( 'init' , 'register_custom_post_type' );
function register_custom_post_type ( ) {
register_post_type ( 'book' , [
'labels' => [
'name' => __ ( 'Books' , 'my-plugin' ),
'singular_name' => __ ( 'Book' , 'my-plugin' ),
],
'public' => true ,
'has_archive' => true ,
'supports' => [ 'title' , 'editor' , 'thumbnail' ],
'show_in_rest' => true , // Enable block editor
]);
}
plugins_loaded - Initialize plugin after all plugins loaded:
add_action ( 'plugins_loaded' , 'my_plugin_init' );
function my_plugin_init ( ) {
load_plugin_textdomain ( 'my-plugin' , false , dirname ( plugin_basename ( __FILE__ ) ) . '/languages' );
MyPlugin\Core ::get_instance ()->run ();
}
wp_enqueue_scripts - Enqueue frontend CSS/JS:
add_action ( 'wp_enqueue_scripts' , 'enqueue_frontend_assets' );
function enqueue_frontend_assets ( ) {
wp_enqueue_style ( 'my-style' , plugins_url ( 'assets/css/style.css' , __FILE__ ), [], '1.0.0' );
wp_enqueue_script ( 'my-script' , plugins_url ( 'assets/js/script.js' , __FILE__ ), [ 'jquery' ], '1.0.0' , true );
}
admin_enqueue_scripts - Enqueue admin CSS/JS:
add_action ( 'admin_enqueue_scripts' , 'enqueue_admin_assets' );
function enqueue_admin_assets ( $hook ) {
if ( 'toplevel_page_my-plugin' !== $hook ) {
return ;
}
wp_enqueue_style ( 'my-admin-style' , plugins_url ( 'assets/css/admin.css' , __FILE__ ) );
}
save_post - Runs when post is saved/updated:
add_action ( 'save_post' , 'save_custom_meta' , 10 , 3 );
function save_custom_meta ( $post_id , $post , $update ) {
if ( ! isset ( $_POST ['my_meta_nonce' ] ) || ! wp_verify_nonce ( $_POST ['my_meta_nonce' ], 'save_meta' ) ) {
return ;
}
if ( defined ( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return ;
}
if ( ! current_user_can ( 'edit_post' , $post_id ) ) {
return ;
}
if ( isset ( $_POST ['custom_field' ] ) ) {
update_post_meta ( $post_id , '_custom_field' , sanitize_text_field ( $_POST ['custom_field' ] ) );
}
}
Common WordPress Filters the_content - Modify post content before output:
add_filter ( 'the_content' , 'add_reading_time' );
function add_reading_time ( $content ) {
if ( ! is_single () || ! in_the_loop () || ! is_main_query () ) {
return $content ;
}
$word_count = str_word_count ( strip_tags ( $content ) );
$reading_time = ceil ( $word_count / 200 );
$message = sprintf (
'<p class="reading-time">%s</p>' ,
sprintf ( __ ( 'Estimated reading time: %d min' , 'my-plugin' ), $reading_time )
);
return $message . $content ;
}
pre_get_posts - Modify WP_Query before execution:
add_filter ( 'pre_get_posts' , 'modify_archive_query' );
function modify_archive_query ( $query ) {
if ( ! is_admin () && $query ->is_main_query () && is_post_type_archive ( 'book' ) ) {
$query ->set ( 'posts_per_page' , 20 );
$query ->set ( 'orderby' , 'title' );
$query ->set ( 'order' , 'ASC' );
}
}
excerpt_length - Change excerpt word count:
add_filter ( 'excerpt_length' , 'custom_excerpt_length' );
function custom_excerpt_length ( $length ) {
return 30 ;
}
Hook Priority and Execution Order
add_action ( 'init' , 'my_early_function' , 5 );
add_action ( 'init' , 'my_normal_function' );
add_action ( 'init' , 'my_late_function' , 20 );
remove_action ( 'init' , 'my_normal_function' , 10 );
remove_filter ( 'the_content' , 'wpautop' );
Creating Custom Hooks
function my_plugin_process_order ( $order_id ) {
$order_data = [
'total' => 99.99 ,
'items' => [ 'item1' , 'item2' ],
];
do_action ( 'my_plugin_order_processed' , $order_id , $order_data );
}
add_action ( 'my_plugin_order_processed' , 'send_order_notification' , 10 , 2 );
function send_order_notification ( $order_id , $order_data ) {
wp_mail (
get_option ( 'admin_email' ),
'New Order: ' . $order_id ,
'Order total: $' . $order_data ['total' ]
);
}
function my_plugin_get_price ( $product_id ) {
$price = get_post_meta ( $product_id , '_price' , true );
return apply_filters ( 'my_plugin_product_price' , $price , $product_id );
}
add_filter ( 'my_plugin_product_price' , 'apply_member_discount' , 10 , 2 );
function apply_member_discount ( $price , $product_id ) {
if ( is_user_logged_in () && current_user_can ( 'member' ) ) {
return $price * 0.9 ;
}
return $price ;
}
Database Interactions
Using $wpdb Global Object Prepared statements (prevent SQL injection):
global $wpdb ;
$user_id = 42 ;
$results = $wpdb ->get_results (
$wpdb ->prepare (
"SELECT * FROM {$wpdb->posts} WHERE post_author = %d AND post_status = %s" ,
$user_id ,
'publish'
)
);
$post = $wpdb ->get_row (
$wpdb ->prepare (
"SELECT * FROM {$wpdb->posts} WHERE ID = %d" ,
$post_id
)
);
$count = $wpdb ->get_var (
$wpdb ->prepare (
"SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s" ,
'book'
)
);
$post_ids = $wpdb ->get_col (
"SELECT ID FROM {$wpdb->posts} WHERE post_type = 'book' ORDER BY post_date DESC LIMIT 10"
);
global $wpdb ;
$wpdb ->insert (
$wpdb ->prefix . 'my_custom_table' ,
[
'column1' => 'value1' ,
'column2' => 123 ,
'created_at' => current_time ( 'mysql' ),
],
[ '%s' , '%d' , '%s' ] // Data format : %s (string ), %d (integer ), %f (float )
);
$inserted_id = $wpdb ->insert_id;
global $wpdb ;
$wpdb ->update (
$wpdb ->prefix . 'my_custom_table' ,
[ 'column1' => 'new_value' , 'updated_at' => current_time ( 'mysql' ) ], // Data
[ 'id' => 5 ], // WHERE
[ '%s' , '%s' ], // Data format
[ '%d' ] // WHERE format
);
global $wpdb ;
$wpdb ->delete (
$wpdb ->prefix . 'my_custom_table' ,
[ 'id' => 5 ],
[ '%d' ]
);
Creating Custom Tables Activation hook with dbDelta() :
function my_plugin_create_tables ( ) {
global $wpdb ;
$table_name = $wpdb ->prefix . 'my_custom_table' ;
$charset_collate = $wpdb ->get_charset_collate ();
$sql = "CREATE TABLE $table_name (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
user_id bigint(20) unsigned NOT NULL,
title varchar(255) NOT NULL,
content longtext,
status varchar(20) DEFAULT 'draft',
priority int(11) DEFAULT 0,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY user_id (user_id),
KEY status (status),
KEY priority (priority)
) $charset_collate ;" ;
require_once ABSPATH . 'wp-admin/includes/upgrade.php' ;
dbDelta ( $sql );
add_option ( 'my_plugin_db_version' , '1.0.0' );
}
register_activation_hook ( __FILE__ , 'my_plugin_create_tables' );
function my_plugin_check_db_version ( ) {
$current_version = get_option ( 'my_plugin_db_version' , '0.0.0' );
$required_version = '1.1.0' ;
if ( version_compare ( $current_version , $required_version , '<' ) ) {
my_plugin_upgrade_database ( $current_version );
}
}
add_action ( 'plugins_loaded' , 'my_plugin_check_db_version' );
function my_plugin_upgrade_database ( $from_version ) {
global $wpdb ;
if ( version_compare ( $from_version , '1.1.0' , '<' ) ) {
$table_name = $wpdb ->prefix . 'my_custom_table' ;
$wpdb ->query ( "ALTER TABLE $table_name ADD COLUMN email varchar(255) AFTER user_id" );
}
update_option ( 'my_plugin_db_version' , '1.1.0' );
}
Best Practices ✅ Always use $wpdb->prepare() for dynamic queries
✅ Use $wpdb->prefix (never hard-code wp_)
✅ Use $wpdb->get_charset_collate() for correct encoding
✅ Use dbDelta() for table creation/updates
✅ Store schema version for migrations
⚠️ Consider using post_meta/options before custom tables
Settings API
Options API (Simple Storage)
add_option ( 'my_plugin_setting' , 'default_value' );
$value = get_option ( 'my_plugin_setting' , 'default_if_not_exists' );
update_option ( 'my_plugin_setting' , 'new_value' );
delete_option ( 'my_plugin_setting' );
update_option ( 'my_plugin_settings' , [
'api_key' => 'abc123' ,
'enabled' => true ,
'threshold' => 50 ,
]);
$settings = get_option ( 'my_plugin_settings' , [] );
Settings API (Admin Pages) add_action ( 'admin_init' , 'my_plugin_register_settings' );
function my_plugin_register_settings ( ) {
register_setting (
'my_plugin_options' , // Option group
'my_plugin_settings' , // Option name
[
'type' => 'array' ,
'sanitize_callback' => 'my_plugin_sanitize_settings' ,
'default' => [],
]
);
add_settings_section (
'my_plugin_main_section' , // Section ID
__ ( 'Main Settings' , 'my-plugin' ), // Title
'my_plugin_section_callback' , // Callback
'my_plugin_settings_page' // Page slug
);
add_settings_field (
'api_key' , // Field ID
__ ( 'API Key' , 'my-plugin' ), // Label
'my_plugin_api_key_callback' , // Render callback
'my_plugin_settings_page' , // Page slug
'my_plugin_main_section' , // Section ID
[ 'label_for' => 'api_key' ] // Extra args
);
add_settings_field (
'enable_feature' ,
__ ( 'Enable Feature' , 'my-plugin' ),
'my_plugin_enable_feature_callback' ,
'my_plugin_settings_page' ,
'my_plugin_main_section' ,
[ 'label_for' => 'enable_feature' ]
);
}
function my_plugin_section_callback ( ) {
echo '<p>' . esc_html__ ( 'Configure plugin settings below:' , 'my-plugin' ) . '</p>' ;
}
function my_plugin_api_key_callback ( $args ) {
$options = get_option ( 'my_plugin_settings' , [] );
$value = isset ( $options ['api_key' ] ) ? $options ['api_key' ] : '' ;
?>
<input
type="text"
id="<?php echo esc_attr( $args ['label_for'] ); ?>"
name="my_plugin_settings[api_key]"
value="<?php echo esc_attr( $value ); ?>"
class ="regular -text "
/>
<p class ="description ">
<?php esc_html_e ( 'Enter your API key from the service provider .', 'my -plugin ' ); ?>
</p >
<?php
}
function my_plugin_enable_feature_callback ( $args ) {
$options = get_option ( 'my_plugin_settings' , [] );
$checked = isset ( $options ['enable_feature' ] ) && $options ['enable_feature' ];
?>
<label>
<input
type="checkbox"
id="<?php echo esc_attr( $args ['label_for'] ); ?>"
name="my_plugin_settings[enable_feature]"
value="1"
<?php checked ( $checked , true ); ?>
/>
<?php esc_html_e ( 'Enable this feature' , 'my-plugin' ); ?>
</label>
<?php
}
function my_plugin_sanitize_settings ( $input ) {
$sanitized = [];
if ( isset ( $input ['api_key' ] ) ) {
$sanitized ['api_key' ] = sanitize_text_field ( $input ['api_key' ] );
}
if ( isset ( $input ['enable_feature' ] ) ) {
$sanitized ['enable_feature' ] = (bool ) $input ['enable_feature' ];
}
return $sanitized ;
}
function my_plugin_settings_page ( ) {
if ( ! current_user_can ( 'manage_options' ) ) {
wp_die ( __ ( 'You do not have sufficient permissions to access this page.' , 'my-plugin' ) );
}
?>
<div class ="wrap ">
<h1 ><?php echo esc_html ( get_admin_page_title () ); ?></h1 >
<?php settings_errors ( 'my_plugin_settings ' ); ?>
<form action ="options .php " method ="post ">
<?php
// Output security fields
settings_fields ( 'my_plugin_options ' );
// Output settings sections
do_settings_sections ( 'my_plugin_settings_page ' );
// Submit button
submit_button ( __ ( 'Save Settings ', 'my -plugin ' ) );
?>
</form >
</div >
<?php
}
WordPress Coding Standards (WPCS)
Installation and Configuration <?xml version="1.0" ?>
<ruleset name ="WordPress Coding Standards" >
<description > Custom ruleset for WordPress plugin</description >
<file > ./includes</file >
<file > ./my-plugin.php</file >
<exclude-pattern > */vendor/*</exclude-pattern >
<exclude-pattern > */node_modules/*</exclude-pattern >
<exclude-pattern > */tests/*</exclude-pattern >
<rule ref ="WordPress-Extra" >
<exclude name ="Generic.Arrays.DisallowShortArraySyntax" />
<exclude name ="Squiz.PHP.DisallowMultipleAssignments" />
</rule >
<config name ="testVersion" value ="8.1-" />
<rule ref ="PHPCompatibilityWP" />
<rule ref ="WordPress.WP.I18n" >
<properties >
<property name ="text_domain" type ="array" >
<element value ="my-plugin" />
</property >
</properties >
</rule >
<rule ref ="WordPress.NamingConventions.PrefixAllGlobals" >
<properties >
<property name ="prefixes" type ="array" >
<element value ="my_plugin" />
<element value ="MyPlugin" />
</property >
</properties >
</rule >
<arg value ="ps" />
<arg name ="colors" />
<arg name ="extensions" value ="php" />
</ruleset >
Running PHPCS
vendor/bin/phpcs
vendor/bin/phpcbf
vendor/bin/phpcs includes/Core.php
vendor/bin/phpcs -ps
vendor/bin/phpcs --report=summary
Key Coding Rules Indentation : Tabs (not spaces)
function my_function ( ) {
if ( true ) {
echo 'Hello' ;
}
}
function my_function ( ) {
if ( true ) {
echo 'Hello' ;
}
}
Yoda Conditions : Constant on left side
if ( true === $value ) {
}
if ( 'active' === $status ) {
}
if ( $value === true ) {
}
function my_plugin_process_data ( ) { }
$user_name = 'John' ;
class MyPlugin_Database { }
define ( 'MY_PLUGIN_VERSION' , '1.0.0' );
Documentation : PHPDoc blocks required
function my_plugin_register_user ( $username , $email ) {
}
Best Practices
Security Considerations Cross-reference : See ../security-validation/SKILL.md for comprehensive security patterns.
Three-layer security model :
Sanitize on input - Remove dangerous characters
Validate for logic - Check business rules
Escape on output - Prevent XSS
$title = sanitize_text_field ( $_POST ['title' ] );
$email = sanitize_email ( $_POST ['email' ] );
if ( empty ( $title ) || strlen ( $title ) < 3 ) {
wp_die ( 'Invalid title' );
}
if ( ! is_email ( $email ) ) {
wp_die ( 'Invalid email' );
}
echo '<h1>' . esc_html ( $title ) . '</h1>' ;
echo '<a href="mailto:' . esc_attr ( $email ) . '">' . esc_html ( $email ) . '</a>' ;
Prefix Everything
function my_plugin_init ( ) { }
class MyPlugin_Settings { }
define ( 'MY_PLUGIN_VERSION' , '1.0.0' );
do_action ( 'my_plugin_loaded' );
apply_filters ( 'my_plugin_content' , $content );
$wpdb ->prefix . 'my_plugin_data' ;
update_option ( 'my_plugin_settings' , $data );
Translation-Ready (i18n)
__ ( 'Hello World' , 'my-plugin' );
esc_html__ ( 'Hello World' , 'my-plugin' );
esc_attr__ ( 'Hello World' , 'my-plugin' );
esc_html_e ( 'Hello World' , 'my-plugin' );
_n ( 'One item' , '%d items' , $count , 'my-plugin' );
_x ( 'Post' , 'noun' , 'my-plugin' );
_x ( 'Post' , 'verb' , 'my-plugin' );
sprintf ( __ ( 'Hello %s' , 'my-plugin' ), $name );
load_plugin_textdomain ( 'my-plugin' , false , dirname ( plugin_basename ( __FILE__ ) ) . '/languages' );
Use WordPress Functions Over PHP
$url = esc_url ( $link );
$current_time = current_time ( 'mysql' );
$user_ip = $_SERVER ['REMOTE_ADDR' ];
$url = htmlspecialchars ( $link );
$current_time = date ( 'Y-m-d H:i:s' );
Performance Considerations
wp_cache_set ( 'my_key' , $data , 'my_plugin' , 3600 );
$data = wp_cache_get ( 'my_key' , 'my_plugin' );
if ( false === $data ) {
$data = expensive_operation ();
wp_cache_set ( 'my_key' , $data , 'my_plugin' , 3600 );
}
Transients (database-backed cache):
set_transient ( 'my_plugin_data' , $data , 12 * HOUR_IN_SECONDS );
$data = get_transient ( 'my_plugin_data' );
if ( false === $data ) {
$data = expensive_api_call ();
set_transient ( 'my_plugin_data' , $data , 12 * HOUR_IN_SECONDS );
}
delete_transient ( 'my_plugin_data' );
Common Patterns
Singleton Pattern class MyPlugin_Service {
private static $instance = null ;
public static function get_instance ( ) {
if ( null === self ::$instance ) {
self ::$instance = new self ();
}
return self ::$instance ;
}
private function __construct ( ) {
}
private function __clone ( ) { }
private function __wakeup ( ) { }
}
Dependency Injection
class MyPlugin_Controller {
private $database ;
private $settings ;
public function __construct ( MyPlugin_Database $database , MyPlugin_Settings $settings ) {
$this ->database = $database ;
$this ->settings = $settings ;
}
public function process ( ) {
$data = $this ->database->get_data ();
$config = $this ->settings->get_config ();
}
}
$database = new MyPlugin_Database ();
$settings = new MyPlugin_Settings ();
$controller = new MyPlugin_Controller ( $database , $settings );
Service Container Pattern class MyPlugin_Container {
private $services = [];
public function register ( $name , $callback ) {
$this ->services[ $name ] = $callback ;
}
public function get ( $name ) {
if ( ! isset ( $this ->services[ $name ] ) ) {
throw new Exception ( "Service not found: $name " );
}
$callback = $this ->services[ $name ];
return $callback ( $this );
}
}
$container = new MyPlugin_Container ();
$container ->register ( 'database' , function( $c ) {
return new MyPlugin_Database ();
});
$container ->register ( 'settings' , function( $c ) {
return new MyPlugin_Settings ();
});
$container ->register ( 'controller' , function( $c ) {
return new MyPlugin_Controller (
$c ->get ( 'database' ),
$c ->get ( 'settings' )
);
});
$controller = $container ->get ( 'controller' );
Related Skills When developing WordPress plugins, consider these complementary skills (available in the skill library):
security-validation : WordPress security, nonces, sanitization, validation, escaping - critical for securing plugin functionality
block-editor : Block Editor development, FSE, theme.json, custom blocks - extend plugins with modern block-based interfaces
phpunit : PHPUnit testing for WordPress plugins - comprehensive testing strategies for WordPress plugin development
Resources
Summary
Modern architecture : OOP with PSR-4 autoloading, Composer dependencies
Hooks system : Actions for execution, filters for modification
Database : Use $wpdb with prepared statements, custom tables via dbDelta()
Settings API : Structured admin pages with sanitization callbacks
WPCS compliance : WordPress coding standards via PHPCS
Security-first : Sanitize input, validate logic, escape output
Translation-ready : Use i18n functions for all user-facing text
Performance : Object caching, transients, query optimization