Cookbook/ Schema & Data Types/ Value Too Long Varchar Truncation
Schema & Data Types · Runbook

Value Too Long Varchar Truncation

An INSERT or UPDATE that used to succeed now fails with 'ERROR: value too long for type character varying(N)' and SQLSTATE 22001, and the failure is...

🧩 5 steps ⏱ ~10 min 📊 Beginner 🐘 PostgreSQL 14--18 Last reviewed June 26, 2026

In 10 seconds

Writes to a table have started failing with 'ERROR: value too long for type character varying(N)'. A column was declared varchar(N) with a length someone guessed when the schema was first written, and the application has now produced a value longer than N. PostgreSQL does not truncate the value and does not warn -- it rejects the entire INSERT or UPDATE with SQLSTATE 22001. The immediate fix is trivial: widen the column, which on a modern server is a metadata-only change with no table rewrite. The dangerous part is everything you cannot see: every other varchar(N) column that is one long value away from the same outage. This recipe reproduces the failure on a varchar(20) username column, shows the free single-column lookup and the premium fleet-wide headroom audit that finds the next outage before it happens, widens the column, and replaces the length guess with an explicit, changeable CHECK constraint.

Why This Happens

A varchar(N) (character varying(N)) column stores its declared maximum length in the system catalog as pg_attribute.atttypmod, encoded as the character limit plus 4 (the varlena header size, VARHDRSZ), so a varchar(20) column has atttypmod = 24 and the true limit is atttypmod -- 4 = 20. When a value is stored, PostgreSQL enforces that limit and raises error 22001 (string_data_right_truncation) if the value is longer -- it never truncates, because silently losing data would be worse than failing. Crucially, varchar(N), varchar, and text are the same underlying storage with the same performance; the only difference is that varchar(N) carries this length check. That means the N is almost always a guess about future data, and when the guess is wrong the column does not degrade gracefully -- it takes writes down. Widening the limit (increasing N, or converting to varchar/text) only changes the catalog's atttypmod and is therefore a metadata-only operation with no table rewrite; shrinking the limit, by contrast, must scan the table to validate every existing row and will itself raise 22001 if any value is too long.

DIAGNOSE

Diagnose

Free Tier -- Basic Approach

The free diagnosis is reading the error and confirming the column. The 22001 message already names the type and the limit it violated; one catalog lookup confirms which column is declared character varying(20). That is enough to fix the column you were just bitten by.

SQL
SELECT a.attname AS column_name,
       format_type(a.atttypid, a.atttypmod) AS declared_type,
       a.atttypmod - 4 AS max_chars
FROM pg_attribute a
WHERE a.attrelid = 'users'::regclass AND a.attname = 'username';
OUTPUT -- captured on real PostgreSQL
column_name |     declared_type     | max_chars
-------------+-----------------------+-----------
 username    | character varying(20) |        20
(1 row)

Look up the offending column in pg_attribute and render its type with format_type: username is 'character varying(20)' with max_chars = atttypmod -- 4 = 20 -- exactly the cap the error named. The decision it drives is immediate: this one column needs widening.

What it does not tell you is whether any other column is about to fail the same way, which is what the premium audit answers.

This is a Pro lesson

Get every Learning Pathway and cookbook recipe -- grounded in PostgreSQL source code, with diagnostics, fixes, and prevention for each topic.

Continue this lesson to learn:

  • Advanced Approach -- Production-Safe
  • All 36 Learning Pathway lessons
  • 170+ cookbook recipes
  • Source-grounded diagnostics & fixes

Secure checkout Cancel anytime Source-grounded

FIX

Fix

The fix is to widen the column to a length that fits the real data. Increasing a varchar limit (or dropping it) is a catalog-only change in modern PostgreSQL -- no table rewrite, no row scan -- so it is fast and safe even on a large table.

SQL
ALTER TABLE users ALTER COLUMN username TYPE varchar(64);
OUTPUT -- captured on real PostgreSQL
ALTER TABLE
SQL
INSERT INTO users(username, status, bio) VALUES (repeat('y',25), 'active', 'now fits');
OUTPUT -- captured on real PostgreSQL
INSERT 0 1
SQL
SELECT format_type(atttypid, atttypmod) AS new_type
FROM pg_attribute WHERE attrelid = 'users'::regclass AND attname = 'username';
OUTPUT -- captured on real PostgreSQL
new_type
-----------------------
 character varying(64)
(1 row)

ALTER TABLE users ALTER COLUMN username TYPE varchar(64) updates only the column's atttypmod; the 'ALTER TABLE' returns immediately with no rewrite. The identical 25-character insert that raised 22001 now returns 'INSERT 0 1', and the column reports its wider declared type, 'character varying(64)'.

Widen to a size that comfortably covers your real maximum, or convert to text and enforce the true limit with a CHECK constraint (see prevention).

VERIFY

Verify

Healthy-state proof: a value that used to fail now inserts, the column reports its widened type, and the headroom that was 0 is now comfortably positive -- the landmine is cleared, not just the single row.

SQL
INSERT INTO users(username, status, bio) VALUES (repeat('z',30), 'active', 'verify insert');
OUTPUT -- captured on real PostgreSQL
INSERT 0 1
SQL
SELECT format_type(atttypid, atttypmod) AS declared_type
FROM pg_attribute WHERE attrelid = 'users'::regclass AND attname = 'username';
OUTPUT -- captured on real PostgreSQL
declared_type
-----------------------
 character varying(64)
