<? phpukraine СПІВБЕСІДИ
⌕Пошук по платформі
WORDPRESS · JUNIOR ЧАСТО ПИТАЮТЬ

Де класти логіку у WordPress: плагін чи functions.php теми, і чому це важливо при зміні теми?

Тема відповідає за вивід, плагін - за дані й поведінку: усе, що має жити довше за оформлення (CPT, шорткоди, інтеграції, cron), винось у плагін, бо functions.php вимикається разом зі зміною теми.

Ми змінили тему, і з сайту зникли всі товари й шорткоди. Чому?
Чим must-use плагін відрізняється від звичайного і коли він доречний?
Що ви покладете у functions.php, а що винесете в плагін?
Де реєструвати custom post type: у темі чи в плагіні?
плагіни functions.php mu-plugins register_activation_hook

WordPress завантажує плагіни до теми й для будь-якої теми, а functions.php читає тільки з активної теми (і з батьківської, якщо активна дочірня). Звідси й весь розподіл: код у плагіні переживає редизайн, код у темі вимикається в ту секунду, коли хтось натиснув «Активувати» на іншій темі. Кордон проходить приблизно так: тема описує, як контент виглядає, плагін - що це за контент і що з ним відбувається.

Найболючіший сценарій це register_post_type у темі. Після зміни теми клієнт бачить, що з адмінки пропав розділ «Товари», сторінки товарів віддають 404, а на головній порожньо. У базі при цьому все ціле: рядки в wp_posts з post_type = 'shop_product', метадані, зв'язки з термінами. Просто в $wp_post_types більше немає такого типу, тому адмінка не малює меню, WP_Query за публічним запитом його ігнорує, а rewrite-правила не знають про /products/. Лікується поверненням реєстрації в плагін, без відкату бекапу. Так само поводяться шорткоди: контент статей зберігає [product_price id="12"] як текст, і без add_shortcode читач бачить саму дужку.

Окремо стоїть код, який не має бути вимкненим навіть випадково, і тут працюють mu-plugins. Файли з wp-content/mu-plugins підключаються раніше за звичайні плагіни й не мають кнопки деактивації в адмінці. Туди кладуть примусову конфігурацію пошти, заборону редактора файлів, мережеві налаштування Multisite. Обмежень два, і про них питають: хуків активації в mu-plugins не існує взагалі, а сканується лише перший рівень теки, тому плагін із власною структурою підключають однорядковим loader-файлом із require.

Хуки життєвого циклу часто розуміють навпаки. register_activation_hook виконується один раз при активації: там доречні dbDelta для власних таблиць, add_option з версією схеми, add_role, wp_schedule_event і flush_rewrite_rules(), але саме після виклику функції реєстрації CPT, бо інакше правила перебудуються без знання про новий тип. Реєструвати в цьому хуку add_action безглуздо: до наступного запиту від нього не лишиться нічого. register_deactivation_hook симетрично прибирає розписання й кеші, але не дані: плагін вимикають і для діагностики, і втратити каталог через це неприпустимо. Видалення користувацьких даних живе в uninstall.php, який WordPress виконує окремим запитом і без завантаження самого плагіна, тому перший рядок там це перевірка defined('WP_UNINSTALL_PLUGIN').

Мінімальна структура невеликого плагіна: головний файл із докблоком Plugin Name (без нього WordPress плагіна не побачить), inc/ або src/ із класами, uninstall.php, readme.txt за потреби публікації, languages/ для перекладів. Один файл на 200 рядків цілком нормальний плагін; ділити на класи варто тоді, коли з'являються дві незалежні відповідальності. Composer з автозавантаженням у WordPress-плагіні цілком доречний, але префіксуйте залежності (Mozart, PHP-Scoper): у середовищі, де паралельно живуть десятки плагінів, конфлікт версій однієї бібліотеки трапляється регулярно.

<?php
/**
 * Plugin Name: Shop Catalog
 * Version: 1.0.0
 */

// Логіка даних живе в плагіні: тема може змінитись, тип запису лишиться
add_action('init', 'shop_register_product_type');
function shop_register_product_type(): void
{
    register_post_type('shop_product', [
        'labels' => ['name' => 'Товари'],
        'public' => true,
        'has_archive' => true,
        'rewrite' => ['slug' => 'products'],
        'show_in_rest' => true, // потрібно для блокового редактора
    ]);
}

// Шорткод теж у плагіні, інакше старі статті віддадуть сирий текст
add_shortcode('product_price', function (array $atts): string {
    $id = (int) ($atts['id'] ?? 0);

    return esc_html((string) get_post_meta($id, '_price', true));
});

// Активація: одноразові дії, жодних add_action тут
register_activation_hook(__FILE__, function (): void {
    shop_register_product_type();  // спершу реєструємо тип...
    flush_rewrite_rules();         // ...тоді перебудовуємо правила URL
    add_option('shop_schema_version', '1.0.0');
});

// Деактивація: знімаємо розписання, дані користувача не чіпаємо
register_deactivation_hook(__FILE__, function (): void {
    wp_clear_scheduled_hook('shop_sync_stock');
    flush_rewrite_rules();
});
Що functions.php завантажується лише для активної теми, тому вся логіка звідти мовчки вимикається при перемиканні теми або переході на інший шаблон.
Що дані в базі (пости CPT, terms, опції) нікуди не зникають - ламається саме реєстрація, тому записи стають недоступними в адмінці, а шорткоди виводяться як текст.
Критерій розподілу: тема це презентація (шаблони, enqueue стилів, розміри зображень, theme_support), плагін це дані й поведінка (CPT, таксономії, шорткоди, REST-ендпоінти, cron, інтеграції з API).
Що mu-plugins із wp-content/mu-plugins завантажуються автоматично, їх не можна деактивувати з адмінки, і WordPress бачить там лише файли першого рівня, а не теки.
Що register_activation_hook і register_deactivation_hook викликаються один раз і не місце для add_action; flush_rewrite_rules треба після реєстрації CPT, а не замість неї.
Реєструвати register_post_type у functions.php теми, а потім дивуватись, що після зміни теми сотні записів «зникли».
Казати, що зі зміною теми втрачаються дані: втрачається код, який їх описує, самі рядки в wp_posts лишаються.
Класти шорткоди в тему: контент із [gallery_slider] після переходу на іншу тему віддає сирий текст у статтях.
Викликати flush_rewrite_rules() на init при кожному завантаженні сторінки замість активаційного хука - це запис в опції на кожен запит.
Вішати add_action у register_activation_hook: хук активації відпрацює один раз, і жоден із зареєстрованих у ньому колбеків більше не спрацює.
Складати mu-plugin у підтеку wp-content/mu-plugins/my-plugin/my-plugin.php без loader-файлу й вважати, що він завантажиться.
ПОРАДА

Сформулюйте правило одним реченням: «якщо після зміни теми це має продовжити працювати - це плагін». Додайте, що деактивація не має видаляти дані, а видалення користувацьких таблиць чи опцій - робота uninstall.php, а не деактиваційного хука.

оновлено 30 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

functions.php читається лише для активної теми, тому реєстрація зникає, а дані лишаються недоступними, поки код не повернеться в плагін.