This error occurs when the number of parameters you bind to a prepared statement does not match the number of placeholders in the query. It usually happens when parameter placeholders ($1, $2) are accidentally quoted or when there's a mismatch between your prepared statement definition and the bind call.
PostgreSQL prepared statements work by separating query structure from parameters. When you create a prepared statement with placeholders like $1 and $2, PostgreSQL counts exactly how many parameters the query expects. When you later bind (execute) that statement, you must provide exactly that many parameters. If the counts don't match, PostgreSQL raises this error. The error message shows the number of parameters you supplied (N) versus the number the prepared statement requires (M). Common root causes include accidentally putting parameter placeholders inside quoted strings (where they're treated as literals, not placeholders), using parameters inside LIKE patterns with quotes, or mismatched parameter counts between the parse and bind phases of the PostgreSQL wire protocol.
Review your SQL query and ensure parameter placeholders ($1, $2, etc.) are NOT inside single quotes.
Incorrect:
PREPARE stmt AS SELECT * FROM users WHERE name = '$1';The $1 inside quotes is treated as a literal string, not a parameter placeholder.
Correct:
PREPARE stmt AS SELECT * FROM users WHERE name = $1;Parameter placeholders must be outside quotes to be recognized by PostgreSQL.
If using LIKE clauses, parameters must not be inside the string quotes.
Incorrect:
PREPARE search AS SELECT * FROM products WHERE description LIKE '%$1%';
-- When binding 1 parameter, PostgreSQL sees 0 placeholders (the $1 is a literal string)Correct:
PREPARE search AS SELECT * FROM products WHERE description LIKE '%' || $1 || '%';
-- Parameter $1 is now recognized outside the quotesUse string concatenation (||) to combine the LIKE pattern with your parameter.
Count the parameter placeholders ($1, $2, ...) in your prepared statement definition and ensure you provide exactly that many values when binding.
Example with 2 parameters:
PREPARE get_user AS SELECT * FROM users WHERE id = $1 AND status = $2;
-- This prepared statement requires exactly 2 parameters
EXECUTE get_user(123, 'active');
-- Correct: 2 values provided
EXECUTE get_user(123);
-- Error: only 1 value provided, but prepared statement requires 2Double-check that all placeholders are counted and all are provided.
Ensure the dollar sign in parameter placeholders is not escaped or quoted.
Incorrect (common in some ORMs or string builders):
PREPARE stmt AS SELECT * FROM logs WHERE data = '$1';
-- The escaped dollar sign is treated as a literal $ character
PREPARE stmt AS SELECT * FROM data WHERE value = "$1";
-- Double quotes make $1 a literal string in some contextsCorrect:
PREPARE stmt AS SELECT * FROM logs WHERE data = $1;
-- Plain $1 is recognized as a parameter placeholderPostgreSQL has a hard limit of 65,535 parameters per query. If you're building a very large INSERT or UPDATE with many parameters, split it into multiple queries.
Problematic (65,536 parameters - exceeds limit):
# Trying to insert 10,000 rows with 7 columns each = 70,000 parameters
values = [(id, name, email, ...) for i in range(10000)]
placeholders = ','.join(f'(${{i*7+1}}, ${{i*7+2}}, ...)' for i in range(10000))
cursor.execute(f'INSERT INTO users VALUES {placeholders}', values)Solution: Batch insert into chunks:
BATCH_SIZE = 1000
for i in range(0, len(values), BATCH_SIZE):
batch = values[i:i+BATCH_SIZE]
placeholders = ','.join(f'(${{j*7+1}}, ${{j*7+2}}, ...)' for j in range(len(batch)))
cursor.execute(f'INSERT INTO users VALUES {placeholders}', [v for row in batch for v in row])Use psql directly to test your prepared statement and verify the parameter count:
psql -U postgres -d your_database-- Test query with parameters unquoted
PREPARE test_stmt AS SELECT * FROM users WHERE id = $1 AND role = $2;
-- Should work - 2 parameters provided
EXECUTE test_stmt(1, 'admin');
-- Should fail - only 1 parameter
EXECUTE test_stmt(1);
-- Error: bind message supplies 1 parameters, but prepared statement requires 2If the statement works in psql but fails in your application, the issue is likely in how your driver is constructing or binding the parameters.
This error operates at the PostgreSQL wire protocol level, which is why it can be cryptic for developers using ORMs and database drivers. The Parse phase (preparing the statement) determines how many parameters PostgreSQL expects by scanning for $N placeholders outside of quotes. The Bind phase (executing) then supplies the actual values. Mismatches between these phases cause the error. Some ORMs (like Sequelize, TypeORM) may hide this by caching prepared statements inappropriately across different queries on the same connection pool. If you encounter this sporadically in production but not development, suspect connection pooling issues where a stale prepared statement is being reused. Named prepared statements can exacerbate this if not properly scoped per statement name. For debugging, enable query logging in PostgreSQL (log_statement = 'all') to see the exact PARSE and BIND messages, though the wire protocol messages themselves show more detail than standard SQL logging.
ERROR: syntax error at end of input
Syntax error at end of input in PostgreSQL
Bind message supplies N parameters but prepared statement requires M
Bind message supplies N parameters but prepared statement requires M in PostgreSQL
Multidimensional arrays must have sub-arrays with matching dimensions
Multidimensional arrays must have sub-arrays with matching dimensions
ERROR: value too long for type character varying
Value too long for type character varying
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL