PostgreSQL Error Code Lookup

Paste a SQLSTATE code, a condition name or the whole error line your driver printed, and this Postgres error code lookup answers with the condition behind it, the class it sits in and what usually produces it. Matching happens in the browser, against a table generated from the PostgreSQL 18 source, and the most common codes are explained one by one further down the page.

Error code lookup

Takes 23505, unique_violation, or a line lifted out of a log with the code somewhere in it.

Result
23505unique_violation

Class 23, Integrity Constraint Violation

What it means

A row you tried to write carries a value a unique index already holds.

How it arrives

ERROR:  duplicate key value violates unique constraint "users_email_key"
DETAIL:  Key (email)=(ada@example.com) already exists.

While loading data

Loading the same table twice without emptying it first returns this code for every row with a unique column, which is why it is the failure people meet most often.

What causes it

  • The same row is written twice, usually because a script reran after a partial failure.
  • The sequence behind an id column sits below the ids already in the table, so the next generated value collides with one of them.
  • Two sources disagree about what makes a row distinct, and trimming or case folding turns what looked like two rows into one key.

What to check

The DETAIL line names both the column and the value. Query the table for that value before changing anything, because the answer decides whether the incoming row is a duplicate or the constraint is stricter than the data can be.

When the failure follows an import, a restore or a run of inserts with explicit ids, the sequence behind the column has fallen behind the table, and the insert that fails right after a successful load walks through that case.

23505, unique_violation. Read straight off the five character code in what you pasted. Whatever you paste stays in this tab.

Enter jumps to the code's card, or to its row in the list when it has none. Escape clears the field.

The full PostgreSQL error codes list

Every SQLSTATE value PostgreSQL 18 can report, class by class, in the order the server source lists them. A teal dot marks a code with a written card further down the page, and the code itself is the link to it; ten of the rest are warnings rather than errors, and one reports success.

Showing all 262 codes.

Class 00Successful Completion

  • 00000successful_completion

Class 01Warning

  • 01000warning
  • 0100Cdynamic_result_sets_returned
  • 01008implicit_zero_bit_padding
  • 01003null_value_eliminated_in_set_function
  • 01007privilege_not_granted
  • 01006privilege_not_revoked
  • 01004string_data_right_truncation
  • 01P01deprecated_feature

Class 02No Data (this is also a warning class per the SQL standard)

  • 02000no_data
  • 02001no_additional_dynamic_result_sets_returned

Class 03SQL Statement Not Yet Complete

  • 03000sql_statement_not_yet_complete

Class 08Connection Exception

Class 09Triggered Action Exception

  • 09000triggered_action_exception

Class 0AFeature Not Supported

Class 0BInvalid Transaction Initiation

  • 0B000invalid_transaction_initiation

Class 0FLocator Exception

  • 0F000locator_exception
  • 0F001invalid_locator_specification

Class 0LInvalid Grantor

  • 0L000invalid_grantor
  • 0LP01invalid_grant_operation

Class 0PInvalid Role Specification

  • 0P000invalid_role_specification

Class 0ZDiagnostics Exception

  • 0Z000diagnostics_exception
  • 0Z002stacked_diagnostics_accessed_without_active_handler

Class 10XQuery Error

  • 10608invalid_argument_for_xquery

Class 20Case Not Found

  • 20000case_not_found

Class 21Cardinality Violation

Class 22Data Exception

  • 22000data_exception
  • 2202Earray_subscript_error
  • Has a card. 22021character_not_in_repertoire
  • Has a card. 22008datetime_field_overflow
  • Has a card. 22012division_by_zero
  • 22005error_in_assignment
  • 2200Bescape_character_conflict
  • 22022indicator_overflow
  • 22015interval_field_overflow
  • 2201Einvalid_argument_for_logarithm
  • 22014invalid_argument_for_ntile_function
  • 22016invalid_argument_for_nth_value_function
  • 2201Finvalid_argument_for_power_function
  • 2201Ginvalid_argument_for_width_bucket_function
  • 22018invalid_character_value_for_cast
  • Has a card. 22007invalid_datetime_format
  • 22019invalid_escape_character
  • 2200Dinvalid_escape_octet
  • 22025invalid_escape_sequence
  • 22P06nonstandard_use_of_escape_character
  • 22010invalid_indicator_parameter_value
  • 22023invalid_parameter_value
  • 22013invalid_preceding_or_following_size
  • 2201Binvalid_regular_expression
  • 2201Winvalid_row_count_in_limit_clause
  • 2201Xinvalid_row_count_in_result_offset_clause
  • 2202Hinvalid_tablesample_argument
  • 2202Ginvalid_tablesample_repeat
  • 22009invalid_time_zone_displacement_value
  • 2200Cinvalid_use_of_escape_character
  • 2200Gmost_specific_type_mismatch
  • 22004null_value_not_allowed
  • 22002null_value_no_indicator_parameter
  • Has a card. 22003numeric_value_out_of_range
  • 2200Hsequence_generator_limit_exceeded
  • 22026string_data_length_mismatch
  • Has a card. 22001string_data_right_truncation
  • 22011substring_error
  • 22027trim_error
  • 22024unterminated_c_string
  • 2200Fzero_length_character_string
  • 22P01floating_point_exception
  • Has a card. 22P02invalid_text_representation
  • 22P03invalid_binary_representation
  • 22P04bad_copy_file_format
  • 22P05untranslatable_character
  • 2200Lnot_an_xml_document
  • 2200Minvalid_xml_document
  • 2200Ninvalid_xml_content
  • 2200Sinvalid_xml_comment
  • 2200Tinvalid_xml_processing_instruction
  • 22030duplicate_json_object_key_value
  • 22031invalid_argument_for_sql_json_datetime_function
  • 22032invalid_json_text
  • 22033invalid_sql_json_subscript
  • 22034more_than_one_sql_json_item
  • 22035no_sql_json_item
  • 22036non_numeric_sql_json_item
  • 22037non_unique_keys_in_a_json_object
  • 22038singleton_sql_json_item_required
  • 22039sql_json_array_not_found
  • 2203Asql_json_member_not_found
  • 2203Bsql_json_number_not_found
  • 2203Csql_json_object_not_found
  • 2203Dtoo_many_json_array_elements
  • 2203Etoo_many_json_object_members
  • 2203Fsql_json_scalar_required
  • 2203Gsql_json_item_cannot_be_cast_to_target_type

