Skip to content
Postgres Error: There Is No Parameter $1 (Fix)

Click to use (opens in a new tab)

Postgres Error: There Is No Parameter $1 (Fix)

November 9, 2026 by Chat2DBChat2DB Team

You copy a query from application code into psql, press Enter, and get this:

ERROR:  there is no parameter $1
LINE 1: SELECT * FROM pg_class WHERE relname = $1;
                                               ^

$1, $2 and so on are positional parameters. They are placeholders that a client fills in separately from the SQL text, and they only exist when the query is sent through a path that supplies them: a prepared statement, the extended query protocol of a driver, a function body, or EXECUTE ... USING in PL/pgSQL. When the same text arrives as a plain query with no parameters attached, PostgreSQL has nothing to substitute and stops with SQLSTATE 42P02 (undefined parameter).

This article covers the places where the error shows up and the right fix for each. Every example was run on PostgreSQL 18.6, with psql 18 and node-postgres 8. The PL/pgSQL and function cases behave the same on PostgreSQL 15.19.

Why Parameters Are Not Text Substitution

It helps to know what a driver actually does with $1. In the extended query protocol, the client sends the SQL text with its placeholders in one message (Parse), and the values in a separate message (Bind). The server parses and plans the statement before it ever sees the values, and then executes it with them. The values are never pasted into the SQL text, which is why parameters protect against SQL injection: a value like '; DROP TABLE users; -- is just a string that gets compared with a column, not code.

That design has two consequences for this error. First, a placeholder can only stand for a value, never for a table name, column name or keyword, so SELECT * FROM $1 cannot work even with parameters attached. Second, when the SQL text is sent through the simple query protocol, which is what psql does by default and what drivers do when no values are given, there is no Bind message at all. The server finds $1 in the text, has no value for it, and reports there is no parameter $1. Every fix below is a way of either providing that Bind step or removing the placeholder.

In psql: Running a Query Copied from Code

The quickest way to reproduce the error is to type a parameterized query directly:

SELECT $1;
ERROR:  there is no parameter $1
LINE 1: SELECT $1;
               ^

psql sends this as a simple query, which cannot carry parameter values. You have four options, depending on what you are trying to do.

Option 1: Replace the Placeholder with a Literal

For a one-off check, substitute the value by hand. Keep the quoting and the type that the application would use:

SELECT relname, relkind FROM pg_class WHERE relname = 'pg_class';

This is fine for debugging, but it does not test the same plan the application gets, because a literal lets the planner see the value while a parameter may use a generic plan.

Option 2: Use PREPARE and EXECUTE

To run the statement exactly as a driver would, prepare it with parameter types and execute it with values:

PREPARE find_rel(text) AS
  SELECT relname, relkind FROM pg_class WHERE relname = $1;
 
EXECUTE find_rel('pg_class');
 relname  | relkind
----------+---------
 pg_class | r
(1 row)

DEALLOCATE find_rel; removes it when you are done. Prepared statements live for the session, so a second PREPARE with the same name fails until you deallocate the first one.

Option 3: Use \bind in psql 16 and Newer

psql 16 added \bind, which sends the query through the extended protocol with the values you give it. This is the closest thing to what a driver does:

SELECT relname FROM pg_class WHERE relname = $1 \bind 'pg_class' \g
 relname
----------
 pg_class
(1 row)

The number of values must match the number of placeholders. Too few or too many produces a protocol error rather than the "no parameter" message:

SELECT $1::int + $2::int \bind 1 \g
ERROR:  bind message supplies 1 parameters, but prepared statement "" requires 2

On psql 15 and older, \bind does not exist and the line is treated as part of the next query, which leads to confusing syntax errors. Check psql --version first.

Option 4: Use psql Variables

If you want named, reusable values in a script, psql variables with :'name' interpolation are often more convenient than $n:

\set rel 'pg_class'
SELECT relkind FROM pg_class WHERE relname = :'rel';
 relkind
---------
 r
(1 row)

psql substitutes the variable on the client before sending the query. :'rel' quotes it as a string literal and :"rel" as an identifier. This is text substitution, not a server-side parameter, so it is meant for scripts you control, not for user input.

In PL/pgSQL: EXECUTE Without USING

Dynamic SQL inside a function or DO block is the second most common source. The $1 inside the string refers to a parameter of the dynamic statement, not to the function's own first argument:

DO $$
DECLARE n int;
BEGIN
  EXECUTE 'SELECT count(*) FROM pg_class WHERE relkind = $1' INTO n;
  RAISE NOTICE '%', n;
END $$;
ERROR:  there is no parameter $1
LINE 1: SELECT count(*) FROM pg_class WHERE relkind = $1
                                                      ^
QUERY:  SELECT count(*) FROM pg_class WHERE relkind = $1
CONTEXT:  PL/pgSQL function inline_code_block line 4 at EXECUTE

The QUERY and CONTEXT lines tell you the statement came from EXECUTE. Supply the value with USING:

