Base64 Decode у JavaScript: atob() та Buffer

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

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

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

Коли я налагоджую проблему з автентифікацією у продакшені, перше, за що я беруся, — це Base64 decoder: JWT-пейлоади, підписи вебхуків та закодовані конфігураційні значення ховаються всередині Base64-рядків. JavaScript надає два вбудованих підходи до base64-декодування: atob() (браузер + Node.js 16+) та Buffer.from(encoded, 'base64').toString() (Node.js) — і вони поводяться дуже по-різному, коли вихідні дані містять символи Unicode. Для швидкого одноразового декодування без написання коду Base64 Decoder від ToolDeck впорається миттєво прямо у браузері. Цей посібник охоплює обидва середовища — Node.js 16+ та сучасні браузери (Chrome 80+, Firefox 75+, Safari 14+) — із готовими до продакшену прикладами: відновлення UTF-8, URL-безпечні варіанти, декодування JWT, файлів, відповідей API, потоків Node.js і чотири помилки, які стабільно призводять до спотвореного виводу в реальних проектах.

  • atob(encoded) вбудований у браузер і доступний у Node.js 16+ глобально, але повертає бінарний рядок — використовуйте TextDecoder для відновлення UTF-8 тексту з будь-якого вмісту вище ASCII.
  • Buffer.from(encoded, "base64").toString("utf8") — ідіоматичний підхід у Node.js, який обробляє UTF-8 автоматично без додаткових кроків.
  • URL-безпечний Base64 (що використовується в JWT) замінює + на -, / на _ та прибирає паддинг =. Відновіть їх перед викликом atob() або використовуйте Buffer.from(encoded, "base64url").toString() у Node.js 18+.
  • Видаляйте пробіли та переноси рядків перед декодуванням — GitHub Contents API і багато MIME-енкодерів переносять вивід Base64 кожні 60–76 символів.
  • Uint8Array.prototype.fromBase64() (TC39 Stage 3) вже доступний у Node.js 22+ і Chrome 130+ та зрештою уніфікує обидва середовища.

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

Декодування Base64 — це зворотна операція до кодування: вона перетворює 64-символьне ASCII-представлення назад у вихідні бінарні дані або текст. Кожні 4 символи Base64 відповідають рівно 3 байтам. Символи = наприкінці закодованого рядка повідомляють декодеру, скільки додаткових байт було додано для завершення останньої 3-байтової групи.

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

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

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

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

Функція повертає бінарний рядок: JavaScript-рядок, у якому кожен символ має кодову точку, рівну одному значенню сирого байта (0–255). Це важливо: якщо вихідні дані були UTF-8 текстом, що містить символи вище U+007F (літери з діакритикою, кирилиця, CJK, емодзі), повернутий рядок є сирою послідовністю байт, а не читаємим текстом. Використовуйте TextDecoder, щоб відновити його (детальніше в наступному розділі).

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

JavaScript (browser / Node.js 16+)
// Decoding an HTTP Basic Auth credential pair received in a request header
// Authorization: Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=

function parseBasicAuth(header: string): { serviceId: string; apiKey: string } {
  const base64Part = header.replace(/^Basics+/i, '')
  const decoded    = atob(base64Part)
  const [serviceId, apiKey] = decoded.split(':')
  return { serviceId, apiKey }
}

const auth = parseBasicAuth('Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=')

console.log(auth.serviceId) // deploy-bot
console.log(auth.apiKey)    // sk-prod-a7f2c91e4b3d8

Перевірка коректності відновлення даних

JavaScript
// Verify lossless recovery for ASCII-only content
const original = 'service:payments region:eu-west-1 env:production'

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

console.log(encoded)
// c2VydmljZTpwYXltZW50cyByZWdpb246ZXUtd2VzdC0xIGVudjpwcm9kdWN0aW9u

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

Відновлення UTF-8 тексту після декодування

Найпоширеніша пастка з atob() — нерозуміння типу значення, що повертається. Коли вихідний текст був закодований у UTF-8 перед Base64, atob() повертає Latin-1 бінарний рядок, а не читаємий текст:

