Base64 у JavaScript: btoa(), Buffer та Unicode

·Front-end & Node.js Developer·ПеревіреноSophie Laurent·Опубліковано

Використовуйте безкоштовний Base64 Encode Online прямо в браузері — без встановлення.

Спробувати Base64 Encode Online онлайн →

Коли ви вбудовуєте зображення у data URI CSS, передаєте облікові дані у HTTP-заголовку Authorization або зберігаєте бінарний сертифікат у змінній середовища, вам потрібно надійно кодувати дані в Base64 у JavaScript — як у браузері, так і в Node.js. JavaScript надає два окремих вбудованих API:btoa() для браузерних середовищ (також доступний у Node.js 16+) та Buffer.from() для Node.js — кожен зі своїми обмеженнями щодо Unicode, бінарних даних та URL-безпечності. Для швидкого одноразового кодування без написання коду Base64 Encoder від ToolDeck справляється миттєво у браузері. Цей посібник охоплює обидва середовища з готовими до продакшену прикладами: обробка Unicode, URL-безпечні варіанти, кодування файлів та відповідей API, використання CLI і чотири помилки, які постійно спричиняють баги в реальних проектах.

  • btoa() вбудований у браузер і доступний у Node.js 16+ глобально, але приймає лише Latin-1 (кодові точки 0–255) — Unicode-введення викидає DOMException
  • Buffer.from(text, "utf8").toString("base64") — еквівалент для Node.js, який нативно підтримує Unicode без додаткових кроків
  • URL-безпечний Base64 замінює + → -, / → _ та прибирає паддинг = — використовуйте Buffer.from().toString("base64url") у Node.js 18+ для одного рядка коду
  • Для бінарних даних (ArrayBuffer, Uint8Array, файли) використовуйте Buffer у Node.js або підхід arrayBuffer() + Uint8Array у браузері — ніколи response.text()
  • Uint8Array.prototype.toBase64() (TC39 Stage 3) вже доступний у Node.js 22+ і Chrome 130+ та уніфікує обидва середовища

Що таке кодування Base64?

Base64 перетворює довільні бінарні дані на рядок із 64 друкованих ASCII-символів: A–Z, a–z, 0–9, + та /. Кожні 3 байти вхідних даних відображаються рівно в 4 символи Base64; якщо довжина вхідних даних не кратна 3, додається один або два символи = для вирівнювання. Закодований вивід завжди приблизно на 33% більший за оригінал.

Base64 — це не шифрування: воно не забезпечує конфіденційності. Будь-хто, хто має закодований рядок, може декодувати його одним викликом функції. Мета Base64 — транспортна сумісність: багато протоколів і форматів зберігання розроблені для 7-бітного ASCII-тексту і не можуть працювати з довільними бінарними байтами. Base64 усуває цей розрив. Поширені випадки використання у JavaScript: data URI для вбудовування ресурсів, заголовки HTTP Basic Auth, сегменти JWT-токенів, вкладення MIME в електронних листах та зберігання бінарних блобів у JSON API.

Before · text
After · text
deploy-bot:sk-prod-a7f2c91e4b3d8
ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=

btoa() — вбудована функція кодування для браузерів

btoa() (binary-to-ASCII) доступна у браузерах починаючи з IE10 і стала глобальною у Node.js 16.0 у рамках ініціативи WinterCG. Вона також працює нативно у Deno, Bun та Cloudflare Workers. Імпорт не потрібен.

Функція приймає один рядковий аргумент і повертає його в кодуванні Base64. Симетрична функція atob()(ASCII-to-binary) декодує його назад. Обидві функції синхронні й працюють із постійним обсягом пам'яті відносно розміру вхідних даних.

Мінімальний робочий приклад

JavaScript (browser / Node.js 16+)
// Кодування пари облікових даних API для заголовка HTTP Basic Auth
const serviceId  = 'deploy-bot'
const apiKey     = 'sk-prod-a7f2c91e4b3d8'

const credential = btoa(`${serviceId}:${apiKey}`)
// → 'ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg='

