Base64 Decode JavaScript — atob() y Buffer

·Front-end & Node.js Developer·Revisado porSophie Laurent·Publicado

Usa el Decodificador Base64 Online gratuito directamente en tu navegador — sin instalación.

Probar Decodificador Base64 Online online →

Cuando depuro un problema de autenticación en producción, lo primero que busco es un decodificador Base64 — los payloads de JWT, las firmas de webhooks y los valores de configuración codificados se esconden dentro de cadenas Base64. JavaScript ofrece dos enfoques integrados principales para decodificar base64: atob() (navegador + Node.js 16+) y Buffer.from(encoded, 'base64').toString() (Node.js) — y se comportan de manera muy diferente cuando los datos originales contienen caracteres Unicode. Para una decodificación puntual sin escribir código, el Decodificador Base64 de ToolDeck lo resuelve al instante en tu navegador. Esta guía cubre ambos entornos — orientada a Node.js 16+ y navegadores modernos (Chrome 80+, Firefox 75+, Safari 14+) — con ejemplos listos para producción: recuperación UTF-8, variantes seguras para URL, decodificación de JWT, archivos, respuestas de API, streams de Node.js y los cuatro errores que generan sistemáticamente salidas corruptas en proyectos reales.

  • atob(encoded) es nativo en el navegador y está disponible globalmente en Node.js 16+, pero devuelve una cadena binaria — usa TextDecoder para recuperar texto UTF-8 de cualquier contenido por encima de ASCII.
  • Buffer.from(encoded, "base64").toString("utf8") es el enfoque idiomático de Node.js y maneja UTF-8 automáticamente sin pasos adicionales.
  • El Base64 seguro para URL (usado en JWTs) reemplaza + por -, / por _, y elimina el relleno =. Restáuralos antes de llamar a atob(), o usa Buffer.from(encoded, "base64url").toString() en Node.js 18+.
  • Elimina los espacios en blanco y saltos de línea antes de decodificar — la API de Contenidos de GitHub y muchos codificadores MIME envuelven la salida Base64 a 60–76 caracteres por línea.
  • Uint8Array.prototype.fromBase64() (TC39 Etapa 3) ya está disponible en Node.js 22+ y Chrome 130+ y eventualmente unificará ambos entornos.

¿Qué es la Decodificación Base64?

La decodificación Base64 es la operación inversa a la codificación — convierte la representación ASCII de 64 caracteres de vuelta a los datos binarios o texto originales. Cada 4 caracteres Base64 se corresponden exactamente con 3 bytes. Los caracteres de relleno = al final de una cadena codificada indican al decodificador cuántos bytes adicionales se añadieron para completar el último grupo de 3 bytes.

Base64 no es cifrado — la operación es completamente reversible por cualquiera que tenga la cadena codificada. Su propósito es la seguridad en el transporte: los protocolos y formatos de almacenamiento diseñados para texto ASCII de 7 bits no pueden manejar bytes binarios arbitrarios, y Base64 salva esa brecha. Los escenarios comunes de decodificación en JavaScript incluyen inspeccionar payloads de JWT, desempaquetar configuraciones JSON codificadas en Base64 desde variables de entorno, extraer contenido de archivos binarios desde APIs REST y decodificar URIs de datos en el navegador.

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

atob() — La Función de Decodificación Nativa del Navegador

atob() (ASCII-to-binary, ASCII a binario) ha estado disponible en navegadores desde IE10 y se convirtió en un global de Node.js 16.0 como parte de la iniciativa de compatibilidad WinterCG. También funciona nativamente en Deno, Bun y Cloudflare Workers — sin necesidad de importación.

La función devuelve una cadena binaria: una cadena JavaScript donde cada carácter tiene un punto de código igual a un valor de byte sin procesar (0–255). Esto importa: si los datos originales eran texto UTF-8 con caracteres por encima de U+007F (letras acentuadas, cirílico, CJK, emoji), la cadena devuelta es la secuencia de bytes sin procesar, no texto legible. Usa TextDecoder para recuperarlo (se trata en la siguiente sección).

Ejemplo mínimo funcional

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

Verificación de ida y vuelta

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
Nota:atob() y btoa() forman parte de la API Mínima Común de WinterCG — la misma especificación que rige Fetch, URL y crypto en entornos que no son navegadores. Se comportan de forma idéntica en Node.js 16+, Bun, Deno y Cloudflare Workers.

