Foreign Key

Learn how PostgreSQL foreign keys enforce referential integrity between tables, including ON DELETE CASCADE, SET NULL, RESTRICT, and how to add a foreign key to an existing table.

6 min read · Last updated: March 2026 · Back to overview

Quick Answer

A PostgreSQL foreign key is a column (or group of columns) in one table that references the primary key or unique constraint of another table. It enforces referential integrity, preventing rows from referencing non-existent parent records.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

A foreign key establishes a link between two tables by referencing the primary key or a unique constraint of a parent table. The table containing the foreign key is called the child table; the referenced table is the parent table. Foreign keys prevent invalid data from being inserted and can automatically maintain consistency when parent rows are updated or deleted.

Foreign key syntax

[CONSTRAINT fk_name]
FOREIGN KEY (fk_columns)
REFERENCES parent_table (parent_key_columns)
[ON DELETE delete_action]
[ON UPDATE update_action]

PostgreSQL supports five delete/update actions: NO ACTION (default), RESTRICT, CASCADE, SET NULL, and SET DEFAULT.

NO ACTION — the default

Without an explicit action, deleting a parent row that is still referenced by a child row raises an error:

CREATE TABLE customers (
  customer_id INT GENERATED ALWAYS AS IDENTITY,
  customer_name VARCHAR(255) NOT NULL,
  PRIMARY KEY (customer_id)
);

CREATE TABLE contacts (
  contact_id   INT GENERATED ALWAYS AS IDENTITY,
  customer_id  INT,
  contact_name VARCHAR(255) NOT NULL,
  phone        VARCHAR(15),
  email        VARCHAR(100),
  PRIMARY KEY (contact_id),
  CONSTRAINT fk_customer
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);

DELETE FROM customers WHERE customer_id = 1;
-- ERROR:  update or delete on table "customers" violates foreign key constraint
-- "fk_customer" on table "contacts"

ON DELETE SET NULL

When the parent row is deleted, the foreign key column in child rows is set to NULL:

CONSTRAINT fk_customer
  FOREIGN KEY (customer_id)
  REFERENCES customers(customer_id)
  ON DELETE SET NULL

After deleting customer id 1, the contacts for that customer show NULL in customer_id.

ON DELETE CASCADE

The most commonly used action — child rows are automatically deleted when the parent row is deleted:

CONSTRAINT fk_customer
  FOREIGN KEY (customer_id)
  REFERENCES customers(customer_id)
  ON DELETE CASCADE
DELETE FROM customers WHERE customer_id = 1;
-- The matching rows in contacts are deleted automatically.

SELECT * FROM contacts;
--  contact_id | customer_id | contact_name |     phone      |          email
-- ------------+-------------+--------------+----------------+--------------------------
--           3 |           2 | David Wright | (408)-222-1234 | [email protected]
-- (1 row)

Adding a foreign key to an existing table

ALTER TABLE child_table
ADD CONSTRAINT constraint_name
FOREIGN KEY (fk_columns)
REFERENCES parent_table (parent_key_columns)
ON DELETE CASCADE;

To change an existing foreign key action, drop the old constraint and add a new one:

ALTER TABLE contacts DROP CONSTRAINT fk_customer;

ALTER TABLE contacts
ADD CONSTRAINT fk_customer
  FOREIGN KEY (customer_id)
  REFERENCES customers(customer_id)
  ON DELETE CASCADE;

Production tips

  • Always index the foreign key column in the child table — PostgreSQL does not create this index automatically, and unindexed foreign keys cause slow lookups on joins and cascades.
  • Use ON DELETE CASCADE when child records have no meaning without the parent (e.g., order items without an order). Use ON DELETE SET NULL when child records should be preserved but unlinked.
  • Adding a foreign key to an existing table validates all current rows against the parent table — ensure data is clean before running the migration.
  • Name foreign key constraints explicitly with the CONSTRAINT clause to make error messages and migrations self-documenting.

Continue in Database Constraints: CHECK Constraint.

Related in this section: Primary Key · CHECK Constraint · UNIQUE Constraint

Frequently Asked Questions

What is the difference between ON DELETE CASCADE and ON DELETE SET NULL?

ON DELETE CASCADE automatically deletes all child rows when the parent row is deleted. ON DELETE SET NULL sets the foreign key column in child rows to NULL instead of deleting them. Use CASCADE when child records should not exist without the parent; use SET NULL when child records should be preserved but unlinked.

Does PostgreSQL automatically create an index on the foreign key column?

No. PostgreSQL creates an index on the referenced column in the parent table (because it must be a primary key or unique constraint), but it does not automatically index the foreign key column in the child table. You should create that index manually to avoid slow joins and cascades.

What is the difference between NO ACTION and RESTRICT for foreign keys?

Both prevent deletion of a parent row that has child references. The difference is timing: RESTRICT is checked immediately, while NO ACTION can be deferred until the end of the transaction if the constraint is declared DEFERRABLE. In practice they behave the same for non-deferred constraints.

Can a table have multiple foreign keys?

Yes. A table can have as many foreign key constraints as needed, each referencing a different parent table or even different columns in the same parent table.

How do I find all foreign key constraints on a table?

Run \d table_name in psql to see constraints, or query information_schema.referential_constraints and information_schema.key_column_usage for a programmatic listing.