---
title: "Plugin Architecture"
description: "``` plugin-name/ ├── plugin-name.php # Main plugin file with header ├── uninstall.php # Cleanup on uninstall ├── includes/ │ ├── class-plugin-name.php │ ├── class-activator.php │ ├── class-deactivator.php │ └── class-loader.php ├── admin/ │ ├── class-admin.php │ ├── css/ │ └── js/ ├── public/ │ ├── class-public.php │ ├── css/ │ └── js/ ├── languages/ │ └── plugin-name.pot └── README.txt ```"
type: skill
canonical_url: https://claudary.paisolsolutions.com/skills/plugin-architecture
source: "Claudary"
difficulty: intermediate
author: "Claude Code Knowledge Pack"
date: 2026-07-10T11:32:05.007Z
license: CC-BY-4.0
attribution: "Plugin Architecture — Claudary (https://claudary.paisolsolutions.com/skills/plugin-architecture)"
---

# Plugin Architecture
``` plugin-name/ ├── plugin-name.php # Main plugin file with header ├── uninstall.php # Cleanup on uninstall ├── includes/ │ ├── class-plugin-name.php │ ├── class-activator.php │ ├── class-deactivator.php │ └── class-loader.php ├── admin/ │ ├── class-admin.php │ ├── css/ │ └── js/ ├── public/ │ ├── class-public.php │ ├── css/ │ └── js/ ├── languages/ │ └── plugin-name.pot └── README.txt ```

## Overview

# Plugin Architecture

---

## Plugin Structure

### Minimal Plugin Structure

```
plugin-name/
├── plugin-name.php        # Main plugin file with header
├── uninstall.php          # Cleanup on uninstall
├── includes/
│   ├── class-plugin-name.php
│   ├── class-activator.php
│   ├── class-deactivator.php
│   └── class-loader.php
├── admin/
│   ├── class-admin.php
│   ├── css/
│   └── js/
├── public/
│   ├── class-public.php
│   ├── css/
│   └── js/
├── languages/
│   └── plugin-name.pot
└── README.txt
```

### Full Plugin Structure (Enterprise)

```
plugin-name/
├── plugin-name.php
├── uninstall.php
├── composer.json
├── phpcs.xml.dist
├── phpunit.xml.dist
├── includes/
│   ├── class-plugin.php           # Main plugin class
│   ├── class-activator.php        # Activation logic
│   ├── class-deactivator.php      # Deactivation logic
│   ├── class-loader.php           # Hook loader
│   ├── class-i18n.php             # Internationalization
│   ├── Traits/
│   │   └── Singleton.php
│   ├── Interfaces/
│   │   ├── Registrable.php
│   │   └── Hookable.php
│   ├── Services/
│   │   └── class-api-service.php
│   └── Repositories/
│       └── class-data-repository.php
├── admin/
│   ├── class-admin.php
│   ├── class-settings.php
│   ├── partials/
│   │   └── settings-page.php
│   ├── css/
│   └── js/
├── public/
│   ├── class-frontend.php
│   ├── partials/
│   ├── css/
│   └── js/
├── blocks/
│   └── custom-block/
├── templates/
│   └── single-custom-type.php
├── languages/
├── tests/
│   ├── bootstrap.php
│   └── unit/
└── vendor/
```

---

## Main Plugin File

### Plugin Header

```php
<?php
/**
 * Plugin Name:       Plugin Name
 * Plugin URI:        https://example.com/plugin-name
 * Description:       A brief description of what this plugin does.
 * Version:           1.0.0
 * Requires at least: 6.4
 * Requires PHP:      8.1
 * Author:            Author Name
 * Author URI:        https://example.com
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       plugin-name
 * Domain Path:       /languages
 * Update URI:        https://example.com/plugin-name
 *
 * @package PluginName
 */

declare(strict_types=1);

namespace PluginName;

// Prevent direct access
defined('ABSPATH') || exit;

// Plugin constants
define('PLUGIN_NAME_VERSION', '1.0.0');
define('PLUGIN_NAME_FILE', __FILE__);
define('PLUGIN_NAME_PATH', plugin_dir_path(__FILE__));
define('PLUGIN_NAME_URL', plugin_dir_url(__FILE__));
define('PLUGIN_NAME_BASENAME', plugin_basename(__FILE__));

// Autoloader
if (file_exists(PLUGIN_NAME_PATH . 'vendor/autoload.php')) {
    require_once PLUGIN_NAME_PATH . 'vendor/autoload.php';
}

// Manual includes if no autoloader
require_once PLUGIN_NAME_PATH . 'includes/class-plugin.php';
require_once PLUGIN_NAME_PATH . 'includes/class-activator.php';
require_once PLUGIN_NAME_PATH . 'includes/class-deactivator.php';

/**
 * Plugin activation hook
 */
function activate(): void {
    Activator::activate();
}
register_activation_hook(__FILE__, __NAMESPACE__ . '\\\\activate');

/**
 * Plugin deactivation hook
 */
function deactivate(): void {
    Deactivator::deactivate();
}
register_deactivation_hook(__FILE__, __NAMESPACE__ . '\\\\deactivate');

/**
 * Initialize the plugin
 */
function init(): void {
    $plugin = new Plugin();
    $plugin->run();
}
add_action('plugins_loaded', __NAMESPACE__ . '\\\\init');
```

