Кодирование и декодирование одной строкой
API добавляет три метода для Base64 и три для hex:
| Метод | Что делает |
|---|---|
bytes.toBase64(options) | Uint8Array в строку Base64 |
Uint8Array.fromBase64(string, options) | Строку Base64 в новый Uint8Array |
bytes.setFromBase64(string, options) | Декодирует в существующий Uint8Array |
bytes.toHex() | Uint8Array в hex-строку в нижнем регистре |
Uint8Array.fromHex(string) | Hex-строку в новый Uint8Array |
bytes.setFromHex(string) | Декодирует hex в существующий Uint8Array |
Текст сначала нужно превратить в байты. TextEncoder всегда выдаёт UTF-8, поэтому Unicode работает без всяких фокусов:
const bytes = new TextEncoder().encode("Grüße, 世界");
const b64 = bytes.toBase64();
console.log(b64); // "R3LDvMOfZSwg5LiW55WM"
const back = new TextDecoder().decode(Uint8Array.fromBase64(b64));
console.log(back); // "Grüße, 世界"
Для ArrayBuffer, например из fetch().arrayBuffer() или crypto.subtle.digest(), нужно представление: new Uint8Array(buffer).toBase64(). У других типизированных массивов этих методов нет вовсе. Чтобы закодировать сырую память Uint16Array, оберните её: new Uint8Array(u16.buffer, u16.byteOffset, u16.byteLength).
Опции: alphabet, omitPadding, lastChunkHandling
alphabet
"base64" (по умолчанию) использует + и /. "base64url" использует - и _. Опция действует и на кодирование, и на декодирование, а декодер, настроенный на один алфавит, отвергает символы другого.
const data = new Uint8Array([251, 255]);
data.toBase64(); // "+/8="
data.toBase64({ alphabet: "base64url" }); // "-_8="
data.toBase64({ alphabet: "base64url", omitPadding: true }); // "-_8"
Uint8Array.fromBase64("-_8", { alphabet: "base64url" }); // Uint8Array [251, 255]
Uint8Array.fromBase64("-_8"); // SyntaxError
omitPadding
toBase64 по умолчанию добавляет дополнение =, даже с алфавитом Base64URL. Для вывода в стиле JWT передайте omitPadding: true. При декодировании дополнением управляет lastChunkHandling.
lastChunkHandling
Эта опция решает, что fromBase64 и setFromBase64 делают с последней группой символов. Три режима, проверенные на вводе, который декодируется в байты Hi?>:
| Ввод | "loose" (по умолчанию) | "strict" | "stop-before-partial" |
|---|---|---|---|
SGk/Pg== | Hi?> | Hi?> | Hi?> |
SGk/Pg (без дополнения) | Hi?> | SyntaxError | Hi? (останавливается перед Pg) |
SGk/Ph== (ненулевые биты-заполнители) | Hi?> | SyntaxError | Hi?> |
SGk/P (один оставшийся символ) | SyntaxError | SyntaxError | Hi? |
- loose ведёт себя как
atob: дополнение необязательно, лишние биты в последнем символе игнорируются. - strict принимает только канонический ввод с дополнением, как его описывает RFC 4648. Используйте его, когда закодированная форма должна быть единственной, например если строки Base64 сравниваются или подписываются.
- stop-before-partial декодирует все полные группы по 4 символа и не трогает неполный хвост. Это нужно для потоковой обработки, когда остаток группы придёт в следующем куске.
В любом режиме пробельные символы ASCII (пробелы, табуляции, переводы строк) пропускаются, так что ввод с переносами в стиле MIME декодируется напрямую. Любой другой символ вне алфавита вызывает SyntaxError.
Запись в существующий буфер
setFromBase64 и setFromHex декодируют в уже имеющийся Uint8Array и сообщают, сколько использовали:
const target = new Uint8Array(8);
const result = target.setFromBase64("SGVsbG8gd29ybGQ=");
console.log(result); // { read: 8, written: 6 }
Hello world занимает 11 байт, а в буфер помещается 8. Декодер записывает только целые 3-байтные группы, которые влезают, поэтому останавливается после 6 байт и сообщает, что прочитал 8 символов. Чтобы продолжить, вызовите метод снова с остатком строки и subarray буфера. Вместе с stop-before-partial это позволяет декодировать поток кусок за куском в заранее выделенный буфер:
const out = new Uint8Array(1024);
const { read, written } = out.setFromBase64("SGk/Pg", {
lastChunkHandling: "stop-before-partial",
});
// read: 4, written: 3. Keep "Pg" and prepend it to the next chunk.
Методы для hex устроены так же. toHex() всегда пишет в нижнем регистре, fromHex() принимает оба регистра, а на нечётном количестве цифр выбрасывает SyntaxError:
new Uint8Array([0xde, 0xad, 0xbe, 0xef]).toHex(); // "deadbeef"
Uint8Array.fromHex("CAFE"); // Uint8Array [202, 254]
Чем неудобны btoa и atob
btoa и atob родом из ранних браузеров и работают с «двоичными строками»: строками JavaScript, где код каждого символа равен значению байта от 0 до 255. Отсюда три проблемы.
Unicode либо вызывает исключение, либо молча ломается. Символы выше U+00FF заставляют btoa выбросить InvalidCharacterError (в Chrome: «The string to be encoded contains characters outside of the Latin1 range»). Символы от U+0080 до U+00FF ещё хуже: они кодируются без ошибки, но как Latin-1. btoa("Grüße") возвращает R3L832U=, хотя все остальные системы ожидают кодирование UTF-8, то есть R3LDvMOfZQ==.
Декодирование возвращает строку вместо байтов. atob("Q2Fmw6k=") возвращает "Café". Чтобы получить настоящий текст, нужно перевести строку в байты и декодировать их как UTF-8, а это цикл или Uint8Array.from(atob(s), c => c.charCodeAt(0)).
С большими данными нужна осторожность. Популярный однострочник btoa(String.fromCharCode(...bytes)) передаёт каждый байт как аргумент функции, и на массиве в 1 МБ падает с RangeError: Maximum call stack size exceeded. Строку приходится собирать кусками.
Кроме того, нет поддержки Base64URL и никакого контроля над дополнением и строгостью. Старый обходной путь btoa(unescape(encodeURIComponent(text))) действительно даёт Base64 от UTF-8, но опирается на устаревший unescape. С новым API всё это сводится к new TextEncoder().encode(text).toBase64().
Поддержка в браузерах и Node.js
Методы пришли из предложения TC39 «Uint8Array to/from Base64», которое достигло Stage 4 в июле 2025 года и вошло в ECMAScript 2026.
| Среда | Первая версия со всеми шестью методами |
|---|---|
| Chrome, Edge | 140 |
| Firefox | 133 |
| Safari (macOS и iOS) | 18.2 |
| Node.js | 25.0.0 |
С выходом Chrome 140 в сентябре 2025 года возможность получила статус Baseline, так что она есть во всех актуальных основных браузерах. В Node.js мы проверили методы напрямую. В Node 25.0.0 и 26.12.1 есть все шесть. В Node 24.22.1, LTS-релизе, по умолчанию нет ни одного. Его V8 принимает экспериментальный флаг --js-base-64, но в нашем тесте этот флаг открыл toBase64, fromBase64, toHex и fromHex без setFromBase64 и setFromHex, так что считайте Node 24 неподдерживаемым. В Node 24 и более ранних версиях используйте Buffer.from(b64, "base64") и buf.toString("base64"), которые принимают и "base64url". По данным совместимости MDN, API поддерживают также Deno 2.5 и Bun 1.1.22.
Запасной вариант для старых сред
Проверяйте наличие метода и откатывайтесь на btoa/atob с обработкой кусками. Эта версия проверена на Node 24 (запасной путь) и Node 26 (нативный путь) с текстом в UTF-8 и массивом в 1 МБ:
function bytesToBase64(bytes) {
if (typeof bytes.toBase64 === "function") return bytes.toBase64();
let bin = "";
for (let i = 0; i < bytes.length; i += 0x8000) {
bin += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
}
return btoa(bin);
}
function base64ToBytes(b64) {
if (typeof Uint8Array.fromBase64 === "function") return Uint8Array.fromBase64(b64);
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
}
Запасной вариант покрывает только опции по умолчанию. Если base64url, strict или setFromBase64 нужны везде, используйте полноценный полифил: в core-js он есть в виде модулей es.uint8-array.to-base64, es.uint8-array.from-base64 и связанных с ними.
Попробовать кодирование без кода можно в кодировщике Base64 и декодере, а URL-safe алфавит в Base64URL. Больше примеров на JavaScript, включая Buffer в Node, собрано в статьях Base64 в JavaScript и Base64 в Node.js.
Вопросы и ответы
Можно ли уже использовать новый Base64 API в продакшене?
В браузерах да: он есть в Chrome и Edge 140, Firefox 133 и Safari 18.2. В Node.js нужна версия 25 или новее. Если вы поддерживаете старые браузеры или Node 24 и ниже, оставьте запасной вариант.
Принимает ли Uint8Array.fromBase64 Base64URL?
Да, с опцией { alphabet: "base64url" }. Без неё - и _ вызывают SyntaxError. В режиме loose по умолчанию дополнение необязательно для обоих алфавитов.
Как закодировать строку через toBase64?
Сначала превратите её в байты UTF-8: new TextEncoder().encode(text).toBase64(). Декодирование делается обратной операцией: new TextDecoder().decode(Uint8Array.fromBase64(b64)).
Почему у моего Buffer или Uint16Array нет метода toBase64?
Методы есть только у Uint8Array и его подклассов. Buffer в Node получает их начиная с Node 25. Для других типизированных массивов оберните память: new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength).