Основи безпеки
Supabase PGRST204: "Could not find the column in the schema cache"
Supabase "Could not find the column in the schema cache" зазвичай означає, що назва не збігається. Чотири перевірки і рядок для перезавантаження.

Коротко
- Supabase "Could not find the column in the schema cache" (PGRST204) означає, що ваш застосунок спробував зберегти поле, якого таблиця не має у своєму списку. PostgREST звіряє кожен ключ insert або update зі своєю копією вашої схеми, літера в літеру.
- Звичайна причина в назві: phoneNumber, надісланий у стовпець phone_number, або ключ, який ніколи не був стовпцем, наприклад пов'язані рядки, вкладені в insert.
- Supabase сам перезавантажує цю копію щоразу, коли змінюється таблиця. Коли вона справді застаріла, NOTIFY pgrst, 'reload schema'; у SQL Editor перезавантажує її, поки застосунок працює далі.
Ви попросили свій білдер додати номер телефону у форму профілю. Стовпець видно в Table Editor. Ви натискаєте «Зберегти», нічого не відбувається, а консоль браузера показує ось це:
{
"code": "PGRST204",
"details": null,
"hint": null,
"message": "Could not find the 'phoneNumber' column of 'profiles' in the schema cache"
}
Supabase "Could not find the column in the schema cache" означає, що ваш
застосунок спробував зберегти поле, якого таблиця, такою, якою її бачить
PostgREST, не має. PostgREST це частина Supabase, яка перетворює виклики
supabase.from(...) вашого застосунку на запити до бази даних. Він веде власний
список кожної таблиці й кожного стовпця, який називається schema cache, щоб не
питати базу про її будову під час кожного запиту.
Ось що більшість відповідей подає неправильно: вони радять перезавантажити schema cache. У Supabase кеш перезавантажується сам щоразу, коли змінюється таблиця. Тож якщо ви щойно додали стовпець і все одно бачите цю помилку, кеш буде найменш імовірною причиною. Зазвичай причина в назві.
Що означає "Could not find the column in the schema cache"?
Один ключ у записі, який надіслав ваш застосунок, не має відповідного стовпця в цій таблиці.
Уявіть schema cache як дошку-покажчик у вестибюлі будівлі: рядок для кожного офісу, тобто таблиці, а під ним кожна кімната, тобто стовпець. Кожен запит вашого застосунку схожий на накладну. PostgREST звіряє кожен рядок накладної з дошкою, і кімнату, якої на дошці немає, розвертають ще на рецепції, перш ніж щось дістанеться ваших даних.
Повідомлення називає обидві сторони цього порівняння. У Could not find the 'phoneNumber' column of 'profiles' in the schema cache phoneNumber означає те,
що надіслав ваш застосунок, а profiles означає таблицю, куди це пішло. Ще три
речі закладені у
власному коді PostgREST:
- код
PGRST204, статус HTTP 400, тобто неправильним був сам запит - він з'являється лише під час запису:
.insert(),.update()і.upsert(). Читання, в якому названо відсутній стовпець, падає з іншою помилкою, яку пише сам Postgres - він не дає жодної підказки. Для
відсутньої таблиці PostgREST
пропонує найближчу назву, яку знає; для відсутнього стовпця
hintдорівнюєnull, і порівнювати доведеться вам
Кожен ключ в об'єкті, який ви передаєте в один із цих трьох викликів, має бути стовпцем цієї таблиці, написаним так, як його пише таблиця.
Причина 1: назва не збігається літера в літеру
Перевірка точна. Для PostgREST phoneNumber, phone_number і phonenumber
три різні стовпці, і у вашій таблиці є щонайбільше один із них.
Сюди зазвичай потрапляють двома шляхами. Перший: зустрічаються дві звички
називати. JavaScript зазвичай пише поле як phoneNumber, а стовпець Postgres
зазвичай називається phone_number. Код, який написав ваш білдер, і SQL, який
він виконав, можуть бути правильними кожен окремо і все одно не збігатися.
Другий: пастка з лапками. У SQL назва, набрана без подвійних лапок, переводиться в нижній регістр. Документація Postgres каже про це прямо: "Quoting an identifier also makes it case-sensitive, whereas unquoted names are always folded to lower case."
| Що ви виконали в SQL Editor | Який стовпець з'явився | Що має надсилати застосунок |
|---|---|---|
alter table profiles add column phone_number text; | phone_number | phone_number |
alter table profiles add column phoneNumber text; | phonenumber | phonenumber |
alter table profiles add column "phoneNumber" text; | phoneNumber | phoneNumber |
Саме середній рядок вводить в оману. SQL виглядає так, ніби створив
phoneNumber, код надсилає phoneNumber, а стовпець називається phonenumber.
Щоб побачити справжні назви, відкрийте таблицю в Table Editor і прочитайте заголовки стовпців або виконайте це в SQL Editor:
select column_name
from information_schema.columns
where table_schema = 'public' and table_name = 'profiles';
Потім подивіться, що надіслав ваш застосунок. В інструментах розробника
браузера відкрийте вкладку «Мережа» (Network), клацніть невдалий запит до
/rest/v1/profiles і прочитайте його payload.
Безпечніше виправлення полягає в тому, щоб змінити застосунок, бо це нічого не
чіпає у ваших даних. Попросіть білдер використовувати точну назву стовпця всюди,
де це поле зберігається: "стовпець у profiles називається phone_number;
використовуй цю назву в кожному insert і update". Перейменувати стовпець
натомість означає змінити таблицю, якою вже користуються, і будь-який інший
екран, що досі використовує стару назву, зламається під час першого ж запуску.
Якщо ви будуєте з Lovable, прогорніть чат і пошукайте зміну бази даних, яку вас просили схвалити. Документація Lovable про Supabase каже, що він "writes the SQL, shows it to you, and asks for your approval in the project chat before running it". Поки ви її не схвалите, стовпця не існує, хоч би чого чекав код.
Причина 2: ключ, який ніколи не був стовпцем
Запит несе щось, що таблиця ніколи не мала зберігати: пов'язані рядки, вкладені в insert, або поле, яке форма тримає для себе.
Саме від вкладеного insert люди чекають, що він спрацює:
await supabase.from('orders').insert({
customer_id: user.id,
total: 42,
order_items: [{ product_id: 7, quantity: 2 }],
})
Він падає з Could not find the 'order_items' column of 'orders' in the schema cache. Прочитати таку саму форму можна: .select('*, order_items(*)') повертає
замовлення разом із його позиціями, тож здається, що і зворотний шлях мав би
працювати. PostgREST читає кожен ключ запису як стовпець тієї однієї таблиці,
куди йде запит. Один користувач Supabase
повідомив про це як про баг,
і відповідь, перш ніж issue закрили 18 жовтня 2026 року, була така: вкладених
insert просто немає серед можливостей. Запит на них відкритий у
трекері PostgREST з лютого
2017 року.
Ще дві форми тієї самої проблеми:
- рядок, прочитаний разом із пов'язаними рядками, відредагований у формі й
відправлений назад цілим у
.update(), тож доданийorder_itemsчиprofilesіде разом із ним - увесь стан форми, переданий просто в
.insert(), з полями, які потрібні формі, а не таблиці, наприкладconfirmPassword
Виправлення: записуйте кожну таблицю окремо, спершу батьківську. Вставте
замовлення й попросіть повернути його id через .select('id').single(), потім
вставте позиції з цим order_id. Це два запити, тож якщо другий впаде,
замовлення залишиться збереженим без позицій. Коли такого не можна допустити,
покладіть обидва insert в одну функцію Postgres і викликайте її через .rpc();
відповідь у тому issue називає це стандартним підходом. Тоді збережеться все або
нічого. Якщо цей виклик натомість відповідає PGRST202,
версія цієї помилки для функцій пояснює
чому.
Причина 3: зміна пішла в інший проєкт
Стовпець існує, але в базі даних, з якою ваш застосунок ніколи не говорить. Один акаунт Supabase може мати кілька проєктів, а гілка є окремим проєктом з власною базою даних. Стовпець, доданий у SQL Editor не того проєкту, змінює дошку в іншій будівлі.
У issue #42183 від Supabase, скарзі на те, що кеш "does not update", що б не пробували, один із розробників PostgREST відповів, що у скарзі названо два різні посилання на проєкт, і попросив підтвердити, що зміну зроблено в тому проєкті, куди йшли запити.
Для перевірки потрібні дві адреси. Ваш застосунок звертається до
https://<reference>.supabase.co, і це видно на будь-якому запиті у вкладці
«Мережа». Дашборд показує проєкт, у якому ви перебуваєте, за адресою
supabase.com/dashboard/project/<reference>. Обидва посилання мають бути одним і
тим самим рядком.
Який проєкт мій пояснює, як його
знайти, якщо Supabase налаштовував хтось інший.
Причина 4: кеш справді застарів
У Supabase це рідкісний випадок, бо Supabase перезавантажує кеш за вас. Кожну
базу даних Supabase налаштовано з тригером подій pgrst_ddl_watch, який
спрацьовує після кожної зміни будови бази, ALTER TABLE теж, і надсилає
NOTIFY pgrst, 'reload schema': повідомлення, яке каже PostgREST перебудувати
свій список. Його можна прочитати в
налаштуванні бази даних Supabase.
Додавання стовпця в Table Editor чи SQL Editor саме перемальовує дошку.
Усе йде не так, коли це повідомлення не доходить.
Посібник Supabase з усунення несправностей
описує цей випадок: "an underlying Postgres notification queue issue prevents
PostgREST from receiving cache reload signals after schema changes". Тоді журнал
PostgREST показує Failed listening for database notifications on the "pgrst" channel, і дошка лишається такою, як була, скільки б стовпців ви не додали.
Як перезавантажити schema cache у Supabase?
Виконайте один рядок у SQL Editor:
NOTIFY pgrst, 'reload schema';
Сторінка Supabase про це каже, що під Results ви маєте побачити "Success. No rows returned". Для застосунку, яким користуються, це безпечно. Документація PostgREST каже "Requests will wait until the schema cache reload is done", тож ніхто з користувачів застосунку не бачить помилки, поки це триває.
Якщо проблема в черзі сповіщень, повідомлення про перезавантаження застрягне так само, як автоматичне. Посібник з усунення несправностей дає для цього випадку другий рядок:
select pg_notification_queue_usage();
За словами Supabase, він змушує PostgREST оновити кеш і "does not require a database instance or PostgREST service restart". Потім спробуйте зберегти ще раз.
Коли перезавантаження не допомагає
Перезапустіть проєкт, а якщо помилка переживе і це, поверніться до причин з 1 по 3.
Перезапуск завжди дає PostgREST свіжий список. Той самий розробник PostgREST написав у #42183, що перезапущений PostgREST "won't go live if it doesn't load a new schema cache". Restart project розташовано в Project Settings, далі General, а як перезапустити проєкт Supabase проводить через усе це, разом із кількома хвилинами, поки проєкт офлайн.
Отже, помилка, яка пережила перезапуск, походить зі свіжого й точного списку: у цій таблиці цього проєкту немає стовпця з такою назвою. Сам #42183, відкритий у січні 2026 року людиною, яка вже пробувала перезавантаження, перезапуск і видалення та повторне додавання стовпця, був ще відкритий, коли ми читали його 21 жовтня 2026 року. Відповідь розробника там також радила оновитися до PostgREST 14 у налаштуваннях Infrastructure проєкту.
Викресліть зі свого списку видалення й повторне додавання.
alter table … drop column стирає кожне значення, збережене в цьому стовпці, а
новий стовпець з тією самою назвою починається порожнім. Якщо ви збираєтеся
змінити таблицю, в яку пишуть справжні користувачі, збережіть копію бази даних
до зміни. Reeve Care робить її за розкладом і зберігає
зашифрованою, у власному сховищі Reeve в ЄС.
Як тримати згенеровані типи в актуальному стані
Перегенеровуйте свої типи TypeScript після кожної зміни таблиці, і назва стовпця з помилкою може проявитися в редакторі раніше, ніж потрапить на живий сайт.
Supabase будує файл із ваших справжніх стовпців, через дашборд або однією командою з його посібника з генерації типів:
npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > database.types.ts
Коли клієнт створено з цими типами, createClient<Database>(…), тип Insert
кожної таблиці перелічує стовпці, які приймає .insert(). Стовпець із помилкою
в назві в об'єкті, написаному повністю прямо у виклику, тоді позначається як
помилка ще до публікації. Документація Lovable каже, що він "regenerates the
TypeScript types your app uses" після зміни бази даних. Файл типів,
згенерований до вашої останньої зміни таблиці, і далі перелічує старі стовпці.
Як змінити таблицю, якою користуються, і не втратити те, що в ній є
Збережіть копію до зміни і одразу знайте, чи застосунок після неї ще відповідає.
Reeve Care зберігає копію вашої бази даних Supabase:
- зашифровану копію за розкладом, у власному сховищі Reeve в ЄС, поза вашим акаунтом Supabase
- остання копія перед вашою зміною містить таблиці й стовпці такими, якими вони були, разом із рядками
- кожну копію перевірено, перш ніж вона зарахується, підрахунком рядків у кожній таблиці
- також ваші завантажені файли, щойно ви підключите обліковий доступ до Storage
- відновлення в один клік, перед яким робиться страхова копія поточного стану
- усе, що робить Monitor
Reeve Monitor і далі перевіряє ззовні:
- чи ваш застосунок доступний, перевірка щохвилини, тож зламану кнопку «Зберегти» легко відрізнити від збою
- повну перевірку безпеки щогодини, до трьох застосунків
- повідомлення, коли результат змінюється, тож деплой, який змінює те, що відкриває ваш застосунок, дійде до вас без того, щоб ви ходили дивитися
- щомісячний звіт простою мовою про те, що він бачив
Що зробити зараз
Що робити
- Прочитайте дві назви в повідомленні: ключ, який надіслав ваш застосунок, і таблицю, куди він пішов.
- Порівняйте цей ключ зі стовпцем у Table Editor, літера в літеру. Назва, набрана в SQL без лапок, зберігається в нижньому регістрі.
- Пошукайте ключі, які не є стовпцями: пов'язані рядки, вкладені в insert, або цілий рядок, відправлений назад разом із пов'язаними рядками. Записуйте кожну таблицю окремо або покладіть обидва записи в одну функцію.
- Перевірте, що посилання в адресі Supabase вашого застосунку збігається з проєктом, який ви змінили.
- Якщо все це збігається, виконайте
NOTIFY pgrst, 'reload schema';, потімselect pg_notification_queue_usage();, і перезапускайте проєкт, лише якщо жодне з них не допомогло. - Перегенеровуйте свої типи TypeScript після кожної зміни таблиці.
Почніть із назви стовпця, яку цитує повідомлення, бо вона точно відповідає тому, що надіслав ваш застосунок, а Table Editor точно показує, що є в таблиці. Перш ніж перейменовувати чи видаляти щось у таблиці, якою користуються люди, збережіть копію бази даних до зміни.
Поширені запитання
Що означає PGRST204?
PGRST204 це код, який PostgREST, частина Supabase, що відповідає на звернення вашого застосунку до бази даних, дає запису, в якому названо стовпець, якого таблиця не має. Він повертається з HTTP 400 і повідомленням "Could not find the 'x' column of 'y' in the schema cache", де x означає ключ, який надіслав ваш застосунок, а y означає таблицю, куди його надіслано. Його видають insert, update та upsert.
Я додав стовпець, а воно все одно не працює. Чому?
Зазвичай через назву. Перевірте, що ваш застосунок надсилає точно ту назву, яка збережена (стовпець, доданий у SQL як phoneNumber без лапок, зберігається як phonenumber), що ви додали його в тому проєкті, до якого звертається застосунок, і що більше нічого в запиті не є ключем, якого таблиця не має. Supabase сам перезавантажує schema cache після кожного ALTER TABLE, тож застарілий кеш варто підозрювати останнім.
Як перезавантажити schema cache у Supabase?
Виконайте NOTIFY pgrst, 'reload schema'; у SQL Editor. Якщо нічого не змінилося, посібник Supabase з усунення несправностей дає другу команду, select pg_notification_queue_usage();, для випадку, коли повідомлення про перезавантаження застрягли. Запити, що надходять під час перезавантаження, чекають, поки воно закінчиться.
Чи очищує кеш пауза проєкту?
Так. PostgREST будує свіжий schema cache щоразу, коли запускається, а відновлення призупиненого проєкту запускає його знову. Restart project у Project Settings, далі General, робить те саме без паузи. Якщо помилка переживає будь-що з цього, кеш від початку був правий: у цій таблиці цього проєкту стовпця немає.
Чому вкладені insert падають з цією помилкою?
Бо PostgREST читає кожен ключ insert як стовпець однієї таблиці, а ключ, у якому лежать пов'язані рядки, виявляється стовпцем, якого він не знаходить. Читати пов'язані рядки одним запитом можна, а записувати їх так ніколи не було можливо. Спершу вставте батьківський рядок, потім дочірні з його id, або зробіть обидва записи в одній функції Postgres, яку викликаєте через rpc().