Загрузить файл
  1. base64hub
  2. Base64 в коде
  3. JavaScript
Код

Base64 в JavaScript

В браузерах есть btoa и atob, которые понимают только Latin-1, и более новые Uint8Array.toBase64() / fromBase64(), которые нормально работают с байтами. Результаты ниже получены в headless Chromium.

btoa и atob: только Latin-1

btoa («binary to ASCII») кодирует строку, считая каждый её символ одним байтом. atob делает обратное. Для ASCII это работает:

btoa("hello");        // "aGVsbG8="
atob("aGVsbG8=");     // "hello"

А на всём, что выходит за кодовые точки 0–255, ломается:

btoa("héllo ✓");
// InvalidCharacterError: Failed to execute 'btoa' on 'Window':
// The string to be encoded contains characters outside of the Latin1 range.

Хуже того, символы, которые помещаются в Latin-1, исключения не вызывают, а просто кодируются не теми байтами. btoa("héllo") возвращает aOlsbG8= (é в Latin-1 – это 0xE9), тогда как любой другой язык и кодировщик Base64 выдают вариант в UTF-8: aMOpbGxv.

btoa и atob есть и в Node.js 16+ как устаревшие глобальные функции, но на сервере API Buffer быстрее и умеет base64url из коробки. См. Base64 в Node.js.

Закодировать и декодировать строки в UTF-8

Переносимое решение – перевести строку в байты UTF-8 через TextEncoder и передать их в btoa в виде «двоичной строки»:

function encodeUtf8(str) {
  const bytes = new TextEncoder().encode(str);
  let bin = "";
  for (const b of bytes) bin += String.fromCharCode(b);
  return btoa(bin);
}