JavaScript
// 'Алексей Иванов' was UTF-8 encoded then Base64 encoded before transmission
const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy'

// ❌ atob() returns the raw UTF-8 bytes as a Latin-1 string — garbled output
console.log(atob(encoded))
// "Алексей Р˜РІР°РЅРѕРІ"  ← byte values misread as Latin-1

Правильний підхід використовує TextDecoder для інтерпретації цих сирих байт як UTF-8:

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

JavaScript (browser + Node.js 16+)
// Unicode-safe Base64 decode utilities
function fromBase64(encoded: string): string {
  const binary = atob(encoded)
  const bytes  = Uint8Array.from(binary, ch => ch.charCodeAt(0))
  return new TextDecoder().decode(bytes)
}

function toBase64(text: string): string {
  const bytes = new TextEncoder().encode(text)
  const chars = Array.from(bytes, byte => String.fromCharCode(byte))
  return btoa(chars.join(''))
}

// Works with any language or script
const orderNote = 'Confirmed: 田中太郎 — São Paulo warehouse, qty: 250'
const encoded   = toBase64(orderNote)
const decoded   = fromBase64(encoded)

console.log(decoded === orderNote) // true
console.log(decoded)
// Confirmed: 田中太郎 — São Paulo warehouse, qty: 250
Примітка:У Node.js пропустіть крок із TextDecoder — використовуйте Buffer.from(encoded, 'base64').toString('utf8'). Він автоматично інтерпретує декодовані байти як UTF-8 і працює швидше для великих вхідних даних.

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

У Node.js Buffer є ідіоматичним API для всіх бінарних операцій, включаючи декодування Base64. Він обробляє UTF-8 нативно, повертає справжній Buffer (безпечний для бінарних даних), а починаючи з Node.js 18 підтримує скорочення 'base64url' для URL-безпечних варіантів.

Декодування конфігу зі змінної середовища

Node.js
// Server config stored as Base64 in an env variable (avoids JSON escaping in shell)
// DB_CONFIG=eyJob3N0IjoiZGItcHJpbWFyeS5pbnRlcm5hbCIsInBvcnQiOjU0MzIsImRhdGFiYXNlIjoiYW5hbHl0aWNzX3Byb2QiLCJtYXhDb25uZWN0aW9ucyI6MTAwfQ==

const raw = Buffer.from(process.env.DB_CONFIG!, 'base64').toString('utf8')
const dbConfig = JSON.parse(raw)

console.log(dbConfig.host)           // db-primary.internal
console.log(dbConfig.port)           // 5432
console.log(dbConfig.maxConnections) // 100

Відновлення бінарного файлу з .b64-файлу

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

// Read the Base64-encoded certificate and restore the original binary
const encoded = readFileSync(join(process.cwd(), 'dist', 'cert.b64'), 'utf8').trim()
const certBuf  = Buffer.from(encoded, 'base64')

writeFileSync('./ssl/server.crt', certBuf)

console.log(`Restored ${certBuf.length} bytes`)
// Restored 2142 bytes

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

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

async function decodeBase64File(
  encodedPath: string,
  outputPath:  string,
): Promise<number> {
  try {
    const encoded = await readFile(encodedPath, 'utf8')
    const binary  = Buffer.from(encoded.trim(), 'base64')
    await writeFile(outputPath, binary)
    return binary.length
  } catch (err) {
    const code = (err as NodeJS.ErrnoException).code
    if (code === 'ENOENT') throw new Error(`File not found: ${encodedPath}`)
    if (code === 'EACCES') throw new Error(`Permission denied: ${encodedPath}`)
    throw err
  }
}

// Restore a PDF stored as Base64
const bytes = await decodeBase64File('./uploads/invoice.b64', './out/invoice.pdf')
console.log(`Decoded ${bytes} bytes — PDF restored`)

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

Короткий довідник параметрів двох основних нативних API декодування для використання під час написання та перевірки коду.

atob(encodedData)

