bytea_output controls how PostgreSQL formats binary data when sending it to a client. Two values: hex (the default since PostgreSQL 9.0, released in 2010) and escape (the traditional format, dating back to the early 2000s). Context is user. The parameter affects output only — bytea input has accepted both formats forever, and a SET bytea_output setting changes nothing about how you write literal values.
The two formats, given the same five-byte value:
1 SET bytea_output = 'hex';
2 SELECT '\x48656c6c6f'::bytea;
3 -- \x48656c6c6f
4
5 SET bytea_output = 'escape';
6 SELECT '\x48656c6c6f'::bytea;
7 -- Hello
Why the format changed
The escape format is the original PostgreSQL bytea representation: printable bytes as themselves, non-printable bytes as three-digit octal escapes (\001, \377), and certain meta-characters as backslash escapes. It looks superficially friendly — 'Hello' is just Hello on output — but the niceness is a trap. The docs put it bluntly: the format “fuzzes up the distinction between binary strings and character strings, and… the particular escape mechanism that was chosen is somewhat unwieldy.” That is the rare moment when the PostgreSQL documentation editorializes, and it does so accurately.
The actual problems with the escape format, in operation:
- Double-escaping hell. A literal backslash in the data becomes
\\in the output, which becomes\\\\if the consuming code goes through another string-literal parser, and so on. Every layer of the stack has to agree on how many backslashes mean one. - Locale-dependent output. What counts as “printable” depends on locale, so the same
byteavalue can render differently depending onLC_CTYPE. Round-tripping through different systems is fragile. - Slower to parse. The escape format requires character-by-character interpretation; hex is a fixed-width transformation that a tight loop can rip through.
- Genuinely confusing for binary data. Looking at
\336\255\276\357and recognizing it as0xDEADBEEFis not a skill anyone needs in 2026.
Hex format fixes all of that. Every byte is two hex digits, the entire value is prefixed \x to distinguish it from escape format, and the encoding is locale-independent and trivial to parse in any language. The docs’ recommendation is unambiguous: “its use is preferred.”
When you might need to set this
Almost never. The hex format has been the default for sixteen years and every actively-maintained client library handles it. The legitimate reasons to set bytea_output = 'escape' reduce to:
- Pre-9.0 client tooling that genuinely cannot parse hex. This is a vanishing population. If you have one, you have a bigger upgrade problem than this GUC.
- Custom application code that hand-parses
byteaand was written against the escape format. Easier to fix the application than to commit to escape format forever — at minimum, leave a comment explaining why this is set if you do. - PHP’s
pg_unescape_bytea(), which historically only handled the escape format. Recent PHP versions handle hex; older PHP applications may still need it.
The parameter can be set per-session (SET bytea_output = 'escape'), per-role (ALTER ROLE legacy_app SET bytea_output = 'escape'), or per-database. Per-role is the right scope when one application needs the old format but the rest of your workload does not.
Recommendation: Leave it at hex. If you have an application that requires escape, set it per-role rather than globally, document the reason, and put a calendar reminder to revisit it in a year. The legacy format is not going anywhere — like array_nulls, backslash_quote, and bonjour, this is another piece of PostgreSQL’s commitment to not breaking things — but its operational relevance dwindles with every PostgreSQL release.