API Rules — механизм контроля доступа в PocketBase. Для каждой коллекции вы задаёте выражения-фильтры для пяти операций: list, view, create, update и delete. Правила проверяются на сервере: если выражение не выполняется, запись не попадёт в выдачу или операция будет отклонена. Если вы только знакомитесь с PocketBase, начните со статьи что такое PocketBase.
Синтаксис API Rules: основы
Есть три состояния правила:
null(правило заблокировано, в панели — замок) — операция доступна только superuser;- пустая строка
""— операция доступна всем, включая неавторизованных; - выражение — операция доступна, если выражение выполняется.
# Открыто всем
listRule: ""
# Только superuser
listRule: null
# Только авторизованные
listRule: @request.auth.id != ""
# Только владелец
listRule: user = @request.auth.id
Не путайте null и "": это противоположные по смыслу значения.
Доступные переменные
@request.auth — авторизованный пользователь:
@request.auth.id— ID (пустая строка, если не авторизован);@request.auth.email,@request.auth.verified;@request.auth.collectionName— имя auth-коллекции;@request.auth.<поле>— любое поле записи пользователя.
@request.body.<поле> — значение поля из тела запроса (create/update).
@request.query.<параметр>, @request.headers.<заголовок>, @request.method — параметры запроса.
@collection.<коллекция>.<поле> — обращение к другой коллекции, даже если прямой связи нет.
@now — текущее время (UTC).
Операторы сравнения
= равно
!= не равно
> больше
>= больше или равно
< меньше
<= меньше или равно
~ содержит (LIKE '%value%')
!~ не содержит
?= хотя бы один элемент равен
?!= хотя бы один элемент не равен
?~ хотя бы один элемент содержит
Операторы с ? нужны для полей с несколькими значениями: multiple relation, multiple select, обратные связи. Без ? условие должно выполняться для всех элементов.
Логические операторы
&& И
|| ИЛИ
() группировка
Паттерн 1: Публичная коллекция (блог, каталог)
# Коллекция: articles
listRule: published = true
viewRule: published = true || author = @request.auth.id
createRule: @request.auth.id != "" && @request.body.author = @request.auth.id
updateRule: author = @request.auth.id
deleteRule: author = @request.auth.id
- список: всем видны только опубликованные статьи;
- просмотр: опубликованные — всем, черновики — автору;
- создание: только авторизованные и только от своего имени;
- изменение и удаление: только автор.
Паттерн 2: Личная коллекция
Пользователь видит и меняет только свои записи:
# Коллекция: notes
listRule: owner = @request.auth.id
viewRule: owner = @request.auth.id
createRule: @request.auth.id != "" && @request.body.owner = @request.auth.id
updateRule: owner = @request.auth.id &&
(@request.body.owner:isset = false || @request.body.owner = @request.auth.id)
deleteRule: owner = @request.auth.id
Проверка @request.body.owner в createRule не даёт создать запись от имени другого пользователя. В updateRule модификатор :isset запрещает «передать» запись чужому владельцу.
Паттерн 3: Командный доступ (SaaS с ролями)
Структура: коллекция team_members с полями team, user, role (admin, member, viewer). У самой teams нет поля со списком участников, поэтому членство проверяется через @collection.team_members или обратную связь team_members_via_team.
# Коллекция: projects (поле team → teams)
listRule: @collection.team_members.team ?= team &&
@collection.team_members.user ?= @request.auth.id
viewRule: @collection.team_members.team ?= team &&
@collection.team_members.user ?= @request.auth.id
createRule: @collection.team_members.team ?= @request.body.team &&
@collection.team_members.user ?= @request.auth.id &&
@collection.team_members.role ?!= "viewer"
updateRule: @collection.team_members.team ?= team &&
@collection.team_members.user ?= @request.auth.id &&
@collection.team_members.role ?!= "viewer"
deleteRule: team.owner = @request.auth.id ||
(@collection.team_members.team ?= team &&
@collection.team_members.user ?= @request.auth.id &&
@collection.team_members.role ?= "admin")
Главная ловушка. Проверки team, user и role должны сработать для одной и той же записи членства. Правило вида team.members.role ?!= "viewer", где роль проверяется отдельно от пользователя, означает «в команде есть хоть кто-то не-viewer» — и тогда viewer получает права на запись. Каждое командное правило проверяйте на тестовых пользователях: admin, member и viewer в двух разных командах. Если правило становится нечитаемым, перенесите проверку роли в хук (пример ниже).
Паттерн 4: Публичное чтение, запись для авторизованных
Форумы, комментарии, отзывы. Предполагается, что в users есть поле role:
# Коллекция: comments
listRule: ""
viewRule: ""
createRule: @request.auth.id != "" && @request.body.author = @request.auth.id
updateRule: author = @request.auth.id
deleteRule: author = @request.auth.id || @request.auth.role = "moderator"
Паттерн 5: Только подтверждённые пользователи
# Коллекция: premium_content
listRule: @request.auth.id != "" && @request.auth.verified = true
viewRule: @request.auth.id != "" && @request.auth.verified = true
Create, update и delete для такого контента обычно оставляют заблокированными (null) — их выполняет superuser или серверный код.
Паттерн 6: Доступ до определённой даты
# Коллекция: temp_access
listRule: user = @request.auth.id && expires_at >= @now
viewRule: user = @request.auth.id && expires_at >= @now
Паттерн 7: Роли в коллекции users
Если роль хранится в самой записи пользователя (role: Select [user, moderator, admin]):
# Коллекция: reports
listRule: @request.auth.id != ""
viewRule: owner = @request.auth.id || @request.auth.role = "admin"
createRule: @request.auth.id != ""
updateRule: owner = @request.auth.id || @request.auth.role = "admin"
deleteRule: @request.auth.role = "admin"
Используйте =, а не ~: @request.auth.role ~ "admin" совпадёт и с ролью superadmin. И запретите пользователю менять собственную роль: в updateRule коллекции users добавьте @request.body.role:isset = false.
Superuser обходит все правила
Запросы от superuser игнорируют API Rules. Поэтому учётные данные superuser нельзя отправлять в браузер — только серверный код.
// Неправильно: токен superuser в браузере
const pb = new PocketBase('https://myapp.ru')
pb.authStore.save(SUPERUSER_TOKEN, null) // никогда так не делайте
// Правильно: только на сервере (Route Handler, Server Action, воркер)
const adminPb = new PocketBase(process.env.PB_URL!)
await adminPb.collection('_superusers').authWithPassword(
process.env.PB_SUPERUSER_EMAIL!,
process.env.PB_SUPERUSER_PASSWORD!
)
const allUsers = await adminPb.collection('users').getFullList()
В старых версиях PocketBase для этого был pb.admins.authWithPassword; в актуальных администраторы — это записи системной коллекции _superusers.
Отладка API Rules
Запустите PocketBase в режиме разработки — он будет печатать выполняемые SQL-запросы, по ним видно, во что превратилось правило:
./pocketbase serve --dev
Проверяйте правила запросами от разных пользователей:
# Без авторизации
curl http://localhost:8090/api/collections/posts/records
# С авторизацией
TOKEN=$(curl -s -X POST http://localhost:8090/api/collections/users/auth-with-password \
-H "Content-Type: application/json" \
-d '{"identity":"test@example.com","password":"password123"}' | jq -r '.token')
curl http://localhost:8090/api/collections/posts/records \
-H "Authorization: $TOKEN"
Обратите внимание: если listRule не выполняется, PocketBase не возвращает ошибку, а просто отдаёт пустой список. Для view/update/delete недоступная запись выглядит как 404.
Сложные проверки через хуки
Лимиты по тарифу, подсчёты и проверки, которые не выражаются фильтром, переносите в хуки:
// pb_hooks/check_quota.pb.js
onRecordCreateRequest((e) => {
const auth = e.auth
if (!auth) {
throw new UnauthorizedError('Требуется авторизация')
}
const count = $app.countRecords(
'projects',
$dbx.exp('owner = {:userId}', { userId: auth.id })
)
const maxProjects = auth.getString('plan') === 'pro' ? 50 : 5
if (count >= maxProjects) {
throw new ForbiddenError(`Достигнут лимит проектов (${maxProjects}) для вашего тарифа`)
}
e.next()
}, 'projects')
Перед изменением правил — бэкап pb_data
Правила и схема коллекций хранятся в pb_data/data.db (а при включённых автомиграциях — ещё и в pb_migrations). Ошибочная миграция или неудачная правка схемы может удалить поле вместе с данными. Перед такими изменениями сделайте копию базы — например, встроенным бэкапом в панели или sqlite3 pb_data/data.db ".backup 'before-rules.db'". Автоматическая схема с версиями и проверкой восстановления описана в статье бэкап PocketBase.
Частые вопросы
Как разрешить операцию только superuser?
Оставьте правило заблокированным (null), а не пустой строкой: "" открывает доступ всем. Для частичных прав без superuser добавьте в users поле is_admin (Bool) и проверяйте @request.auth.is_admin = true, запретив пользователям менять это поле.
Почему для связей нужен ?=, а не =?
Для полей с несколькими значениями = означает «все элементы равны», а ?= — «хотя бы один равен». Для проверки членства почти всегда нужен ?=. Для одиночной relation, которая хранит один ID, работает обычный =.
Можно ли скрыть поля на уровне сервера?
Параметр ?fields= выбирает клиент, поэтому он не защищает данные. На сервере отметьте поле как скрытое (Hidden) в настройках коллекции — тогда его увидит только superuser — или меняйте ответ в хуке onRecordEnrich. Другой вариант — отдельная view-коллекция с нужными полями.
Как проверить правила без клиентского кода?
В панели у каждой коллекции есть API Preview: он показывает эндпоинты, параметры и действующие правила, но запросы не выполняет. Сами проверки удобно делать через curl, Postman или небольшой скрипт на SDK от имени разных тестовых пользователей.
Работают ли API Rules для realtime-подписок?
Да. При подписке на всю коллекцию (subscribe('*')) применяется listRule, при подписке на конкретную запись — viewRule. Пользователь получает события только по записям, которые ему разрешено видеть.