Class 23Integrity Constraint Violation

Class 24Invalid Cursor State

  • 24000invalid_cursor_state

Class 25Invalid Transaction State

  • 25000invalid_transaction_state
  • 25001active_sql_transaction
  • 25002branch_transaction_already_active
  • 25008held_cursor_requires_same_isolation_level
  • 25003inappropriate_access_mode_for_branch_transaction
  • 25004inappropriate_isolation_level_for_branch_transaction
  • 25005no_active_sql_transaction_for_branch_transaction
  • Has a card. 25006read_only_sql_transaction
  • 25007schema_and_data_statement_mixing_not_supported
  • 25P01no_active_sql_transaction
  • Has a card. 25P02in_failed_sql_transaction
  • 25P03idle_in_transaction_session_timeout
  • 25P04transaction_timeout

Class 26Invalid SQL Statement Name

Class 27Triggered Data Change Violation

  • 27000triggered_data_change_violation

Class 28Invalid Authorization Specification

Class 2BDependent Privilege Descriptors Still Exist

Class 2DInvalid Transaction Termination

  • 2D000invalid_transaction_termination

Class 2FSQL Routine Exception

  • 2F000sql_routine_exception
  • 2F005function_executed_no_return_statement
  • 2F002modifying_sql_data_not_permitted
  • 2F003prohibited_sql_statement_attempted
  • 2F004reading_sql_data_not_permitted

Class 34Invalid Cursor Name

  • 34000invalid_cursor_name

Class 38External Routine Exception

  • 38000external_routine_exception
  • 38001containing_sql_not_permitted
  • 38002modifying_sql_data_not_permitted
  • 38003prohibited_sql_statement_attempted
  • 38004reading_sql_data_not_permitted

Class 39External Routine Invocation Exception

  • 39000external_routine_invocation_exception
  • 39001invalid_sqlstate_returned
  • 39004null_value_not_allowed
  • 39P01trigger_protocol_violated
  • 39P02srf_protocol_violated
  • 39P03event_trigger_protocol_violated

Class 3BSavepoint Exception

  • 3B000savepoint_exception
  • 3B001invalid_savepoint_specification

Class 3DInvalid Catalog Name

Class 3FInvalid Schema Name

Class 40Transaction Rollback

Class 42Syntax Error or Access Rule Violation

Class 44WITH CHECK OPTION Violation

  • 44000with_check_option_violation

Class 53Insufficient Resources

Class 54Program Limit Exceeded

Class 55Object Not In Prerequisite State

Class 57Operator Intervention

Class 58System Error (errors external to PostgreSQL itself)

  • 58000system_error
  • 58030io_error
  • 58P01undefined_file
  • 58P02duplicate_file
  • 58P03file_name_too_long

Class F0Configuration File Error

  • F0000config_file_error
  • F0001lock_file_exists

Class HVForeign Data Wrapper Error (SQL/MED)

  • HV000fdw_error
  • HV005fdw_column_name_not_found
  • HV002fdw_dynamic_parameter_value_needed
  • HV010fdw_function_sequence_error
  • HV021fdw_inconsistent_descriptor_information
  • HV024fdw_invalid_attribute_value
  • HV007fdw_invalid_column_name
  • HV008fdw_invalid_column_number
  • HV004fdw_invalid_data_type
  • HV006fdw_invalid_data_type_descriptors
  • HV091fdw_invalid_descriptor_field_identifier
  • HV00Bfdw_invalid_handle
  • HV00Cfdw_invalid_option_index
  • HV00Dfdw_invalid_option_name
  • HV090fdw_invalid_string_length_or_buffer_length
  • HV00Afdw_invalid_string_format
  • HV009fdw_invalid_use_of_null_pointer
  • HV014fdw_too_many_handles
  • HV001fdw_out_of_memory
  • HV00Pfdw_no_schemas
  • HV00Jfdw_option_name_not_found
  • HV00Kfdw_reply_handle
  • HV00Qfdw_schema_not_found
  • HV00Rfdw_table_not_found
  • HV00Lfdw_unable_to_create_execution
  • HV00Mfdw_unable_to_create_reply
  • HV00Nfdw_unable_to_establish_connection

Class P0PL/pgSQL Error

  • P0000plpgsql_error
  • Has a card. P0001raise_exception
  • P0002no_data_found
  • P0003too_many_rows
  • P0004assert_failure

Class XXInternal Error

  • XX000internal_error
  • XX001data_corrupted
  • XX002index_corrupted

What it does and how to use it

The field takes whatever you have in front of you. A bare 23505 resolves on its own, and so does the condition name unique_violation. Paste a line out of a log and the code is pulled out of the noise around it; paste one with no code in it at all and the wording is matched instead, which is how duplicate key value violates unique constraint lands on 23505 without a number being typed.

Every driver carries the code under a name of its own. On node-postgres it arrives as err.code; psycopg 2 spells it pgcode and psycopg 3 renamed the attribute to sqlstate. Java reads it back from getSQLState() on the SQLException that was thrown. When you are sitting in psql and the code is not on screen, \errverbose reprints the last error at full verbosity with the SQLSTATE in it.

Under the panel sits the whole Postgres error codes list, class by class, and under that the written cards. A link ending #23503 opens the page on the foreign key card with the field above already filled in, and a code that has no card lands on its row in the list instead.

The Postgres error codes you meet while loading data

Four integrity constraints account for most of what stops a data load. 23505 is the unique violation a rerun of a script comes back with, and it also turns up once manual inserts have left an identity sequence behind the table, which the insert that fails right after a successful load goes through. 23503 is the foreign key violation, and its DETAIL line separates a key missing from the parent table from a parent row a child still points at, which is referential integrity doing its job. 23502 prints the whole failing row, so the null is visible in position, and 23514 names the check constraint that turned the row down.

The other two arrive from somewhere other than your own rows. A deadlock, 40P01, means two transactions were each waiting on what the other held, and PostgreSQL rolled one of them back to break the cycle. 55P03 is a lock that was not free when a statement asked for it without waiting, which a loader meets when it empties a table something else is still reading.

