How to fix 22P06: nonstandard_use_of_escape_character in PostgreSQL

PostgreSQLINTERMEDIATELOWUpdated Dec 12, 2025· 125 views

This PostgreSQL error occurs when non-standard escape character sequences are used in PL/pgSQL code or SQL queries. The error indicates that escape sequences don't conform to PostgreSQL's standard escape syntax, often happening with Unicode escapes, hexadecimal escapes, or custom escape patterns in procedural code.

Quick fix

Fixes 22P06

-- Problematic Unicode escapes
SELECT E'Greek alpha: \u3B1';  -- Missing digit (should be 4 hex digits)
SELECT E'Smiley: \U1F600';     -- Wrong: should be 8 hex digits for \U
SELECT E'Letter: \u03b1';      -- Wrong: lowercase hex digits not always supported

-- Correct Unicode escapes
SELECT E'Greek alpha: \u03B1';  -- 4 hex digits, uppercase
SELECT E'Smiley: \U0001F600';   -- 8 hex digits for \U escapes
SELECT E'Letter: \u03B1';       -- Uppercase hex digits (A-F, 0-9)

-- In PL/pgSQL functions:
CREATE OR REPLACE FUNCTION test_unicode() RETURNS text AS $$
BEGIN
  -- Correct:
  RETURN E'Unicode: \u03B1\u03B2\u03B3';  -- αβγ
  -- Wrong:
  -- RETURN E'Unicode: \u3b1';  -- Missing digit, lowercase
END;
$$ LANGUAGE plpgsql;

-- Rules for Unicode escapes:
-- 1. \u must be followed by exactly 4 hexadecimal digits (0-9, A-F)
-- 2. \U must be followed by exactly 8 hexadecimal digits
-- 3. Digits should be uppercase (A-F, not a-f) for consistency
-- 4. Valid Unicode code points only (check unicode.org)
-- 5. Use E'...' string syntax for escape sequences
  1. Fix Unicode escape sequence syntax
  2. Correct hexadecimal and octal escape sequences
  3. Standardize escape sequences across string operations
  4. Check PL/pgSQL parser settings and compatibility
postgresql — error
-- Problematic Unicode escapes
SELECT E'Greek alpha: \u3B1'; -- Missing digit (should be 4 hex digits)
SELECT E'Smiley: \U1F600'; -- Wrong: should be 8 hex digits for \U
SELECT E'Letter: \u03b1'; -- Wrong: lowercase hex digits not always supported
-- Correct Unicode escapes
SELECT E'Greek alpha: \u03B1'; -- 4 hex digits, uppercase
SELECT E'Smiley: \U0001F600'; -- 8 hex digits for \U escapes
SELECT E'Letter: \u03B1'; -- Uppercase hex digits (A-F, 0-9)
-- In PL/pgSQL functions:
CREATE OR REPLACE FUNCTION test_unicode() RETURNS text AS $$
BEGIN
-- Correct:
RETURN E'Unicode: \u03B1\u03B2\u03B3'; -- αβγ
-- Wrong:
-- RETURN E'Unicode: \u3b1'; -- Missing digit, lowercase
END;
$$ LANGUAGE plpgsql;
-- Rules for Unicode escapes:
-- 1. \u must be followed by exactly 4 hexadecimal digits (0-9, A-F)
-- 2. \U must be followed by exactly 8 hexadecimal digits
-- 3. Digits should be uppercase (A-F, not a-f) for consistency
-- 4. Valid Unicode code points only (check unicode.org)
-- 5. Use E'...' string syntax for escape sequences
22P06: nonstandard_use_of_escape_character

What this error means

