Права ролей в WordPress: четыре подхода на живом проекте
На проекте четыре человека с разными задачами, и всем выдали «Администратора», потому что иначе половина кнопок пропадает. Разбираем, как развести права в WordPress 6.x и чем платит каждый из четырёх способов.

Расклад, который встречается чаще всего: на корпоративном сайте с магазином, кроме владельца, работают четверо — штатный редактор, SEO-подрядчик, контент-менеджер товаров и бухгалтер, который смотрит заказы. Всем четырём выдана роль «Администратор», потому что при роли «Редактор» у SEO-специалиста пропадает меню плагина, а у контент-менеджера — кнопка «Добавить товар». Через месяц кто-то отключил плагин кеширования и поменял структуру постоянных ссылок. Кто именно — неизвестно: журнала изменений в ядре WordPress нет. Ниже — четыре рабочих способа развести права в WordPress 6.x и цена каждого.
Что кому нужно на самом деле
Прежде чем трогать роли, полезно перевести задачи в capability — права, из которых роли и собраны:
- Штатный редактор. Записи и страницы свои и чужие, медиафайлы, модерация комментариев — это стандартная роль Editor. Плюс просит доступ к меню навигации, а это
edit_theme_options, которого у Editor нет. - SEO-подрядчик. Нужны настройки SEO-плагина и правка заголовков в опубликованных материалах. Большинство плагинов вешают свои страницы на
manage_options— то есть на весь раздел «Настройки» целиком. - Контент-менеджер товаров. Только тип записи «товар», ничего больше. Если тип записи зарегистрирован со стандартным
capability_typepost, отделить товары от новостей штатно невозможно. - Бухгалтер. Чтение заказов и выгрузка. Из прав —
readи доступ к одному экрану.
Почему «просто дать админку» дороже, чем кажется
Роль «Администратор» на одиночной установке — это не «чуть больше кнопок». Там лежат четыре права, каждое из которых означает контроль над сайтом:
install_pluginsиactivate_plugins— возможность положить на сервер произвольный PHP-код под видом плагина;promote_users— возможность менять роли, в том числе поднять себе или коллеге уровень доступа;unfiltered_html— произвольный HTML иscriptв контенте, то есть сохранённый XSS руками сотрудника;manage_options— постоянные ссылки, главная страница, адрес сайта. Смена структуры ссылок на живом проекте ломает индексацию, и откатывается это не кнопкой.
Отсюда и цена вопроса: без разделения прав любой инцидент разбирается не по логу, а по бэкапу и памяти участников.
Роли в WordPress живут в базе данных, а не в коде. Поэтому «кто может удалить страницу» на проде и на тестовом стенде — это два разных вопроса, пока вы не договорились, где источник правды.
Подход 1. Подровнять стандартные роли
Самое дешёвое: взять существующую роль и добавить или убрать у неё одно-два права через объект WP_Role.
// WordPress 6.x. Выполнять один раз, не на каждый запрос.
function acme_tune_editor_role() {
$editor = get_role( 'editor' );
if ( ! $editor ) {
return;
}
// Доступ к меню навигации, виджетам и настройкам темы.
$editor->add_cap( 'edit_theme_options' );
// Запрещаем удалять уже опубликованное.
$editor->remove_cap( 'delete_published_posts' );
$editor->remove_cap( 'delete_published_pages' );
}
Что выигрываете: минимум кода, всё работает с любыми плагинами, которые проверяют стандартные права. Чем платите: изменение уходит в опцию wp_user_roles в базе и становится невидимым — следующий разработчик будет искать причину в коде плагинов. И edit_theme_options нельзя сузить до одного пункта: вместе с меню открываются виджеты, кастомайзер и настройки темы.
Подход 2. Своя роль, зарегистрированная из кода
Для контент-менеджера и бухгалтера стандартных ролей нет, их надо создавать. Ключевая деталь — add_role() не перезаписывает уже существующую роль и возвращает null, поэтому «поправил массив прав и обновил плагин» не работает. Нужен версионный флаг.
// Плагин acme-roles, главный файл. WordPress 6.x, PHP 7.4+.
define( 'ACME_ROLES_VERSION', '3' );
register_activation_hook( __FILE__, 'acme_sync_roles' );
add_action( 'admin_init', 'acme_maybe_sync_roles' );
function acme_maybe_sync_roles() {
// Пересобираем роли только после смены версии набора прав.
if ( get_option( 'acme_roles_version' ) === ACME_ROLES_VERSION ) {
return;
}
acme_sync_roles();
update_option( 'acme_roles_version', ACME_ROLES_VERSION );
}
function acme_sync_roles() {
// Слаг роли хранится в метаданных пользователя, поэтому
// пересоздание роли с тем же слагом не отвязывает людей.
remove_role( 'acme_shop_content' );
add_role( 'acme_shop_content', 'Контент-менеджер магазина', array(
'read' => true,
'upload_files' => true,
'create_products' => true,
'edit_products' => true,
'edit_published_products' => true,
'publish_products' => true,
'delete_products' => true,
) );
}
Права вида edit_products появляются сами, если тип записи зарегистрирован с собственным типом прав:
register_post_type( 'product', array(
'label' => 'Товары',
'public' => true,
'show_in_rest' => true,
// Ядро сгенерирует edit_products, publish_products,
// edit_others_products, delete_published_products и так далее.
'capability_type' => array( 'product', 'products' ),
'map_meta_cap' => true,
) );
Без 'map_meta_cap' => true проверки по конкретной записи не отобразятся на эти права, и редактор будет видеть «Недостаточно прав» на своём же товаре. Если магазин на WooCommerce, учтите, что плагин приносит свою роль управляющего и своё право manage_woocommerce: его права надо смотреть в документации WooCommerce, а не достраивать вслепую.
Что выигрываете: набор прав лежит в репозитории, одинаково разворачивается на стенде и в проде, проходит код-ревью. Чем платите: поддержкой. Каждый новый плагин со своим правом придётся добавлять в массив руками, а при удалении плагина роль остаётся в базе — нужен обработчик удаления.
Primitive capability — атомарное право, которое физически хранится в роли: edit_posts, manage_options. Meta capability — вопрос про конкретный объект: edit_post для записи с ID 42. Ядро превращает второе в первое функцией map_meta_cap() и одноимённым фильтром. Поэтому вся логика «можно, но не эту страницу» живёт именно в фильтре, а не в роли.
Подход 3. Динамические права через фильтры
Классическая просьба на живом проекте: редактор может править всё, кроме главной страницы. В роли это не выражается — право зависит от объекта. Здесь работает map_meta_cap.
// Главную страницу правит только тот, у кого есть manage_options.
add_filter( 'map_meta_cap', 'acme_protect_front_page', 10, 4 );
function acme_protect_front_page( $caps, $cap, $user_id, $args ) {
if ( 'edit_post' !== $cap || empty( $args[0] ) ) {
return $caps;
}
$front_page_id = (int) get_option( 'page_on_front' );
if ( $front_page_id && (int) $args[0] === $front_page_id
&& ! user_can( $user_id, 'manage_options' ) ) {
$caps[] = 'do_not_allow';
}
return $caps;
}
Что выигрываете: в базу ничего не пишется, логика целиком в коде, условие может быть любым — объект, категория, автор. Чем платите: фильтр вызывается на каждой проверке прав, а проверок за запрос десятки. Ошибка в условии выключает доступ всем, включая вас, и починить это можно только правкой файла. Плагины-редакторы ролей таких правил не видят, поэтому в админке набор прав выглядит иначе, чем работает.
Подход 4. Плагин-редактор ролей
Members, User Role Editor, PublishPress Capabilities — все они делают одно и то же: показывают матрицу «роль — право» и пишут результат в ту же опцию wp_user_roles. Это нормальный выбор, когда на проекте нет разработчика и нужен разовый результат.
Что выигрываете: минуты вместо часов, видимый интерфейс, возможность отдать настройку владельцу сайта. Чем платите: настройки живут только в базе этого сайта. Перенос на стенд — через дамп базы или встроенный экспорт плагина, если он есть. Истории изменений нет, и галочка, снятая полгода назад, выглядит точно так же, как намеренное решение.
Сравнение
| Подход | Где хранится | Переносится на другой стенд | Главный риск |
|---|---|---|---|
| Правка стандартных ролей | Опция в базе, вызов в коде | Да, если код выполняется при деплое | Невидимое изменение чужой роли |
| Своя роль из кода | Опция в базе, набор прав в репозитории | Да | Роль остаётся после удаления плагина |
| Фильтры map_meta_cap / user_has_cap | Только код | Да | Ошибка в условии отключает доступ всем |
| Плагин-редактор ролей | Только база | Нет, кроме дампа или экспорта | Нет истории изменений |
Когда что выбирать
- Нужно добавить или убрать одно право у стандартной роли — подход 1, с обязательным комментарием в коде, почему право снято.
- Нужна роль, которой в ядре нет, и она должна одинаково работать в проде и на стенде — подход 2 с версионным флагом.
- Право зависит от объекта или контекста: конкретная страница, только свои товары, только определённая категория — подход 3, в базу не пишем.
- Одиночный сайт, разработчика нет, задача разовая — подход 4, но сразу зафиксируйте итоговую матрицу хотя бы в текстовом виде.
На разобранном проекте рабочая комбинация такая: редактору — подход 1, контент-менеджеру и бухгалтеру — подход 2, защита главной страницы — подход 3. SEO-подрядчик остаётся самым неудобным случаем: если плагин вешает экран на manage_options, аккуратного решения, кроме фильтра на его собственный хук, нет. Иногда проще дать подрядчику отдельный аккаунт с ограниченным сроком, чем выдавать manage_options насовсем.
Что ломается
- Вызов
add_role()на каждый запрос. Регистрация роли пишет в опцию. Код вinitбез проверки версии — это лишние запросы к базе на каждом хите и сброс ручных правок администратора. - Скрытое меню считают защитой. Capability в
add_menu_page()убирает пункт из меню, но не закрывает URL вида/wp-admin/admin.php?page=.... Внутри обработчика нужна отдельная проверкаcurrent_user_can(). - Забытые AJAX и REST. Обработчики
wp_ajax_*и маршруты REST API проверяют права только сами: у маршрута должен быть осмысленныйpermission_callback, а не заглушка, возвращающаяtrue. - Multisite ведёт себя иначе. Часть прав на мультисайте работает не так, как на одиночной установке —
unfiltered_htmlи управление плагинами там завязаны на суперадминистратора. Конфигурацию прав с одиночного сайта нельзя переносить на сеть без проверки. - Права сняли, а путь остался. Если в
wp-config.phpне стоитDISALLOW_FILE_EDIT, любой, кому досталось редактирование файлов темы, правит PHP прямо из браузера. КонстантаDISALLOW_FILE_MODSзакрывает установку и обновление расширений целиком — на проде это обычно правильное состояние.
Что делать дальше
Снимите текущую картину прежде, чем менять что-то ещё. WP-CLI показывает её в двух командах: wp role list выводит роли сайта, wp cap list editor — полный набор прав конкретной роли. Сравните вывод на проде и на стенде: расхождение означает, что кто-то правил роли через админку.
Дальше по порядку: зафиксируйте итоговую матрицу ролей в репозитории, добавьте обработчик удаления плагина, который снимает ваши роли, проверьте permission_callback у своих REST-маршрутов и поставьте DISALLOW_FILE_EDIT. Аудит действий пользователей — отдельная тема: ядро его не ведёт, нужен плагин журналирования.
Задачи: математика, физика, химия: Разбор задачи по шагам с проверкой и похожей задачей
Спросить за 10 ₽Источники: Roles and Capabilities — WordPress Developer Resources, Roles and Capabilities — WordPress Documentation
Частые вопросы
У меня WordPress 5.x, а не 6.x — примеры сработают?
Добавил capability, а нужное меню в админке не появилось. Что не так?
Что будет с моими ролями, если отключить или удалить плагин, который их создал?
Можно дать редактору доступ только к меню навигации, без настроек темы и виджетов?
Код или плагин-редактор ролей на проде?
Какие подводные камни чаще всего вылезают уже в проде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту. Instagram, Facebook и WhatsApp принадлежат компании Meta Platforms Inc., признанной в России экстремистской организацией; её деятельность на территории Российской Федерации запрещена.