SQLSTATE codes explained

These are the codes that turn up in application logs most, explained one by one. Every heading carries the code and its condition name, the line under it is the message with the identifiers taken out, and the anchor on each heading is the code itself.

08006 connection_failure

server closed the connection unexpectedly

A connection that was working ended in the middle of something.

server closed the connection unexpectedly
	This probably means the server terminated abnormally
	before or while processing the request.
org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.

Also the client's own, for the same reason as 08001: by the time the connection dropped there was nothing on the other end to report a code. The three line version is libpq's, which psql and psycopg print; the last line is pgjdbc's.

Where it comes from

  • An idle connection closed by a pooler, a load balancer or a NAT gateway while the pool still believed in it.
  • A backend that was terminated, by pg_terminate_backend, by the OOM killer or by a failover.
  • A restart or a maintenance window on a managed instance.

What to check

Start with the server log, which says whether the backend exited or was killed, and those point at different things. A pool that tests a connection before handing it out turns most of the rest into a retry nobody notices.

08001 sqlclient_unable_to_establish_sqlconnection

the connection attempt failed

The connection was never established.

org.postgresql.util.PSQLException: The connection attempt failed.
org.postgresql.util.PSQLException: Connection to db.internal:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.

Written by the driver, not by a server. Nothing that refused or never answered a connection can send a SQLSTATE back through it, so class 08 codes are the client library's own; the two lines above are pgjdbc's.

Where it comes from

  • Nothing listening on that host and port.
  • listen_addresses left at localhost on a server being reached from somewhere else.
  • A firewall, a security group or a managed instance that has gone to sleep in between.

What to check

Get to the port first, with psql or a plain TCP check from the same machine the application runs on. Until something answers there, the connection string is not the part worth editing.

08P01 protocol_violation

bind message supplies 2 parameters, but prepared statement requires 1

The client and the server disagree about the shape of the conversation, most often about how many parameters a statement takes.

ERROR:  bind message supplies 2 parameters, but prepared statement "" requires 1

Where it comes from

  • A parameter array with more or fewer values than the statement has placeholders, which node-postgres and pgx send straight through as a bind.
  • Placeholders written as ? in the MySQL style, which PostgreSQL reads as nothing at all, so the statement requires 0 and the client still sends its values.
  • Placeholders numbered by hand with a gap, $1 and $3 and no $2, so the count the server derives is not the count the client sent.

What to check

Count the placeholders in the statement against the length of the array the driver was given, remembering that numbering starts at $1. The empty statement name in the message means the query went unnamed, which is the normal case, so nothing is wrong with the driver's caching.

0A000 feature_not_supported

cannot truncate a table referenced in a foreign key constraint

The statement is valid SQL and PostgreSQL will not do it.

ERROR:  cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "orders" references "users".
HINT:  Truncate table "orders" at the same time, or use TRUNCATE ... CASCADE.
ERROR:  FOR UPDATE is not allowed with aggregate functions

Where it comes from

  • TRUNCATE on a table something else references, without CASCADE and without naming the other tables.
  • A locking clause somewhere the server refuses one, such as over an aggregate.
  • An operation a data type or an extension does not implement.

What to check

This is one of the few codes where the message is the whole answer, and the hint usually spells out the way round. Truncating a group of related tables works when every one of them is named in the same statement.

For a whole schema, the truncate all tables generator writes that statement from your DDL with every table named.

21000 cardinality_violation

more than one row returned by a subquery used as an expression

A subquery that had to produce a single value produced several.

ERROR:  more than one row returned by a subquery used as an expression
ERROR:  ON CONFLICT DO UPDATE command cannot affect row a second time
HINT:  Ensure that no rows proposed for insertion within the same command have duplicate constrained values.

Where it comes from

  • A scalar subquery in a SELECT list or a WHERE comparison whose filter turned out not to be unique.
  • col = (SELECT ...) where IN (SELECT ...) was meant.
  • ON CONFLICT DO UPDATE or MERGE meeting the same target row twice in one statement, which the same code reports as the second message.

What to check

Run the subquery on its own with the same parameters and count what comes back. Where several rows are legitimate, LIMIT 1 under an ORDER BY picks one on purpose, and IN or = ANY accepts all of them.

While loading data

A VALUES list with the same natural key twice trips the second form, and the fix is in the file rather than in the statement.

22021 character_not_in_repertoire

invalid byte sequence for encoding "UTF8"

Bytes arrived inside a string that are not valid in the database's encoding, and 0x00 is the one it is nearly always about.

ERROR:  invalid byte sequence for encoding "UTF8": 0x00

Where it comes from

  • A zero byte inside a string, which JSON and most languages allow and no PostgreSQL text column will store.
  • Text read from a file in Latin-1 or Windows-1252 and sent as if it were UTF-8, which the message reports as some byte other than 0x00.
  • Binary data, an image or a hash, headed for a text column instead of bytea.

What to check

The byte in the message is the clue. 0x00 means a zero byte to remove in the application before the insert, because the server refuses to build one even through chr(0); anything else means the encoding of the source, and iconv or the driver's client_encoding is where to look.

While loading data

Fixture files exported from another system carry this in, since a nul inside one field is invisible in every editor and fatal in the load.

22008 datetime_field_overflow

date/time field value out of range

The parts parsed, and one of them is not a real date or time.

ERROR:  date/time field value out of range: "2026-13-45"
HINT:  Perhaps you need a different "DateStyle" setting.
ERROR:  timestamp out of range: "294277-01-01"

Where it comes from

  • Month 13, day 31 of a 30 day month, or a 29 February outside a leap year.
  • DateStyle reading 03/04/2026 as April 3 on one server and March 4 on another.
  • A timestamp past what the type can hold, which for timestamp runs out in the year 294276.

What to check

The DateStyle hint is the one to act on when a string loads on one server and fails on another with the same data, because that is a setting difference rather than a data problem.

22012 division_by_zero

division by zero

A division, a modulo or an average met a zero denominator.

ERROR:  division by zero

Where it comes from

  • A rate computed over a group whose denominator turned out to be zero.
  • A NULLIF meant to guard the denominator that is guarding the other operand.
  • An integer division where the divisor is a count that happens to be empty.

What to check

NULLIF(denominator, 0) turns the failure into a null, which is nearly always what the query wanted from an empty group in the first place.