ПараметрТипОбов'язковийОпис
encodedDatastringТакСтандартний Base64-рядок із символами +, /, =. URL-безпечні варіанти (-, _) викидають InvalidCharacterError. Пробіли не допускаються.
Повертає: бінарний рядок — кодова точка кожного символу дорівнює одному значенню сирого байта (0–255). Це не Unicode-рядок; передайте його через TextDecoder для відновлення UTF-8 тексту.

Buffer.from(input, inputEncoding) / .toString(outputEncoding)

ПараметрТипЗа замовчуваннямОпис
inputstring | Buffer | TypedArray | ArrayBufferобов'язковийBase64-закодований рядок для декодування або буфер із закодованими байтами.
inputEncodingBufferEncoding"utf8"Встановіть "base64" для стандартного Base64 (RFC 4648 §4) або "base64url" для URL-безпечного Base64 (RFC 4648 §5, Node.js 18+).
outputEncodingstring"utf8"Кодування для виводу .toString(). Використовуйте "utf8" для читаємого тексту, "binary" для Latin-1 бінарного рядка, сумісного з виводом atob().
startinteger0Зміщення байта всередині декодованого Buffer, з якого починати читання. Передається у .toString() як другий аргумент.
endintegerbuf.lengthЗміщення байта для зупинки читання (не включно). Передається у .toString() як третій аргумент.
Повертає: Buffer із .from(). Повертає рядок із .toString(). Залишайте як Buffer (не викликайте .toString()), коли декодований вміст є бінарним — зображення, PDF, аудіо.

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

JWT використовують URL-безпечний Base64 (RFC 4648 §5) для всіх трьох сегментів. URL-безпечний Base64 замінює + на - та / на _, а також прибирає завершальний = паддинг. Передача цього напряму в atob() без відновлення дає некоректний вивід або виключення.

Браузер — відновлення символів і паддингу перед декодуванням

JavaScript (browser)
function decodeBase64Url(input: string): string {
  const base64 = input.replace(/-/g, '+').replace(/_/g, '/')
  const padded = base64 + '==='.slice(0, (4 - base64.length % 4) % 4)
  const binary = atob(padded)
  const bytes  = Uint8Array.from(binary, ch => ch.charCodeAt(0))
  return new TextDecoder().decode(bytes)
}

// Inspect a JWT payload segment (the middle part between the two dots)
const jwtToken  = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9'
const payload   = decodeBase64Url(jwtToken)
const claims    = JSON.parse(payload)

console.log(claims.userId)      // usr_9f2a1c3e8b4d
console.log(claims.role)        // editor
console.log(claims.workspaceId) // ws_3a7f91c2

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

Node.js 18+
// Node.js 18 added 'base64url' as a first-class Buffer encoding — no manual replace needed
function decodeJwtSegment(segment: string): Record<string, unknown> {
  const json = Buffer.from(segment, 'base64url').toString('utf8')
  return JSON.parse(json)
}

const token   = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9.SIGNATURE'
const [headerB64, payloadB64] = token.split('.')

const header  = decodeJwtSegment(headerB64)
const payload = decodeJwtSegment(payloadB64)

console.log(header.alg)          // HS256
console.log(payload.role)        // editor
console.log(payload.workspaceId) // ws_3a7f91c2

Декодування Base64 з файлів та відповідей API

У продакшен-коді декодування Base64 найчастіше відбувається під час роботи із зовнішніми API, які повертають контент у закодованому вигляді. В обох сценаріях є важливі нюанси навколо пробілів і бінарного vs текстового виводу. Якщо вам просто потрібно оглянути закодовану відповідь під час налагодження, вставте її напряму в Base64 Decoder — він миттєво обробляє стандартний і URL-безпечний режими.

Декодування вмісту з GitHub Contents API

JavaScript
// GitHub Contents API returns file content as Base64, wrapped at 60 chars per line
async function fetchDecodedFile(
  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 }
  if (data.encoding !== 'base64') throw new Error(`Unexpected encoding: ${data.encoding}`)

  // ⚠️ GitHub wraps at 60 chars — strip newlines before decoding
  const clean = data.content.replace(/\n/g, '')
  return Buffer.from(clean, 'base64').toString('utf8')
}

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

