When you run CREATE SCHEMA with a schema name that already exists in the current database, PostgreSQL raises ERROR: schema "<name>" already exists with SQLSTATE 42P06 and condition name duplicate_schema. The statement is aborted and, if executed inside a transaction, the transaction is placed in an error state.
What This Error Means
SQLSTATE 42P06 belongs to PostgreSQL's error class 42 — the "Syntax Error or Access Rule Violation" class. Within that class, the P06 suffix is a PostgreSQL-specific (non-standard) extension covering schema-level naming conflicts, as opposed to the SQL-standard 42P07 (duplicate_table) or 42710 (duplicate_object) which cover analogous problems for tables and other objects.
When PostgreSQL processes CREATE SCHEMA, it checks the pg_namespace system catalog for an existing row whose nspname matches the requested schema name. If a match is found, it raises 42P06 before performing any other work — no schema is created, no objects within a CREATE SCHEMA ... CREATE TABLE ... block are created, and any ongoing transaction is aborted.
After the error is raised, the current transaction (if any) enters the aborted state. You must issue ROLLBACK (or ROLLBACK TO SAVEPOINT) before executing further statements in that session. A connection that is not inside an explicit transaction simply has the individual statement rolled back with no further side effects.
Common Causes
Running a migration script more than once. A
CREATE SCHEMA myapp;statement in a migration that gets re-executed — either because the migration tool lost track of which scripts have run, or because the script was run manually for troubleshooting — will fail on subsequent runs.Missing
IF NOT EXISTSguard. The developer wroteCREATE SCHEMA myschemawithout the idempotency guard, intending the script to be safe to re-run but forgetting to addIF NOT EXISTS.Race condition in concurrent deployments. Two processes (e.g., two application instances starting simultaneously) each check that the schema does not exist, then both attempt to create it. The second one to execute the DDL receives
42P06.Tenant-provisioning code runs twice. In multi-tenant systems that create a schema per tenant, a retry or a double-click on a "provision" button can trigger duplicate
CREATE SCHEMAcalls for the same tenant slug.Restoring a dump into a non-empty database.
pg_dumpemitsCREATE SCHEMAstatements; restoring into a database that already contains those schemas (e.g., a partial earlier restore) will produce this error for every schema that already exists.
How to Fix duplicate_schema
Add
IF NOT EXISTSto make the statement idempotent. This is the most common fix and the right choice for migration scripts and application startup code:CREATE SCHEMA IF NOT EXISTS myschema;The statement succeeds silently if the schema already exists. PostgreSQL 9.3 and later support this syntax.
Drop the existing schema before recreating it (use with caution). If you genuinely need a clean slate (e.g., in a test harness or CI reset script), drop the schema first:
DROP SCHEMA IF EXISTS myschema CASCADE; CREATE SCHEMA myschema;CASCADEdrops all objects contained in the schema. Never do this on production data without a backup.Check for existence before creating (application-level guard). If you cannot change the DDL itself, query
pg_namespacefirst:DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_namespace WHERE nspname = 'myschema' ) THEN CREATE SCHEMA myschema; END IF; END $$;Note: this pattern has a TOCTOU race in concurrent scenarios — prefer
IF NOT EXISTSwhen possible.Fix the migration tool's state tracking. If the error occurs because a migration script was applied twice, investigate why the migration framework (Flyway, Liquibase, Alembic, golang-migrate, etc.) re-ran the script. Repair the migration history table rather than silencing the error, so the root cause is addressed.
Handle the error in application code. When the duplicate schema creation is expected as a normal race condition (e.g., multi-tenant provisioning), catch
42P06/duplicate_schemaspecifically and treat it as a non-fatal condition:from psycopg2 import errors try: cur.execute("CREATE SCHEMA tenant_42") conn.commit() except errors.DuplicateSchema: conn.rollback() # Schema already exists — safe to continue
Additional Information
IF NOT EXISTSforCREATE SCHEMAwas introduced in PostgreSQL 9.3. On older versions (now well past end-of-life), you must use the PL/pgSQLDOblock workaround or catch the exception in application code.- Related SQLSTATE codes in the same class:
42P07—duplicate_table: raised whenCREATE TABLEnames an already-existing table.42710—duplicate_object: raised for duplicate sequences, types, indexes, etc.42P16—invalid_table_definition: a different class-42 PostgreSQL extension for structural DDL problems.
- Most PostgreSQL drivers expose this as a typed exception. In psycopg2 it is
psycopg2.errors.DuplicateSchema; in psycopg3 (psycopg) it ispsycopg.errors.DuplicateSchema; in asyncpg checkasyncpg.DuplicateSchemaError; in JDBC inspectSQLStateon theSQLException. - ORMs that manage schema namespacing (e.g., Django with
db_schema, or multi-schema Alembic setups) may surface this as a genericdjango.db.utils.ProgrammingErrororsqlalchemy.exc.ProgrammingError— inspect the wrappedpgcodeattribute to confirm it is42P06. - This error has no performance implications — it is a fast catalog lookup that fails early. The cost concern is the aborted transaction and any retry logic around it.
Frequently Asked Questions
Why does my migration fail with 42P06 even though I only ran it once?
Check whether your migration tool is using a case-sensitive comparison for schema names. PostgreSQL folds unquoted identifiers to lowercase, so CREATE SCHEMA MySchema and CREATE SCHEMA myschema refer to the same schema. If your migration history recorded MySchema but the schema was already created as myschema, the framework may consider it a new migration while PostgreSQL sees a duplicate.
Is CREATE SCHEMA IF NOT EXISTS safe to use in production?
Yes. When the schema already exists, the statement is a no-op — it does not modify the schema, its objects, or its owner. It is the standard idiomatic way to write re-runnable DDL in PostgreSQL 9.3+.
My pg_restore is failing with 42P06 — how do I handle it?
Pass --clean to pg_restore to emit DROP SCHEMA statements before each CREATE SCHEMA, or restore into a freshly created (empty) database. Alternatively, use --if-exists along with --clean to avoid errors when the object does not exist in the target.
Does 42P06 roll back my entire transaction?
Yes. If the CREATE SCHEMA that triggers 42P06 is inside an explicit transaction block (BEGIN / START TRANSACTION), PostgreSQL marks the whole transaction as aborted. All subsequent statements in that transaction will fail with ERROR: current transaction is aborted, commands ignored until end of transaction block until you issue ROLLBACK. Use a SAVEPOINT before the CREATE SCHEMA if you want to recover without rolling back the entire transaction.