22007 invalid_datetime_format

invalid input syntax for type date

A date or time string does not match the format it is being read with.

ERROR:  invalid input syntax for type date: "abc"
ERROR:  invalid value "abc" for "YYYY"
DETAIL:  Value must be an integer.

Where it comes from

  • to_date or to_timestamp called with a pattern that does not describe the input.
  • Day and month the other way round, so 11/09/2026 parses under one DateStyle and not another.
  • A month name in a locale the database is not running in.

What to check

Put the string and the pattern side by side, character by character. A pattern with more fields than the string has can still succeed, which is how a wrong value gets in without raising anything at all.

22003 numeric_value_out_of_range

numeric field overflow

A number does not fit the column's declared precision, or the type's own range.

ERROR:  numeric field overflow
DETAIL:  A field with precision 6, scale 2 must round to an absolute value less than 10^4.

Where it comes from

  • numeric(p,s) where the whole part needs more digits than p minus s leaves for it.
  • An integer column meeting a value past 2147483647.
  • A sum or a product that overflows even though every input fitted.

What to check

The DETAIL line does the arithmetic for you and prints the largest absolute value the column will take, so the question is only whether the data is wrong or the column was declared too small.

22001 string_data_right_truncation

value too long for type character varying

A string is longer than the declared width of the column.

ERROR:  value too long for type character varying(3)

Where it comes from

  • A varchar(n) sized for what the data used to look like.
  • Trailing whitespace nobody trimmed on the way in.
  • A generated value, a slug or a concatenation, that grew past the limit as its inputs grew.

What to check

SELECT max(length(col)) over the source data gives the width actually needed. Where there is no real limit to enforce, text has none to declare.

22P02 invalid_text_representation

invalid input syntax for type

A string was read as a type it cannot be read as.

ERROR:  invalid input syntax for type integer: "abc"
ERROR:  invalid input syntax for type uuid: "not-a-uuid"

Where it comes from

  • An empty string where a number, a date or a uuid was expected.
  • Thousands separators, a currency symbol or a stray space inside a numeric string.
  • A placeholder such as N/A or a literal dash sitting in a column that is otherwise numeric.

What to check

The offending value is printed in quotes, which makes whitespace and empty strings visible. NULLIF(col, '') is the usual repair when a CSV loader has turned blanks into empty strings.

23502 not_null_violation

null value in column violates not-null constraint

A column declared NOT NULL was handed a null.

ERROR:  null value in column "email" of relation "users" violates not-null constraint
DETAIL:  Failing row contains (3, null, 20).

Where it comes from

  • The insert leaves the column out and the table has no default to fall back on.
  • A lookup or a join found nothing, so what arrived was null rather than a value.
  • An empty string in a CSV was read as a null, or a null was read as an empty string.

What to check

The DETAIL line prints the entire failing row, so the null is visible in position. Line that up against the column list of the statement and the missing one is usually obvious.

While loading data

Columns added after a load script was written are the common cause here, since the script cannot know about a new NOT NULL column that has no default.

23503 foreign_key_violation

insert or update on table violates foreign key constraint

A referencing column points at a parent row that is not there, or a parent row was about to go while children still point at it.

ERROR:  insert or update on table "orders" violates foreign key constraint "orders_user_id_fkey"
DETAIL:  Key (user_id)=(999) is not present in table "users".
ERROR:  update or delete on table "users" violates foreign key constraint "orders_user_id_fkey" on table "orders"
DETAIL:  Key (id)=(1) is still referenced from table "orders".

Where it comes from

  • The parent row has not been written yet when the child arrives.
  • The key came from the wrong column, so the value is real but belongs to some other table.
  • A DELETE or a TRUNCATE is trying to take a parent out while its children remain.

What to check

Read the DETAIL line: it says either that a key is not present in the parent or that it is still referenced from the child, and those are two different bugs. A constraint declared DEFERRABLE INITIALLY DEFERRED moves the check to commit time, which is the one way the schema itself can relax the ordering.

While loading data

A child row cannot exist before its parent does, and that is the whole of what the constraint enforces, so any load that reaches a child table first ends here.

23505 unique_violation

duplicate key value violates unique constraint

A row you tried to write carries a value a unique index already holds.

ERROR:  duplicate key value violates unique constraint "users_email_key"
DETAIL:  Key (email)=(ada@example.com) already exists.

Where it comes from

  • The same row is written twice, usually because a script reran after a partial failure.
  • The sequence behind an id column sits below the ids already in the table, so the next generated value collides with one of them.
  • Two sources disagree about what makes a row distinct, and trimming or case folding turns what looked like two rows into one key.

What to check

The DETAIL line names both the column and the value. Query the table for that value before changing anything, because the answer decides whether the incoming row is a duplicate or the constraint is stricter than the data can be.

While loading data

Loading the same table twice without emptying it first returns this code for every row with a unique column, which is why it is the failure people meet most often.

When the failure follows an import, a restore or a run of inserts with explicit ids, the sequence behind the column has fallen behind the table, and the insert that fails right after a successful load walks through that case.

23514 check_violation

new row for relation violates check constraint

A value broke a CHECK constraint the table declares.

ERROR:  new row for relation "users" violates check constraint "users_age_check"
DETAIL:  Failing row contains (4, grace@example.com, -1).

Where it comes from

  • A number falls outside the range the constraint allows.
  • A status or type column received a string the constraint does not list.
  • Two columns are checked against each other and the pair is inconsistent, an end date before a start date being the classic.

What to check

The constraint is named in the message, and \d+ tablename in psql prints the expression behind that name. The DETAIL line prints the row that broke it.

While loading data

Generated values trip check constraints more often than production values do, because a rule written for humans, a price above zero or a status from a short list, is invisible to anything that did not read the DDL.

23P01 exclusion_violation

conflicting key value violates exclusion constraint

Two rows overlap in a way an EXCLUDE constraint forbids.

ERROR:  conflicting key value violates exclusion constraint "bookings_during_excl"
DETAIL:  Key (during)=(["2026-09-11 10:00:00","2026-09-11 12:00:00")) conflicts with existing key (during)=(["2026-09-11 09:00:00","2026-09-11 11:00:00")).