Декодування Base64-закодованого бінарного файлу з API (браузер)

JavaScript (browser)
// Some APIs return binary content (images, PDFs) as Base64 JSON fields
async function downloadDecodedFile(endpoint: string, authToken: string): Promise<void> {
  const res = await fetch(endpoint, { headers: { Authorization: `Bearer ${authToken}` } })
  if (!res.ok) throw new Error(`Download failed: ${res.status}`)

  const { filename, content, mimeType } = await res.json() as {
    filename: string; content: string; mimeType: string
  }

  // Decode Base64 → binary bytes → Blob
  const binary = atob(content)
  const bytes  = Uint8Array.from(binary, ch => ch.charCodeAt(0))
  const blob   = new Blob([bytes], { type: mimeType })

  // Trigger browser download
  const url = URL.createObjectURL(blob)
  const a   = Object.assign(document.createElement('a'), { href: url, download: filename })
  a.click()
  URL.revokeObjectURL(url)
}

await downloadDecodedFile('/api/reports/latest', sessionStorage.getItem('auth_token')!)

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

Для CI/CD-скриптів, сесій налагодження або одноразових задач декодування утиліти командного рядка та однорядники Node.js швидші за повноцінний скрипт. Зверніть увагу, що ім'я прапорця відрізняється на macOS і Linux.

bash
# ── macOS / Linux system base64 ───────────────────────────────────────
# Standard decoding (macOS uses -D, Linux uses -d)
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 -d   # Linux
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 -D   # macOS

# Decode a .b64 file to its original binary
base64 -d ./dist/cert.b64 > ./ssl/server.crt       # Linux
base64 -D -i ./dist/cert.b64 -o ./ssl/server.crt   # macOS

# URL-safe Base64 — restore + and / before decoding
echo "eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ" | tr '-_' '+/' | base64 -d

# ── Node.js one-liner — works on Windows too ───────────────────────────
node -e "process.stdout.write(Buffer.from(process.argv[1], 'base64').toString())" "ZGVwbG95LWJvdA=="
# deploy-bot

# URL-safe (Node.js 18+)
node -e "process.stdout.write(Buffer.from(process.argv[1], 'base64url').toString())" "eyJhbGciOiJIUzI1NiJ9"
# {"alg":"HS256"}
Примітка:На macOS команда base64 використовує -D для декодування (велика D), тоді як Linux використовує -d (мала). Це непомітно ламає CI-скрипти — використовуйте однорядник Node.js, коли цільова платформа не гарантовано є Linux.

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

Головна причина використовувати бібліотеку — крос-середовища узгодженість. Якщо ви поставляєте пакет, що працює як у браузері, так і в Node.js без налаштування бандлера, Buffer вимагає визначення середовища, а atob() вимагає обхідного шляху через TextDecoder. js-base64 (понад 100 млн завантажень npm на тиждень) прозоро обробляє обидва варіанти.

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

// Standard decoding — Unicode-safe, works in browser and Node.js
const raw   = fromBase64('eyJldmVudElkIjoiZXZ0XzdjM2E5ZjFiMmQiLCJ0eXBlIjoiY2hlY2tvdXRfY29tcGxldGVkIiwiY3VycmVuY3kiOiJFVVIiLCJhbW91bnQiOjE0OTAwfQ==')
const event = JSON.parse(raw)
console.log(event.type)     // checkout_completed
console.log(event.currency) // EUR

// URL-safe decoding — no manual character replacement needed
const jwtPayload = fromBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciJ9')
const claims     = JSON.parse(jwtPayload)
console.log(claims.role) // editor

// Validate before decoding untrusted input
const untrusted = 'not!valid@base64#'
if (!isValid(untrusted)) {
  console.error('Rejected: invalid Base64 input')
}

// Binary output — second argument true returns Uint8Array
const pngBytes = fromBase64('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', true)
console.log(pngBytes instanceof Uint8Array) // true

Вивід у термінал із підсвіткою синтаксису