const headers = new Headers({
  Authorization: `Basic ${credential}`,
  'Content-Type': 'application/json',
})

console.log(headers.get('Authorization'))
// Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=

Декодування за допомогою atob()

JavaScript
// Туди й назад: кодування, передача, декодування
const payload = 'service:payments region:eu-west-1 env:production'

const encoded = btoa(payload)
const decoded = atob(encoded)

console.log(encoded)
// c2VydmljZTpwYXltZW50cyByZWdpb246ZXUtd2VzdC0xIGVudjpwcm9kdWN0aW9u

console.log(decoded === payload) // true
Примітка:btoa() та atob() є частиною WinterCG Minimum Common API — тієї ж специфікації, що регулює Fetch, URL та crypto у не-браузерних середовищах виконання. Вони поводяться однаково у Node.js 16+, Bun, Deno та Cloudflare Workers.

Обробка Unicode та символів, відмінних від ASCII

Найпоширеніша пастка btoa() — жорстке обмеження Latin-1. Будь-який символ із кодовою точкою вище U+00FF спричиняє негайне виключення:

JavaScript
btoa('Müller & Søren') // ❌ Uncaught DOMException: String contains an invalid character
btoa('résumé')         // ❌ 'é' is U+00E9 = 233 — within Latin-1, this one actually works
btoa('田中太郎')         // ❌ Throws — all CJK characters are above U+00FF

Правильний підхід — спочатку закодувати рядок у байти UTF-8, а потім закодувати ці байти у Base64. JavaScript надає TextEncoder саме для цієї мети:

Підхід із TextEncoder — безпечний для будь-якого Unicode-введення

JavaScript (browser + Node.js 16+)
// Допоміжні функції для Unicode-безпечного Base64
function toBase64(text: string): string {
  const bytes = new TextEncoder().encode(text)
  const chars = Array.from(bytes, byte => String.fromCharCode(byte))
  return btoa(chars.join(''))
}

function fromBase64(encoded: string): string {
  const binary = atob(encoded)
  const bytes  = Uint8Array.from(binary, ch => ch.charCodeAt(0))
  return new TextDecoder().decode(bytes)
}

// Працює з будь-якою мовою або алфавітом
const orderNote = 'Підтверджено: Олексій Петренко — склад у Харкові, к-сть: 250'
const encoded   = toBase64(orderNote)
const decoded   = fromBase64(encoded)

console.log(encoded)
// 0J/RltC00YLQstC10YDQttC00LU6INCe0LvQtdC60YHRltC5INCf0LXRgtGA0LXQvdC60L4g4oCTINGB0LrQu9Cw0LQg0YMg0Sko0LDRgNC60L7QstGWLCDQutGW0LvRjC0t0YHRgtGMLjogMjUw

console.log(decoded === orderNote) // true
Примітка:Якщо ви вже в Node.js, пропустіть обхідний шлях із TextEncoder — використовуйте Buffer.from(text, 'utf8').toString('base64'). Це обробляє Unicode нативно і працює швидше для великих рядків.

Buffer.from() у Node.js — повний посібник із прикладами

У Node.js Buffer— ідіоматичний API для всіх операцій із бінарними даними, включно з конвертацією кодувань. Він з'явився раніше за TextEncoder на кілька років і залишається кращим вибором для серверного коду. Ключові переваги перед btoa(): нативна підтримка UTF-8, робота з бінарними даними та скорочення 'base64url' доступне з Node.js 18.

Базове кодування та декодування тексту

Node.js
// Кодування об'єкта конфігурації сервера для зберігання у змінній середовища
const dbConfig = JSON.stringify({
  host:           'db-primary.internal',
  port:           5432,
  database:       'analytics_prod',
  maxConnections: 100,
  ssl:            { rejectUnauthorized: true },
})

const encoded = Buffer.from(dbConfig, 'utf8').toString('base64')
console.log(encoded)
// eyJob3N0IjoiZGItcHJpbWFyeS5pbnRlcm5hbCIsInBvcnQiOjU0MzIsImRhdGFiYXNlIjoiYW5h...

