column "user_id" does not exist
What it means
Postgres raises SQLSTATE 42703 when a query references a column name that is not present on the target table in the current search_path. During a migration this usually means the column was not created yet, you are pointed at the wrong schema, or a camelCase name like "userId" was created quoted but referenced unquoted, so Postgres folded it to lowercase and looked for a column that does not exist.
Why it happens
Postgres raises ERROR: column "user_id" does not exist (SQLSTATE 42703) when a statement names a column the planner cannot resolve on the referenced table within the current search_path. During a Lovable-to-Supabase migration there are three common causes:
-
Unquoted camelCase folded to lowercase. Postgres lowercases every unquoted identifier. If the source project created the column as
"userId"(quoted, mixed case), it is stored literally asuserIdand is only reachable with double quotes. A generated statement that writesINSERT INTO profiles (userId) ...is parsed asuserid, which is not the real column, so you get 42703. This is the single most common cause when the source ORM (Prisma, Drizzle, TypeORM) emitted quoted camelCase DDL. -
The column has not been created yet — wrong DDL order. An index, foreign key, trigger, or RLS policy references
user_idbefore theALTER TABLE ... ADD COLUMN user_id(or theCREATE TABLE) that defines it has run. Replaying schema statements out of dependency order surfaces this immediately. -
Wrong schema / wrong
search_path. The column exists, but on a table in a schema that is not on the currentsearch_path(for exampleauth.usersvspublic.users, or a custom schema). The unqualified reference resolves to a different table that genuinely has nouser_id.
A plain typo (user_id vs userid vs owner_id) produces the same message and is worth ruling out first with the diagnostic query below.
How to fix it
First, confirm the exact column names Postgres actually stored. information_schema.columns returns the real, case-sensitive names:
SELECT table_schema, table_name, column_name, data_type
FROM information_schema.columns
WHERE table_name = 'profiles'
ORDER BY table_schema, ordinal_position;
If you see userId (or any capital letter) in the output, the column was created quoted. Reference it with double quotes everywhere:
-- Wrong: parsed as userid, does not exist
SELECT userId FROM profiles;
-- Correct: quotes preserve the stored case
SELECT "userId" FROM profiles;
If the column is genuinely missing because DDL ran out of order, add it, then re-run the dependent statement:
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS user_id uuid REFERENCES auth.users (id);
If the problem is search_path, confirm where the column lives and qualify the reference:
SHOW search_path;
SELECT current_schemas(true);
-- Qualify the table so the right columns resolve
SELECT p.user_id FROM public.profiles AS p;
To normalize a quoted camelCase column to snake_case so unquoted references stop failing (do this on the destination, after data is copied, only if you control all downstream code):
ALTER TABLE public.profiles RENAME COLUMN "userId" TO user_id;
If the error names the same identifier as a missing table, it may actually be relation "profiles" does not exist surfacing one step earlier in the plan.
How to prevent it
- Extract the destination schema from
information_schemaandpg_catalog, not from hand-written DDL, so the exact stored identifier case is carried over verbatim. See schema replay. - Replay statements in dependency order: extensions, types, tables and columns, then indexes, constraints, triggers, and RLS policies. Columns must exist before anything references them. The migrate schema step in SupaMigrate applies this order automatically.
- Pick one identifier convention. If the source uses quoted camelCase, keep it quoted end to end, or rename to snake_case in a single pass and update application code. Do not mix.
- Always qualify table references as
schema.tablein migration scripts so a straysearch_pathnever resolves to the wrong table. - When you hit 42703, run the
information_schema.columnsquery before editing anything — it tells you in one step whether the cause is case, order, or a real typo. The Postgres error decoder maps the SQLSTATE and identifier to the likely cause.
Frequently asked questions
- Why does Postgres say the column does not exist when I can see it in the table?
- The column almost certainly exists under a different case. Postgres folds unquoted identifiers to lowercase, so a column you created as "userId" is only reachable as "userId" with double quotes. Referencing it as userId looks for userid, which does not exist. Run the information_schema query below to see the exact stored name.
- How do I know which schema Postgres is checking?
- Run SELECT current_schemas(true); and SHOW search_path;. If your table lives in a schema that is not on the search_path (for example auth or a custom schema), Postgres never sees its columns. Qualify the reference as schema.table.column or set the search_path first.
- Does the order of my DDL statements matter?
- Yes. If an INSERT, index, constraint, or trigger references a column before the ALTER TABLE ... ADD COLUMN that creates it has run, you get 42703. Replay schema in dependency order: tables and columns first, then indexes, constraints, and triggers.
Related errors