function decodeUtf8(b64) {
  const bin = atob(b64);
  const bytes = Uint8Array.from(bin, c => c.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}

encodeUtf8("héllo wörld");            // "aMOpbGxvIHfDtnJsZA=="
decodeUtf8("aMOpbGxvIHfDtnJsZA==");   // "héllo wörld"
atob("aMOpbGxvIHfDtnJsZA==");         // "héllo wörld"  (no TextDecoder)

Старый трюк btoa(unescape(encodeURIComponent(str))) даёт тот же результат, но опирается на устаревшую unescape.

Uint8Array.toBase64 и fromBase64

Новый API работает прямо с байтами, без промежуточной двоичной строки. Его поддерживают Chrome и Edge 140+, Firefox 133+ и Safari 18.2+.

const bytes = new TextEncoder().encode("héllo wörld");
bytes.toBase64();                                      // "aMOpbGxvIHfDtnJsZA=="

new TextDecoder().decode(Uint8Array.fromBase64("aMOpbGxvIHfDtnJsZA=="));
// "héllo wörld"

Есть ещё setFromBase64: он декодирует в существующий буфер и сообщает, сколько прочитал и записал:

const target = new Uint8Array(8);
target.setFromBase64("aGVsbG8K");      // { read: 8, written: 6 }

Прежде чем на него полагаться, проверьте поддержку: typeof Uint8Array.prototype.toBase64 === "function". Все параметры разобраны в статье про Uint8Array.toBase64.

URL-safe Base64 с дополнением и без

В новом API достаточно передать параметры:

const d = new TextEncoder().encode("<<???>>");
d.toBase64({ alphabet: "base64url" });                     // "PDw_Pz8-Pg=="
d.toBase64({ alphabet: "base64url", omitPadding: true });  // "PDw_Pz8-Pg"
Uint8Array.fromBase64("PDw_Pz8-Pg", { alphabet: "base64url" }); // bytes of "<<???>>"

С btoa/atob переводите алфавит сами:

const toB64Url = b64 =>
  b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");

const fromB64Url = s => {
  s = s.replace(/-/g, "+").replace(/_/g, "/");
  return s + "=".repeat((4 - s.length % 4) % 4);
};

toB64Url(btoa("<<???>>"));        // "PDw_Pz8-Pg"
atob(fromB64Url("PDw_Pz8-Pg"));   // "<<???>>"

Именно так устроены JWT; см. Base64 и Base64URL или вставьте токен в декодер JWT.

Файлы, Blob и большие данные

Чтобы прочитать выбранный пользователем файл, вызовите FileReader.readAsDataURL: он вернёт data URI. Для чистого Base64 отрежьте всё до запятой включительно:

const reader = new FileReader();
reader.onload = () => {
  const dataUrl = reader.result;           // "data:text/plain;base64,aGVsbG8K"
  const b64 = dataUrl.slice(dataUrl.indexOf(",") + 1);
};
reader.readAsDataURL(file);

С новым API короче: new Uint8Array(await file.arrayBuffer()).toBase64().

Чтобы превратить Base64 обратно в файл для скачивания, декодируйте его в байты и оберните в Blob:

const bytes = Uint8Array.fromBase64(b64);
const url = URL.createObjectURL(new Blob([bytes], { type: "application/pdf" }));

Большие буферы никогда не разворачивайте в String.fromCharCode(...bytes). На 500 000 байт Chromium бросает RangeError: Maximum call stack size exceeded. Собирайте двоичную строку по кускам:

function bytesToBase64(bytes) {
  let bin = "";
  for (let i = 0; i < bytes.length; i += 0x8000) {
    bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
  }
  return btoa(bin);
}
// 5,000,000 bytes -> 6,666,668 characters

Результат примерно на треть больше входных данных; точная формула – в статье о росте размера.

Проверка и сообщения об ошибках

atob игнорирует пробельные символы ASCII и терпит отсутствие дополнения, поэтому и atob(" aG k= "), и atob("aGk") возвращают "hi". Символы вне стандартного алфавита он отвергает:

atob("PDw_Pz8-Pg");
// InvalidCharacterError: Failed to execute 'atob' on 'Window':
// The string to be decoded is not correctly encoded.

fromBase64 бросает SyntaxError, и его можно сделать строже через lastChunkHandling:

Uint8Array.fromBase64("PDw_Pz8-Pg");
// SyntaxError: Found a character that cannot be part of a valid base64 string.

Uint8Array.fromBase64("aGl=");                                   // 2 bytes, accepted
Uint8Array.fromBase64("aGl=", { lastChunkHandling: "strict" });
// SyntaxError: The base64 input terminates with non-zero padding bits.

Значение по умолчанию, "loose", допускает отсутствие дополнения. "strict" требует правильного дополнения и нулевых битов дополнения; "stop-before-partial" оставляет неполный последний кусок для потоковых декодеров. Текст сообщений в разных браузерах разный, но имена ошибок стандартные. Для наглядной проверки откройте валидатор Base64: он подсветит конкретную проблему, а причины перечислены в статье частые ошибки Base64.

Вопросы и ответы

Почему btoa бросает InvalidCharacterError?

btoa принимает только символы из диапазона Latin-1 (кодовые точки 0–255). Любой эмодзи, кириллица или иероглиф вызывают исключение. Сначала переведите строку в байты UTF-8 через TextEncoder или используйте Uint8Array.prototype.toBase64().

Можно ли использовать Uint8Array.toBase64 в продакшене?

Метод есть в актуальных Chrome, Edge, Firefox и Safari, но не в старых браузерах и не в Node.js 24. Проверяйте поддержку через typeof Uint8Array.prototype.toBase64 === "function" и откатывайтесь на вспомогательную функцию с btoa. Подробности в статье Uint8Array.toBase64.

Почему atob возвращает странные символы вроде é вместо é?

atob возвращает по одному символу на байт, так что двухбайтовая последовательность UTF-8 превращается в два символа Latin-1. Пропустите результат через Uint8Array.from(bin, c => c.charCodeAt(0)) и TextDecoder, чтобы получить нормальный текст.

Как декодировать Base64URL через atob?

Замените - на +, а _ на /, затем допишите = до длины, кратной 4. С новым API вызовите Uint8Array.fromBase64(s, { alphabet: "base64url" }) и пропустите оба шага.

Читайте также