Разработка кастомного плагина WordPress

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомного плагина WordPress
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Разработка кастомного плагина WordPress

Когда готовый плагин делает 90% нужного, а оставшиеся 10% не реализовать без взлома его кода — пора писать собственный. Кастомный плагин — это не обязательно мегабиблиотека: иногда это 200 строк кода, которые добавляют конкретную бизнес-логику, недоступную в маркетплейсе. Разработка плагина средней сложности занимает от 3 до 10 рабочих дней.

Структура плагина

WordPress не требует строгой структуры папок, но есть устоявшиеся конвенции:

wp-content/plugins/my-plugin/
├── my-plugin.php          # Главный файл, точка входа
├── includes/
│   ├── class-my-plugin.php        # Основной класс
│   ├── class-my-plugin-admin.php  # Логика для /wp-admin
│   └── class-my-plugin-public.php # Логика для фронтенда
├── admin/
│   ├── css/admin.css
│   └── js/admin.js
├── public/
│   ├── css/public.css
│   └── js/public.js
└── languages/
    └── my-plugin-ru_RU.po

Главный файл содержит заголовок и bootstrapping:

<?php
/**
 * Plugin Name:       My Custom Plugin
 * Plugin URI:        https://example.com/my-plugin
 * Description:       Описание функциональности плагина.
 * Version:           1.0.0
 * Requires at least: 6.0
 * Requires PHP:      8.1
 * Author:            Company Name
 * Text Domain:       my-plugin
 */

if (!defined('ABSPATH')) {
    exit;
}

define('MY_PLUGIN_VERSION', '1.0.0');
define('MY_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('MY_PLUGIN_URL', plugin_dir_url(__FILE__));

require_once MY_PLUGIN_DIR . 'includes/class-my-plugin.php';

function my_plugin_init(): void {
    $plugin = new My_Plugin();
    $plugin->run();
}
add_action('plugins_loaded', 'my_plugin_init');

Основной класс и реестр хуков

Паттерн «Loader» — регистрация всех хуков в одном месте вместо разброса add_action по файлам:

class My_Plugin {
    private array $actions = [];
    private array $filters = [];

    public function __construct() {
        $this->define_admin_hooks();
        $this->define_public_hooks();
    }

    private function define_admin_hooks(): void {
        $admin = new My_Plugin_Admin();
        $this->add_action('admin_enqueue_scripts', $admin, 'enqueue_styles');
        $this->add_action('admin_menu', $admin, 'add_plugin_admin_menu');
    }

    private function add_action(string $hook, object $component, string $callback, int $priority = 10): void {
        $this->actions[] = compact('hook', 'component', 'callback', 'priority');
    }

    public function run(): void {
        foreach ($this->actions as $hook) {
            add_action($hook['hook'], [$hook['component'], $hook['callback']], $hook['priority']);
        }
        foreach ($this->filters as $hook) {
            add_filter($hook['hook'], [$hook['component'], $hook['callback']], $hook['priority'], $hook['accepted_args'] ?? 1);
        }
    }
}

Работа с базой данных

Для кастомных таблиц — создание через dbDelta при активации плагина:

register_activation_hook(__FILE__, function () {
    global $wpdb;
    $table = $wpdb->prefix . 'my_plugin_data';
    $charset = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE {$table} (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        user_id bigint(20) NOT NULL,
        data_key varchar(255) NOT NULL,
        data_value longtext,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY user_id (user_id),
        KEY data_key (data_key)
    ) {$charset};";

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

    add_option('my_plugin_db_version', MY_PLUGIN_VERSION);
});

dbDelta умеет обновлять существующие таблицы при обновлении плагина — не удаляет данные, только добавляет недостающие столбцы и индексы.

Для запросов — только через $wpdb->prepare(), никаких конкатенаций строк с пользовательскими данными:

$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}my_plugin_data WHERE user_id = %d AND data_key = %s",
        get_current_user_id(),
        $key
    )
);

