Розробка кастомного плагіна 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);
});
Для запитів — тільки через $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. Детальніше — в окремій послузі.
Інтернаціоналізація
Всі рядки у плагіні обернути у функції перекладу з вказанням 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 днів.