Where it comes from

  • Two bookings, shifts or price windows cover the same period.
  • A range was built with inclusive bounds at both ends, so rows that merely touch are read as overlapping.
  • The constraint includes a column the data does not vary, so rows that should be independent end up compared against each other.

What to check

Both ranges are printed in full in the DETAIL line. Compare the bracket characters as carefully as the timestamps, since [ and ) are what decide whether two ranges meeting at one instant overlap.

25006 read_only_sql_transaction

cannot execute INSERT in a read-only transaction

A write was sent through a session, or a transaction, that is not allowed to write.

ERROR:  cannot execute INSERT in a read-only transaction

Where it comes from

  • The connection string points at a read replica or a standby, which accepts every SELECT and refuses every write.
  • default_transaction_read_only set on the role or the database, often as a safety measure somebody forgot about.
  • BEGIN READ ONLY, or SET TRANSACTION READ ONLY, earlier in the same transaction.
  • A managed instance switched to read only by its provider, which some do when the disk fills.

What to check

Two queries answer it from inside the session. SHOW default_transaction_read_only says whether the setting is on, and SELECT pg_is_in_recovery() says whether you are talking to a standby; the statement itself is fine and belongs on the primary.

25P02 in_failed_sql_transaction

current transaction is aborted, commands ignored until end of transaction block

An earlier statement in this transaction failed, and everything after it is refused until the block ends.

ERROR:  current transaction is aborted, commands ignored until end of transaction block

Where it comes from

  • A client that caught the first error and carried on sending statements.
  • A script wrapped in one long BEGIN, where a single bad row takes the rest of the file with it.
  • A pool handing back a connection that is still sitting inside a failed transaction.

What to check

The error worth reading is the first one, not this one. Scroll back through the log until you find the statement that actually failed, and if some statements are expected to fail, a SAVEPOINT before each one lets you ROLLBACK TO it and keep the transaction alive.

26000 invalid_sql_statement_name

prepared statement does not exist

EXECUTE named a prepared statement, and the backend it reached has never heard of it.

ERROR:  prepared statement "missing" does not exist

Where it comes from

  • A transaction mode pooler routing the EXECUTE to a different backend from the one the PREPARE went to, which is behind most reports of this code.
  • A connection reset by DISCARD ALL, or by a pooler's server_reset_query, between the prepare and the execute.
  • A name spelled differently in EXECUTE than in PREPARE.

What to check

Same territory as 42P05, and the same settings put it right. A statement prepared per session cannot survive a pooler that moves the session between backends, so either the pooler runs in session mode for that client or the driver stops naming its statements.

28000 invalid_authorization_specification

no pg_hba.conf entry for host

The server has no rule in pg_hba.conf that lets this combination of host, user and database connect.

FATAL:  no pg_hba.conf entry for host "10.0.0.4", user "app", database "app", no encryption

Where it comes from

  • A new application host outside every CIDR in the file.
  • A rule that requires SSL meeting a client that did not use it, or the reverse.
  • A role that exists but is not named by any line that matches.

What to check

The message prints all four things the server matched on, which is more than most failures give you. pg_hba_file_rules shows the rules as the server parsed them, and the file takes a reload rather than a restart.

28P01 invalid_password

password authentication failed for user

The server took the connection attempt and turned down the password.

FATAL:  password authentication failed for user "app"

Where it comes from

  • The wrong password, or one that has been URL encoded once too often on its way into a connection string.
  • Characters in the password that the connection string parser reads as delimiters.
  • A rotated password that one deployment, one cron job or one developer machine still has the old half of.

What to check

Look at the server log rather than the client. The message sent to the client is deliberately vague about which half was wrong, and the log line carries the detail that is missing from it.

2BP01 dependent_objects_still_exist

cannot drop table because other objects depend on it

A view, a foreign key or a function is built on the object you are dropping, and the server will not drop it alone.

ERROR:  cannot drop table users because other objects depend on it
DETAIL:  constraint orders_user_id_fkey on table orders depends on table users
view user_orders depends on table users
HINT:  Use DROP ... CASCADE to drop the dependent objects too.

Where it comes from

  • A view or a materialized view selecting from the table.
  • A foreign key on another table pointing at it, the dependency people forget most.
  • A down migration written by hand that drops tables in the order the up migration created them.
  • A type, a sequence or a function that other objects use, since the code covers those as well.

What to check

Every dependent object is listed in the DETAIL line, and it is worth reading to the end before adding CASCADE, because CASCADE drops all of them and the view you forgot about is the one you wanted to keep. Dropping the dependents by name first gets the same result with no surprises.

3D000 invalid_catalog_name

database does not exist

The database named in the connection or the statement is not on this server.

ERROR:  database "nowhere" does not exist

Where it comes from

  • A typo in the connection string, or a DATABASE_URL pointing at the wrong environment.
  • A database nobody has created yet, which is the normal state of a fresh container on its first run.
  • Case: a database created as "MyApp" is not myapp.

What to check

psql -l lists what the server actually has. Driver constants disagree about the name of this one: the condition is invalid_catalog_name and the same code is called undefined_database in the server source, so both spellings turn up in the wild.

3F000 invalid_schema_name

schema does not exist

The statement names a schema, in a qualified name or through search_path, that this database does not have.

ERROR:  schema "nowhere" does not exist
ERROR:  no schema has been selected to create in

Where it comes from

  • A migration creating tables in a schema before the CREATE SCHEMA that should have preceded it.
  • A dump restored into a fresh database that never had the schema the dump was taken from.
  • search_path set to a schema that does not exist here, which turns every unqualified CREATE into the second message.
  • Different environments using different schema names, with one of them hard coded.

What to check

\dn lists the schemas that exist, and SHOW search_path says where an unqualified CREATE was going to put things. A role can carry its own search_path through ALTER ROLE ... SET, so the value in the session is not always the one in postgresql.conf.

40001 serialization_failure

could not serialize access due to concurrent update

The transaction could not be placed in a serial order with the ones running beside it, so it was rolled back.

ERROR:  could not serialize access due to concurrent update
ERROR:  could not serialize access due to read/write dependencies among transactions
HINT:  The transaction might succeed if retried.