При написанні CLI-інструментів налагодження або скриптів інспекції простий console.log важко читати для великих JSON-пейлоадів. chalk (найбільш завантажуваний npm-пакет для розфарбування терміналу) у поєднанні з декодуванням Base64 дає читаємий, легко переглядуваний вивід — корисний для інспекції JWT, налагодження відповідей API та аудиту конфігів.

bash
npm install chalk
# chalk v5+ is ESM-only — use import, not require
Node.js
import chalk from 'chalk'

// Decode and display any Base64 value with smart type detection
function inspectBase64(encoded: string, label = 'Decoded value'): void {
  let decoded: string
  try {
    decoded = Buffer.from(encoded.trim(), 'base64').toString('utf8')
  } catch {
    console.error(chalk.red('✗ Invalid Base64 input'))
    return
  }

  console.log(chalk.bold.cyan(`\n── ${label} ──`))

  // Attempt JSON pretty-print
  try {
    const parsed = JSON.parse(decoded)
    console.log(chalk.green('Type:'), chalk.yellow('JSON'))
    for (const [key, value] of Object.entries(parsed)) {
      const display = typeof value === 'object' ? JSON.stringify(value) : String(value)
      console.log(chalk.green(`  ${key}:`), chalk.white(display))
    }
    return
  } catch { /* not JSON */ }

  // Plain text fallback
  console.log(chalk.green('Type:'), chalk.yellow('text'))
  console.log(chalk.white(decoded))
}

// Inspect a Base64-encoded JWT payload
const tokenParts = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsImV4cCI6MTcxNzIwMzYwMH0.SIGNATURE'.split('.')
inspectBase64(tokenParts[0], 'JWT Header')
inspectBase64(tokenParts[1], 'JWT Payload')
// ── JWT Header ──
// Type:   JSON
//   alg:  HS256
//   typ:  JWT
//
// ── JWT Payload ──
// Type:   JSON
//   userId: usr_9f2a1c3e8b4d
//   role:   editor
//   exp:    1717203600
Примітка:Використовуйте chalk лише для виводу в термінал/CLI — ніколи для вмісту, що записується у файли, відповіді API або агрегатори логів. ANSI-escape-коди псують не-термінальних споживачів: log-платформи (Datadog, Splunk), парсери JSON-логів та переглядачі логів CI відображають їх як нечитаємі послідовності символів.

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

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

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

async function streamDecodeBase64(inputPath: string, outputPath: string): Promise<void> {
  const readStream  = createReadStream(inputPath, { encoding: 'utf8', highWaterMark: 4 * 1024 * 192 })
  const writeStream = createWriteStream(outputPath)

  let buffer = ''

  await pipeline(
    readStream,
    async function* (source) {
      for await (const chunk of source) {
        buffer += (chunk as string).replace(/\s/g, '') // strip any whitespace/newlines

        // Decode only complete 4-char groups to avoid mid-stream padding issues
        const remainder = buffer.length % 4
        const safe      = buffer.slice(0, buffer.length - remainder)
        buffer          = buffer.slice(buffer.length - remainder)

        if (safe.length > 0) yield Buffer.from(safe, 'base64')
      }
      if (buffer.length > 0) yield Buffer.from(buffer, 'base64')
    },
    writeStream,
  )
}

// Decode a 200 MB video that was stored as Base64
await streamDecodeBase64('./uploads/product-demo.b64', './dist/product-demo.mp4')
console.log('Stream decode complete')
Примітка:Розмір чанку повинен бути кратним 4 символам при читанні Base64-тексту, щоб кожен чанк містив лише повні 4-символьні групи. У прикладі використовується 4 × 1024 × 192 = 786 432 символи (768 КБ). Для файлів до 50 МБ readFile() + Buffer.from(content.trim(), 'base64') простіше і достатньо швидко.

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

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

Помилка 1 — Використання atob() без TextDecoder для UTF-8 вмісту

Проблема: atob() повертає бінарний рядок, де кожен символ — це одне значення сирого байта. Багатобайтові UTF-8 послідовності (кирилиця, CJK, літери з діакритикою) відображаються як спотворені Latin-1 символи. Рішення: загорніть вивід у TextDecoder.

