Postgres ADD COLUMN IF NOT EXISTS: Syntax and Examples
How to add a column in PostgreSQL only when it is missing: the IF NOT EXISTS syntax, multiple columns, DEFAULT and NOT NULL, a fallback for versions before 9.6, and how to check the result in psql.
Quick answer
Use ALTER TABLE ... ADD COLUMN IF NOT EXISTS. PostgreSQL adds the column if it is missing. If a column with that name already exists, it skips the step with a NOTICE instead of raising an error, so the statement is safe to run again.
ALTER TABLE users
ADD COLUMN IF NOT EXISTS phone VARCHAR(20);
Supported since PostgreSQL 9.6. On a second run you get NOTICE: column "phone" of relation "users" already exists, skipping. It only checks the name: an existing column with a different type or default is left unchanged. On 9.5 and older, use the DO block fallback.
Introduction
PostgreSQL makes adding columns fast and safe. With volatile and non-volatile defaults handled differently, understanding the behavior helps you avoid downtime. The examples below use a simple users, orders and products schema and were run on PostgreSQL 16.
Add Column IF NOT EXISTS
Safely add a column without error if it already exists. Useful in migrations that may run more than once.
-- Won't error if column exists:
ALTER TABLE users
ADD COLUMN IF NOT EXISTS phone VARCHAR(20);
-- Multiple safe additions in one statement:
ALTER TABLE users
ADD COLUMN IF NOT EXISTS phone VARCHAR(20),
ADD COLUMN IF NOT EXISTS avatar_url TEXT;
Each ADD COLUMN clause needs its own IF NOT EXISTS. Clauses for columns that already exist are skipped with a NOTICE, and the rest are added. When the column already exists, psql prints:
NOTICE: column "phone" of relation "users" already exists, skipping
ALTER TABLE
Tip: IF NOT EXISTS makes migrations idempotent: you can run them multiple times safely. It works on PostgreSQL 9.6 and newer.
With DEFAULT and NOT NULL
IF NOT EXISTS combines with any column definition. To add a NOT NULL column to a table that already has rows, give it a DEFAULT so existing rows get a value:
ALTER TABLE users
ADD COLUMN IF NOT EXISTS is_verified BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT now();
Without the DEFAULT, adding a NOT NULL column to a non-empty table fails with ERROR: column "..." of relation "users" contains null values.
If the column exists with a different type
IF NOT EXISTS only compares the column name. If phone already exists as VARCHAR(20), this statement does nothing and the type stays VARCHAR(20):
-- Skipped with a NOTICE; phone is still VARCHAR(20):
ALTER TABLE users
ADD COLUMN IF NOT EXISTS phone TEXT;
-- To actually change the type, use ALTER COLUMN:
ALTER TABLE users
ALTER COLUMN phone TYPE TEXT;
The same applies to defaults and constraints: an existing column keeps them. Change them with ALTER COLUMN ... SET DEFAULT, SET NOT NULL or ADD CONSTRAINT.
Check the column with \d or information_schema
In psql, \d users lists every column with its type, nullability and default:
\d users
In a script or another client, query information_schema.columns:
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'users'
AND column_name = 'phone';
Before PostgreSQL 9.6: DO Block Fallback
PostgreSQL 9.5 and older reject ADD COLUMN IF NOT EXISTS with a syntax error. Check information_schema.columns in a DO block instead:
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'users'
AND column_name = 'phone'
) THEN
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
END IF;
END
$$;
This also works on newer versions, so it is an option when one migration has to run on old and new servers. On 9.6 and later, the plain IF NOT EXISTS form is shorter and does the same thing.
Add a Single Column
Basic column addition with optional default value.
-- Add a nullable column (default is NULL):
ALTER TABLE users
ADD COLUMN nickname VARCHAR(50);
-- Add NOT NULL column with default:
ALTER TABLE users
ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
Tip: Since PostgreSQL 11, adding a column with a constant DEFAULT does not rewrite the table. The default is stored in the catalog, so the change is fast even on large tables. Without IF NOT EXISTS, running this twice fails with ERROR: column "nickname" of relation "users" already exists.
Add Generated (Computed) Column
PostgreSQL 12+ supports generated columns that auto-compute from other columns.
-- Stored generated column:
ALTER TABLE products
ADD COLUMN profit NUMERIC
GENERATED ALWAYS AS (price - cost) STORED;
-- Full name from first + last:
ALTER TABLE users
ADD COLUMN full_name TEXT
GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED;
Tip: STORED means the value is physically saved and updated automatically. PostgreSQL 12 to 17 support only STORED generated columns. PostgreSQL 18 adds VIRTUAL generated columns (computed on read), and VIRTUAL is the default there when you omit the keyword.
Add Multiple Columns
Add several columns in a single statement.
ALTER TABLE orders
ADD COLUMN tracking_number VARCHAR(100),
ADD COLUMN shipped_at TIMESTAMPTZ,
ADD COLUMN carrier TEXT DEFAULT 'unknown';
Tip: Multiple column additions in one ALTER TABLE are atomic: either all succeed or none do. Add IF NOT EXISTS to each clause to make the statement safe to re-run.
Add Column with Constraint
Add columns with CHECK constraints, foreign keys, or unique constraints.
-- With CHECK constraint:
ALTER TABLE products
ADD COLUMN rating SMALLINT DEFAULT 0
CONSTRAINT chk_rating CHECK (rating BETWEEN 0 AND 5);
-- With foreign key:
ALTER TABLE orders
ADD COLUMN warehouse_id INTEGER
REFERENCES warehouses(id);
-- With unique constraint:
ALTER TABLE users
ADD COLUMN username TEXT UNIQUE;
Tip: Adding a foreign key validates all existing rows. On large tables, add the column first, then add the constraint using NOT VALID + VALIDATE separately.
Best Practices
- Use IF NOT EXISTS for idempotent migrations
- Constant defaults are fast (PG 11+): no table rewrite
- On PostgreSQL 9.5 and older, use a DO block that checks information_schema.columns
- For large tables, add foreign keys with NOT VALID then VALIDATE CONSTRAINT separately
- Use generated columns for computed values instead of triggers
- Wrap DDL in transactions (PostgreSQL supports transactional DDL)
Supabase runs on PostgreSQL, so the same ALTER TABLE syntax applies there. For ready-made queries, see these Supabase SQL examples and the Supabase auth.users SQL template.
Generate PostgreSQL Queries with AI2SQL
Skip the syntax lookup. Describe what you need in plain English and AI2SQL generates the correct PostgreSQL query instantly.
No credit card required
Frequently Asked Questions
Which PostgreSQL version supports ADD COLUMN IF NOT EXISTS?
PostgreSQL 9.6 and later. If the column already exists, PostgreSQL skips it with a NOTICE instead of an error. On 9.5 and older, use a DO block that checks information_schema.columns before running ALTER TABLE.
Does IF NOT EXISTS change an existing column's type or default?
No. It only checks whether a column with that name exists. If it does, the whole clause is skipped and the existing type, default and constraints stay as they are. Use ALTER COLUMN ... TYPE or SET DEFAULT to change them.
Does adding a column lock the table in PostgreSQL?
ADD COLUMN always takes a brief ACCESS EXCLUSIVE lock on the table. Since PostgreSQL 11, a column with no default or a constant DEFAULT is added without rewriting the table, so the lock is held only for a moment. A volatile default such as clock_timestamp() or random() rewrites the whole table and holds the lock for the duration. now() counts as constant here because it is evaluated once for the statement.
Can I add a column at a specific position in PostgreSQL?
No. PostgreSQL always adds columns at the end. Column order cannot be changed without recreating the table. Select columns by name instead of relying on their position.
Can AI2SQL generate PostgreSQL DDL?
Yes. Tell AI2SQL "add an email verification column to users" and it generates correct ALTER TABLE with appropriate constraints.