The PostgreSQL error "22P06: nonstandard_use_of_escape_character" is a PL/pgSQL error (SQLSTATE class 22 with subclass P) that occurs when the database encounters escape character sequences that don't conform to PostgreSQL's standard escape syntax. This error is specific to PL/pgSQL (PostgreSQL's procedural language) and occurs when: 1. Using non-standard Unicode escape sequences (e.g., \uXXXX without proper formatting) 2. Using hexadecimal escapes (\xXX) with incorrect formatting 3. Custom escape patterns in PL/pgSQL string manipulation 4. Mixing escape syntax from other programming languages or databases 5. Incorrect use of dollar-quoted string delimiters with escape characters The "P" in 22P06 indicates this is a PL/pgSQL error, meaning it typically occurs within stored procedures, functions, or triggers written in PostgreSQL's procedural language. The parser detects escape sequences that don't match PostgreSQL's expected patterns for: - Unicode escapes: \uXXXX or \UXXXXXXXX (4 or 8 hex digits) - Hexadecimal escapes: \xXX (2 hex digits) - Octal escapes: \ooo (1-3 octal digits) - Standard C-style escapes: \n, \t, \r, etc. This error often appears when migrating code from other databases or programming languages that use different escape conventions.

Typical symptoms

  • PL/pgSQL function or trigger fails with SQLSTATE 22P06 error
  • Error appears when executing stored procedures with string literals
  • Unicode or hexadecimal escape sequences cause parsing failures
  • Error message includes 'nonstandard_use_of_escape_character'
  • String manipulation in PL/pgSQL functions fails
  • Dynamic SQL execution with escape sequences fails
  • Error occurs during function compilation or execution
  • Migration from other databases fails due to escape sequence differences

Common causes

  1. Incorrect Unicode escape syntax (e.g., missing digits, wrong case) (most common)
  2. Malformed hexadecimal escapes (\xXX with wrong number of hex digits)
  3. Octal escapes with invalid digits (outside 0-7 range)
  4. Mixing escape syntax from other languages (JavaScript, Python, etc.)
  5. Using backslash escapes in contexts where they're not supported
  6. Incorrect dollar-quoted string delimiter usage with escapes
  7. PL/pgSQL code copied from other databases with different escape rules
  8. Dynamic SQL construction with improperly escaped strings
  9. Unicode escapes without the 'u' or 'U' prefix in lowercase
  10. Escape sequences split across multiple string concatenations
  11. Using escape characters that conflict with PL/pgSQL's parser rules
  12. Legacy code using deprecated escape conventions

How to fix "22P06: nonstandard_use_of_escape_character"

1Fix Unicode escape sequence syntax

Unicode escapes in PostgreSQL must follow specific patterns. Check and correct your Unicode escapes:

sql
-- Problematic Unicode escapes
SELECT E'Greek alpha: \u3B1';  -- Missing digit (should be 4 hex digits)
SELECT E'Smiley: \U1F600';     -- Wrong: should be 8 hex digits for \U
SELECT E'Letter: \u03b1';      -- Wrong: lowercase hex digits not always supported

-- Correct Unicode escapes
SELECT E'Greek alpha: \u03B1';  -- 4 hex digits, uppercase
SELECT E'Smiley: \U0001F600';   -- 8 hex digits for \U escapes
SELECT E'Letter: \u03B1';       -- Uppercase hex digits (A-F, 0-9)

-- In PL/pgSQL functions:
CREATE OR REPLACE FUNCTION test_unicode() RETURNS text AS $$
BEGIN
  -- Correct:
  RETURN E'Unicode: \u03B1\u03B2\u03B3';  -- αβγ
  -- Wrong:
  -- RETURN E'Unicode: \u3b1';  -- Missing digit, lowercase
END;
$$ LANGUAGE plpgsql;

-- Rules for Unicode escapes:
-- 1. \u must be followed by exactly 4 hexadecimal digits (0-9, A-F)
-- 2. \U must be followed by exactly 8 hexadecimal digits
-- 3. Digits should be uppercase (A-F, not a-f) for consistency
-- 4. Valid Unicode code points only (check unicode.org)
-- 5. Use E'...' string syntax for escape sequences
2Correct hexadecimal and octal escape sequences

Hexadecimal (\x) and octal (\ooo) escapes have specific formatting requirements:

sql
-- Problematic hex/octal escapes
SELECT E'Byte: \xG';     -- 'G' is not a hex digit
SELECT E'Byte: \x';      -- Missing hex digits
SELECT E'Byte: \x123';   -- Too many digits (max 2)
SELECT E'Octal: \89';    -- '8' and '9' not valid octal digits
SELECT E'Octal: \';      -- Incomplete escape

-- Correct hex/octal escapes
SELECT E'Byte: \x41';     -- 'A' (hex 41)
SELECT E'Byte: \x0A';     -- Newline (hex 0A)
SELECT E'Byte: \x00';     -- Null byte
SELECT E'Octal: \101';    -- 'A' (octal 101)
SELECT E'Octal: \012';    -- Newline (octal 12)
SELECT E'Octal: \0';      -- Null byte (octal 0)

-- In PL/pgSQL:
CREATE OR REPLACE FUNCTION test_hex_octal() RETURNS text AS $$
DECLARE
  result text;
BEGIN
  -- Correct usage:
  result := E'Hex: \x48\x65\x6C\x6C\x6F';  -- "Hello"
  result := result || E' Octal: \110\145\154\154\157';  -- "Hello"
  RETURN result;
END;
$$ LANGUAGE plpgsql;

-- Rules:
-- Hexadecimal escapes (\xXX):
--   - Exactly 2 hexadecimal digits (0-9, A-F, a-f)
--   - Represents a single byte
--   - Case insensitive for hex digits

-- Octal escapes (\ooo):
--   - 1-3 octal digits (0-7)
--   - Maximum value \377 (decimal 255)
--   - Shorter sequences allowed: \0, \12, \123

-- Both require E'...' string syntax
3Standardize escape sequences across string operations

Ensure consistent escape sequence usage, especially in string concatenation and dynamic SQL:

sql
-- Problematic: Mixed escape sequences
CREATE OR REPLACE FUNCTION problematic_escape() RETURNS text AS $$
DECLARE
  part1 text := E'\u03B1';      -- Unicode escape
  part2 text := '\x62';         -- Hex escape without E'...'
  part3 text := E'\n';          -- Newline
BEGIN
  RETURN part1 || part2 || part3;  -- Mixing escape types
END;
$$ LANGUAGE plpgsql;

-- Solution: Standardize all strings
CREATE OR REPLACE FUNCTION fixed_escape() RETURNS text AS $$
DECLARE
  part1 text := E'\u03B1';      -- Unicode
  part2 text := E'\x62';        -- Hex with E'...'
  part3 text := E'\n';          -- Newline
  dynamic_sql text;
BEGIN
  -- For dynamic SQL, be careful with escapes
  dynamic_sql := format('SELECT %L', E'Text with \n newline');
  -- format() handles escaping properly

  RETURN part1 || part2 || part3;
END;
$$ LANGUAGE plpgsql;

-- Using dollar-quoted strings to avoid escape issues
CREATE OR REPLACE FUNCTION dollar_quoted_example() RETURNS text AS $$
DECLARE
  -- Dollar-quoted strings don't process backslash escapes
  no_escape text := $$Raw string with ackslash and 'quote'$$;

  -- But you can combine with E'...' if needed
  with_escape text := E'\u03B1' || $$ and raw text$$;
BEGIN
  RETURN with_escape;
END;
$$ LANGUAGE plpgsql;

-- Best practices:
-- 1. Use E'...' consistently for all strings needing escapes
-- 2. Avoid mixing escaped and non-escaped string concatenation
-- 3. Use format() or quote_literal() for dynamic SQL
-- 4. Consider dollar-quoted strings for complex string literals
-- 5. Test escape sequences in simple SELECT statements first
4Check PL/pgSQL parser settings and compatibility

PostgreSQL has settings that affect escape sequence parsing. Check and adjust if needed:

sql
-- Check relevant settings
SHOW standard_conforming_strings;
SHOW escape_string_warning;
SHOW backslash_quote;

-- standard_conforming_strings = on (default since 9.1):
-- - Backslashes are literal in regular strings
-- - Use E'...' for escape sequences
-- - Recommended for new code

-- standard_conforming_strings = off:
-- - Backslashes are escape characters in all strings
-- - Legacy behavior for compatibility
-- - Can cause confusion with escape sequences

-- If migrating legacy code:
SET standard_conforming_strings = off;  -- Temporary
-- Update code to use E'...' for escapes
SET standard_conforming_strings = on;   -- Back to default

-- escape_string_warning = on (default):
-- - Warns about backslashes in regular strings
-- - Helps identify escape sequence issues

-- backslash_quote settings:
-- - Controls whether \' is allowed in strings
-- - Affects escape sequence parsing

-- For consistent behavior:
SET standard_conforming_strings = on;
SET escape_string_warning = on;

-- Update your PL/pgSQL functions:
-- Old style (with standard_conforming_strings = off):
-- CREATE FUNCTION old() RETURNS text AS $$
-- BEGIN
--   RETURN 'Line 1
Line 2';  -- Backslash escape
-- END;
-- $$ LANGUAGE plpgsql;

-- New style:
CREATE OR REPLACE FUNCTION new() RETURNS text AS $$
BEGIN
  RETURN E'Line 1\nLine 2';  -- Explicit escape string
END;
$$ LANGUAGE plpgsql;

-- Or use dollar-quoted strings:
CREATE OR REPLACE FUNCTION dollar() RETURNS text AS $$
BEGIN
  RETURN $$Line 1
Line 2$$;  -- Actual newline in source
END;
$$ LANGUAGE plpgsql;
5Validate and test escape sequences

Test your escape sequences to ensure they're valid:

sql
-- Test individual escape sequences
SELECT E'\u03B1' AS test1;      -- Should work: α
SELECT E'\x41\x42\x43' AS test2;  -- Should work: ABC
SELECT E'\101\102\103' AS test3;  -- Should work: ABC (octal)

-- Test problematic sequences (will error)
-- SELECT E'\u3B1' AS bad1;      -- Error: missing digit
-- SELECT E'\xGH' AS bad2;       -- Error: invalid hex digit
-- SELECT E'\89' AS bad3;        -- Error: invalid octal digit

-- Test in PL/pgSQL context
CREATE OR REPLACE FUNCTION test_escapes() RETURNS text AS $$
DECLARE
  test_result text;
BEGIN
  -- Test each escape type
  test_result := E'Unicode: \u03B1\u03B2\u03B3';
  test_result := test_result || E' Hex: \x48\x65\x6C\x6C\x6F';
  test_result := test_result || E' Octal: \110\145\154\154\157';
  test_result := test_result || E' Standard: \tTab\nNewline';

  RETURN test_result;
EXCEPTION
  WHEN SQLSTATE '22P06' THEN
    RETURN 'ERROR: Invalid escape sequence';
END;
$$ LANGUAGE plpgsql;

-- Call the function
SELECT test_escapes();

-- Check function definition for escape issues
\df+ test_escapes

-- Use pg_get_functiondef to see the source
SELECT pg_get_functiondef('test_escapes'::regproc);

-- For dynamic testing, use DO blocks
DO $$
BEGIN
  -- This will error if escapes are invalid
  PERFORM E'\u03B1';
  RAISE NOTICE 'Unicode escape test passed';
EXCEPTION
  WHEN SQLSTATE '22P06' THEN
    RAISE WARNING 'Unicode escape test failed';
END;
$$;

-- Create a test table with various string literals
CREATE TEMP TABLE escape_test AS
SELECT
  E'\u03B1' AS unicode_escape,
  E'\x41' AS hex_escape,
  E'\101' AS octal_escape,
  E'\n' AS std_escape;
6Migrate from other database escape conventions

If migrating from other databases, adapt escape sequences to PostgreSQL syntax:

sql
-- Common migration scenarios:

-- 1. Oracle-style escapes (q'[...]')
-- Oracle: q'[It's raining]'
-- PostgreSQL: $$It's raining$$ or E'It\'s raining'