Recuperación de Texto UTF-8 Después de la Decodificación

El error más común con atob() es malinterpretar su tipo de retorno. Cuando el texto original fue codificado como UTF-8 antes de Base64, atob() devuelve una cadena binaria Latin-1, no el texto legible:

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

El enfoque correcto usa TextDecoder para interpretar esos bytes sin procesar como UTF-8:

Enfoque con TextDecoder — seguro para cualquier salida 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
Nota:En Node.js, omite el paso de TextDecoder por completo — usa Buffer.from(encoded, 'base64').toString('utf8'). Interpreta los bytes decodificados como UTF-8 automáticamente y es más rápido para entradas grandes.

Buffer.from() en Node.js — Guía Completa de Decodificación

En Node.js, Buffer es la API idiomática para todas las operaciones binarias incluyendo la decodificación Base64. Maneja UTF-8 de forma nativa, devuelve un Buffer apropiado (seguro para binarios) y, desde Node.js 18, soporta el atajo de codificación 'base64url' para variantes seguras de URL.

Decodificación de una variable de entorno de configuración

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

Restauración de un archivo binario desde un archivo .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

Decodificación asíncrona con manejo de errores

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`)

Funciones de Decodificación Base64 — Referencia de Parámetros

Referencia rápida de los parámetros de las dos principales APIs de decodificación nativas, formateada para su uso como consulta al escribir o revisar código.

atob(encodedData)

ParámetroTipoRequeridoDescripción
encodedDatastringCadena Base64 estándar que usa los caracteres +, /, =. Las variantes seguras para URL (-, _) lanzan InvalidCharacterError. No se permiten espacios en blanco.
Devuelve: cadena binaria — el punto de código de cada carácter equivale a un valor de byte sin procesar (0–255). No es una cadena Unicode; pásala por TextDecoder para recuperar texto UTF-8.

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

ParámetroTipoPredeterminadoDescripción
inputstring | Buffer | TypedArray | ArrayBufferrequeridoLa cadena codificada en Base64 a decodificar, o un buffer que contiene bytes codificados.
inputEncodingBufferEncoding"utf8"Establece "base64" para Base64 estándar (RFC 4648 §4), o "base64url" para Base64 seguro para URL (RFC 4648 §5, Node.js 18+).
outputEncodingstring"utf8"Codificación para la salida de .toString(). Usa "utf8" para texto legible, "binary" para una cadena binaria Latin-1 compatible con la salida de atob().
startinteger0Desplazamiento de byte dentro del Buffer decodificado para empezar a leer. Se pasa a .toString() como segundo argumento.
endintegerbuf.lengthDesplazamiento de byte donde se deja de leer (exclusivo). Se pasa a .toString() como tercer argumento.
Devuelve: Buffer desde .from(). Devuelve string desde .toString(). Mantén el resultado como Buffer (sin llamar a .toString()) cuando el contenido decodificado es binario — imágenes, PDFs, audio.

Base64 Seguro para URL — Decodificación de JWTs y Parámetros de URL

Los JWTs usan Base64 seguro para URL (RFC 4648 §5) para los tres segmentos. El Base64 seguro para URL reemplaza + por - y / por _, y elimina el relleno = final. Pasar esto directamente a atob() sin restaurarlo produce una salida incorrecta o lanza una excepción.

Navegador — restaurar caracteres y relleno antes de decodificar

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+ — codificación nativa '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

Decodificación de Base64 desde Archivos y Respuestas de API

En código de producción, la decodificación Base64 ocurre más frecuentemente al consumir APIs externas que entregan contenido en formato codificado. Ambos escenarios tienen advertencias importantes sobre espacios en blanco y salida binaria frente a texto. Si solo necesitas inspeccionar una respuesta codificada durante la depuración, pégala directamente en el Decodificador Base64 — gestiona los modos estándar y seguro para URL al instante.

Decodificación de contenido de la API de Contenidos de GitHub

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}`)

Decodificación de un binario Base64 desde una API (navegador)

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')!)

Decodificación Base64 por Línea de Comandos en Node.js y Shell

Para scripts de CI/CD, sesiones de depuración o tareas de decodificación puntuales, las herramientas de shell y los comandos de una línea de Node.js son más rápidos que un script completo. Ten en cuenta que el nombre del flag difiere entre macOS y 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"}
Nota:En macOS, base64 usa -D para decodificar (D mayúscula), mientras que Linux usa -d (minúscula). Esto rompe los scripts de CI silenciosamente — usa un comando de una línea de Node.js cuando la plataforma de destino no esté garantizada como Linux.