(1 row)
SQL
SELECT (atttypmod - 4) AS declared_limit,
       (SELECT max(length(username)) FROM users) AS max_observed,
       (atttypmod - 4) - (SELECT max(length(username)) FROM users) AS headroom
FROM pg_attribute WHERE attrelid = 'users'::regclass AND attname = 'username';
OUTPUT -- captured on real PostgreSQL
declared_limit | max_observed | headroom
----------------+--------------+----------
             64 |           30 |       34
(1 row)

Insert a 30-character username (well past the old 20-character limit): it returns 'INSERT 0 1'. The column now reports 'character varying(64)', and the headroom calculation -- declared limit (64) minus the longest stored value (30) -- is 34.

A positive, generous headroom is the durable signal that this column is no longer at risk.

⏪ Rollback

Every change in this recipe is reversible and non-destructive. Widening with ALTER TABLE ... ALTER COLUMN ... TYPE varchar(64) or TYPE text only updates the catalog; to go back you would ALTER it to a narrower type, which is only safe if no stored value exceeds the new limit (otherwise it raises 22001 during the validating scan). The CHECK constraint added in prevention is removed with ALTER TABLE ... DROP CONSTRAINT username_len_chk. No data is rewritten or lost by any step -- the failing writes simply never landed, and the surviving rows are untouched.

📦 Version Matrix (PG 14--18)

The length check and its fix are core character-type behaviour, identical across supported releases. On PostgreSQL 14 through 18 the over-length insert fails with the same 22001 'value too long' error, and after widening the same insert succeeds.

Version Behavior Version Notes
PG 14.23 broken=value too long for type character varying ✗ Not available -- use stats_reset age
PG 15.18 broken=value too long for type character varying ✗ Not available -- use stats_reset age
PG 16.14 broken=value too long for type character varying ✗ Not available -- use stats_reset age
PG 17.10 broken=value too long for type character varying ✗ Not available -- use stats_reset age
PG 18.4 broken=value too long for type character varying ✗ Not available -- use stats_reset age

Common Mistakes

varchar(N) is not a performance optimization. varchar(N), varchar, and text are stored identically and perform identically in PostgreSQL; the only thing N buys you is a hard length check. Picking a small N 'to save space' saves nothing and adds an outage mode.
PostgreSQL never truncates to fit. Unlike some databases that silently shorten an over-length value (or do so unless a strict mode is on), PostgreSQL always raises 22001 and rejects the whole statement. Code that assumed truncation will see failed writes instead of shortened data.
Shrinking a varchar limit is not free and can fail. Going from varchar(64) to varchar(20) scans the entire table to validate existing rows and raises 22001 if any value is too long -- the opposite of widening, which is metadata-only. Never assume changing a varchar length is always cheap.
char(N) (blank-padded bpchar) has the same limit trap plus its own surprises -- it pads values to N with spaces and the length check still applies. The headroom audit covers bpchar as well as varchar for exactly this reason.
Fixing only the column named in the error leaves every sibling column exposed. The error points at one column; the same guessed-length pattern is usually repeated across the schema. Auditing headroom across all length-constrained columns is what turns a one-off fix into prevention.
🔒 Pro · Prevent

Stop this incident before it pages you

The free runbook clears today's failure. Pro adds the standing guardrails -- the alert query, the dashboard, and the managed-service knobs -- so it never becomes an incident again.

  • A ready-to-paste alert query with the right thresholds.
  • The topology change that prevents recurrence.
  • The exact knobs on RDS, Azure Flexible Server & Cloud SQL.
Unlock Pro$24.99/mo · $199/yr

Frequently Asked Questions

Why doesn't PostgreSQL just truncate the value to fit?

Because silently discarding data is almost always worse than refusing the write. The SQL standard behaviour for an over-length assignment to character varying is an error (string_data_right_truncation, SQLSTATE 22001), and PostgreSQL follows it. If you genuinely want truncation you must do it explicitly in the application or with left(value, N) in the query -- the database will never do it for you.

Is widening the column going to lock the table or rewrite it?

No. Increasing a varchar length, or converting varchar(N) to varchar or text, changes only the column's atttypmod in the catalog -- it is a metadata-only operation with no table rewrite and only a brief lock. This has been true since PostgreSQL 9.2. Shrinking the limit is the expensive direction, because it must scan every row to prove the new limit holds.

Should I use varchar(N) or text?

Prefer text unless you have a genuine, externally-mandated length limit. Since text and varchar(N) store and perform identically, varchar(N) only adds a brittle failure mode when your length guess is wrong. When you do need a real limit, express it as a CHECK constraint (for example CHECK (char_length(col) <= 100)) -- it is explicit, self-documenting, and can be relaxed later without a table rewrite.

How do I find which columns are about to hit this error?

That is the premium step. Decode pg_attribute.atttypmod (declared limit = atttypmod -- 4) for every varchar/char column, then measure the longest value actually stored in each and compute headroom = limit minus longest stored. A column at or near headroom 0 is one write away from a 22001 outage. Ranking by headroom turns a reactive fix into a proactive audit you can run fleet-wide.

What does the (N) in character varying(N) map to in the catalog?

It is stored in pg_attribute.atttypmod as N + 4: the extra 4 bytes are VARHDRSZ, the varlena length header. So a varchar(20) column has atttypmod = 24, and you recover the character limit with atttypmod -- 4. A plain varchar with no length has atttypmod = -1, which is why the audit filters on atttypmod > 0 to consider only length-constrained columns.

References

Keep going

Related & next steps