Skip to main content
< All Topics
Print

Chapter 14: WordPress Plugin Development

Chapter 14: WordPress Plugin Development

Last Updated: 2026-03

## 14.1 Overview

The majority of ITI’s commercial products are WordPress plugins. They are delivered to users as standard WordPress plugins (.zip files) that install via the WordPress admin UI or WP-CLI.

Every ITI WordPress plugin:

– Uses the ITI Shared Library (see Chapter 13)

– Follows the ITI plugin architecture pattern

– Integrates with n8n via the Workflow Adapter (see Chapter 12)

– Meets all security standards (see Chapter 26)

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 from ITI/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

Table of Contents