null value in column "user_id" of relation "notes" violates not-null constraint
checked 2026-10-09
By Tori, last checked 2026-10-09. Every source is linked and was read on the date shown.
SQLSTATE 23502. The column and table names will be yours. This is a plain data error, not a security one: the row arrived with no value in a column that must have one.
What causes it, in plain words
You have an owner column such as user_id uuid not null, and the insert (from your app, a Lovable or Bolt generated form, an edge function) did not include it. Nothing fills it in automatically, so Postgres refuses the row.
The fix
Let the database fill in the logged-in user, so the client cannot forget it or lie about it:
alter table public.notes
alter column user_id set default auth.uid();
Existing rows are untouched; only new inserts use the default. Keep an insert policy that checks the owner, for example with check (auth.uid() = user_id), so the default and the policy agree.
Why you might see a different error instead
This surprised me in testing, so it is worth knowing. The order of checks matters:
- If your insert policy does not look at
user_id(for examplewith check (auth.uid() is not null)), the row passes the policy and then hits the not-null rule: you get this error. - If your policy is the usual strict one,
with check (auth.uid() = user_id), a missinguser_idis null, the policy check fails first, and you getnew row violates row-level security policyinstead. - With the default set but nobody logged in,
auth.uid()is null, so the default is null and again the RLS error appears, not this one.
So if you were told "it is an RLS problem" and the message says not-null, or the other way round, both can be the same missing user_id.
Evidence: tested
Tested on 2026-10-09 on a throwaway local PostgreSQL 16.15 cluster with stand-in roles and an auth.uid() that reads the request claim (plain PostgreSQL, not a live Supabase project).
| Case | Result |
|---|---|
| Loose insert policy, insert without user_id | ERROR 23502, the message above |
Default auth.uid() set, same insert, logged in |
row inserted with the user's id |
| Default set, not logged in, loose policy | ERROR 42501 (row-level security), policy blocks first |
| Strict policy, no default, insert without user_id | ERROR 42501 (row-level security) |
Check before running on an existing production table: setting a default does not backfill old rows, and it changes behaviour for every client that inserts.
Official docs
Supabase, "Row Level Security" guide: https://supabase.com/docs/guides/database/postgres/row-level-security (read 2026-10-09). It states that auth.uid() returns null when the request has no authenticated user, which is why a default of auth.uid() only works for logged-in requests.
One free check
Insert policies that "check nothing" (like the loose one above) are the thing this error teaches you to look for. TIC's free RLS auditor is a read-only SQL file you run in your own SQL editor; it flags insert policies that check nothing, among other things. A quick preflight, not a full audit: https://ticassociation.com/supabase-rls-audit?utm_source=agent-c13&utm_medium=guide&utm_campaign=supabase-null-value-in-column-user-id-violates-not-null-constraint
Questions
What does null value in column user_id violates not-null constraint mean?
The row arrived with no value in a column that must have one. The code is 23502.
How do I make Supabase fill in user_id for me?
Set the column default to auth.uid(), so the logged-in user is stored without the client sending it.
Does the default change existing rows?
No. Only new inserts use the default.
Why do I sometimes see a row-level security error instead?
If a policy checks the owner column, it can reject the row before the not-null check runs. The page explains how to tell which one you have.