Where it comes from

  • REPEATABLE READ or SERIALIZABLE isolation, where another transaction changed a row this one had already read.
  • Two transactions writing the same row under SERIALIZABLE.
  • A read and write pattern across three transactions that SERIALIZABLE cannot order, even though no two of them touch the same row.

What to check

The hint is an instruction rather than a suggestion. Anything running at these isolation levels needs a retry loop around the whole transaction, and the server says so in the message it sends.

40P01 deadlock_detected

deadlock detected

Two transactions are each holding something the other needs, and PostgreSQL rolled one of them back to break the cycle.

ERROR:  deadlock detected
DETAIL:  Process 4521 waits for ShareLock on transaction 8831; blocked by process 4520.
HINT:  See server log for query details.

Where it comes from

  • Two sessions updating the same rows in a different order.
  • A foreign key check taking a lock on a parent row that another transaction is busy updating.
  • Batch jobs running side by side over overlapping key ranges.

What to check

The detail line names both processes, and the hint is pointing at the server log, which is where the statements are. Turning on log_lock_waits makes the next one far easier to read.

While loading data

Parallel loaders meet this one: inserting a child row takes a lock on its parent row, and two workers that reach each other's parents in the opposite order leave the server to choose which of them dies.

42601 syntax_error

syntax error

The parser gave up. What it names is where it stopped, and the mistake is usually the token just before that.

ERROR:  syntax error at or near "selct"
LINE 1: selct 1

Where it comes from

  • A missing comma in a column list, or one comma too many before a closing parenthesis.
  • An unquoted identifier that happens to be a reserved word, user and order catching almost everybody at some point.
  • A string closed with the wrong quote, or a dollar quoted body whose opening and closing tags do not match.

What to check

Read the token before the one the message names. The position psql prints counts characters from the start of the statement rather than from the start of the line, which matters once a statement runs over several lines.

Pasting the statement into the syntax checker reports the same error and position the server would, with the statement in front of you.

42501 insufficient_privilege

permission denied for table

The role you are connected as is not allowed to do that.

ERROR:  permission denied for table users

Where it comes from

  • The object is owned by another role and no grant was ever made.
  • Row level security is enabled and no policy matches the current role.
  • The connection string points at a read only or pooled user with fewer rights than the one the query was tested with.

What to check

SELECT current_user says who the server thinks you are, which is not always who you meant to be, and \dp tablename prints the grants that exist on the object.

42830 invalid_foreign_key

there is no unique constraint matching given keys for referenced table

A foreign key is being declared against columns the parent table does not guarantee to be unique.

ERROR:  there is no unique constraint matching given keys for referenced table "users"

Where it comes from

  • REFERENCES users (email) where email is indexed but not unique.
  • A composite key referenced by only some of its columns.
  • A parent whose key is covered by a partial unique index, which does not count.

What to check

Add the UNIQUE constraint on the parent first, or point the foreign key at the parent's primary key, which is what it usually meant. \d parent shows which constraints exist to be referenced.

42804 datatype_mismatch

column is of type bigint but expression is of type text

The value's type does not match the column's and no implicit cast joins them.

ERROR:  column "user_id" is of type bigint but expression is of type text
HINT:  You will need to rewrite or cast the expression.

Where it comes from

  • A number arrived quoted, so as far as the parser is concerned it is text.
  • The driver bound a parameter as a string.
  • A CASE or a UNION whose branches return types PostgreSQL cannot reconcile.

What to check

An explicit cast makes the statement run. Whether the value should have been cast or the column should have been a different type is the better question, and it is worth asking before the cast becomes permanent.

42P18 indeterminate_datatype

could not determine data type of parameter

A parameter is used somewhere that gives the parser no clue what type it should be.

ERROR:  could not determine data type of parameter $1

Where it comes from

  • A placeholder compared with IS NULL, or standing on its own in a SELECT list, with no column or cast beside it to borrow a type from.
  • A null passed from the application by a driver that sends values without a type, node-postgres being the usual one.
  • PREPARE without a type list, on a statement where the placeholder's type cannot be inferred.

What to check

Cast the placeholder in the SQL itself, $1::int or $1::text, and the parser has what it needs. Drivers that attach types to the values they send avoid this for anything they can see, which is why the same statement can fail from one client and run from another.

42703 undefined_column

column does not exist

The name does not resolve to a column of anything in scope.

ERROR:  column "emai" does not exist
HINT:  Perhaps you meant to reference the column "users.email".

Where it comes from

  • A typo, which the HINT line will often catch for you.
  • A column a migration renamed or dropped, in a query that stayed as it was.
  • Case: a column created as "userId" is not userid, and only the quoted spelling finds it.

What to check

Take the hint seriously, since PostgreSQL only offers one when it found a near match. After that \d tablename settles what the table really holds.

42883 undefined_function

function does not exist

No function of that name accepts those argument types.

ERROR:  function lenght(unknown) does not exist
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

Where it comes from

  • A typo in the name.
  • Arguments of the wrong type, which is what the hint about casts means: the name exists, that signature does not.
  • A function belonging to an extension that is not installed in this database.

What to check

\df name* lists the signatures that do exist. An unknown in the message is a bare string literal whose type the parser never settled, and an explicit cast on that literal is often the whole fix.

42P01 undefined_table

relation does not exist

No table, view, sequence or index of that name is visible from the current search_path.

ERROR:  relation "userz" does not exist

Where it comes from

  • The object lives in a schema that is not on search_path.
  • The migration that creates it has not run against this database.
  • Case again: a table created as "Users" is a different relation from users.

What to check

SHOW search_path first, then \dt . to find which schema the name actually lives in. A pooler or a framework that sets search_path per session is worth ruling out early.

42704 undefined_object

type does not exist

A type, a role, an extension, a constraint or a collation is named that this database does not have.

ERROR:  type "nope" does not exist
ERROR:  role "nobody_here" does not exist
ERROR:  constraint "nope" of relation "t" does not exist

Where it comes from

  • An extension type such as citext, vector or hstore used before CREATE EXTENSION ran in this database.
  • A custom enum or domain living in a schema that is not on search_path.
  • A role named in a GRANT or in a connection string that was created on one server and never on this one.
  • A constraint named in DROP CONSTRAINT that was renamed, or that only ever existed under a generated name.

What to check