Before · JavaScript
After · JavaScript
// ❌ atob() returns the raw UTF-8 bytes as a Latin-1 string
const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy'
const decoded  = atob(encoded)
console.log(decoded)
// "Алексей Р˜РІР°РЅРѕРІ"  ← wrong
// ✅ Use TextDecoder to correctly interpret the UTF-8 bytes
const encoded  = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy'
const binary   = atob(encoded)
const bytes    = Uint8Array.from(binary, ch => ch.charCodeAt(0))
const decoded  = new TextDecoder().decode(bytes)
console.log(decoded) // Алексей Иванов ✓

Помилка 2 — Передача URL-безпечного Base64 напряму в atob()

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

Before · JavaScript
After · JavaScript
// ❌ URL-safe JWT segment passed directly — unreliable
const jwtPayload = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ'
const decoded    = atob(jwtPayload) // May produce wrong result or throw
// ✅ Restore standard Base64 chars 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)
  const bin  = atob(pad)
  const bytes = Uint8Array.from(bin, ch => ch.charCodeAt(0))
  return new TextDecoder().decode(bytes)
}
const decoded = decodeBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ')
// {"userId":"usr_9f2a1c3e"} ✓

Помилка 3 — Не видаляти переноси рядків із перенесеного Base64

Проблема: GitHub Contents API та MIME-енкодери переносять вивід Base64 кожні 60–76 символів. atob() викидає InvalidCharacterError на символах \n. Рішення: видаліть усі пробіли перед декодуванням.

Before · JavaScript
After · JavaScript
// ❌ GitHub API content field contains embedded newlines
const data    = await res.json()
const decoded = atob(data.content) // ❌ throws InvalidCharacterError
// ✅ Strip newlines (and any other whitespace) before decoding
const data    = await res.json()
const clean   = data.content.replace(/\s/g, '')
const decoded = atob(clean) // ✓

Помилка 4 — Виклик .toString() на декодованому бінарному вмісті

Проблема: Коли вихідні дані є бінарними (зображення, PDF, аудіо), виклик .toString('utf8') замінює нерозпізнані байтові послідовності на U+FFFD, непомітно пошкоджуючи вивід. Рішення: залишайте результат як Buffer — не перетворюйте на рядок.

Before · JavaScript
After · JavaScript
// ❌ .toString('utf8') corrupts binary content
import { readFileSync, writeFileSync } from 'node:fs'
const encoded   = readFileSync('./uploads/invoice.b64', 'utf8').trim()
const corrupted = Buffer.from(encoded, 'base64').toString('utf8') // ❌
writeFileSync('./out/invoice.pdf', corrupted) // ❌ unreadable PDF
// ✅ Keep the Buffer as binary — do not convert to a string
import { readFileSync, writeFileSync } from 'node:fs'
const encoded = readFileSync('./uploads/invoice.b64', 'utf8').trim()
const binary  = Buffer.from(encoded, 'base64') // ✓ raw bytes preserved
writeFileSync('./out/invoice.pdf', binary)      // ✓ valid PDF

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

МетодUTF-8 вивідБінарний вивідURL-безпечнийСередовищаПотребує встановлення
atob()❌ потрібен TextDecoder✅ бінарний рядок❌ ручне відновленняBrowser, Node 16+, Bun, DenoНі
TextDecoder + atob()✅ UTF-8✅ через Uint8Array❌ ручне відновленняBrowser, Node 16+, DenoНі
Buffer.from().toString()✅ utf8✅ залишити як Buffer✅ base64url (Node 18+)Node.js, BunНі
Uint8Array.fromBase64() (TC39)✅ через TextDecoder✅ нативний✅ опція alphabetChrome 130+, Node 22+Ні
js-base64✅ завжди✅ Uint8Array✅ вбудованийУніверсальноnpm install