Страница настроек в админке

Settings API — стандартный способ добавить страницу настроек с нативным интерфейсом WordPress:

class My_Plugin_Admin {
    public function add_plugin_admin_menu(): void {
        add_options_page(
            'Настройки My Plugin',
            'My Plugin',
            'manage_options',
            'my-plugin',
            [$this, 'display_plugin_setup_page']
        );
    }

    public function __construct() {
        add_action('admin_init', [$this, 'register_settings']);
    }

    public function register_settings(): void {
        register_setting('my_plugin_options', 'my_plugin_api_key', [
            'type'              => 'string',
            'sanitize_callback' => 'sanitize_text_field',
        ]);

        add_settings_section('my_plugin_main', 'Основные настройки', null, 'my-plugin');

        add_settings_field('api_key', 'API ключ', function () {
            $value = get_option('my_plugin_api_key', '');
            printf('<input type="text" name="my_plugin_api_key" value="%s" class="regular-text">', esc_attr($value));
        }, 'my-plugin', 'my_plugin_main');
    }

    public function display_plugin_setup_page(): void {
        if (!current_user_can('manage_options')) {
            return;
        }
        echo '<div class="wrap"><h1>' . esc_html(get_admin_page_title()) . '</h1>';
        echo '<form method="post" action="options.php">';
        settings_fields('my_plugin_options');
        do_settings_sections('my-plugin');
        submit_button();
        echo '</form></div>';
    }
}

AJAX-обработчики

// Регистрация обработчика
add_action('wp_ajax_my_plugin_action', [$this, 'handle_ajax']);
add_action('wp_ajax_nopriv_my_plugin_action', [$this, 'handle_ajax']); // для незалогиненных

public function handle_ajax(): void {
    check_ajax_referer('my_plugin_nonce', 'nonce');

    $input = sanitize_text_field($_POST['data'] ?? '');

    // бизнес-логика
    $result = $this->process($input);

    wp_send_json_success(['result' => $result]);
    // или wp_send_json_error(['message' => 'Ошибка'], 400);
}

На клиенте:

fetch(childTheme.ajaxUrl, {
  method: 'POST',
  body: new URLSearchParams({
    action: 'my_plugin_action',
    nonce: childTheme.nonce,
    data: inputValue,
  }),
})
  .then(r => r.json())
  .then(r => { if (r.success) console.log(r.data.result) });

REST API вместо AJAX

Для современных интерфейсов на React/Vue предпочтительнее REST API. Подробнее — в отдельной услуге «Разработка кастомных REST API эндпоинтов WordPress».

Интернационализация

Все строки в плагине должны быть обёрнуты в функции перевода с указанием text domain:

__('Строка для перевода', 'my-plugin')
_e('Вывести строку', 'my-plugin')
esc_html__('Безопасный вывод', 'my-plugin')
sprintf(__('Привет, %s!', 'my-plugin'), $username)

.pot-файл генерируется через WP-CLI: wp i18n make-pot . languages/my-plugin.pot.

Деактивация и удаление

register_deactivation_hook(__FILE__, function () {
    // Убираем cron-задания, временные данные
    wp_clear_scheduled_hook('my_plugin_cron');
});

register_uninstall_hook(__FILE__, 'my_plugin_uninstall');

function my_plugin_uninstall(): void {
    // Удаляем только если пользователь согласился
    if (get_option('my_plugin_delete_data_on_uninstall')) {
        global $wpdb;
        $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}my_plugin_data");
        delete_option('my_plugin_api_key');
    }
}

Типовые сроки по сложности

Простой плагин (шорткод + страница настроек) — 2–3 дня. Плагин со своими таблицами, AJAX-интерфейсом и интеграцией со сторонним API — 5–8 дней. Плагин с метабоксами, кастомными таблицами в списке записей, сложной логикой ролей и правил — от 10 дней.