DO $$
DECLARE n int;
BEGIN
  EXECUTE 'SELECT count(*) FROM pg_class WHERE relkind = $1' INTO n USING 'r';
  RAISE NOTICE 'tables: %', n;
END $$;
NOTICE:  tables: 68

The count depends on your database. Each $n in the string maps to the n-th expression after USING, so USING a, b fills $1 with a and $2 with b. If you need to build identifiers such as table names, use format() with %I for the identifier and keep values in USING:

CREATE FUNCTION count_kind(p_kind "char") RETURNS bigint
LANGUAGE plpgsql AS $$
DECLARE n bigint;
BEGIN
  EXECUTE format('SELECT count(*) FROM %I WHERE relkind = $1', 'pg_class')
    INTO n USING p_kind;
  RETURN n;
END $$;
 
SELECT count_kind('r') > 0 AS ok;
 ok
----
 t
(1 row)

$n placeholders in plain PL/pgSQL statements (outside EXECUTE) also fail, for example PERFORM $1; in a DO block. A DO block takes no arguments at all, so there is nothing for $1 to refer to. Use a declared variable instead.

In SQL Functions: A Placeholder Beyond the Argument List

In a LANGUAGE sql function, $1, $2... refer to the function's arguments. Referring to an argument that does not exist fails when the function is created:

CREATE FUNCTION add_two(int) RETURNS int
LANGUAGE sql AS 'SELECT $2 + 1';
ERROR:  there is no parameter $2
LINE 2: LANGUAGE sql AS 'SELECT $2 + 1';
                                ^

Either add the missing argument to the signature or fix the number. Named arguments are easier to read and avoid the problem entirely, because the body refers to x instead of a position:

CREATE FUNCTION add_one(x int) RETURNS int
LANGUAGE sql AS 'SELECT x + 1';
 
SELECT add_one(41);
 add_one
---------
      42
(1 row)

In Drivers: Parameters Not Passed

Drivers send a query with parameters only when you give them values. With node-postgres, calling query with SQL that contains $1 but no values array, or with an empty array, sends a simple query, and the server reports the familiar error:

await client.query('SELECT $1::text AS v');      // ERROR 42P02: there is no parameter $1
await client.query('SELECT $1::text AS v', []);  // ERROR 42P02: there is no parameter $1
await client.query('SELECT $1::text AS v', ['ok']); // [{ v: 'ok' }]

We ran these three calls with node-postgres 8 against PostgreSQL 18.6 and got exactly those results. If you pass some values but not enough, you get a different message, because now the extended protocol is used and the server counts the values:

await client.query('SELECT $1::int + $2::int AS v', [1]);
// ERROR 08P01: bind message supplies 1 parameters, but prepared statement "" requires 2

Common causes in application code:

  • A values array built conditionally. An optional filter adds AND status = $2 to the SQL but the code path forgets to push the value.
  • SQL logged and re-run elsewhere. Query logs and APM tools often show the SQL with $1 placeholders. Running that text in a console reproduces the error, not the bug.
  • Mixing placeholder styles. Some libraries use ? or :name and translate them to $n. Passing $1 to such a library, or ? to node-postgres, leaves placeholders unfilled.
  • Placeholders inside string literals. '$1' in quotes is just text and is never replaced. Concatenate on the SQL side instead, for example '%' || $1 || '%' for a LIKE pattern.

The last point is easy to verify:

await client.query("SELECT '$1' AS v, $1::text AS w", ['x']);
// [{ v: '$1', w: 'x' }]

A Related Error: Could Not Determine Data Type of Parameter

Once parameters are attached, PostgreSQL still needs to know their type. Most of the time it infers it from context, such as the column it is compared with. When there is no context, you get SQLSTATE 42P18:

PREPARE p1 AS SELECT $1 IS NULL;
ERROR:  could not determine data type of parameter $1

Cast the placeholder ($1::text IS NULL) or declare the type in PREPARE p1(text) AS .... Drivers hit the same problem with expressions like $1 IS NULL OR col = $1; a cast on the first use solves it.

Quick Reference

WhereCauseFix
psqlQuery with $n sent as plain SQLLiteral value, PREPARE/EXECUTE, \bind (psql 16+), or psql variables
PL/pgSQL EXECUTEDynamic SQL uses $n without USINGAdd USING value1, value2
DO block$n used outside EXECUTEUse declared variables
SQL function$n higher than the number of argumentsFix the signature or use named arguments
DriverValues array missing or emptyPass one value per placeholder
Any'$1' inside quotesMove the placeholder out of the literal

Testing Parameterized Queries Comfortably

Debugging parameterized SQL is easier in a client that understands placeholders. In Chat2DB (opens in a new tab) you can paste a query copied from your application or logs, fill in the parameter values, and run it against the same database, which avoids hand-editing literals into the SQL and getting the quoting wrong. For more on the protocol-level message, see bind message supplies parameters, but prepared statement requires.