Как добавить свой плагин в репозиторий WordPress
Как добавить свой плагин в официальный репозиторий WordPress и пройти модерацию?
Официальный репозиторий WordPress.org — это не просто каталог, куда можно загрузить ZIP-файл. Это публичная экосистема, где плагин должен быть безопасным, понятным, совместимым с WordPress Coding Standards, лицензированным под GPL-совместимой лицензией и готовым к поддержке. WordPress.org прямо пишет, что цель каталога — дать пользователям безопасное место для скачивания плагинов, совместимых с целями проекта WordPress.
Ниже практическая инструкция от подготовки плагина до повторной отправки после замечаний модерации.
1. Сначала проверьте, подходит ли плагин для WordPress.org
Перед отправкой нужно понять: плагин должен быть полноценным, законченным и рабочим. WordPress.org указывает, что complete plugin должен быть доступен уже на момент отправки, а версии плагина должны увеличиваться при каждом новом релизе.
Плохая идея отправлять:
- сырой MVP без проверки на чистом WordPress;
- плагин, который требует ручной правки кода после установки;
- плагин без readme.txt;
- плагин с тестовыми файлами, архивами, логами, .git, node_modules, vendor, если они не нужны;
- плагин с непонятной лицензией;
- плагин, который подключает сторонние сервисы, но не раскрывает это в описании.
Для репозитория важно, чтобы пользователь мог установить плагин стандартным способом: Плагины → Добавить новый → Загрузить плагин, активировать его и понять, что делать дальше.
2. Подготовьте правильную структуру плагина
Минимальная структура хорошего плагина:
your-plugin-slug/
├── your-plugin-slug.php
├── readme.txt
├── LICENSE.txt
├── uninstall.php
├── languages/
│ └── index.php
└── assets или includes, если нужны
Главный PHP-файл должен содержать plugin headers. Например:
<?php
/**
* Plugin Name: RZ Email Collector
* Plugin URI: https://example.ru/
* Description: Collect email subscribers and send newsletter digests.
* Version: 1.0.0
* Requires at least: 5.8
* Tested up to: 6.6
* Requires PHP: 7.4
* Author: Roman Bondar
* Author URI: https://example.ru/
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: your-plugin-slug
* Domain Path: /languages
*/
Важный нюанс: начиная с WordPress 5.8, поля Requires PHP и Requires at least для каталога берутся из главного PHP-файла плагина, а не из readme.txt. Это указано в официальной документации WordPress по readme.txt.
Если вы указали:
Domain Path: /languages
то папка /languages должна реально существовать. Иначе Plugin Check выдаст предупреждение.
3. Сделайте корректный readme.txt
readme.txt управляет тем, как плагин будет отображаться на странице WordPress.org. Документация WordPress прямо указывает, что каждый плагин должен иметь readme.txt, соответствующий стандарту WordPress plugin readme file standard.
Пример структуры:
=== Plugin Name ===
Contributors: yourwordpressorglogin
Tags: email, newsletter, subscribers, smtp, recaptcha
Requires at least: 5.8
Tested up to: 6.6
Stable tag: 1.0.0
Requires PHP: 7.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Short description up to 150 characters.
== Description ==
Detailed plugin description.
== Installation ==
1. Upload the plugin.
2. Activate it.
3. Configure settings.
== Frequently Asked Questions ==
= Does this plugin use third-party services? =
Yes, if enabled by the administrator.
== Screenshots ==
1. Settings page.
2. Subscribers list.
== Changelog ==
= 1.0.0 =
Initial release.
== Upgrade Notice ==
= 1.0.0 =
Initial release.
Нюансы:
- Contributors — это именно логины WordPress.org, а не произвольные имена.
- Tags — от 1 до 5 тегов; WordPress.org указывает, что теги должны быть 1–5 терминами, описывающими плагин.
- Stable tag должен совпадать с версией релиза.
- Описание первой строки лучше держать до 150 символов, потому что именно оно выводится под названием плагина.
4. Лицензия: только GPL-совместимые компоненты
WordPress.org требует, чтобы плагины были совместимы с GNU GPL. В официальных правилах сказано: весь код, данные, изображения и сторонние библиотеки, которые размещаются в каталоге WordPress.org, должны соответствовать GPL или GPL-compatible лицензии.
Практически это значит:
- добавьте LICENSE.txt;
- укажите License: GPLv2 or later;
- не включайте в ZIP картинки, шрифты, библиотеки или шаблоны, если вы не уверены в лицензии;
- не добавляйте платные ассеты без права распространения;
- не копируйте чужой код без лицензии.
Если плагин использует сторонние сервисы, например Google reCAPTCHA, SMTP-сервер, API рассылок, CRM или аналитику, это нужно честно описать в readme.txt: что подключается, когда подключается, какие данные передаются и где находятся условия сервиса. WordPress.org указывает, что разработчик отвечает за содержимое и действия своего плагина, а также за соблюдение условий сторонних API и сервисов.
5. Уникальный префикс — один из главных пунктов модерации
Это частая причина отказа. Все функции, классы, опции, хуки, shortcode, AJAX actions, cron hooks, transients, global variables и таблицы БД должны иметь уникальный префикс.
В моем случае модерация WordPress.org прямо указала, что короткий префикс rz не подходит, потому что префикс должен быть минимум 4 символа, а короткие префиксы повышают риск конфликтов.
Плохо:
class RZ_Email_Collector {}
add_shortcode( 'rz_email_collector', ... );
update_option( 'rz_email_collector_settings', $data );
set_transient( 'rz_ec_lock', '1' );
Лучше:
class RZEMAILCOLLECTOR_Plugin {}
add_shortcode( 'rzemailcollector', ... );
update_option( 'rzemailcollector_settings', $data );
set_transient( 'rzemailcollector_lock', '1' );
Важно: нельзя использовать wp_, _ или __ как собственный префикс, потому что такие обозначения зарезервированы для WordPress. В письме модерации также было указано, что if ( ! function_exists() ) не решает проблему конфликтов: если другой код загрузится раньше, ваш плагин может просто сломаться.
Префикс должен быть везде:
- functions
- classes
- traits
- interfaces
- constants
- options
- transients
- cron hooks
- shortcodes
- AJAX actions
- admin_post actions
- database tables
- CSS classes
- JS globals
- uninstall.php variables
Даже переменные в глобальной области uninstall.php желательно префиксовать, потому что Plugin Check может показать предупреждение по Naming Conventions.
6. Безопасность: sanitize, validate, escape
WordPress.org в разделе common issues прямо формулирует базовый принцип: входящие данные нужно sanitize/validate, а выводимые данные escape. Там же приводится практическая формула: “Sanitize early, Escape late, Always Validate”.
Входящие данные
Любые данные из:
$_POST $_GET $_REQUEST $_FILES $_SERVER
нужно обрабатывать.
Примеры:
$email = isset( $_POST['email'] )
? sanitize_email( wp_unslash( $_POST['email'] ) )
: '';
if ( ! is_email( $email ) ) {
return;
}
$page = isset( $_GET['paged'] )
? absint( $_GET['paged'] )
: 1;
$url = isset( $_POST['buy_url'] )
? esc_url_raw( wp_unslash( $_POST['buy_url'] ) )
: '';
$text = isset( $_POST['intro'] )
? sanitize_textarea_field( wp_unslash( $_POST['intro'] ) )
: '';
Главная ошибка новичков — просто проверить, что поле существует, но не очистить его.
Плохо:
if ( isset( $_POST['subject'] ) ) {
update_option( 'myplugin_subject', $_POST['subject'] );
}
Хорошо:
$subject = isset( $_POST['subject'] )
? sanitize_text_field( wp_unslash( $_POST['subject'] ) )
: '';
update_option( 'myplugin_subject', $subject );
Вывод данных
В HTML:
echo esc_html( $title );
В атрибуте:
echo esc_attr( $value );
В URL:
echo esc_url( $url );
В textarea:
echo esc_textarea( $text );
Если нужно разрешить ограниченный HTML:
echo wp_kses_post( $content );
Или свой allowlist:
echo wp_kses( $html, $allowed_tags );
7. Nonce и права доступа
Для любых админских действий нужен nonce и проверка прав.
Пример:
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Access denied.', 'your-text-domain' ) );
}
check_admin_referer( 'yourplugin_save_settings' );
Для admin_post:
add_action( 'admin_post_yourplugin_save_settings', 'yourplugin_save_settings' );
function yourplugin_save_settings() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Access denied.', 'your-text-domain' ) );
}
check_admin_referer( 'yourplugin_save_settings' );
// Save data.
}
Для публичных ссылок подтверждения email или отписки nonce может быть не нужен, если используется криптографически стойкий одноразовый токен или HMAC-токен. Но Plugin Check может всё равно показать предупреждение. В таких местах лучше:
- убедиться, что токен действительно безопасный;
- не менять критичные данные без проверки токена;
- добавить комментарий для ревьюеров;
- не использовать обычный
id=123как ссылку отписки.
8. Работа с базой данных
Если плагин использует собственные таблицы, это допустимо, но все SQL-запросы должны быть безопасными.
Пользовательские значения — только через $wpdb->prepare():
$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$table_name} WHERE email = %s LIMIT 1",
$email
)
);
Но есть нюанс: имена таблиц нельзя передать как placeholder %s, потому что это не значение, а SQL identifier. Поэтому имя таблицы должно формироваться только внутри плагина, например:
$table_name = esc_sql( $wpdb->prefix . 'yourplugin_subscribers' );
А пользовательские значения — через placeholders:
$wpdb->prepare(
"SELECT * FROM {$table_name} WHERE email = %s",
$email
);
Plugin Check может ругаться на интерполяцию имени таблицы даже тогда, когда таблица внутренняя и безопасная. В таких случаях важно не маскировать реальную проблему, а проверить:
- таблица не берётся из
$_GETили$_POST; - таблица строится из
$wpdb->prefix+ фиксированная строка; - имя таблицы проходит через
esc_sql(); - все пользовательские значения идут через
$wpdb->prepare().
Для создания таблиц лучше использовать dbDelta(). В документации WordPress отдельный раздел посвящён созданию таблиц плагином через dbDelta, и этот подход используется именно для управления структурой собственных таблиц плагина.
9. Uninstall.php: удалить данные безопасно и только по правилам
Если плагин создаёт свои таблицы или опции, лучше добавить uninstall.php.
Минимальный шаблон:
<?php
/**
* Uninstall cleanup.
*/
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
exit;
}
global $wpdb;
$yourplugin_delete_on_uninstall = get_option( 'yourplugin_delete_on_uninstall', '0' );
if ( '1' !== $yourplugin_delete_on_uninstall ) {
return;
}
$yourplugin_table = esc_sql( $wpdb->prefix . 'yourplugin_subscribers' );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange
$wpdb->query( "DROP TABLE IF EXISTS {$yourplugin_table}" );
delete_option( 'yourplugin_delete_on_uninstall' );
Важный нюанс: если вы удаляете таблицы сразу при удалении плагина, пользователь может случайно потерять данные. Лучше добавить настройку: “Удалять данные при uninstall”. По умолчанию — выключено.
Также префиксуйте переменные в uninstall.php. Даже если это кажется мелочью, Plugin Check может показать предупреждение вида NonPrefixedVariableFound.
10. Не используйте прямые PHP-файловые операции без необходимости
Plugin Check часто ругается на:
fopen()
fclose()
file_put_contents()
unlink()
WordPress предпочитает WP Filesystem API для работы с файловой системой. Если речь о CSV-экспорте в поток php://output, иногда прямой поток оправдан, но лучше использовать точечный комментарий для PHPCS и объяснить, почему это не запись файлов на сервер.
Пример:
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Required for streaming CSV output to the browser.
$output = fopen( 'php://output', 'w' );
Но такие комментарии нельзя использовать как способ закрыть глаза на проблему. Если файл реально записывается на диск, используйте WP Filesystem API.
11. Подключайте скрипты и стили правильно
Нельзя просто выводить:
echo '<script src="..."></script>';
или подключать CSS напрямую в HTML админки.
Правильно:
wp_enqueue_script(
'yourplugin-admin',
plugin_dir_url( __FILE__ ) . 'assets/admin.js',
array( 'jquery' ),
'1.0.0',
true
);
wp_enqueue_style(
'yourplugin-admin',
plugin_dir_url( __FILE__ ) . 'assets/admin.css',
array(),
'1.0.0'
);
Если используется внешний скрипт, например reCAPTCHA, это нужно документировать в readme.txt, потому что это сторонний сервис.
12. Раскройте сторонние сервисы
Если плагин передаёт данные наружу, это нужно описать. Например:
- Google reCAPTCHA;
- SMTP-сервер;
- API рассылки;
- CRM;
- аналитика;
- CDN;
- внешние изображения;
- внешние шрифты.
В readme.txt можно добавить раздел:
== Third-party services ==
This plugin can connect to Google reCAPTCHA v3 if the site administrator enables it and enters API keys. The service is used to verify subscription form submissions.
This plugin can send emails through an SMTP server configured by the site administrator. Email addresses and message content are sent to the configured SMTP server for delivery.
Нужно указать, что сервис включается администратором, какие данные передаются, и дать ссылки на terms/privacy стороннего сервиса. WordPress.org в правилах указывает, что разработчик отвечает за соблюдение условий сторонних API и сервисов.
13. Интернационализация
Если плагин публикуется в WordPress.org, текст должен быть готов к переводу:
esc_html__( 'Settings saved.', 'your-text-domain' );
esc_html_e( 'Subscribe', 'your-text-domain' );
Text Domain должен совпадать со slug плагина. Если указан Domain Path: /languages, папка должна существовать.
14. Проверьте плагин перед отправкой
Минимальный чек-лист:
- Установить чистый WordPress.
- Включить WP_DEBUG.
- Установить плагин через ZIP.
- Активировать.
- Проверить публичную часть.
- Проверить админку.
- Проверить сохранение настроек.
- Проверить uninstall.
- Проверить Plugin Check.
- Проверить PHP syntax.
- Проверить ZIP integrity.
- Проверить, что нет .git, node_modules, лишних архивов и логов.
WordPress.org прямо просит после замечаний ревью протестировать исправленный плагин на чистой установке WordPress с WP_DEBUG = true. Это требование было указано и в вашем письме от команды проверки.
Для проверки синтаксиса:
php -l your-plugin-slug.php
php -l uninstall.php
Для проверки архива:
unzip -t your-plugin.zip
Для поиска опасных функций:
grep -R "eval\|exec\|shell_exec\|system\|passthru\|proc_open\|popen" your-plugin-folder/
15. Используйте Plugin Check, PHPCS и WPCS
Plugin Check полезен тем, что показывает многие ошибки ещё до отправки. Он может подсветить:
- отсутствие экранирования;
- непроверенный
$_POST; - отсутствие nonce;
- короткие префиксы;
- прямые SQL-запросы;
- неподготовленные SQL-запросы;
- прямые файловые операции;
- неправильный
readme.txt; - отсутствие папки
languages; - проблемы с лицензией.
WordPress Developer Blog писал, что Plugin Check помогает разработчикам проверить код перед отправкой, сократить цикл ревью и исправить проблемы до официальной проверки.
Но важно понимать: Plugin Check не заменяет ручную модерацию. В официальном разделе common issues сказано, что список распространённых проблем не является полным, а итог ревью зависит от ручной проверки команды.
16. Как отправить плагин на проверку
Официальная инструкция WordPress.org описывает три шага: зарегистрироваться на WordPress.org с актуальным email, добавить plugins@wordpress.org в whitelist и отправить готовый ZIP через форму добавления плагина. ZIP должен быть полной версией плагина, такой же, как для ручной установки через админку WordPress.
После отправки плагин становится в очередь. В документации указано, что после постановки в очередь код проверяется на проблемы в течение 14 рабочих дней; если проблемы найдены, с разработчиком связываются, а после одобрения высылают доступ к SVN-репозиторию.
На практике сроки могут отличаться, поэтому не стоит планировать запуск рекламной кампании на завтра до одобрения плагина.
17. Что делать, если пришёл отказ
Отказ — это нормально. Главное — читать письмо внимательно.
В письме обычно есть:
- проблема
- объяснение
- примеры
- строки кода
- ожидаемое исправление
- чек-лист перед повторной отправкой
- Review ID
Не нужно отвечать эмоционально. Нужно:
- Исправить конкретные проблемы.
- Самостоятельно найти похожие проблемы по всему коду.
- Прогнать Plugin Check.
- Протестировать на чистом WordPress.
- Загрузить новую версию через форму.
- Ответить на письмо коротко и по делу.
В вашем письме команда прямо указала: исправьте все проблемы по обратной связи и по собственному ревью, используйте Plugin Check, PHPCS/WPCS или похожие инструменты, протестируйте на чистой установке с WP_DEBUG = true, загрузите обновлённую версию и ответьте на письмо.
Хороший ответ:
Hello Plugin Review Team,
Thank you for the review.
I have updated the plugin and addressed the reported prefixing issue across classes, options, shortcodes, hooks, cron events, transients, database tables, and uninstall variables.
I tested the updated version on a clean WordPress installation with WP_DEBUG enabled and uploaded the corrected ZIP through the plugin submission page.
Best regards,
Roman
Не нужно отправлять длинный список всех правок, если команда прямо просит быть кратким.
18. Что происходит после одобрения
После одобрения WordPress.org выдаёт SVN-репозиторий. Документация WordPress объясняет, что SVN используется для размещения плагинов на WordPress.org, а workflow отличается от Git.
Стандартная структура SVN:
/your-plugin/
├── trunk/
├── tags/
├── assets/
└── branches/
Обычно:
trunk— текущий код разработки или код будущего релиза;tags/1.0.0— зафиксированная версия релиза;assets— баннеры, иконки, скриншоты для страницы плагина.
WordPress.org требует использовать tags для новых версий: нельзя выпускать плагин только из trunk. Также нужно обновлять Stable Tag в trunk/readme.txt, а после выпуска tagged version уже нельзя изменять.
19. Assets: иконка, баннер, скриншоты
После одобрения желательно оформить страницу плагина.
В /assets можно добавить:
- banner-772×250.png
- banner-1544×500.png
- icon-128×128.png
- icon-256×256.png
- screenshot-1.png
- screenshot-2.png
WordPress.org указывает, что assets — это верхняя директория SVN рядом с trunk, а не trunk/assets. Также документация указывает размеры: например, banner-772×250.png должен быть 772×250, а icon-256×256.png должен быть квадратом 256×256.
Подписи к скриншотам берутся из раздела Screenshots в readme.txt.
20. На что модерация обращает внимание чаще всего
Уникальность имён
Проверяют, чтобы не было коротких или общих префиксов. Префикс rz слишком короткий; лучше использовать что-то вроде rzemailcollector.
Безопасность
Проверяют sanitization, validation, escaping, nonce, capabilities, SQL, загрузку файлов, прямой доступ к файлам.
Прямой доступ к PHP-файлам
В каждом исполняемом PHP-файле обычно должно быть:
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
В uninstall.php:
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
exit;
}
SQL
Проверяют $wpdb->prepare(), отсутствие SQL-инъекций, безопасную работу с именами таблиц.
Внешние сервисы
Проверяют, раскрыты ли reCAPTCHA, SMTP, API и другие внешние подключения.
Лицензия
Проверяют GPL-совместимость всего, что лежит в ZIP.
Лишние файлы
Не должно быть:
.git .svn node_modules tests, если они не нужны в релизе composer.lock без необходимости package-lock.json без необходимости backup.zip debug.log .env .DS_Store .idea .vscode
Нельзя делать скрытые или вредные действия
Плагин не должен собирать лишние данные, скрыто отправлять информацию, подменять чужие настройки, включать рекламу без согласия, выполнять удалённый код или обходить правила WordPress.org.
21. Практический чек-лист перед финальной отправкой
Название и slug:
- Slug уникальный.
- Plugin Name корректный.
- Text Domain совпадает со slug.
Префиксы:
- Функции имеют уникальный префикс 4+ символа.
- Классы имеют уникальный префикс.
- Опции имеют уникальный префикс.
- Cron hooks имеют уникальный префикс.
- Shortcode имеет уникальный префикс.
- AJAX/admin_post actions имеют уникальный префикс.
- Transients имеют уникальный префикс.
- Таблицы БД имеют уникальный префикс.
- Переменные в глобальной области uninstall.php имеют уникальный префикс.
Безопасность:
- Все $_POST/$_GET проходят wp_unslash().
- Все входные данные sanitize.
- Все значения validate.
- HTML-вывод escape.
- URL выводится через esc_url().
- Атрибуты выводятся через esc_attr().
- Email проверяется через sanitize_email() и is_email().
- Админские действия закрыты current_user_can().
- Формы защищены nonce.
- SQL использует $wpdb->prepare().
- Имена таблиц внутренние и безопасные.
- CSV защищён от formula injection, если есть экспорт.
Файлы:
- Нет .git.
- Нет node_modules.
- Нет debug.log.
- Нет .env.
- Нет backup.zip.
- Есть LICENSE.txt.
- Есть readme.txt.
- Есть languages/, если указан Domain Path.
- Есть uninstall.php, если плагин создаёт опции/таблицы.
Readme:
- Contributors — реальные WordPress.org username.
- Tags — не больше 5.
- Stable tag совпадает с версией.
- Changelog заполнен.
- Installation заполнен.
- Third-party services раскрыты.
- Privacy notes добавлены, если плагин работает с персональными данными.
Тесты:
- Установка на чистый WordPress.
- WP_DEBUG = true.
- Активация без ошибок.
- Деактивация без ошибок.
- Удаление без ошибок.
- Настройки сохраняются.
- Frontend работает.
- Plugin Check пройден.
- php -l пройден.
- unzip -t пройден.
Чтобы пройти модерацию WordPress.org, недостаточно написать рабочий плагин. Его нужно оформить как безопасный, поддерживаемый и совместимый open-source продукт: уникальные префиксы, GPL-совместимость, правильный readme.txt, безопасная обработка данных, nonce, capabilities, escaping, прозрачное описание сторонних сервисов и чистая структура ZIP.
Самые частые причины замечаний — короткие префиксы, неочищенные $_POST/$_GET, неэкранированный вывод, неподготовленные SQL-запросы, отсутствие раскрытия сторонних сервисов и мусорные файлы в архиве. В вашем случае реальный отказ был именно по короткому префиксу rz, который WordPress.org посчитал недостаточно уникальным для функций, классов, опций и других элементов плагина.
Правильный подход такой: сначала довести плагин до стандартов WordPress.org локально, потом отправлять ZIP, а если пришли замечания — исправлять не только указанные строки, но и все аналогичные места по всему проекту.