The word before the quoted name says what kind of object was looked up. For a type, \dT . shows what exists and where; for an extension type, SELECT extname FROM pg_extension says whether the extension is installed in this particular database, because extensions are installed per database rather than per server.

42P05 duplicate_prepared_statement

prepared statement already exists

A prepared statement is being created under a name this backend already has one for.

ERROR:  prepared statement "q" already exists

Where it comes from

  • A pooler in transaction mode, PgBouncer or Supavisor, handing the session's next statement to a backend where a statement of that name was already prepared by somebody else.
  • Prisma, pgjdbc or Npgsql naming statements by counter, s0 and S_1, over connections a pooler multiplexes.
  • PREPARE run twice in one session with the same name, which is the plain case.

What to check

The fix lives in the driver's URL rather than in the SQL. Through a transaction mode pooler, switch server side prepared statements off, pgbouncer=true for Prisma or prepareThreshold=0 for pgjdbc, or run the migration step over a direct connection.

42P07 duplicate_table

relation already exists

Something with that name is already there and CREATE will not replace it.

ERROR:  relation "users" already exists

Where it comes from

  • A migration ran twice.
  • Two migrations create the same object in the same schema under different filenames.
  • An object left behind by a run that failed halfway and was never cleaned up.

What to check

CREATE TABLE IF NOT EXISTS silences it, which is the right answer in a bootstrap script and the wrong one in a migration, where the duplicate is telling you the recorded history and the database no longer agree.

42710 duplicate_object

type already exists

The name is taken already, by a type, a role, a constraint or an extension, and CREATE refuses to reuse it.

ERROR:  type "mood" already exists
ERROR:  role "r" already exists
ERROR:  check constraint "c1" already exists

Where it comes from

  • A migration that creates an enum, rerun after a partial failure or applied to a database it had already been applied to.
  • Two constraints in one table given the same name by hand.
  • A role created by hand on the server that a provisioning script later tries to create again.

What to check

CREATE TYPE has no IF NOT EXISTS, so the usual guard for an enum is a DO block that catches duplicate_object and carries on. For a constraint, \d tablename shows the one already holding the name.

42702 ambiguous_column

column reference is ambiguous

A column name in the query matches a column in more than one of the tables it joins, and PostgreSQL will not guess which one you meant.

ERROR:  column reference "id" is ambiguous

Where it comes from

  • A join between two tables that both have an id, a name or a created_at, with the column written bare in the SELECT list or the WHERE clause.
  • A query that used to read one table and had a join added later, so names that were unique are not any more.
  • A CTE or a subquery whose output column shares a name with a column of the table it is joined to.

What to check

Qualify the name with the table or its alias, u.id rather than id. The message quotes the bare name, and searching the statement for it finds the one place it appears without a prefix.

42P10 invalid_column_reference

there is no unique or exclusion constraint matching the ON CONFLICT specification

ON CONFLICT names columns, and no unique index or constraint covers exactly those columns, so the server has nothing to detect the conflict with.

ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification

Where it comes from

  • ON CONFLICT (email) on a table where email has an ordinary index rather than a unique one.
  • A unique constraint over two columns while ON CONFLICT names only one of them.
  • A partial unique index, whose WHERE clause has to be repeated after the column list before the planner will use it.

What to check

\d tablename lists the unique indexes with their column lists, and the ON CONFLICT target has to match one of them column for column. ON CONFLICT ON CONSTRAINT constraint_name is the other spelling, where naming the constraint replaces matching its columns.

While loading data

Upsert loaders meet this one on the first row, because the natural key the script conflicts on, an email or a slug, was never declared unique in the DDL.

42P16 invalid_table_definition

multiple primary keys for table are not allowed

The CREATE TABLE or ALTER TABLE describes something a table cannot be.

ERROR:  multiple primary keys for table "dup" are not allowed
ERROR:  column "a" is in a primary key

Where it comes from

  • PRIMARY KEY written on a column and again as a table constraint, or an ALTER TABLE adding one to a table that already has one.
  • A migration generated from a model in which two fields were both marked as the key.
  • DROP NOT NULL on a column that belongs to the primary key, which is the second message.

What to check

\d tablename in psql shows the constraint that already exists. A second unique key is a UNIQUE constraint rather than a second PRIMARY KEY, and a key column stays NOT NULL for as long as it is part of the key.

53200 out_of_memory

out of memory

The server asked for memory, from the operating system or from its own shared segment, and did not get it.

ERROR:  out of memory
DETAIL:  Failed on request of size 1073741824 in memory context "ExecutorState".
ERROR:  out of shared memory
HINT:  You might need to increase "max_locks_per_transaction".

Where it comes from

  • A sort, a hash join or an aggregate that outgrew work_mem and then outgrew the machine, often a query that was fine until the table got large.
  • Too many backends each allowed a generous work_mem, so the sum is what the host cannot supply.
  • One transaction touching thousands of relations, partitions or savepoints and filling the lock table, which is what the shared memory form means.

What to check

The DETAIL line names the memory context, and the server log around it prints the whole context tree at the moment of failure. For the shared memory form, count rows in pg_locks and raise max_locks_per_transaction, a setting that only takes effect after a restart.

53300 too_many_connections

sorry, too many clients already

Every connection slot on the server is taken.

FATAL:  sorry, too many clients already

Where it comes from

  • More application instances, workers and cron jobs than max_connections was ever set for.
  • A pool that opens a connection per request and forgets to give it back.
  • Sessions parked in idle in transaction, holding a slot and a snapshot.

What to check

Count what is connected with SELECT count(*) FROM pg_stat_activity and compare that against SHOW max_connections. Raising the setting buys time; a pooler in front of the server is what fixes the arithmetic.

The connection pool size calculator turns RAM, cores and the number of application instances into a max_connections figure and the PgBouncer lines to go with it.

54000 program_limit_exceeded

index row size exceeds btree version 4 maximum

A value is larger than a hard limit inside the server, most often the size of one index entry.

ERROR:  index row size 3216 exceeds btree version 4 maximum 2704 for index "notes_body_idx"
DETAIL:  Index row references tuple (0,1) in relation "notes".
HINT:  Values larger than 1/3 of a buffer page cannot be indexed.
Consider a function index of an MD5 hash of the value, or use full text indexing.
ERROR:  null character not permitted