---

## Activation & Deactivation

### Activator Class

```php
<?php
declare(strict_types=1);

namespace PluginName;

/**
 * Fired during plugin activation
 */
class Activator {

    /**
     * Activation tasks
     */
    public static function activate(): void {
        // Check requirements
        self::check_requirements();

        // Create database tables
        self::create_tables();

        // Set default options
        self::set_default_options();

        // Schedule cron events
        self::schedule_events();

        // Add capabilities
        self::add_capabilities();

        // Flush rewrite rules (if registering CPT/taxonomy)
        flush_rewrite_rules();

        // Set activation flag for welcome notice
        set_transient('plugin_name_activated', true, 30);
    }

    /**
     * Check system requirements
     */
    private static function check_requirements(): void {
        if (version_compare(PHP_VERSION, '8.1', '<')) {
            deactivate_plugins(PLUGIN_NAME_BASENAME);
            wp_die(
                esc_html__('This plugin requires PHP 8.1 or higher.', 'plugin-name'),
                'Plugin Activation Error',
                ['back_link' => true]
            );
        }

        global $wp_version;
        if (version_compare($wp_version, '6.4', '<')) {
            deactivate_plugins(PLUGIN_NAME_BASENAME);
            wp_die(
                esc_html__('This plugin requires WordPress 6.4 or higher.', 'plugin-name'),
                'Plugin Activation Error',
                ['back_link' => true]
            );
        }
    }

    /**
     * Create custom database tables
     */
    private static function create_tables(): void {
        global $wpdb;

        $charset_collate = $wpdb->get_charset_collate();
        $table_name = $wpdb->prefix . 'plugin_name_data';

        $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            user_id bigint(20) unsigned NOT NULL DEFAULT 0,
            data_key varchar(191) NOT NULL,
            data_value longtext,
            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 data_key (data_key)
        ) {$charset_collate};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);

        // Store DB version
        update_option('plugin_name_db_version', PLUGIN_NAME_VERSION);
    }

    /**
     * Set default options
     */
    private static function set_default_options(): void {
        $defaults = [
            'enabled'          => true,
            'api_key'          => '',
            'cache_duration'   => 3600,
            'items_per_page'   => 10,
            'allowed_roles'    => ['administrator', 'editor'],
        ];

        if (get_option('plugin_name_settings') === false) {
            add_option('plugin_name_settings', $defaults);
        }
    }

    /**
     * Schedule cron events
     */
    private static function schedule_events(): void {
        if (!wp_next_scheduled('plugin_name_daily_cleanup')) {
            wp_schedule_event(time(), 'daily', 'plugin_name_daily_cleanup');
        }
    }

    /**
     * Add custom capabilities
     */
    private static function add_capabilities(): void {
        $admin = get_role('administrator');
        if ($admin) {
            $admin->add_cap('manage_plugin_name');
            $admin->add_cap('edit_plugin_name_data');
        }
    }
}
```

### Deactivator Class

```php
<?php
declare(strict_types=1);

namespace PluginName;

/**
 * Fired during plugin deactivation
 */
class Deactivator {

    /**
     * Deactivation tasks
     */
    public static function deactivate(): void {
        // Clear scheduled events
        self::clear_scheduled_events();

        // Clear transients
        self::clear_transients();

        // Flush rewrite rules
        flush_rewrite_rules();

        // Note: Do NOT delete options or tables here
        // That should only happen in uninstall.php
    }

    /**
     * Clear all scheduled cron events
     */
    private static function clear_scheduled_events(): void {
        $timestamp = wp_next_scheduled('plugin_name_daily_cleanup');
        if ($timestamp) {
            wp_unschedule_event($timestamp, 'plugin_name_daily_cleanup');
        }

        // Clear all instances of our events
        wp_clear_scheduled_hook('plugin_name_daily_cleanup');
    }

    /**
     * Clear transients
     */
    private static function clear_transients(): void {
        global $wpdb;

        // Clear specific transients
        delete_transient('plugin_name_cache');
        delete_transient('plugin_name_activated');

        // Clear all plugin transients (use with caution)
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
                '_transient_plugin_name_%',
                '_transient_timeout_plugin_name_%'
            )
        );
    }
}
```

### uninstall.php

```php
<?php
/**
 * Uninstall script - runs when plugin is deleted
 *
 * @package PluginName
 */

// If uninstall not called from WordPress, exit
if (!defined('WP_UNINSTALL_PLUGIN')) {
    exit;
}

// Check if we should preserve data
$settings = get_option('plugin_name_settings', []);
$preserve_data = $settings['preserve_data_on_uninstall'] ?? false;

if (!$preserve_data) {
    global $wpdb;

    // Delete options
    delete_option('plugin_name_settings');
    delete_option('plugin_name_db_version');

    // Delete user meta
    $wpdb->query("DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE 'plugin_name_%'");

    // Delete post meta
    $wpdb->query("DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '_plugin_name_%'");

    // Delete custom tables
    $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}plugin_name_data");

    // Delete transients
    $wpdb->query(
        $wpdb->prepare(
            "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
            '_transient_plugin_name_%',
            '_transient_timeout_plugin_name_%'
        )
    );

    // Remove capabilities
    $roles = ['administrator', 'editor'];
    foreach ($roles as $role_name) {
        $role = get_role($role_name);
        if ($role) {
            $role->remove_cap('manage_plugin_name');
            $role->remove_cap('edit_plugin_name_data');
        }
    }

    // Clear any cached data
    wp_cache_flush();
}
```

