iconv-lite: Pure JS character encoding conversion

- No need for native code compilation. Quick to install, works on Windows, Web, and in sandboxed environments.
- Used in popular projects like Express.js (body_parser),
Grunt, Nodemailer, Yeoman and others.
- Faster than node-iconv (see below for performance comparison).
- Intuitive encode/decode API, including Streaming support.
- Native ES modules for Node.js and browsers, with automatic backend selection in compatible bundlers.
- Typescript type definition file included.
- React Native is supported (need to install
stream module to enable Streaming API).
Usage
This package uses ES modules and requires Node.js 22 or later. CommonJS callers
can use const { default: iconv } = await import('iconv-lite') inside an async
function. Direct require('iconv-lite') is no longer a supported entry point.
Encoding and decoding remain synchronous once the module has loaded.
Use the default export for the conversion API and pass settings through options.
Functions are also available as named exports, for example
import { encode, decode } from 'iconv-lite'.
The public package entry points are iconv-lite, iconv-lite/web, and
iconv-lite/encodings/*.js.
Basic API
import iconv from 'iconv-lite';
const str = iconv.decode(Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]), 'win1251');
const buf = iconv.encode("Sample input string", 'win1251');
const replacedBuf = iconv.encode("Hola 🙂", 'latin1', {
invalidCharHandler: () => ":)"
});
try {
iconv.encode("Hello 世界", 'latin1', {
invalidCharHandler: function(char, index) {
throw new Error("Cannot encode character " + char + " at index " + index);
}
});
} catch (error) {
console.warn(error.message);
}
const checkedBuf = iconv.encode("Hello 世界", 'latin1', {
invalidCharHandler: function(char, index) {
console.warn("Unsupported character " + char + " at index " + index);
return true;
}
});
iconv.encodingExists("us-ascii")
External encodings
Import an encoding package’s definition map and register it on the entry point
your application uses:
import iconv from 'iconv-lite';
import definitions from './custom-encodings.js';
iconv.addEncoding(definitions);
const bytes = iconv.encode('Hello', 'custom-encoding');
addEncoding(definitions, { replace: true }) explicitly allows replacing existing
codecs and aliases. Registration is atomic, normalizes labels, and rejects missing
alias targets and cycles. Repeating equivalent definitions is a no-op. Node and Web
instances keep their registrations separate.
See External encoding API for registration examples,
codec and backend contracts, and TypeScript types.
Handling unrepresentable characters
For table-based single-byte and multibyte encodings, pass defaultCharByte to
select the fallback byte for that encoder. It must be an integer between 0x00
and 0xFF:
iconv.encode('Hello 世界', 'windows-1252', { defaultCharByte: 0x21 });
The option also works with getEncoder(), encodeStream() and encodeWebStream().
Each encoder captures its setting at creation. The default is 0x3F (?).
ISO-2022-JP restricts the replacement to ASCII bytes other than ESC, SO and SI.
Replacement settings do not modify shared codec tables.
Single-byte encoders emit one replacement byte per unrepresentable Unicode code
point. For example, iconv.encode('🙂', 'windows-1252') returns one 0x3F byte.
Incremental encoders buffer a trailing high surrogate across empty writes until
the next nonempty write or end(). Consume the bytes returned by end() as well.
invalidCharHandler(char, index) runs when encoding would replace or truncate an
input character. Its result controls what happens:
| Handler result |
Behavior |
| A string |
Encode that replacement text; "" omits the character. |
false or undefined |
Keep the encoding’s default replacement bytes. |
true |
Discard the current write and return null. |
| Throw an error |
Propagate the error to the caller or encoding stream. |
Replacement text must be representable in the target encoding. An invalid
replacement or any other return value throws a TypeError, without calling the
handler recursively. Replacements use the same encoding context as surrounding
text, including multicharacter Big5 mappings. Indices refer to the original input,
not the replacement text.
The handler covers unmappable characters in single-byte and multibyte encodings,
unpaired surrogates in UTF-8 and UTF-32, and characters above U+00FF in binary.
UTF-16, CESU-8, and UTF-7 preserve UTF-16 code units, including unpaired surrogates,
so they do not invoke the handler. hex and base64 reject this option because
they convert byte representations rather than text. Omitting the handler keeps
the existing encoding behavior.
Pass { fatal: true } to throw a TypeError when decoding invalid input.
This is supported by UTF-8, UTF-16, UTF-32, ISO-2022-JP and table-based single-byte and multibyte encodings,
including Windows-874, Windows-125x, Big5, Shift_JIS, EUC-JP, EUC-KR, GBK and GB18030.
The default remains replacement decoding.
iconv.decode(Uint8Array.of(0x81), 'windows-1252', { fatal: true });
iconv.decode(Uint8Array.of(0xAA), 'windows-1253');
iconv.decode(Uint8Array.of(0xAA), 'windows-1253', { fatal: true });
The option also applies to getDecoder(), decodeStream() and decodeWebStream().
A fatal single-byte decoder rejects the failing write and can process a later
valid write. Stream errors propagate through the stream’s error mechanism.
Fatal table-based multibyte decoders reject invalid and truncated sequences. When an
incremental write throws, restored and unread bytes remain queued for a later write or end().
Finalization resets the decoder even when it throws.
For table-based single-byte and multibyte encodings, ISO-2022-JP, CESU-8 and UTF-32, pass
defaultCharUnicode to choose the text inserted for each decoding error:
iconv.decode(Uint8Array.of(0xAA), 'windows-1253', { defaultCharUnicode: '<invalid>' });
The value must be a string; "" omits invalid input. Text is inserted literally,
and valid input representing U+FFFD is preserved.
The option also works with getDecoder(), decodeStream() and decodeWebStream().
Each decoder captures its replacement at creation. The default is U+FFFD (�).
Where supported, { fatal: true } takes precedence and throws instead.
Browsers
Bundlers select the Web backend automatically. You can also select it explicitly:
import iconv from 'iconv-lite/web';
const bytes = iconv.encode('Hello', 'utf8');
For native browser modules, import dist/lib/index-web.js from a served copy of the
package. encodeWebStream() and decodeWebStream() use the native
TransformStream automatically. iconv.supportsWebStreams reports whether it is
available; older environments can supply a global TransformStream polyfill.
The Web entry point exposes only Web Streams. encodeStream(), decodeStream(),
and supportsStreams belong to the Node entry point.
DBCS table modules are imported with the library, but their arrays and codec
lookup tables are initialized and cached only when the corresponding encoding is used.
Streaming API
In Node.js, encodeStream() and decodeStream() return Node streams.
encodeWebStream() and decodeWebStream() return Web Streams and are available
in both Node.js and browsers. Both APIs are initialized automatically.
http.createServer(function(req, res) {
var converterStream = iconv.decodeStream('win1251');
req.pipe(converterStream);
converterStream.on('data', function(str) {
console.log(str);
});
});
fs.createReadStream('file-in-win1251.txt')
.pipe(iconv.decodeStream('win1251'))
.pipe(iconv.encodeStream('ucs2'))
.pipe(fs.createWriteStream('file-in-ucs2.txt'));
http.createServer(function(req, res) {
req.pipe(iconv.decodeStream('win1251')).collect(function(err, body) {
assert(typeof body == 'string');
console.log(body);
});
});
Web Streams work with fetch(), pipeThrough(), and pipeTo():
import iconv from 'iconv-lite/web';
const response = await fetch('/file-in-win1251.txt');
const utf8Bytes = response.body
.pipeThrough(iconv.decodeWebStream('win1251'))
.pipeThrough(iconv.encodeWebStream('utf8'));
const text = await new Response(utf8Bytes).text();
encodeWebStream(encoding, options) accepts strings and produces encoded bytes
(Buffer with the Node backend, Uint8Array with the Web backend).
decodeWebStream(encoding, options) accepts Uint8Array or Buffer chunks and
produces strings. Both accept the same codec options as encode() and decode().
Closing the writable side flushes buffered codec output; errors and cancellation
propagate through the native stream pipeline, with native backpressure.
Use a reader or pipeTo() to consume output while writing input. Web Streams
expose .readable and .writable; .collect() belongs to the Node stream API.
As with Node streams, hex input is parsed per chunk, so keep each pair of hex
digits in the same chunk.
Returning true from invalidCharHandler discards the entire
current write(), including its valid prefix, and returns null. Later writes
can still succeed. In encodeStream() and encodeWebStream(), this drops the
current chunk without ending the stream; throw from the handler to signal a
stream error instead.
With a handler, SBCS, DBCS, UTF-8, UTF-32, and binary encoders buffer a trailing
high surrogate until the next nonempty write or end(), so a split surrogate
pair is processed as one character. The
callback index counts Unicode code points within the input of the write where
the character starts. A buffered character retains its index from that write.
When using getEncoder(), consume the bytes returned by end() too; it can
return null if the handler cancels the buffered character. A cancellation
during either write() or end() makes the one-shot encode() return null.
Supported encodings
- Node.js-compatible encodings: utf8, ucs2 / utf16-le, binary, base64, hex.
- Additional unicode encodings: utf16, utf16-be, utf-7, utf-7-imap, utf32, utf32-le, and utf32-be.
- All widespread singlebyte encodings: Windows 125x family, ISO-8859 family,
IBM/DOS codepages, Macintosh family, KOI8 family, all others supported by iconv library.
Aliases like ‘latin1’, ‘us-ascii’ also supported.
- All widespread multibyte encodings: CP932, CP936, CP949, CP950, GB2312, GBK, GB18030, Big5, Shift_JIS, EUC-JP, ISO-2022-JP.
x-user-defined: preserves ASCII and maps bytes 80–FF to U+F780–U+F7FF, following WHATWG. Encoding reverses these mappings and uses the configured replacement or handler for other characters.
See all supported encodings on wiki.
The following legacy label families use the Windows mappings defined by the
WHATWG Encoding Standard,
for both encoding and decoding:
| Common labels |
Encoding |
ascii, us-ascii, latin1, iso-8859-1 |
Windows-1252 |
latin5, iso-8859-9 |
Windows-1254 |
tis-620, iso-8859-11 |
Windows-874 |
For example, iconv.decode(Uint8Array.of(0x80), 'ascii') now returns €, and
iconv.encode('€', 'latin1') produces byte 0x80. These labels differ from
Node.js Buffer’s ascii and latin1 encodings. Use binary for byte-preserving
Latin-1 conversion between bytes and U+0000–U+00FF.
Numeric code page labels retain their explicit encodings, including in streams.
Both the number as a string and its cp-prefixed form are supported:
| Code page |
Encoding |
1200, 1201 |
UTF-16LE, UTF-16BE |
12000, 12001 |
UTF-32LE, UTF-32BE |
65000, 65001 |
UTF-7, UTF-8 |
20127 |
7-bit ASCII (also cp367 and ibm367) |
28591, 28599 |
ISO-8859-1, ISO-8859-9 |
28601 |
Existing iconv-lite alias for ISO-8859-11 |
Thus cp28591 still decodes 0x80 to U+0080, while the web label iso-8859-1
decodes it to €. Code pages 1200/1201 and 12000/12001 use fixed byte
order, without the autodetection of utf16 or utf32.
Encoding labels reject non-ASCII characters, including Unicode whitespace and
letters that Unicode lowercasing would turn into ASCII. Surrounding ASCII
whitespace and ASCII letter case are accepted. Numeric code pages and lenient
ASCII separator matching are iconv-lite extensions, not WHATWG TextDecoder labels.
Most singlebyte encodings are generated automatically from node-iconv. Thank you Ben Noordhuis and libiconv authors!
Multibyte encodings are generated from Unicode.org mappings and WHATWG Encoding Standard mappings. Thank you, respective authors!
Encoding/decoding speed
Comparison with node-iconv module (1000x256kb, on MacBook Pro, Core i5/2.6 GHz, Node v0.12.0).
Note: your results may vary, so please always check on your hardware.
operation [email protected] [email protected]
----------------------------------------------------------
encode('win1251') ~96 Mb/s ~320 Mb/s
decode('win1251') ~95 Mb/s ~246 Mb/s
BOM handling
- Decoding: BOM is stripped by default, unless overridden by passing
stripBOM: false in options
(f.ex. iconv.decode(buf, enc, {stripBOM: false})).
A callback might also be given as a stripBOM parameter - it’ll be called if BOM character was actually found.
- If you want to detect UTF-8 BOM when decoding other encodings, use node-autodetect-decoder-stream module.
- Encoding: No BOM added, unless overridden by
addBOM: true option.
UTF-16 Encodings
This library supports UTF-16LE, UTF-16BE and UTF-16 encodings. First two are straightforward, but UTF-16 is trying to be
smart about endianness in the following ways:
- Decoding: uses BOM and ‘spaces heuristic’ to determine input endianness. Default is UTF-16LE, but can be
overridden with
defaultEncoding: 'utf-16be' option. Strips BOM unless stripBOM: false.
- Encoding: uses UTF-16LE and writes BOM by default. Use
addBOM: false to override.
UTF-32 Encodings
This library supports UTF-32LE, UTF-32BE and UTF-32 encodings. Like the UTF-16 encoding above, UTF-32 defaults to UTF-32LE, but uses BOM and ‘spaces heuristics’ to determine input endianness.
- The default of UTF-32LE can be overridden with the
defaultEncoding: 'utf-32be' option. Strips BOM unless stripBOM: false.
- Encoding: uses UTF-32LE and writes BOM by default. Use
addBOM: false to override. (defaultEncoding: 'utf-32be' can also be used here to change encoding.)
- Decoding is strict (per the Unicode Standard): a code unit that is a surrogate code point (U+D800–U+DFFF), is above U+10FFFF, or is a truncated trailing code unit is replaced with �. Pass
{ fatal: true } to throw on such input instead.
- Encoding replaces a lone (unpaired) surrogate in the input with � so the output is always valid UTF-32.
UTF-7 Encodings
This library supports UTF-7 (RFC 2152) and UTF-7-IMAP / Modified UTF-7 (RFC 3501).
- Encoding: for UTF-7, the optional “Set O” punctuation is left as direct ASCII (per RFC 2152), so output stays compact.
- Decoding follows RFC 2152: ill-formed input — an incomplete code unit, non-zero Base64 padding bits, a shift-in
not followed by Base64 or “-”, or a non-ASCII byte outside a shifted run — is replaced with �. Lone surrogates
pass through as their raw 16-bit code units.
- The
fatal decode option is not supported (RFC 2152 doesn’t define it); decoding is always lenient (replacement with �).
UTF-7 is designed for short, 7-bit-safe strings (mail headers, IMAP mailbox names) and is best used that way.
It is not recommended for large or bulk text, where the native utf8/utf16 encodings are faster.
ISO-2022-JP
ISO-2022-JP is the stateful 7-bit Japanese encoding common in email. Encoding and decoding follow the
WHATWG Encoding Standard.
- Decoding supports ASCII, JIS X 0201 Roman and Katakana, and JIS X 0208 (
ESC $ @ and ESC $ B). Escape sequences
and characters may be split across stream chunks. Ill-formed input is replaced with �, including two escape
sequences in a row with nothing between them (a WHATWG rule that prevents hiding content).
- Encoding uses ASCII, JIS X 0201 Roman (for ¥ and ‾) and JIS X 0208, and always ends in ASCII. CR and LF preserve
Roman state; they switch JIS X 0208 back to ASCII. Halfwidth katakana are encoded as fullwidth, and characters
with no mapping become
? using the same state transitions as an ASCII input character.
- Supports
fatal, defaultCharUnicode, defaultCharByte and invalidCharHandler. The replacement byte must
be ASCII and cannot be ESC (0x1B), SO (0x0E) or SI (0x0F), which would change or invalidate the encoding state.
- Character mappings are the same as the
EUC-JP codec (and browsers). They differ from the JIS mappings used by
GNU iconv and Python for 6 characters: for example, 0x2141 decodes to U+FF5E (~), not U+301C (〜).
Other notes
When decoding, be sure to supply a Buffer to decode() method, otherwise bad things usually happen.
Untranslatable characters are set to � or ?. When encoding, invalidCharHandler can customize replacements, observe unsupported characters, cancel a write, or throw. Automatic transliteration is not supported.
Node versions 0.10.31 and 0.11.13 are buggy, don’t use them (see #65, #77).
评论