Chapter 14: WordPress Plugin Development
Chapter 14: WordPress Plugin Development
Last Updated: 2026-03
14.2 Plugin File Structure
Every ITI plugin follows this standard structure:
my-plugin/
├── my-plugin.php # Main plugin file (headers, initialization)
├── includes/
│ ├── class-my-plugin.php # Core plugin class (singleton)
│ ├── class-my-plugin-admin.php # Admin UI (settings pages)
│ ├── class-my-plugin-api.php # AJAX/REST handlers (user-facing)
│ ├── class-my-plugin-db.php # Database operations (extends ITI_Database_Base)
│ └── class-my-plugin-workflow.php # n8n Workflow Adapter integration
├── assets/
│ ├── css/ # Styles
│ └── js/ # Frontend scripts
├── templates/ # PHP template files for front-end output
├── vendor/ # Composer dependencies (if any)
├── CLAUDE.md # AI context file
├── REQUIREMENTS.md # User stories and acceptance criteria
├── ARCHITECTURE.md # Technical decisions
├── DEPLOY.md # Deployment instructions
├── THIRD_PARTY.md # Dependency registry
└── readme.txt # WordPress.org-formatted readme
14.3 Main Plugin File
The main plugin file (my-plugin.php) contains the WordPress plugin headers and bootstraps the plugin:
<?php
/**
* Plugin Name: ITI Career Coach
* Plugin URI: https://it-influentials.com/career-coach
* Description: AI-powered career coaching for WordPress.
* Version: 1.0.0
* Author: IT Influentials LLC
* License: GPL-2.0+
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Define plugin constants
define( 'ITI_CAREER_COACH_VERSION', '1.0.0' );
define( 'ITI_CAREER_COACH_PATH', plugin_dir_path( __FILE__ ) );
define( 'ITI_CAREER_COACH_URL', plugin_dir_url( __FILE__ ) );
define( 'ITI_SHARED_PATH', ITI_CAREER_COACH_PATH . '../shared/' );
// Autoload shared library and plugin classes
require_once ITI_SHARED_PATH . 'wordpress/api-clients/class-iti-workflow-adapter.php';
require_once ITI_CAREER_COACH_PATH . 'includes/class-career-coach.php';
// Initialize the plugin
add_action( 'plugins_loaded', [ 'Career_Coach', 'get_instance' ] );
14.4 Core Plugin Class (Singleton)
class Career_Coach {
private static $instance = null;
public static function get_instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->setup_hooks();
}
private function setup_hooks(): void {
add_action( 'admin_menu', [ $this, 'register_admin_menu' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
add_action( 'wp_ajax_iti_career_coach_request', [ $this, 'handle_ajax' ] );
add_action( 'wp_ajax_nopriv_iti_career_coach_request', [ $this, 'handle_ajax' ] );
register_activation_hook( ITI_CAREER_COACH_PATH . 'career-coach.php', [ $this, 'activate' ] );
}
public function handle_ajax(): void {
// Always verify nonce first
check_ajax_referer( 'iti_career_coach_nonce', 'nonce' );
// Sanitize all input
$message = sanitize_text_field( $_POST['message'] ?? '' );
if ( empty( $message ) ) {
wp_send_json_error( [ 'message' => 'Message is required.' ] );
}
// Call the workflow adapter
$adapter = new ITI_Workflow_Adapter([
'webhook_url' => get_option( 'iti_career_coach_webhook_url' ),
'timeout' => 30,
]);
$response = $adapter->request([
'user_message' => $message,
'user_id' => get_current_user_id(),
]);
if ( is_wp_error( $response ) ) {
wp_send_json_error( [ 'message' => 'Service unavailable.' ] );
}
wp_send_json_success( $response );
}
}
14.5 WordPress Hooks Architecture
ITI plugins follow WordPress hook conventions strictly:
| Hook Type | Used For |
|---|---|
add_action() |
Side effects: registering menus, enqueuing assets, handling AJAX |
add_filter() |
Transforming data: modifying content output, query args |
register_activation_hook() |
Plugin activation: create DB tables, set default options |
register_deactivation_hook() |
Plugin deactivation: clean up scheduled events |
register_uninstall_hook() |
Plugin uninstall: remove all plugin data from DB |
14.6 Security Checklist
Every AJAX handler must implement all of the following:
public function handle_ajax(): void {
// 1. Verify nonce (CSRF protection)
check_ajax_referer( 'my_plugin_nonce', 'nonce' );
// 2. Check user permissions if action is privileged
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( [ 'message' => 'Permission denied.' ], 403 );
}
// 3. Sanitize all input — choose the appropriate sanitization function
$text_field = sanitize_text_field( $_POST['field'] ?? '' );
$email = sanitize_email( $_POST['email'] ?? '' );
$url = esc_url_raw( $_POST['url'] ?? '' );
$integer = absint( $_POST['count'] ?? 0 );
$html = wp_kses_post( $_POST['html'] ?? '' ); // Only allow safe HTML
// 4. Validate — reject if required fields are missing or invalid
if ( empty( $text_field ) ) {
wp_send_json_error( [ 'message' => 'Field is required.' ] );
}
// 5. Process...
// 6. Escape output
wp_send_json_success([
'output' => esc_html( $result ),
]);
}
14.7 Storing Plugin Settings
Use wp_options for plugin settings. Never store API keys in plain text:
// Store settings
update_option( 'iti_my_plugin_settings', [
'api_key' => iti_encrypt( $api_key ), // Always encrypt API keys
'webhook_url' => esc_url_raw( $webhook_url ),
'max_tokens' => absint( $max_tokens ),
]);
// Retrieve settings
$settings = get_option( 'iti_my_plugin_settings', [] );
$api_key = iti_decrypt( $settings['api_key'] ?? '' );
Warning: Never store API keys in plain text in
wp_options. Use the encryption utilities fromITI/shared/wordpress/utilities/.
14.8 Database Tables
Use ITI_Database_Base for all plugin-specific tables. Never write raw SQL outside this base class:
class My_Plugin_DB extends ITI_Database_Base {
protected $table_name = 'my_plugin_sessions';
protected function get_schema(): string {
return "
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
session_data LONGTEXT NOT NULL,
status VARCHAR(20) DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX (user_id),
INDEX (status)
";
}
}
Create the table on activation:
public function activate(): void {
$db = new My_Plugin_DB();
$db->create_table();
}
14.9 Testing WordPress Plugins
WordPress plugin testing is covered in Chapter 25. The recommended test environment setup is in ITI/operations/documentation/WORDPRESS-PLUGIN-TESTING-ENVIRONMENT-SETUP.md.
Previous: Chapter 13 — The ITI Shared Library | Next: Chapter 15 — Desktop Apps with Tauri 2