---

## Settings API

### Settings Class

```php
<?php
declare(strict_types=1);

namespace PluginName\\Admin;

/**
 * Plugin settings management
 */
class Settings {

    private const OPTION_GROUP = 'plugin_name_settings';
    private const OPTION_NAME = 'plugin_name_settings';
    private const PAGE_SLUG = 'plugin-name-settings';

    /**
     * Initialize settings
     */
    public function init(): void {
        add_action('admin_menu', [$this, 'add_menu_page']);
        add_action('admin_init', [$this, 'register_settings']);
    }

    /**
     * Add admin menu page
     */
    public function add_menu_page(): void {
        add_options_page(
            __('Plugin Name Settings', 'plugin-name'),
            __('Plugin Name', 'plugin-name'),
            'manage_options',
            self::PAGE_SLUG,
            [$this, 'render_settings_page']
        );
    }

    /**
     * Register all settings
     */
    public function register_settings(): void {
        register_setting(
            self::OPTION_GROUP,
            self::OPTION_NAME,
            [
                'type'              => 'array',
                'sanitize_callback' => [$this, 'sanitize_settings'],
                'default'           => $this->get_defaults(),
            ]
        );

        // General section
        add_settings_section(
            'plugin_name_general',
            __('General Settings', 'plugin-name'),
            [$this, 'render_general_section'],
            self::PAGE_SLUG
        );

        // API section
        add_settings_section(
            'plugin_name_api',
            __('API Settings', 'plugin-name'),
            [$this, 'render_api_section'],
            self::PAGE_SLUG
        );

        // Fields
        $this->add_fields();
    }

    /**
     * Add settings fields
     */
    private function add_fields(): void {
        // Enable field
        add_settings_field(
            'enabled',
            __('Enable Plugin', 'plugin-name'),
            [$this, 'render_checkbox_field'],
            self::PAGE_SLUG,
            'plugin_name_general',
            [
                'label_for'   => 'enabled',
                'description' => __('Enable or disable the plugin functionality.', 'plugin-name'),
            ]
        );

        // Items per page
        add_settings_field(
            'items_per_page',
            __('Items Per Page', 'plugin-name'),
            [$this, 'render_number_field'],
            self::PAGE_SLUG,
            'plugin_name_general',
            [
                'label_for'   => 'items_per_page',
                'min'         => 1,
                'max'         => 100,
                'description' => __('Number of items to display per page.', 'plugin-name'),
            ]
        );

        // API Key
        add_settings_field(
            'api_key',
            __('API Key', 'plugin-name'),
            [$this, 'render_text_field'],
            self::PAGE_SLUG,
            'plugin_name_api',
            [
                'label_for'   => 'api_key',
                'type'        => 'password',
                'description' => __('Enter your API key for external service.', 'plugin-name'),
            ]
        );

        // Cache duration
        add_settings_field(
            'cache_duration',
            __('Cache Duration', 'plugin-name'),
            [$this, 'render_select_field'],
            self::PAGE_SLUG,
            'plugin_name_api',
            [
                'label_for'   => 'cache_duration',
                'options'     => [
                    '900'   => __('15 minutes', 'plugin-name'),
                    '1800'  => __('30 minutes', 'plugin-name'),
                    '3600'  => __('1 hour', 'plugin-name'),
                    '86400' => __('1 day', 'plugin-name'),
                ],
                'description' => __('How long to cache API responses.', 'plugin-name'),
            ]
        );
    }

    /**
     * Get default settings
     */
    private function get_defaults(): array {
        return [
            'enabled'        => true,
            'api_key'        => '',
            'cache_duration' => 3600,
            'items_per_page' => 10,
        ];
    }

    /**
     * Sanitize settings
     */
    public function sanitize_settings(array $input): array {
        $sanitized = [];

        $sanitized['enabled'] = !empty($input['enabled']);

        $sanitized['api_key'] = sanitize_text_field($input['api_key'] ?? '');

        $sanitized['cache_duration'] = absint($input['cache_duration'] ?? 3600);
        if (!in_array($sanitized['cache_duration'], [900, 1800, 3600, 86400], true)) {
            $sanitized['cache_duration'] = 3600;
        }

        $sanitized['items_per_page'] = absint($input['items_per_page'] ?? 10);
        $sanitized['items_per_page'] = max(1, min(100, $sanitized['items_per_page']));

        return $sanitized;
    }

    /**
     * Render settings page
     */

---

Source: [Claudary](https://claudary.paisolsolutions.com/skills/plugin-architecture) · https://claudary.paisolsolutions.com