Використовуйте atob() лише коли декодований вміст гарантовано є ASCII-текстом. Для будь-якого призначеного для користувача або багатомовного тексту в браузері використовуйте TextDecoder + atob(). Для серверного коду Node.js Buffer — правильний вибір за замовчуванням: він обробляє UTF-8 автоматично та зберігає бінарні дані незайманими. Для крос-середовищних бібліотек js-base64 усуває всі граничні випадки.

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

Чому atob() повертає спотворені символи замість читаємого тексту?
atob() повертає бінарний рядок, де кожен символ представляє один сирий байт (0–255), а не кодову точку Unicode. Якщо вихідний текст був закодований у UTF-8, будь-який символ вище U+007F — кирилиця, арабська, ієрогліфи CJK, літери з діакритикою — з'явиться як два або більше спотворених Latin-1 символи. Рішення: передайте вивід через TextDecoder: const bytes = Uint8Array.from(atob(encoded), ch => ch.charCodeAt(0)); const text = new TextDecoder().decode(bytes). У Node.js використовуйте Buffer.from(encoded, 'base64').toString('utf8') — він обробляє це автоматично.
Як декодувати JWT-токен у JavaScript?
JWT складається з трьох URL-безпечних Base64-сегментів, розділених крапками: header.payload.signature. Для декодування пейлоаду: const [, payloadB64] = token.split('.'). У браузері: відновіть стандартні символи, додайте паддинг, декодуйте за допомогою atob() та TextDecoder. У Node.js 18+: Buffer.from(payloadB64, 'base64url').toString('utf8'). Важливо: декодування лише розкриває claims — воно НЕ верифікує підпис. Використовуйте спеціалізовану JWT-бібліотеку (jsonwebtoken, jose) для верифікованого декодування у продакшені.
У чому різниця між atob() та Buffer.from() для декодування?
atob() доступний у всіх JavaScript-середовищах (браузер, Node.js 16+, Bun, Deno) без імпортів, але повертає бінарний рядок — для конвертації UTF-8 вмісту в читаємий текст потрібен TextDecoder. Buffer.from(encoded, 'base64') працює лише в Node.js / Bun, повертає справжній Buffer (безпечний для бінарних даних), нативно обробляє UTF-8 і підтримує 'base64url' у Node.js 18+. Для серверного коду Buffer простіший. Для браузерного коду стандарт — atob() + TextDecoder. Для крос-середовищних бібліотек js-base64 абстрагує різницю.
Як декодувати URL-безпечний Base64 у браузері?
URL-безпечний Base64 замінює + на -, / на _ та прибирає паддинг =. Відновіть їх перед викликом atob(): const b64 = input.replace(/-/g, '+').replace(/_/g, '/'); const padded = b64 + '==='.slice(0, (4 - b64.length % 4) % 4); const text = new TextDecoder().decode(Uint8Array.from(atob(padded), c => c.charCodeAt(0))). У Node.js 18+: Buffer.from(input, 'base64url').toString('utf8') обробляє це одним викликом.
Як декодувати Base64-вміст із GitHub API у JavaScript?
GitHub Contents API повертає вміст файлу як стандартний Base64 із символами переносу рядка кожні 60 символів. Видаліть їх перед декодуванням: const clean = data.content.replace(/\n/g, ''). У браузері: new TextDecoder().decode(Uint8Array.from(atob(clean), c => c.charCodeAt(0))). У Node.js: Buffer.from(clean, 'base64').toString('utf8'). Для бінарних файлів (зображення, PDF) залишайте Buffer без виклику .toString() — передавайте його напряму у writeFile або потік відповіді.
Чи можна декодувати Base64-зображення у JavaScript без бібліотеки?
Так. У браузері: const binary = atob(encoded); const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0)); const blob = new Blob([bytes], { type: 'image/png' }); const url = URL.createObjectURL(blob). Для src тега img побудуйте data URI: const src = 'data:image/png;base64,' + encoded — це повністю пропускає крок декодування. У Node.js: Buffer.from(encoded, 'base64'), потім writeFileSync('./out.png', buffer). Головне правило: ніколи не викликайте .toString() на декодованому Buffer, якщо вміст є бінарним.

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

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

Також доступно на:PythonGoJavaC#
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.