How to fix Bind message supplies N parameters but prepared statement requires M in PostgreSQL

PostgreSQLINTERMEDIATEMEDIUMUpdated Dec 29, 2025· 264 views

This error occurs when executing a prepared statement with a mismatched number of parameters. The SQL query expects M parameter placeholders (like $1, $2) but your application is providing N values instead. This commonly happens when parameter placeholders are accidentally placed inside string literals or when parameters are missing from the query.

Quick fix

Fixes Bind message supplies N parameters but prepared statement requires M

SELECT * FROM users WHERE email = '$1'
  1. Verify parameter placeholders are not quoted
  2. Fix INTERVAL expressions and string concatenation
  3. Count placeholders and verify parameter count
  4. Check for escaped or accidentally quoted placeholders
postgresql — error
SELECT * FROM users WHERE email = '$1'
Bind message supplies N parameters but prepared statement requires M

What this error means

This error indicates a parameter count mismatch between your prepared statement and the bind message sent by the PostgreSQL client driver. When PostgreSQL prepares a statement, it counts the parameter placeholders ($1, $2, etc.) in your SQL. When you execute the statement, the client sends a bind message with values. If the number of values doesn't match the number of placeholders PostgreSQL found during preparation, this error occurs. The error can manifest as: - "bind message supplies 0 parameters, but prepared statement requires 2" - "bind message supplies 2 parameters, but prepared statement requires 1" - "bind message supplies 1 parameters, but prepared statement requires 0" The mismatch usually stems from placeholders being unintentionally quoted or from the application passing the wrong number of arguments.

Typical symptoms

  • Database query execution fails with parameter mismatch error
  • ORMs or database drivers throw errors mentioning parameter counts
  • Queries work in psql directly but fail when executed through application code
  • Parameter placeholders in LIKE patterns or INTERVAL expressions don't work as expected
  • Bulk insert operations fail when exceeding PostgreSQL's 65535 parameter limit
  • Escaped parameter placeholders are treated as literal values instead of parameters

Common causes

  1. Parameter placeholder inside single quotes: using '$1' instead of $1 in the SQL causes PostgreSQL to see it as a literal string
  2. INTERVAL or string concatenation with placeholders: 'INTERVAL \'$1 ms\'' treats $1 as a literal, not a parameter
  3. LIKE patterns with quoted placeholders: '\'%$1%\'' instead of '%' || $1 || '%'
  4. Mismatch between number of $N placeholders and values provided by application code
  5. Framework/ORM automatic query generation that doesn't properly count parameters
  6. Using wrong placeholder syntax in some database drivers (e.g., ? instead of $1)
  7. Parameter limit exceeded: trying to bind more than 65535 parameters in a single statement
  8. Application building query string incorrectly, resulting in fewer placeholders than expected

How to fix "Bind message supplies N parameters but prepared statement requires M"

1Verify parameter placeholders are not quoted

Parameter placeholders must NOT be inside string literals. Check your SQL for any placeholders wrapped in quotes.

WRONG - placeholder inside quotes:

sql
SELECT * FROM users WHERE email = '$1'

RIGHT - placeholder outside quotes:

sql
SELECT * FROM users WHERE email = $1

Search your code for patterns like '$1', '$2', etc. and move them outside quotes.

2Fix INTERVAL expressions and string concatenation

When using INTERVAL or string patterns, don't put placeholders inside quotes. Use string concatenation or type casting instead.

WRONG:

sql
SELECT NOW() + INTERVAL '$1 ms'
SELECT * FROM users WHERE name LIKE '%$1%'

RIGHT:

sql
SELECT NOW() + ($1 || ' ms')::INTERVAL
SELECT * FROM users WHERE name LIKE '%' || $1 || '%'

For INTERVAL with duration, cast properly:

sql
SELECT NOW() + ($1::TEXT || ' ms')::INTERVAL
3Count placeholders and verify parameter count

Count all $N placeholders in your SQL query and ensure your application passes exactly that many values.

typescript
// Example: SQL with 3 placeholders
const sql = 'SELECT * FROM users WHERE id = $1 AND status = $2 AND role = $3';

// Must pass exactly 3 values
const result = await db.query(sql, [userId, 'active', 'admin']);
// ✓ Correct: 3 placeholders, 3 values

// This would fail:
const result = await db.query(sql, [userId, 'active']); // ✗ Only 2 values!

For debugging, log the SQL and parameter count:

typescript
console.log('SQL:', sql);
console.log('Placeholder count:', (sql.match(/\$(\d+)/g) || []).length);
console.log('Values provided:', values.length);
4Check for escaped or accidentally quoted placeholders

In some string escape scenarios, placeholders get accidentally quoted:

typescript
// WRONG - escaped quotes make it a literal string:
const query = `SELECT * FROM users WHERE email = '$1'`;

// RIGHT - no quotes around placeholder:
const query = `SELECT * FROM users WHERE email = $1`;