// Декодування назад
const decoded = Buffer.from(encoded, 'base64').toString('utf8')
const config  = JSON.parse(decoded)

console.log(config.host)           // db-primary.internal
console.log(config.maxConnections) // 100

Кодування бінарних файлів із диска

Node.js
import { readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'

// Читаємо TLS-сертифікат і кодуємо його для вбудовування в конфігураційний файл
const certPem     = readFileSync(join(process.cwd(), 'ssl', 'server.crt'))
const certBase64  = certPem.toString('base64')

// Зберігаємо як однорядковий — підходить для змінних середовища або JSON-конфігів
writeFileSync('./dist/cert.b64', certBase64, 'utf8')

console.log(`Сертифікат закодовано: ${certBase64.length} символів`)
// Сертифікат закодовано: 2856 символів

// Відновлюємо бінарний сертифікат із закодованого значення
const restored = Buffer.from(certBase64, 'base64')
console.log(restored.equals(certPem)) // true

Асинхронне кодування файлів із обробкою помилок

Node.js
import { readFile } from 'node:fs/promises'

async function encodeFileToBase64(filePath: string): Promise<string> {
  try {
    const buffer = await readFile(filePath)
    return buffer.toString('base64')
  } catch (err) {
    const code = (err as NodeJS.ErrnoException).code
    if (code === 'ENOENT') throw new Error(`Файл не знайдено: ${filePath}`)
    if (code === 'EACCES') throw new Error(`Немає прав доступу: ${filePath}`)
    throw err
  }
}

// Кодуємо PDF для вкладення до email
const reportBase64 = await encodeFileToBase64('./reports/q1-financials.pdf')

const emailPayload = {
  to:          'finance-team@company.internal',
  subject:     'Фінансовий звіт за Q1',
  attachments: [{
    filename:    'q1-financials.pdf',
    content:     reportBase64,
    encoding:    'base64',
    contentType: 'application/pdf',
  }],
}

console.log(`Вкладення: ${reportBase64.length} символів`)

Функції Base64 у JavaScript — довідник параметрів

На відміну від модуля base64 у Python, у JavaScript немає єдиної уніфікованої функції Base64. API залежить від цільового середовища. Ось повний довідник усіх нативних підходів:

ФункціяТип вхідних данихUnicodeURL-безпечністьДоступно в
btoa(string)string (Latin-1)❌ throws above U+00FF❌ manual replaceBrowser, Node 16+, Bun, Deno
atob(string)Base64 string❌ returns binary string❌ manual replaceBrowser, Node 16+, Bun, Deno
Buffer.from(src, enc) .toString(enc)string | Buffer | Uint8Array✅ utf8 encoding✅ base64url in Node 18+Node.js, Bun
TextEncoder().encode(str) + btoa()string (any Unicode)✅ via UTF-8 bytes❌ manual replaceBrowser, Node 16+, Deno
Uint8Array.toBase64() (TC39)Uint8Array✅ binary✅ omitPadding + alphabetChrome 130+, Node 22+

Сигнатура Buffer.from(src, enc).toString(enc)приймає кілька значень кодування, пов'язаних із Base64:

"base64"
Стандартний Base64 (RFC 4648 §4). Використовує + та / з паддингом =.
"base64url"
URL-безпечний Base64 (RFC 4648 §5, Node.js 18+). Використовує - та _ без паддингу.
"utf8"
За замовчуванням для рядкових джерел. Використовуйте, коли джерело — читабельний текст.
"binary"
Latin-1 / ISO-8859-1. Використовується, коли джерело — необроблений бінарний рядок (наприклад, з atob()).

URL-безпечний Base64 — кодування для JWT, URL та імен файлів

Стандартний Base64 використовує + та /, які зарезервовані у URL — + декодується як пробіл у рядках запиту, а / — роздільник шляху. JWT, параметри URL, імена файлів і значення cookie вимагають URL-безпечного варіанту: +-, /_, кінцевий = видаляється.

Браузер — ручна заміна символів

JavaScript (browser)
function toBase64Url(text: string): string {
  // For ASCII-safe input (e.g., JSON with only ASCII chars)
  return btoa(text)
    .replace(/+/g, '-')
    .replace(///g, '_')
    .replace(/=/g, '')
}

function fromBase64Url(encoded: string): string {
  // Restore standard Base64 characters and padding before decoding
  const base64  = encoded.replace(/-/g, '+').replace(/_/g, '/')
  const padded  = base64 + '==='.slice(0, (4 - base64.length % 4) % 4)
  return atob(padded)
}

// JWT header — must be URL-safe Base64
const header  = JSON.stringify({ alg: 'HS256', typ: 'JWT' })
const encoded = toBase64Url(header)
console.log(encoded) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

const decoded = fromBase64Url(encoded)
console.log(decoded) // {"alg":"HS256","typ":"JWT"}

Node.js 18+ — нативне кодування 'base64url'

Node.js 18+
// Node.js 18 added 'base64url' as a first-class Buffer encoding
const sessionPayload = JSON.stringify({
  userId:     'usr_9f2a1c3e8b4d',
  role:       'editor',
  workspaceId:'ws_3a7f91c2',
  exp:        Math.floor(Date.now() / 1000) + 3600,
})

const encoded = Buffer.from(sessionPayload, 'utf8').toString('base64url')
// No + or / or = characters in the output
// eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9

const decoded = Buffer.from(encoded, 'base64url').toString('utf8')
console.log(JSON.parse(decoded).role) // editor

Кодування файлів та відповідей API у JavaScript

У production-коді кодування Base64 найчастіше застосовується до файлів, що передаються, та до відповідей від зовнішніх API, які доставляють бінарний контент. Підходи відрізняються для браузера та Node.js, і бінарні дані потребують особливої обережності.

Браузер — кодування файлу з елемента input

JavaScript (browser)
// Modern approach: File.arrayBuffer() (Chrome 76+, Firefox 69+, Safari 14+)
async function encodeFile(file: File): Promise<string> {
  const buffer = await file.arrayBuffer()
  const bytes  = new Uint8Array(buffer)
  const chars  = Array.from(bytes, b => String.fromCharCode(b))
  return btoa(chars.join(''))
}

const uploadInput = document.getElementById('avatar') as HTMLInputElement

uploadInput.addEventListener('change', async (e) => {
  const file = (e.target as HTMLInputElement).files?.[0]
  if (!file) return

  try {
    const encoded = await encodeFile(file)
    const dataUri = `data:${file.type};base64,${encoded}`

    // Попередній перегляд зображення
    const img   = document.getElementById('preview') as HTMLImageElement
    img.src     = dataUri
    img.hidden  = false

    console.log(`Закодовано ${file.name} (${file.size} байт) → ${encoded.length} символів Base64`)
  } catch (err) {
    console.error('Помилка кодування:', err)
  }
})

Отримання бінарних даних у Base64 з API

JavaScript
// GitHub Contents API returns file content as Base64 with embedded newlines
async function fetchRepoFile(
  owner: string,
  repo:  string,
  path:  string,
  token: string,
): Promise<string> {
  const res = await fetch(
    `https://api.github.com/repos/${owner}/${repo}/contents/${path}`,
    {
      headers: {
        Authorization: `Bearer ${token}`,
        Accept: 'application/vnd.github.v3+json',
      },
    }
  )

  if (!res.ok) throw new Error(`GitHub API ${res.status}: ${res.statusText}`)

  const data = await res.json() as { content: string; encoding: string; size: number }

  if (data.encoding !== 'base64') {
    throw new Error(`Unexpected encoding from GitHub: ${data.encoding}`)
  }

  // GitHub wraps output at 60 chars — strip newlines before decoding
  const clean = data.content.replace(/\n/g, '')
  return atob(clean)
}

const openApiSpec = await fetchRepoFile(
  'acme-corp', 'platform-api', 'openapi.json', process.env.GITHUB_TOKEN!
)
const spec = JSON.parse(openApiSpec)
console.log(`API version: ${spec.info.version}`)

Коли вам потрібно просто перевірити закодовану відповідь під час налагодження API без написання скрипту, вставте значення Base64 прямо у Base64 Encoder — він також декодує з миттєвим виводом. Зручно для перевірки відповідей GitHub API, JWT-пейлоадів і підписів webhook.

Кодування Base64 через командний рядок у Node.js та Shell

Для скриптів CI/CD, цілей Makefile або одноразового налагодження повний скрипт рідко потрібен. Системні інструменти та однорядники Node.js покривають більшість випадків кросплатформно.

bash
# ── macOS / Linux system base64 ───────────────────────────────────────
# Standard encoding
echo -n "deploy-bot:sk-prod-a7f2c91e4b3d8" | base64
# ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=

# URL-safe variant (replace chars and strip padding)
echo -n "deploy-bot:sk-prod-a7f2c91e4b3d8" | base64 | tr '+/' '-_' | tr -d '='

# Encode a file inline (macOS: -b 0 removes line wrapping; Linux: --wrap=0)
base64 -b 0 ./config/production.json
# or on Linux:
base64 --wrap=0 ./config/production.json

# Decode
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 --decode

# ── Node.js one-liner — works on Windows too ───────────────────────────
node -e "process.stdout.write(Buffer.from(process.argv[1]).toString('base64'))" "my:secret"
# bXk6c2VjcmV0

# URL-safe from Node.js 18+
node -e "process.stdout.write(Buffer.from(process.argv[1]).toString('base64url'))" "my:secret"
# bXk6c2VjcmV0  (same here since there are no special chars)

# Decode in Node.js
node -e "console.log(Buffer.from(process.argv[1], 'base64').toString())" "ZGVwbG95LWJvdA=="
Примітка:На macOS base64 за замовчуванням переносить вивід кожні 76 символів. Це ламає подальший парсинг. Завжди додавайте -b 0 (macOS) або --wrap=0 (Linux), коли потрібен однорядковий результат — наприклад, при записі у змінну середовища або поле конфігу.

Високопродуктивна альтернатива: js-base64

Вбудовані API підходять для більшості завдань. Основна причина звернутися до бібліотеки — кросередовищна сумісність: якщо ви постачаєте пакет, який працює і в браузері, і в Node.js, використання Buffer вимагає або визначення середовища, або налаштування бандлера, а btoa() потребує обхідного шляху для Unicode. js-base64 (100M+ завантажень на тиждень на npm) вирішує це прозоро.

bash
npm install js-base64
# or
pnpm add js-base64
JavaScript
import { toBase64, fromBase64, toBase64Url, fromBase64Url, isValid } from 'js-base64'

// Standard encoding — Unicode-safe, works in browser and Node.js
const telemetryEvent = JSON.stringify({
  eventId:   'evt_7c3a9f1b2d',
  type:      'checkout_completed',
  currency:  'EUR',
  amount:    14900,
  userId:    'usr_4e2b8d6a5c',
  timestamp: 1717200000,
})

const encoded    = toBase64(telemetryEvent)
const urlEncoded = toBase64Url(telemetryEvent) // No +, /, or = characters

const decoded = fromBase64(encoded)
console.log(JSON.parse(decoded).type) // checkout_completed

// Binary data — pass a Uint8Array as second argument
const pngMagicBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
const binaryEncoded = toBase64(pngMagicBytes, true) // true = binary mode

// Validation before decoding
const suspicious = 'not!valid@base64#'
console.log(isValid(suspicious)) // false

Під капотом js-base64 використовує нативний Buffer, коли він доступний, і повертається до реалізації на чистому JS у браузері. Він у 2–3 рази швидший за підхід TextEncoder+btoa для великих Unicode-рядків, а симетричний API (toBase64 / fromBase64) усуває потребу пам'ятати, у який бік працюють btoa та atob.

Кодування великих бінарних файлів за допомогою потоків Node.js

Коли потрібно закодувати файли розміром більше ~50 МБ, завантаження всього файлу в пам'ять за допомогою readFileSync() стає проблемою. Потоки Node.js дозволяють обробляти дані по частинах — але кодування Base64 має обмеження: потрібно подавати дані в кодувальник кратними 3 байтами, щоб уникнути некоректного паддингу на межах чанків.

Node.js
import { createReadStream, createWriteStream } from 'node:fs'
import { pipeline } from 'node:stream/promises'

// Stream a large binary file to a Base64-encoded output file
async function streamEncodeToBase64(
  inputPath:  string,
  outputPath: string,
): Promise<void> {
  const readStream  = createReadStream(inputPath, { highWaterMark: 3 * 1024 * 256 }) // 768 KB chunks (multiple of 3)
  const writeStream = createWriteStream(outputPath, { encoding: 'utf8' })

  let buffer = Buffer.alloc(0)

  await pipeline(
    readStream,
    async function* (source) {
      for await (const chunk of source) {
        buffer = Buffer.concat([buffer, chunk as Buffer])

        // Encode in complete 3-byte groups to avoid mid-stream padding
        const remainder = buffer.length % 3
        const safe      = buffer.subarray(0, buffer.length - remainder)
        buffer          = buffer.subarray(buffer.length - remainder)

        if (safe.length > 0) yield safe.toString('base64')
      }
      // Flush remaining bytes (may add 1 or 2 '=' padding chars)
      if (buffer.length > 0) yield buffer.toString('base64')
    },
    writeStream,
  )
}

// Usage: encode a 200 MB video attachment
await streamEncodeToBase64(
  './uploads/product-demo.mp4',
  './dist/product-demo.b64',
)
console.log('Потокове кодування завершено')
Примітка:Розмір чанку повинен бути кратним 3 байтам, щоб уникнути зайвого = паддингу в середині виводу. У прикладі використовується 3 * 1024 * 256 = 786 432 байти (768 КБ) — скоригуйте highWaterMarkзалежно від бюджету пам'яті. Для файлів до 50 МБ readFile() + Buffer.toString('base64') простіше і достатньо швидко.

Поширені помилки

Я проаналізував багато JavaScript-проектів із кодуванням Base64, і ці чотири помилки зустрічаються постійно — часто залишаються непоміченими до того часу, поки символ non-ASCII або бінарний файл не потрапляє в шлях кодування в продакшені.

Помилка 1 — Передача Unicode безпосередньо в btoa()

Проблема: btoa() приймає лише символи з кодовими точками 0–255. Символи на зразок ñ, емодзі або CJK-ієрогліфи спричиняють негайну DOMException. Рішення: спочатку закодуйте за допомогою TextEncoder, або використовуйте Buffer.from(text, 'utf8').toString('base64') у Node.js.

Before · JavaScript
After · JavaScript
// ❌ DOMException: The string to be encoded contains
//    characters outside of the Latin1 range
const username = 'Олексій Петренко'
const encoded  = btoa(username)  // throws
// ✅ Encode as UTF-8 bytes first
function safeEncode(text: string): string {
  const bytes = new TextEncoder().encode(text)
  const chars = Array.from(bytes, b => String.fromCharCode(b))
  return btoa(chars.join(''))
}
const encoded = safeEncode('Олексій Петренко')
// 0J7Qu9C10LrRgdGW0LkgUGV0cmVua28=

Помилка 2 — Забутий відновлення паддингу перед atob()

Проблема: URL-безпечний Base64 прибирає паддинг =. Передача рядка без паддингу безпосередньо в atob() дає некоректний вивід або кидає виключення залежно від довжини рядка. Рішення: відновіть + та / і додайте правильну кількість паддингу перед викликом atob().

Before · JavaScript
After · JavaScript
// ❌ atob() may return wrong data or throw
//    on URL-safe Base64 without padding
const jwtSegment = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ'
const decoded    = atob(jwtSegment) // Unreliable
// ✅ Restore characters and padding first
function decodeBase64Url(input: string): string {
  const b64 = input.replace(/-/g, '+').replace(/_/g, '/')
  const pad = b64 + '==='.slice(0, (4 - b64.length % 4) % 4)
  return atob(pad)
}
const decoded = decodeBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ')
// {"userId":"usr_9f2a1c3e"}

Помилка 3 — Конкатенація закодованих чанків замість необроблених буферів

Проблема: Кожен виклик btoa() або .toString('base64') додає власний паддинг. Конкатенація двох доповнених рядків Base64 дає невалідний вивід, бо паддинг повинен бути лише в самому кінці. Рішення: конкатенуйте вихідні дані до кодування.

Before · JavaScript
After · JavaScript
// ❌ Both parts are padded independently —
//    the combined string is not valid Base64
const part1 = Buffer.from('webhook-secret').toString('base64')
// d2ViaG9vay1zZWNyZXQ=  ← has padding
const part2 = Buffer.from('-v2').toString('base64')
// LXYy            ← correct in isolation
const combined = part1 + part2 // ❌ Invalid — padding in the middle
// ✅ Concatenate raw Buffers before encoding
const combined = Buffer.concat([
  Buffer.from('webhook-secret'),
  Buffer.from('-v2'),
]).toString('base64')
// d2ViaG9vay1zZWNyZXQtdjI= — single valid Base64 string

Помилка 4 — Використання response.text() для читання бінарних даних API перед кодуванням

Проблема: response.text() інтерпретує байти як UTF-8 і замінює нерозпізнані послідовності байтів символом заміни U+FFFD. Будь-який бінарний контент — зображення, PDF, аудіо — тихо пошкоджується до того, як потрапляє до btoa(). Рішення: використовуйте response.arrayBuffer() для отримання необроблених байтів.

Before · JavaScript
After · JavaScript
// ❌ response.text() corrupts binary data
const res     = await fetch('/api/exports/invoice.pdf')
const text    = await res.text()   // ❌ PDF bytes mangled as UTF-8
const encoded = btoa(text)         // ❌ Corrupted Base64
// ✅ arrayBuffer() preserves raw bytes
const res     = await fetch('/api/exports/invoice.pdf')
const buffer  = await res.arrayBuffer()
const bytes   = new Uint8Array(buffer)
const chars   = Array.from(bytes, b => String.fromCharCode(b))
const encoded = btoa(chars.join('')) // ✅ Valid Base64

Методи Base64 у JavaScript — швидке порівняння

МетодUnicodeБінарні даніURL-безпечністьСередовищаПотребує встановлення
btoa() / atob()❌ Latin-1❌ workaround needed❌ manual replaceBrowser, Node 16+, Bun, DenoНі
TextEncoder + btoa()✅ UTF-8✅ via Uint8Array❌ manual replaceBrowser, Node 16+, DenoНі
Buffer.from().toString()✅ utf8✅ native✅ base64url (Node 18+)Node.js, BunНі
Uint8Array.toBase64() (TC39)✅ binary✅ native✅ alphabet optionChrome 130+, Node 22+Ні
js-base64✅ always✅ Uint8Array✅ built-inUniversalnpm install

Використовуйте btoa() лише тоді, коли вхідні дані заздалегідь містять тільки ASCII — хеш-дайджести, числові ID або попередньо перевірені рядки Latin-1. Для користувацького тексту в браузері використовуйте TextEncoder + btoa(). Для всього серверного коду Node.js Buffer — правильний вибір за замовчуванням. Для бібліотек, які мають працювати в обох середовищах без налаштування бандлера, js-base64 усуває всі граничні випадки.

Поширені запитання

Чому btoa() кидає "InvalidCharacterError" для мого рядка?
btoa() приймає лише символи з кодовими точками в діапазоні 0–255 (Latin-1 / ISO-8859-1). Будь-який символ вище U+00FF — включно з більшістю кириличних, арабських, CJK-ієрогліфів і багатьма емодзі — спричиняє DOMException. Рішення залежить від середовища: у браузері спочатку закодуйте в байти UTF-8 за допомогою TextEncoder, перетворіть кожен байт у символ за допомогою String.fromCharCode(), потім викличте btoa(). У Node.js використовуйте Buffer.from(text, 'utf8').toString('base64'), який підтримує Unicode нативно.
Чи доступний btoa() у Node.js без будь-якого імпорту?
Так, починаючи з Node.js 16.0. Обидві функції — btoa() та atob() — зареєстровані як глобальні — жодного імпорту не потрібно. Вони поводяться ідентично своїм браузерним аналогам, включно з обмеженням Latin-1. Для серверного коду Node.js Buffer.from() усе одно кращий за btoa(), бо він підтримує UTF-8 нативно, працює з бінарними даними без обхідних шляхів і має опцію 'base64url', додану в Node.js 18.
У чому різниця між стандартним Base64 та URL-безпечним Base64?
Стандартний Base64 (RFC 4648 §4) використовує + для значення 62, / для значення 63 і = для паддингу. Ці символи мають особливе значення в URL: + інтерпретується як пробіл у рядках запиту, а / — роздільник шляху. URL-безпечний Base64 (RFC 4648 §5) замінює - на + та _ на /, і зазвичай повністю пропускає паддинг =. JWT використовує URL-безпечний Base64 для всіх трьох сегментів. У Node.js 18+ Buffer.from(text).toString('base64url') безпосередньо видає URL-безпечний формат.
Як закодувати зображення в Base64 для data URI в CSS на JavaScript?
У браузері: використовуйте file.arrayBuffer() для читання бінарного файлу, перетворіть у Uint8Array, потім викличте btoa(Array.from(bytes, b => String.fromCharCode(b)).join('')). Сформуйте data URI як 'data:' + file.type + ';base64,' + encoded. У Node.js: const encoded = fs.readFileSync('./image.png').toString('base64') і додайте MIME-тип. Для SVG-файлів часто можна взагалі пропустити Base64 і використати data URI з URL-кодуванням — це читабельніше і трохи компактніше.
Чи можна кодувати та декодувати Base64 без npm-бібліотек у браузері?
Так. Для ASCII-введення btoa() та atob() працюють безпосередньо. Для Unicode пара TextEncoder / TextDecoder дає повний інструментарій — обидві вбудовані в усі сучасні браузери та Node.js 16+. Єдиний випадок, коли бібліотека справді додає цінність — кросередовищна сумісність: якщо ви пишете утиліту, яка повинна працювати однаково в браузері та Node.js без налаштування бандлера, js-base64 прибирає логіку визначення середовища.
Як декодувати вміст Base64 з GitHub API?
GitHub Contents API повертає вміст файлу в Base64 із вбудованими символами нового рядка (API переносить вивід кожні 60 символів). Видаліть їх перед декодуванням: const clean = data.content.replace(/\n/g, ''); const text = atob(clean);. У Node.js: const text = Buffer.from(data.content.replace(/\n/g, ''), 'base64').toString('utf8');. GitHub завжди використовує стандартний Base64 (не URL-безпечний), тому заміна + → - або / → _ не потрібна.

Пов'язані інструменти

Для швидкого кодування або декодування без написання коду вставте рядок або бінарні дані прямо у Base64 Encoder — він миттєво обробляє стандартний та URL-безпечний режими у браузері.

Також доступно на:PythonJava
AC
Alex ChenFront-end & Node.js Developer

Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.

SL
Sophie LaurentТехнічний рецензент

Sophie is a full-stack developer focused on TypeScript across the entire stack — from React frontends to Express and Fastify backends. She has a particular interest in type-safe API design, runtime validation, and the patterns that make large JavaScript codebases stay manageable. She writes about TypeScript idioms, Node.js internals, and the ever-evolving JavaScript module ecosystem.