Where it comes from

  • A plain B-tree index on a text column that now holds a document, a long URL or a JSON blob.
  • A unique constraint on a column meant to be short that received one long value.
  • A string containing a zero byte, which no text type in PostgreSQL will store.

What to check

The hint has both answers for the index case, an index on md5(col) when what you need is equality and a full text index when what you need is search. For the zero byte, strip it before the insert, since nothing on the server side will take it.

55006 object_in_use

database is being accessed by other users

Something else still holds the object the statement wants to take away, and the server will not pull it out from under them.

ERROR:  database "app" is being accessed by other users
DETAIL:  There is 1 other session using the database.
ERROR:  cannot drop the currently open database

Where it comes from

  • DROP DATABASE while an application pool, a psql window or a GUI still has a session open on it.
  • The session issuing the drop is connected to that very database, which is the second message.
  • CREATE DATABASE using it as the template while other sessions are connected to it.

What to check

pg_stat_activity filtered by datname lists the sessions in the way. DROP DATABASE ... WITH (FORCE) ends them for you, and for the template case the only fix is to wait for those sessions to close, or to end them by hand.

55P03 lock_not_available

canceling statement due to lock timeout

A lock the statement needed was held elsewhere, and the statement chose not to wait.

ERROR:  canceling statement due to lock timeout
ERROR:  could not obtain lock on relation "users"

Where it comes from

  • NOWAIT on LOCK TABLE or on SELECT ... FOR UPDATE.
  • lock_timeout set, and reached.
  • A migration asking for ACCESS EXCLUSIVE while a long running query still holds the table.

What to check

pg_locks joined to pg_stat_activity names the session holding what you want. Setting a short lock_timeout before DDL is the usual defence, since a migration that fails fast is better than one that queues and blocks everything arriving behind it.

While loading data

Emptying a table before writing to it needs ACCESS EXCLUSIVE, so anything still reading that table holds the load up, and with lock_timeout set the truncate comes back as 55P03 instead of waiting.

57014 query_canceled

canceling statement due to statement timeout

Something cancelled the statement rather than the statement failing on its own.

ERROR:  canceling statement due to statement timeout

Where it comes from

  • statement_timeout reached, whether it was set on the role, the database or just this session.
  • A pooler or a proxy enforcing a timeout of its own, below the server's.
  • A deliberate pg_cancel_backend from somewhere else.

What to check

SHOW statement_timeout inside the session that failed, since the value may have come from the role rather than from anything in your code. The same code covers a user pressing Ctrl-C, so the message text is what tells the two apart.

57P01 admin_shutdown

terminating connection due to administrator command

The backend serving this connection was told to stop, by a person, a tool or the server shutting down.

FATAL:  terminating connection due to administrator command

Where it comes from

  • pg_terminate_backend run against the session, by a DBA or by an idle session reaper.
  • A restart of the server, a failover on a managed instance, or a resize that moves the database to another host.
  • Maintenance a cloud provider applied at the hour its console announced and nobody read.

What to check

The server log says who did it, since a terminate call is logged under the terminating session and a shutdown announces itself a few lines earlier. The connection is gone either way and the client has to reconnect; a pool that checks connections before handing them out hides the next one.

P0001 raise_exception

the text your own RAISE EXCEPTION wrote

A function or a trigger written in PL/pgSQL raised an error on purpose, and this is the code it gets when it does not set one.

ERROR:  quota exceeded for tenant 42

The text is whatever the RAISE statement was given, so no two databases print the same one. The line above is from a DO block raising with a format string.

Where it comes from

  • A BEFORE INSERT or UPDATE trigger enforcing a rule the table's constraints cannot express.
  • A function behind PostgREST or Supabase guarding access, where the exception is the intended way to say no.
  • A migration or a seed script that asserts something about the data and stops when it is not so.

What to check

Search the schema for the text, since whoever wrote the function wrote the message; SELECT proname FROM pg_proc WHERE prosrc LIKE '%quota exceeded%' finds it. RAISE can carry its own SQLSTATE through the ERRCODE option, and a function that sets one turns up under that code instead of this one.

FAQ

Frequently asked questions

SQLSTATE 23505 in Postgres is unique_violation, the Postgres unique violation error. The row being written carries a value that a unique index or a primary key already holds, and the DETAIL line names the column and the value. Scripts that run twice raise it, so do two sources that disagree about what makes a row distinct, and so does an id sequence that has fallen behind rows inserted with explicit ids. The 23505 card above has a check for each of those.

Both sit in class 23, integrity constraint violations, and they point in opposite directions. 23505 says a row duplicates a value that has to be unique within its own table, while 23503 says a row refers to a parent that another table does not have, or that a parent is being removed while children still point at it. Read the DETAIL line, which for 23505 says a key already exists and for 23503 says a key is not present or is still referenced.

More often it is a missing retry than a wrong statement. 40001 means the transaction ran at REPEATABLE READ or SERIALIZABLE and could not be ordered against a concurrent one, so the server rolled it back and said in the hint that a retry might succeed. Code that uses those isolation levels needs a loop that reruns the whole transaction on this code, and the bug to look for is a handler that catches it and carries on as if the write had happened.

Catch the error the driver throws and read the five character string off it rather than parsing the message. node-postgres puts it on err.code, with err.detail, err.constraint and err.table beside it; psycopg 3 uses exc.sqlstate and psycopg 2 exc.pgcode, and both expose the constraint name through exc.diag.constraint_name. Compare it as a string, since codes such as 42P01 are not numbers, and branch on the two character class when every integrity violation should be handled the same way.

The class is the first two characters, the code is all five. Class 23 is integrity constraint violations and 23505 is the one about a unique index, so reading the class first already narrows the problem down.

A few of them map cleanly enough to be worth doing, 23505 as a 409 and 23503 or 23514 as a 422 being the common choices in APIs. Most of the rest do not map at all, and a blanket rule turns a bug in your own SQL into a 400 that blames the caller.

No. The code table ships with the page and the matching runs in this tab, so a pasted log line is never uploaded. What gets recorded is that a lookup ran and which of the 262 codes it landed on.

PostgreSQL 18, generated from errcodes.txt in the server source, the file Appendix A of the manual is built from. Codes get added between versions and almost never change meaning.