Alternativa de Alto Rendimiento: js-base64

La principal razón para recurrir a una librería es la consistencia entre entornos. Si distribuyes un paquete que se ejecuta tanto en navegador como en Node.js sin configuración de bundler, Buffer requiere detección de entorno y atob() requiere el trabajo extra con TextDecoder. js-base64 (más de 100 millones de descargas semanales en npm) maneja ambos de forma transparente.

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

Salida de Terminal con Resaltado de Sintaxis

Al escribir herramientas de depuración CLI o scripts de inspección, la salida simple de console.log es difícil de leer para payloads JSON grandes. chalk (el paquete npm más descargado para colorear terminales) combinado con la decodificación Base64 produce una salida de terminal legible y escaneable — útil para la inspección de JWT, depuración de respuestas de API y auditoría de configuraciones.

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
Nota:Usa chalk solo para salida de terminal/CLI — nunca para contenido escrito en archivos, respuestas de API o agregadores de logs. Los códigos de escape ANSI corrompen los consumidores que no son terminales: plataformas de logs (Datadog, Splunk), parsers de logs JSON y visores de logs de CI los muestran todos como secuencias de caracteres ilegibles.

Decodificar Archivos Base64 Grandes con Streams de Node.js

Cuando un archivo codificado en Base64 supera los ~50 MB, cargarlo completamente en memoria con readFileSync() se convierte en un problema. Los streams de Node.js permiten decodificar datos en fragmentos — pero Base64 requiere múltiplos de 4 caracteres por fragmento (cada grupo de 4 caracteres decodifica exactamente 3 bytes) para evitar errores de relleno en los límites de los fragmentos.

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')
Nota:El tamaño del fragmento debe ser múltiplo de 4 caracteres al leer texto Base64, de modo que cada fragmento contenga solo grupos completos de 4 caracteres. El ejemplo usa 4 × 1024 × 192 = 786.432 caracteres (768 KB). Para archivos de menos de 50 MB, readFile() + Buffer.from(content.trim(), 'base64') es más sencillo y suficientemente rápido.

Errores Comunes

He visto estos cuatro errores repetidamente en proyectos JavaScript — tienden a permanecer ocultos hasta que un carácter no ASCII o una respuesta de API con saltos de línea llega al camino de decodificación en producción.

Error 1 — Usar atob() sin TextDecoder para contenido UTF-8

Problema: atob() devuelve una cadena binaria donde cada carácter es un valor de byte sin procesar. Las secuencias multibyte UTF-8 (cirílico, CJK, caracteres acentuados) aparecen como caracteres Latin-1 corruptos. Solución: envuelve la salida en 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) // Алексей Иванов ✓

Error 2 — Pasar Base64 seguro para URL directamente a atob()

Problema: Los segmentos de JWT usan - y _ en lugar de + y /, sin relleno. atob() puede devolver datos incorrectos o lanzar una excepción. Solución: restaura los caracteres estándar y añade el relleno antes.

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"} ✓

Error 3 — No eliminar saltos de línea del Base64 envuelto por líneas

Problema: La API de Contenidos de GitHub y los codificadores MIME envuelven la salida Base64 a 60–76 caracteres por línea. atob() lanza InvalidCharacterError en los caracteres \n. Solución: elimina todos los espacios en blanco antes de decodificar.

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) // ✓

Error 4 — Llamar a .toString() sobre contenido binario decodificado

Problema: Cuando los datos originales son binarios (imágenes, PDFs, audio), llamar a .toString('utf8') reemplaza secuencias de bytes no reconocidas con U+FFFD, corrompiendo silenciosamente la salida. Solución: mantén el resultado como Buffer — no lo conviertas a cadena.

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

Métodos de Decodificación Base64 en JavaScript — Comparación Rápida

MétodoSalida UTF-8Salida binariaSeguro para URLEntornosRequiere instalación
atob()❌ necesita TextDecoder✅ cadena binaria❌ restauración manualNavegador, Node 16+, Bun, DenoNo
TextDecoder + atob()✅ UTF-8✅ vía Uint8Array❌ restauración manualNavegador, Node 16+, DenoNo
Buffer.from().toString()✅ utf8✅ mantener como Buffer✅ base64url (Node 18+)Node.js, BunNo
Uint8Array.fromBase64() (TC39)✅ vía TextDecoder✅ nativo✅ opción alphabetChrome 130+, Node 22+No
js-base64✅ siempre✅ Uint8Array✅ integradoUniversalnpm install

Elige atob() solo cuando el contenido decodificado sea garantizadamente texto ASCII. Para cualquier texto proporcionado por el usuario o en múltiples idiomas en un navegador, usa TextDecoder + atob(). Para código del lado del servidor en Node.js, Buffer es la opción correcta por defecto — maneja UTF-8 automáticamente y mantiene los datos binarios intactos. Para librerías de múltiples entornos, js-base64 elimina todos los casos extremos.

Preguntas Frecuentes

¿Por qué atob() devuelve caracteres corruptos en lugar de texto legible?
atob() devuelve una cadena binaria donde cada carácter representa un byte sin procesar (0–255), no un punto de código Unicode. Si el texto original fue codificado como UTF-8, cualquier carácter por encima de U+007F — cirílico, árabe, ideogramas CJK, letras acentuadas — aparecerá como dos o más caracteres Latin-1 corruptos. La solución: pasa la salida por TextDecoder: const bytes = Uint8Array.from(atob(encoded), ch => ch.charCodeAt(0)); const text = new TextDecoder().decode(bytes). En Node.js, usa Buffer.from(encoded, 'base64').toString('utf8'), que lo gestiona automáticamente.
¿Cómo decodifico el payload de un token JWT en JavaScript?
Un JWT tiene tres segmentos Base64 seguros para URL separados por puntos: cabecera.payload.firma. Para decodificar el payload: const [, payloadB64] = token.split('.'). En el navegador: restaura los caracteres estándar, añade el relleno, decodifica con atob() y TextDecoder. En Node.js 18+: Buffer.from(payloadB64, 'base64url').toString('utf8'). Importante: decodificar solo revela las claims — NO verifica la firma. Usa una librería JWT apropiada (jsonwebtoken, jose) para decodificación verificada en producción.
¿Cuál es la diferencia entre atob() y Buffer.from() para decodificar?
atob() está disponible en todos los entornos JavaScript (navegador, Node.js 16+, Bun, Deno) sin importaciones, pero devuelve una cadena binaria — necesitas TextDecoder para convertir contenido UTF-8 a texto legible. Buffer.from(encoded, 'base64') es solo para Node.js / Bun, devuelve un Buffer real (seguro para binarios), maneja UTF-8 de forma nativa y soporta 'base64url' en Node.js 18+. Para código del lado del servidor, Buffer es más simple. Para código del navegador, atob() + TextDecoder es el estándar. Para librerías de múltiples entornos, js-base64 abstrae la diferencia.
¿Cómo decodifico Base64 seguro para URL en el navegador?
El Base64 seguro para URL reemplaza + por -, / por _, y elimina el relleno =. Restáuralos antes de llamar a 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))). En Node.js 18+: Buffer.from(input, 'base64url').toString('utf8') lo gestiona en una sola llamada.
¿Cómo decodifico contenido Base64 de la API de GitHub en JavaScript?
La API de Contenidos de GitHub devuelve el contenido de archivos como Base64 estándar con caracteres de nueva línea cada 60 caracteres. Elimínalos antes de decodificar: const clean = data.content.replace(/\n/g, ''). En el navegador: new TextDecoder().decode(Uint8Array.from(atob(clean), c => c.charCodeAt(0))). En Node.js: Buffer.from(clean, 'base64').toString('utf8'). Para archivos binarios (imágenes, PDFs), mantén el Buffer sin llamar a .toString() — pásalo directamente a writeFile o al stream de respuesta.
¿Puedo decodificar una imagen codificada en Base64 en JavaScript sin una librería?
Sí. En el navegador: 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). Para un src de img, construye un URI de datos en su lugar: const src = 'data:image/png;base64,' + encoded — esto omite el paso de decodificación por completo. En Node.js: Buffer.from(encoded, 'base64') seguido de writeFileSync('./out.png', buffer). La regla clave: nunca llames a .toString() en el Buffer decodificado cuando el contenido sea binario.

Herramientas Relacionadas

Para decodificar con un clic sin escribir ningún código, pega tu cadena Base64 directamente en el Decodificador Base64 — gestiona los modos estándar y seguro para URL con salida inmediata en tu navegador.

También disponible en: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 LaurentRevisor técnico

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.