-- 2. MySQL escapes (different Unicode syntax)
-- MySQL: _utf8'α' or N'α'
-- PostgreSQL: E'\u03B1' or U&'\03B1'

-- 3. SQL Server Unicode literals (N'...')
-- SQL Server: N'α'
-- PostgreSQL: E'\u03B1'

-- 4. JavaScript/Python-style escapes
-- JavaScript: '\u03B1' (always works)
-- Python: '\u03B1' or '\x41'
-- PostgreSQL: E'\u03B1' or E'\x41' (with E'...')

-- Migration function example
CREATE OR REPLACE FUNCTION migrate_escape(input_text text) RETURNS text AS $$
BEGIN
  -- Replace common non-standard patterns
  -- Example: Convert \u{03B1} to \u03B1
  input_text := regexp_replace(input_text, '\\u\{([0-9A-Fa-f]{4})\}', '\\u\1', 'g');

  -- Convert \x{41} to \x41
  input_text := regexp_replace(input_text, '\\x\{([0-9A-Fa-f]{2})\}', '\\x\1', 'g');

  -- Ensure E'...' prefix if not present and has escapes
  IF input_text ~ '\\\\(u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8}|x[0-9A-Fa-f]{2}|[0-7]{1,3}|[abfnrtv])' THEN
    IF NOT input_text LIKE 'E''%' THEN
      input_text := 'E''' || input_text || '''';
    END IF;
  END IF;

  RETURN input_text;
END;
$$ LANGUAGE plpgsql;

-- Test migration
SELECT migrate_escape('\u{03B1}');  -- Returns E'\u03B1'
SELECT migrate_escape('\x{41}');    -- Returns E'\x41'
SELECT migrate_escape('Line 1\nLine 2');  -- Returns E'Line 1\nLine 2'

-- For bulk migration of PL/pgSQL functions:
-- 1. Extract function definitions
-- 2. Apply migrate_escape() to string literals
-- 3. Recreate functions with corrected escapes

-- Use pg_dump and sed/awk for bulk updates:
-- pg_dump --schema-only -t 'function_name' | sed "s/old_escapes/new_escapes/g"

-- Or create a migration script:
/*
DO $$
DECLARE
  func_record RECORD;
  old_def text;
  new_def text;
BEGIN
  FOR func_record IN
    SELECT proname, pg_get_functiondef(oid) AS definition
    FROM pg_proc
    WHERE pronamespace = 'public'::regnamespace
  LOOP
    old_def := func_record.definition;
    new_def := migrate_escape(old_def);  -- Your migration logic
    IF old_def != new_def THEN
      EXECUTE 'DROP FUNCTION ' || func_record.proname;
      EXECUTE new_def;
    END IF;
  END LOOP;
END;
$$;
*/

Advanced notes

### PostgreSQL Escape Sequence Reference

Standard C-style escapes (require E'...'):
- \\ = backslash
- \' = single quote
- \" = double quote
- \b = backspace
- \f = form feed
- \n = newline
- \r = carriage return
- \t = tab
- \v = vertical tab

Unicode escapes:
- \uXXXX = Unicode character (4 hex digits, A-F uppercase)
- \UXXXXXXXX = Unicode character (8 hex digits)

Hexadecimal escapes:
- \xXX = byte with hex value XX (2 hex digits, case-insensitive)

Octal escapes:
- \ooo = byte with octal value ooo (1-3 digits, 0-7)

### PL/pgSQL Specific Considerations

1. Function compilation: Escape sequences are validated when functions are created with CREATE FUNCTION, not when they're called.

2. Dynamic SQL: EXECUTE and format() handle escapes differently. Use format() with %L for proper escaping.

3. String concatenation: Escape sequences in concatenated strings must be valid in each part:
E'\u03B1' || 'beta' works, but 'alpha' || E'\u03B2' might not if 'alpha' has issues.

4. Exception handling: Catch 22P06 errors with:

bash
EXCEPTION WHEN SQLSTATE '22P06' THEN
     -- Handle escape sequence error

### Performance Implications

1. Escape parsing: Happens at query planning/function compilation time, not execution time.

2. Unicode normalization: \u escapes are converted to UTF-8, which is efficient.

3. String constants: Escaped strings become constants in query plans.

4. Dynamic SQL: Excessive escape processing in dynamic SQL can impact performance.

### Security Considerations

1. SQL injection: Improper escaping can lead to injection in dynamic SQL.

2. Use parameterized queries: For dynamic values, use EXECUTE ... USING or format().

3. Validation: Validate escape sequences before using them in SQL.

4. Privileges: Functions with dynamic SQL require careful privilege management.

### Debugging Tips

1. Use RAISE NOTICE: Print strings with escapes to see actual values.

2. Check string length: Escape sequences count as single characters.

3. Convert to hex: Use encode(string::bytea, 'hex') to see byte representation.

4. Test in psql: Test escape sequences directly in psql before putting in functions.

### Version Compatibility

- PostgreSQL 8.0+: Basic escape sequences supported
- PostgreSQL 8.3+: standard_conforming_strings introduced
- PostgreSQL 9.0+: Unicode escapes improved
- PostgreSQL 9.1+: standard_conforming_strings defaults to on
- PostgreSQL 10+: Better error messages for escape sequences

### Migration Tools

1. pg_dump: Use --schema-only to extract function definitions
2. sed/awk: For bulk search/replace of escape patterns
3. Custom functions: Like migrate_escape() example above
4. Version upgrade: Test escape sequences after major version upgrades

### Common PL/pgSQL Patterns

1. Building dynamic WHERE clauses: Use format() with %I and %L
2. JSON/XML generation: Escape quotes and backslashes properly
3. Regular expressions: Many backslashes - use dollar-quoted strings
4. Error messages: Include escaped values in RAISE EXCEPTION

Frequently asked questions

What does "22P06" mean?
The PostgreSQL error "22P06: nonstandard_use_of_escape_character" is a PL/pgSQL error (SQLSTATE class 22 with subclass P) that occurs when the database encounters escape character sequences that don't conform to PostgreSQL's standard escape syntax. This error is specific to PL/pgSQL (PostgreSQL's procedural language) and occurs when: 1. , \uXXXX without proper formatting) 2.
How do I fix "22P06"?
This PostgreSQL error occurs when non-standard escape character sequences are used in PL/pgSQL code or SQL queries. The error indicates that escape sequences don't conform to PostgreSQL's standard escape syntax, often happening with Unicode escapes, hexadecimal escapes, or custom escape patterns in procedural code.
What causes "22P06"?
Incorrect Unicode escape syntax (e.g., missing digits, wrong case) (most common) Malformed hexadecimal escapes (\xXX with wrong number of hex digits) Octal escapes with invalid digits (outside 0-7 range)
Is the "22P06" error serious?
It is a low-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading