PostgreSQL error 23503 (Foreign Key Violation) explained: what it means, why it happens, and how to fix it — with copy-paste code examples.
Read the error detail for the constraint name and the offending key value. If that does not apply, verify the referenced parent row exists before inserting the child — the full checklist is below.
SQLSTATE: 23503
Official name: Foreign Key Violation
System name: foreign_key_violation
Service: PostgreSQL
A foreign key constraint was violated because a referenced row does not exist, or an operation would orphan a referenced row.
With orders(user_id) REFERENCES users(id), the app inserts an order for user_id = 42 from a request that carried a stale client-side value. PostgreSQL rejects it with SQLSTATE 23503: insert or update on table "orders" violates foreign key constraint "orders_user_id_fkey" — the users row with id 42 does not exist (deleted, never committed, or the value came from a different environment).
Run these to confirm the cause before applying a fix — each command answers a specific question.
SELECT id FROM users WHERE id = 42;
No row = the classic 23503 on insert; confirm whether the parent was deleted or the id is simply wrong.
SELECT count(*) FROM orders WHERE user_id = 42;
A non-zero count explains a 23503 raised during DELETE of the parent (RESTRICT / NO ACTION).
Error on INSERT/UPDATE of the child?
├─ parent missing → insert the parent first (or fix the id you send)
│ └─ within a transaction: order the statements so the parent commits first,
│ or make the FK DEFERRABLE INITIALLY DEFERRED
└─ parent exists → check you are hitting the right database/environment
Error on DELETE of the parent?
├─ children should go too → ON DELETE CASCADE
├─ children should detach → ON DELETE SET NULL
└─ neither → delete or archive the child rows first
SELECT conname, conrelid::regclass AS table_name,
pg_get_constraintdef(oid) AS definition
FROM pg_constraint
WHERE contype = 'f';
List all FK constraints and their exact definitions so you can see which columns and ON DELETE actions are in play.
ALTER TABLE orders
ADD CONSTRAINT orders_customer_id_fkey
FOREIGN KEY (customer_id) REFERENCES customers(id)
DEFERRABLE INITIALLY DEFERRED;
Deferrable constraints are checked at commit, allowing you to insert child rows before parents within a transaction.
Create parent objects before children, or use bulk_create with explicit ordering; wrap in a transaction.
with transaction.atomic():
parent = Parent.objects.create(name='x')
Child.objects.create(parent=parent)
Flush parent rows before children so ORM assigns generated FK ids before the child insert fires.
parent = Parent(name='x')
session.add(parent)
session.flush() # assigns parent.id
session.add(Child(parent_id=parent.id))
Insert parent records first via the dashboard or API, then reference their id in the child insert.
const { data: parent } = await supabase
.from('parents').insert({ name: 'x' }).select().single();
await supabase.from('children').insert({ parent_id: parent.id });
Most often this happens when inserting a child row whose parent id does not exist, or when deleting or updating a parent row that is still referenced by child rows without ON DELETE/UPDATE CASCADE.
Read the error detail for the constraint name and the offending key value.
This page documents fixes for: django, sqlalchemy, supabase.
Recommendations are editorial — DB Error Reference takes no payment or affiliate fees for tool listings.
This page is based on the official PostgreSQL documentation linked below and adds practical troubleshooting guidance on top.