Основи безпеки
Supabase "permission denied for schema public": три причини
Supabase "permission denied for schema public" означає, що бракує GRANT на схему. Що його прибрало і який рядок його повертає.

Коротко
- Supabase "permission denied for schema public" означає, що роль, від якої прийшов запит, не має права навіть увійти до схеми public, тож Postgres відмовив ще до того, як подивився бодай на одну таблицю.
- Кожен проєкт Supabase стартує з цим правом для anon, authenticated і service_role. Якщо ваш застосунок отримує цю помилку, щось його прибрало: схему видалили й створили заново, або скрипт відкликав GRANT.
- Повернути його можна одним рядком SQL. GRANT лише пускає роль до ваших таблиць, а які рядки отримає кожен відвідувач, і далі вирішує Row Level Security, тож перевірте ці правила, перш ніж вважати проблему розвʼязаною.
Учора ваш застосунок працював. Сьогодні кожен екран, що читає дані із Supabase,
відкривається порожнім або показує помилку, а в консолі браузера для кожного з
них той самий рядок: permission denied for schema public. Ви відкриваєте
дашборд Supabase, і всі таблиці на місці. SQL Editor читає їх без жодних
нарікань.
Відповідь за цим рядком зазвичай приходить як 401 або 403. Якщо залишити два поля, що мають значення, вона виглядає так:
{
"code": "42501",
"message": "permission denied for schema public"
}
Supabase "permission denied for schema public" походить від Postgres, бази
даних, на якій працює Supabase, і список її причин короткий. Кожен проєкт
Supabase створюється з GRANT, який пускає ваш застосунок до схеми public.
Хтось або щось його прибрало, а повернути його можна одним рядком SQL.
Ось що легко зрозуміти навпаки: цей GRANT це вхідні двері будівлі, а замки на ваших даних стоять далі всередині. Якщо хтось прибрав його, щоб не пустити сторонніх, то разом із ними він не пустив і ваш застосунок. А чи безпечно відчинити ці двері знову, залежить від внутрішніх замків, тож саме їх треба перевірити, перш ніж вважати проблему розвʼязаною.
Що означає "permission denied for schema public"?
Це означає, що роль, від якої прийшов запит, не має права увійти до схеми
public, тож Postgres відмовив ще до того, як подивився бодай на одну таблицю.
Схема це іменована тека з таблицями всередині вашої бази даних. public це
та, куди Lovable, Bolt, v0, Cursor, Claude Code і Replit кладуть ваші таблиці,
якщо їм не сказати інакше. Роль це тип відвідувача, від якого приходить
запит: anon означає когось, хто не увійшов, authenticated когось, хто
увійшов, а service_role ваш власний серверний код із секретним ключем.
GRANT дає одній ролі один дозвіл на одну річ.
Дозвіл, про який ця помилка, називається USAGE, і
документація Postgres
описує його як право шукати ("look up") обʼєкти всередині схеми. Роль без нього
не дістанеться жодної таблиці цієї схеми, хоч би що казали налаштування самої
таблиці.
Якщо табличний варіант цієї помилки
навчив вас уявляти кожну таблицю кімнатою з окремими дверима для кожної ролі,
то схема це будівля, у якій стоять ці кімнати, а USAGE її вхідні двері.
permission denied for schema public означає, що ваш запит стоїть перед ними
надворі. Які кімнати мають відчинені двері, не має значення, бо до жодної
кімнати запит не доходить.
Supabase дає ці вхідні двері всім трьом ролям, коли створює проєкт. Власний образ бази даних Supabase виконує цей рядок у своєму першому скрипті налаштування:
grant usage on schema public to postgres, anon, authenticated, service_role;
Тож якщо ваш застосунок отримує цю помилку, GRANT колись був, а тепер його немає.
Чому SQL Editor досі працює?
Бо SQL Editor не використовує жодної з ролей вашого застосунку. У
посібнику Supabase з усунення проблем
сказано, що запит звідти "routed through the postgres role at the database
level", тобто на рівні бази даних іде через роль postgres.
postgres це роль, якій належать ваші таблиці, а якщо саму схему створили
заново з дашборду або це зробив ваш білдер, то й вона належить цій ролі.
Власникові не потрібен GRANT, щоб увійти до власної будівлі. Тож дашборд
показує кожну таблицю, select у SQL Editor повертає кожен рядок, а той самий
запит від вашого застосунку розвертають біля вхідних дверей. Ніщо на цих
екранах не скаже вам, що ролі застосунку втратили шлях усередину.
Щоб побачити те, що бачить застосунок, спитайте Postgres про його ролі напряму. Вставте це в SQL Editor:
select rolname,
has_schema_privilege(rolname, 'public', 'USAGE') as can_enter
from pg_roles
where rolname in ('anon', 'authenticated', 'service_role');
У кожному рядку має бути true. Рядок із false означає роль, якою
користується ваш застосунок і яка більше не може увійти.
GRANT відчиняє будівлю, а замки всередині
USAGE пускає роль до таблиць схеми. Він нічого не каже про те, якими з цих
таблиць роль може користуватися і які рядки вона отримає.
За вхідними дверима стоять ще дві перевірки, і обидві належать кожній таблиці
окремо. Власний GRANT таблиці вирішує, чи може роль узагалі користуватися цією
таблицею, і саме його брак повідомляє permission denied for table. Далі Row
Level Security, правила для рядків, які ви, можливо, вже знаєте, вирішує, які
рядки отримає ця роль. Postgres ставить усі три запитання в такому порядку й
зупиняється на першій відмові.
Саме цей порядок робить GRANT на схему поганим замком. Для ролі він означає все
або нічого: заберіть його в anon, і кожен екран, який бачить відвідувач, що не
увійшов, перестане працювати одразу, зокрема сторінка входу, якщо вона читає
бодай щось із ваших таблиць. Заберіть його в authenticated, і те саме станеться
з усіма, хто увійшов. Документація Postgres додає, що відкликання "is not a
completely secure way to prevent object access", тобто не є цілком надійним
способом заборонити доступ, і що роль без нього все одно може прочитати назви
ваших таблиць у системних каталогах.
Якщо ви бачите цю помилку, вашому застосунку потрібна роль, якій відмовили.
Причина 1: схему видалили й створили заново
Ця причина забирає з собою найбільше. Хтось виконав ці два рядки або щось, що робить те саме:
drop schema public cascade;
create schema public;
Перший рядок видаляє схему, а через cascade ще й кожну таблицю, представлення
й функцію в ній. Другий створює нову, порожню public. Нова схема належить
ролі, яка її створила, і документація Postgres прямо каже, що типово ніхто інший
не отримує на схему жодних привілеїв. GRANT, який Supabase виконав під час
створення вашого проєкту, належав старій схемі й зник разом із нею, а свій
скрипт налаштування Supabase вдруге не запускає.
Ці два рядки це найкоротший спосіб спорожнити базу Postgres, тож це крок, який ШІ-асистент може зробити, коли ви просите його почати базу з нуля або змусити чисто пройти міграцію, що падає.
Після цього ваші таблиці повертаються, якщо міграції застосунку запускаються знову. Міграція це один із файлів зі змінами бази даних, які пише ваш білдер, і запуск усіх міграцій відбудовує таблиці, які вони описують. Не повертається все, про що вони ніколи не згадують, а GRANT для вхідних дверей Supabase створив ще до появи вашої першої міграції. У міграціях, які написав ваш білдер, його може не бути взагалі.
Разом зі старою схемою зникають ще дві речі:
- Автоматичні GRANT для нових таблиць. Postgres зберігає їх як типові привілеї (default privileges) схеми, і вони видаляються разом із нею. Таблиці, створені після цього, стартують без GRANT для будь-якої з ролей застосунку.
- Рядки. Міграції відбудовують порожні таблиці. Те, що було в старих, повертається лише тоді, коли хтось відновив копію, зроблену до скидання.
Якщо ваші таблиці повернулися порожніми, пошук цієї копії стоїть попереду всього іншого на цій сторінці.
Причина 2: відкликання, яке мало захистити
Другий шлях навмисний. Хтось прочитав, що кожна таблиця в public доступна
через Data API Supabase, тобто вебадресу, через яку ваш застосунок читає й
записує дані, і відкликав GRANT на схему, щоб це закрити.
Сама тривога слушна, а інструмент обрано не той. Власний посібник Supabase із
захисту Data API
ніколи не чіпає GRANT на схему. Він відкликає автоматичний доступ, який
отримують нові таблиці в public, тож кожна нова таблиця лишається зачиненою,
доки хтось свідомо не дасть їй доступ. Supabase вмикає це для всіх наявних
проєктів 30 жовтня 2026 року.
Якщо GRANT на схему прибрав скрипт, який ви запустили, або асистент, якого ви попросили посилити захист бази, перевірочний запит вище покаже, які ролі його втратили. Поверніть його тим, якими користується ваш застосунок, а потім захищайте так, як описують розділи нижче.
Причина 3: помилка приходить із міграції
Якщо повідомлення зʼявляється, коли щось створює таблиці, наприклад міграція,
деплой або Prisma чи Drizzle налаштовують вашу базу, то бракує іншого дозволу.
Щоб створити будь-що в схемі, потрібен CREATE на неї, а текст помилки, коли
бракує CREATE, той самий рядок.
Починаючи з Postgres 15, звичайні ролі типово більше не отримують CREATE на
public.
Документація Postgres
каже, що лише бази, оновлені з версії 14 або старшої, досі дають його всім. Тож
окремий користувач бази, створений для інструмента міграцій, отримує відмову
вже на своїй найпершій таблиці.
Посібник Supabase щодо Prisma
створює користувача prisma, і його налаштування містить
grant create on schema public to prisma.
Рішення це GRANT create для одного цього користувача міграцій. Нікому з
відвідувачів вашого застосунку не потрібно створювати таблиці, тож anon і
authenticated його ніколи не отримують.
Як безпечно повернути GRANT
Виконайте один рядок, переконайтеся, що всі три ролі можуть увійти, а потім перезавантажте застосунок і прочитайте, що він скаже далі.
Рядок для SQL Editor:
grant usage on schema public to anon, authenticated, service_role;
Обмежтеся usage. grant all on schema public теж прибирає помилку, але на
схемі all означає usage плюс create, тобто право додавати власні таблиці.
Supabase радить давати кожній ролі
мінімум, який їй потрібен,
а ролям ваших відвідувачів потрібно лише увійти.
Перевірочний запит, що вище, тепер має показати true для всіх трьох ролей.
Перезавантажте застосунок, і станеться одне з двох:
- Він працює. Вхідні двері були єдиним, чого бракувало.
- Нове повідомлення називає таблицю.
permission denied for tableозначає, що бракує й власного GRANT цієї таблиці. Після першої причини цього варто чекати, бо відбудовані таблиці так і не отримали автоматичних GRANT. Дайте кожній таблиці її GRANT, Row Level Security і правило однією зміною.
Не піддавайтеся спокусі дати кожній ролі все на кожній таблиці, аби зникло друге
повідомлення. Таблиця з GRANT для anon і вимкненим Row Level Security віддає
кожен рядок будь-кому, хто попросить, і у вашому застосунку нічого не виглядатиме
інакше.
Якщо повідомлення називає іншу схему, наприклад permission denied for schema api, цю схему хтось створив сам, і вона так і не отримала GRANT, з якими
стартує public. Посібник Supabase про
власні схеми
перелічує рядки, які їй потрібні.
Що захищати натомість
Засоби, які тримають сторонніх подалі від ваших даних, належать кожній таблиці окремо, і посібник Supabase із захисту їх називає:
- GRANT на кожну таблицю для тих ролей, яким він потрібен. Таблиці
замовлень GRANT для
anonне потрібен узагалі. - Row Level Security на кожній таблиці, з правилом.Як його увімкнути й перевірити, що він тримає.
- Нові таблиці типово зачинені. Відкликання автоматичних GRANT робить це вже сьогодні, а 30 жовтня зробить це за вас.
- Внутрішні таблиці поза Data API. Посібник Supabase описує, як покласти те, що читає ваш застосунок, в окрему схему, щоб усе інше лежало у схемах, які Data API не обслуговує.
Потім подивіться на результат ззовні, бо жоден із цих екранів не показує, що отримує сторонній. Наше безкоштовне сканування питає кожну таблицю, яку називає ваш застосунок, скільки рядків отримав би відвідувач, що не увійшов, читає це число, не завантажуючи жодного рядка, і займає близько 20 секунд без облікового запису: проскануйте свій застосунок.
Як зберегти таблиці й те, що в них
Після сьогоднішнього ще дві речі можуть піти не так: пізніша зміна відкриє таблицю для сторонніх, а скидання, як у першій причині, забере ваші рядки.
Reeve Monitor і далі перевіряє ззовні:
- повна перевірка безпеки щогодини, до трьох застосунків
- повідомлення, коли результат змінюється, щоб таблиця, яка відкрилася після міграції, не чекала, поки ви глянете
- чи працює ваш застосунок
- щомісячний звіт простою мовою про те, що він бачив
Reeve Care тримає копію того, що лежить у ваших таблицях:
- зашифрована копія вашої бази даних Supabase за розкладом, у власному сховищі Reeve в ЄС, поза вашим обліковим записом Supabase
- кожна копія перевірена, перш ніж зарахуватися, підрахунком рядків у кожній таблиці
- ваші вивантажені файли теж, щойно ви підключите доступ до Storage
- відновлення в один клік, із безпечною копією поточного стану, зробленою заздалегідь
- усе, що робить Monitor
Що зробити зараз
Що робити
- Запустіть перевірочний запит у SQL Editor. Кожна роль із
falseце роль, яку ваш застосунок утратив. - Поверніть GRANT рядком
grant usage on schema public to anon, authenticated, service_role;і обмежтесяusage. - Якщо схему видаляли, подивіться, чи не порожні ваші таблиці, і знайдіть копію, зроблену до скидання.
- Якщо далі зʼявиться помилка таблиці, дайте цій таблиці її GRANT, Row Level Security і правило разом.
- Захищайте кожну таблицю окремо, там, де це можна налаштувати, і не чіпайте GRANT на схему.
- Якщо помилка прийшла з міграції, дайте
createкористувачеві міграцій і більше нікому.
Почніть із перевірочного запиту, бо один результат скаже вам, які ролі застосунку втратили шлях усередину. А коли двері знову відчинені, проскануйте свій застосунок, щоб побачити, що він віддає сторонньому.
Поширені запитання
Що означає "permission denied for schema public" у Supabase?
Це означає, що роль, від якої прийшов запит, зазвичай anon або authenticated, не має права увійти до схеми public, тож Postgres відмовив ще до того, як подивився на будь-яку таблицю. Помилка має код 42501 і зазвичай доходить до застосунку як 401 або 403. Кожен проєкт Supabase стартує з цим правом, тож помилка означає, що хтось або щось його прибрало.
Чи безпечно надати usage на схему public?
Так. Кожен проєкт Supabase стартує саме з цим GRANT. Він лише пускає роль до таблиць схеми: кожній таблиці й далі потрібен власний GRANT, а Row Level Security і далі вирішує, які рядки отримає кожен відвідувач. Подивитися варто на таблиці за ним, бо таблиця з GRANT для anon і вимкненим Row Level Security віддає кожен рядок будь-кому, хто попросить.
Чим це відрізняється від "permission denied for table"?
Помилка схеми зупиняє запит ще до того, як він дістанеться будь-якої таблиці, тож усі таблиці схеми ламаються одночасно. Помилка таблиці стосується однієї таблиці, у якої немає GRANT для цієї ролі, а решта застосунку працює далі. Обидві мають код 42501, тож розрізнити їх можна лише за словами в повідомленні.
Чому мої таблиці працюють у SQL Editor Supabase, але не в застосунку?
Бо SQL Editor виконує ваші запити від ролі postgres, якій належать ваші таблиці і якій не потрібен GRANT, що його втратили ролі застосунку. Ваш застосунок використовує anon для відвідувачів, які не увійшли, і authenticated для тих, хто увійшов. Спитайте Postgres про ці дві ролі напряму, через has_schema_privilege, і побачите те, що бачить застосунок.
Що варто захищати замість GRANT на схему?
Таблиці. Дайте кожній таблиці GRANT лише для тих ролей, яким він потрібен, увімкніть Row Level Security з правилом на кожній таблиці й залиште нові таблиці без жодного доступу на старті. Саме цю останню частину Supabase робить типовою для наявних проєктів 30 жовтня 2026 року.