Download

column is of type but expression is of type

You’re writing a value of one type into a column of another, and PostgreSQL won’t convert it on its own. Cast the value (::integer, ::jsonb), or fix where its type comes from: a text column in an INSERT … SELECT, or a driver that sends every parameter as varchar.

PostgreSQL error 42804· Tested on PostgreSQL 18.6 (also 14.24)· Updated 11 October 2026

ERROR:  column "qty" is of type integer but expression is of type text

What it means

In an INSERT or UPDATE, each value has to become the column’s type. PostgreSQL only does that on its own when an assignment cast exists, such as integer to bigint or numeric to integer. From text or varchar to integer, jsonb, boolean, uuid or an array there isn’t one, so it stops:

ERROR:  column "qty" is of type integer but expression is of type text
LINE 1: ...NSERT INTO seo_err_pg.events (id, qty) VALUES (1, '3'::text)...
                                                             ^
HINT:  You will need to rewrite or cast the expression.

The key word is typed. A bare literal like '3' has no type yet (“unknown”), and PostgreSQL reads it as an integer for an integer column, so VALUES (1, '3') works. It fails when the value already has a type: a column of a text table, a function result, or a parameter your driver declared as text.

Common causes

  1. INSERT … SELECT from a staging table whose columns are all text, as CSV imports often are.
  2. A driver that types every string parameter. JDBC’s setString() sends varchar by default, so a JSON string into a jsonb column fails with … but expression is of type character varying.
  3. An integer into a boolean column (1 instead of true), common in code moved from MySQL.
  4. A single value into an array column ('red' into text[]).
  5. A CASE or coalesce that mixes types, so the whole expression comes out as text.

How to fix it

Cast the value in SQL

INSERT INTO events (id, happened_at, payload, qty, active)
SELECT id::integer, happened_at::timestamptz, payload::jsonb, qty::integer, active::boolean
FROM staging;

For parameters, cast the placeholder: VALUES ($1, $2::jsonb) or CAST(? AS jsonb). Any value that doesn’t convert then fails with invalid input syntax, which names it.

Send booleans and arrays as what they are

INSERT INTO events (id, active) VALUES (3, true);       -- or 1::boolean
UPDATE events SET tags = ARRAY['red'] WHERE id = 1;     -- or '{red}'

Let the driver send parameters untyped

With pgJDBC, the connection parameter stringtype=unspecified sends setString() values without a type, so the server infers it from the column, the same way it treats a bare literal. Casting the placeholder in the SQL works with any driver.

Fix the column type

If the staging table will be reused, give its columns their real types, or if the target column really holds text, change the target. Either way the casts disappear from your queries.

Reproduce it

On PostgreSQL 18.6, a typed table and a staging table of text:

CREATE TABLE seo_err_pg.events (id integer PRIMARY KEY, happened_at timestamptz, payload jsonb,
                                tags text[], qty integer, active boolean);
CREATE TABLE seo_err_pg.staging (id text, happened_at text, payload text, qty text, active text);
INSERT INTO seo_err_pg.staging VALUES ('1', '2026-10-01 09:00+00', '{"a":1}', '3', 'true');

INSERT INTO seo_err_pg.events (id, happened_at, payload, qty) SELECT id, happened_at, payload, qty FROM seo_err_pg.staging;
ERROR:  column "id" is of type integer but expression is of type text
LINE 1: ..._pg.events (id, happened_at, payload, qty) SELECT id, happen...
                                                             ^
HINT:  You will need to rewrite or cast the expression.

The first column that doesn’t fit is named. Other statements in the same session:

INSERT INTO seo_err_pg.events (id, active) VALUES (3, 1);
ERROR:  column "active" is of type boolean but expression is of type integer

UPDATE seo_err_pg.events SET tags = 'red'::text WHERE id = 1;
ERROR:  column "tags" is of type text[] but expression is of type text

PREPARE ins2(varchar) AS INSERT INTO seo_err_pg.events (id, payload) VALUES (6, $1);
ERROR:  column "payload" is of type jsonb but expression is of type character varying

The PREPARE with a varchar parameter is what a driver does when it types the parameter. Prepared without a type (PREPARE ins3 AS … VALUES (6, $1)), the same statement worked and stored {"b": 2}. VALUES (1, '3') worked, and the INSERT … SELECT with casts worked. With \set VERBOSITY verbose, psql shows the code: ERROR: 42804: column "payload" is of type jsonb but expression is of type text. PostgreSQL 14.24 gives the same messages.

In Inlet

When a statement fails, Inlet shows the error at the position the server reports, with PostgreSQL’s hint, and the structure editor shows each column’s type. With your own Anthropic API key, Ask Claude (Pro, ⌘L) can fix the failed statement; it sends the schema, the SQL and the error, never rows.

Inlet: a database client for the Mac

One native app for PostgreSQL, MySQL, SQL Server, SQLite, MongoDB and Redis. It explains errors where they happen, holds your edits until you save them, and keeps production read-only until you say so.

Version 0.1.0 · macOS 26 Tahoe or later · Apple silicon and Intel