// WRONG - in object/string concatenation:
const query = 'SELECT * FROM users WHERE data = \'' + param + '\'';

// RIGHT - use parameterized query:
const query = 'SELECT * FROM users WHERE data = $1';
const result = await db.query(query, [param]);

Check template literals and string concatenation for quoted placeholders.

5Verify ORM/driver query generation

If using an ORM (Prisma, Sequelize, TypeORM, etc.), enable query logging to see what SQL is being generated:

For Prisma:

typescript
const prisma = new PrismaClient({
  log: ['query', 'error'],
});

For node-postgres:

typescript
const query = { text: 'SELECT * FROM users WHERE id = $1', values: [userId] };
console.log('Query:', query); // Log to inspect
await client.query(query);

Compare the logged query against your expected placeholders and parameters. ORMs sometimes generate queries differently than expected.

6Handle bulk inserts properly when approaching parameter limit

PostgreSQL limits prepared statements to 65535 parameters. For bulk operations (like inserting many records), batch them:

WRONG - too many parameters in one statement:

typescript
// This tries to insert 100,000 records with 2 params each = 200,000 parameters!
const values = records.flatMap((r) => [r.name, r.email]);
const placeholders = values.map((_, i) => \`($\{i + 1}, $\{i + 2})\`);
const sql = \`INSERT INTO users (name, email) VALUES $\{placeholders.join(',')}\`;

RIGHT - batch into chunks:

typescript
const batchSize = 1000; // Adjust based on column count
for (let i = 0; i < records.length; i += batchSize) {
  const batch = records.slice(i, i + batchSize);
  const placeholders = batch
    .map((_, idx) => \`($\{idx * 2 + 1}, $\{idx * 2 + 2})\`)
    .join(',');
  const values = batch.flatMap((r) => [r.name, r.email]);
  const sql = \`INSERT INTO users (name, email) VALUES $\{placeholders}\`;
  await client.query(sql, values);
}

Or use a library like pg-format for safer bulk inserts.

7Test with psql to isolate driver issues

Run your query directly in psql (PostgreSQL command-line tool) to see if the issue is with your SQL or your driver/ORM:

bash
psql -U postgres -d your_database

-- Test a simple parameterized query
PREPARE test_query (TEXT, TEXT) AS SELECT * FROM users WHERE email = $1 AND status = $2;

-- Execute with parameters
EXECUTE test_query('user@example.com', 'active');

If it works in psql but fails in your application, the issue is in your driver/ORM configuration. If it fails in both, the issue is in your SQL.

Advanced notes

Prepared Statements & Security: Using parameterized queries (prepared statements) protects against SQL injection. The parameter mismatch error is PostgreSQL's way of enforcing type safety—make sure you're using parameterized queries correctly, not building SQL strings manually.

Driver-Specific Issues: Different PostgreSQL drivers have different placeholder syntaxes:
- PostgreSQL native: uses $1, $2, etc.
- node-postgres (pg): uses $1, $2
- psycopg2 (Python): uses %s (not numbered)
- JDBC (Java): uses ?
- libpq (C): uses $1, $2

Make sure your driver's placeholder syntax matches your query syntax.

Framework-Specific Quirks: Some ORMs build queries dynamically and may not handle all SQL patterns correctly:
- Prisma: Uses $1 internally but handles parameterization for you; usually won't see this error unless using raw queries
- Sequelize: May have issues with custom bind parameters in raw queries
- TypeORM: Similar to Sequelize; check custom query implementations

Connection Pooling: If using PgBouncer or similar, ensure the pooler correctly forwards prepared statements to the backend. Some pooling modes (statement pooling vs. transaction pooling) handle prepared statements differently.

PostgreSQL Version Compatibility: Very old PostgreSQL versions (< 9.0) had different parameter handling. If using an old server, upgrade if possible or review driver documentation for compatibility.

Frequently asked questions

What does "Bind message supplies N parameters but prepared statement requires M" mean?
This error indicates a parameter count mismatch between your prepared statement and the bind message sent by the PostgreSQL client driver. ) in your SQL. When you execute the statement, the client sends a bind message with values.
How do I fix "Bind message supplies N parameters but prepared statement requires M"?
This error occurs when executing a prepared statement with a mismatched number of parameters. The SQL query expects M parameter placeholders (like $1, $2) but your application is providing N values instead. This commonly happens when parameter placeholders are accidentally placed inside string literals or when parameters are missing from the query.
What causes "Bind message supplies N parameters but prepared statement requires M"?
Parameter placeholder inside single quotes: using '$1' instead of $1 in the SQL causes PostgreSQL to see it as a literal string INTERVAL or string concatenation with placeholders: 'INTERVAL \'$1 ms\'' treats $1 as a literal, not a parameter LIKE patterns with quoted placeholders: '\'%$1%\'' instead of '%' || $1 || '%'
Is the "Bind message supplies N parameters but prepared statement requires M" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading