Codificación Base64 en JavaScript: Guía Completa

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

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

Probar Codificador Base64 Online online →

Cuando incrustas una imagen en una URI de datos CSS, pasas credenciales en un encabezado HTTP Authorization, o almacenas un certificado binario en una variable de entorno, necesitas codificar datos JavaScript en Base64 de forma fiable tanto en el navegador como en Node.js. JavaScript proporciona dos APIs integradas distintas:btoa() para entornos de navegador (también disponible en Node.js 16+) y Buffer.from() para Node.js — cada una con diferentes restricciones en torno a Unicode, datos binarios y seguridad en URLs. Para una codificación rápida sin escribir ningún código, el Codificador Base64 de ToolDeck lo maneja al instante en el navegador. Esta guía cubre ambos entornos con ejemplos listos para producción: manejo de Unicode, variantes URL-safe, codificación de archivos y respuestas de API, uso CLI, y los cuatro errores que causan bugs consistentemente en proyectos reales.

  • btoa() es nativo del navegador y está disponible en Node.js 16+ de forma global, pero solo acepta Latin-1 (puntos de código 0–255) — la entrada Unicode lanza una DOMException
  • Buffer.from(text, "utf8").toString("base64") es el equivalente en Node.js y maneja Unicode de forma nativa sin pasos adicionales
  • URL-safe Base64 reemplaza + → -, / → _, y elimina el relleno = — usa Buffer.from().toString("base64url") en Node.js 18+ para hacerlo en una sola línea
  • Para datos binarios (ArrayBuffer, Uint8Array, archivos), usa Buffer en Node.js o el enfoque arrayBuffer() + Uint8Array en el navegador — nunca response.text()
  • Uint8Array.prototype.toBase64() (TC39 Stage 3) ya está disponible en Node.js 22+ y Chrome 130+ y unificará ambos entornos

¿Qué es la Codificación Base64?

Base64 convierte datos binarios arbitrarios en una cadena formada por 64 caracteres ASCII imprimibles: A–Z, a–z, 0–9, +, y /. Cada 3 bytes de entrada se asignan exactamente a 4 caracteres Base64; si la longitud de la entrada no es múltiplo de 3, se agregan uno o dos caracteres de relleno =. La salida codificada es siempre aproximadamente un 33% más grande que el original.

Base64 no es cifrado — no proporciona confidencialidad. Cualquier persona con la cadena codificada puede decodificarla con una sola llamada a función. Su propósito es la seguridad en el transporte: muchos protocolos y formatos de almacenamiento fueron diseñados para texto ASCII de 7 bits y no pueden manejar bytes binarios arbitrarios. Base64 cubre esa brecha. Los casos de uso comunes en JavaScript incluyen URIs de datos para incrustar recursos, encabezados HTTP Basic Auth, segmentos de tokens JWT, adjuntos MIME de correo electrónico, y almacenamiento de blobs binarios en APIs JSON.

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

btoa() — La Función de Codificación Nativa del Navegador

btoa() (binary-to-ASCII) ha estado disponible en navegadores desde IE10 y se convirtió en global en Node.js 16.0 como parte de la iniciativa de compatibilidad WinterCG. También funciona de forma nativa en Deno, Bun y Cloudflare Workers. No se necesita ninguna importación.

La función toma un único argumento de tipo cadena y devuelve su forma codificada en Base64. La contraparte simétrica atob() (ASCII-to-binary) la decodifica de vuelta. Ambas son síncronas y se ejecutan en memoria constante relativa al tamaño de la entrada.

Ejemplo mínimo funcional

JavaScript (browser / Node.js 16+)
// Encoding an API credential pair for an HTTP Basic Auth header
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=

Decodificación con atob()

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

Manejo de Unicode y Caracteres No-ASCII

El problema más común con btoa() es su estricto límite de Latin-1. Cualquier carácter con un punto de código superior a U+00FF causa una excepción inmediata:

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

El enfoque correcto es codificar primero la cadena a bytes UTF-8, luego codificar esos bytes en Base64. JavaScript proporciona TextEncoder exactamente para este propósito:

Enfoque con TextEncoder — seguro para cualquier entrada Unicode

JavaScript (browser + Node.js 16+)
// Utility functions for Unicode-safe 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)
}

// Works with any language or script
const orderNote = 'Confirmado: Carlos Mendoza — almacén Madrid, cant: 250'
const encoded   = toBase64(orderNote)
const decoded   = fromBase64(encoded)

console.log(encoded)
// Q29uZmlybWFkbzogQ2FybG9zIE1lbmRvemEg4oCTIGFsbWFjw6luIE1hZHJpZCwgY2FudDogMjUw

console.log(decoded === orderNote) // true
Nota:Si ya estás en Node.js, omite completamente el truco con TextEncoder — usa Buffer.from(text, 'utf8').toString('base64'). Maneja Unicode de forma nativa y es más rápido para cadenas largas.

Buffer.from() en Node.js — Guía Completa con Ejemplos

En Node.js, Buffer es la API idiomática para todas las operaciones con datos binarios, incluidas las conversiones de codificación. Precede a TextEncoder por años y sigue siendo la opción preferida para el código del lado del servidor. Ventajas clave frente a btoa(): soporte nativo de UTF-8, manejo de datos binarios y el atajo de codificación 'base64url' disponible desde Node.js 18.

Codificación y decodificación básica de texto

Node.js
// Encoding a server configuration object for storage in an env variable
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...

// Decoding back
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

Codificación de archivos binarios desde disco

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

// Read a TLS certificate and encode it for embedding in a config file
const certPem     = readFileSync(join(process.cwd(), 'ssl', 'server.crt'))
const certBase64  = certPem.toString('base64')

// Store as a single-line string — suitable for env vars or JSON configs
writeFileSync('./dist/cert.b64', certBase64, 'utf8')

console.log(`Certificate encoded: ${certBase64.length} characters`)
// Certificate encoded: 2856 characters

// Restore the binary cert from the encoded value
const restored = Buffer.from(certBase64, 'base64')
console.log(restored.equals(certPem)) // true

Codificación asíncrona de archivos con manejo de errores

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(`File not found: ${filePath}`)
    if (code === 'EACCES') throw new Error(`Permission denied: ${filePath}`)
    throw err
  }
}

// Encode a PDF for an email attachment payload
const reportBase64 = await encodeFileToBase64('./reports/q1-financials.pdf')

const emailPayload = {
  to:          'finance-team@company.internal',
  subject:     'Q1 Financial Report',
  attachments: [{
    filename:    'q1-financials.pdf',
    content:     reportBase64,
    encoding:    'base64',
    contentType: 'application/pdf',
  }],
}

console.log(`Attachment: ${reportBase64.length} chars`)

Funciones Base64 de JavaScript — Referencia de Parámetros

A diferencia del módulo base64 de Python, JavaScript no tiene una única función Base64 unificada. La API depende del entorno de destino. Aquí está la referencia completa para todos los enfoques nativos:

FunciónTipo de entradaUnicodeURL-safeDisponible en
btoa(string)string (Latin-1)❌ lanza por encima de U+00FF❌ reemplazo manualBrowser, Node 16+, Bun, Deno
atob(string)Base64 string❌ devuelve cadena binaria❌ reemplazo manualBrowser, Node 16+, Bun, Deno
Buffer.from(src, enc) .toString(enc)string | Buffer | Uint8Array✅ codificación utf8✅ base64url en Node 18+Node.js, Bun
TextEncoder().encode(str) + btoa()string (cualquier Unicode)✅ vía bytes UTF-8❌ reemplazo manualBrowser, Node 16+, Deno
Uint8Array.toBase64() (TC39)Uint8Array✅ binario✅ omitPadding + alphabetChrome 130+, Node 22+

La firma Buffer.from(src, enc).toString(enc) acepta varios valores de codificación relevantes para Base64:

"base64"
Base64 estándar (RFC 4648 §4). Usa + y / con relleno =.
"base64url"
Base64 URL-safe (RFC 4648 §5, Node.js 18+). Usa - y _ sin relleno.
"utf8"
Predeterminado para fuentes de cadenas. Úsalo cuando la fuente es texto legible por humanos.
"binary"
Latin-1 / ISO-8859-1. Se usa cuando la fuente es una cadena binaria cruda (p. ej., de atob()).

Base64 URL-safe — Codificación para JWTs, URLs y Nombres de Archivo

El Base64 estándar usa + y /, que están reservados en URLs — + se decodifica como espacio en cadenas de consulta, y / es un separador de rutas. Los JWTs, parámetros de URL, nombres de archivo y valores de cookies requieren la variante URL-safe: +-, /_, = final eliminado.

Navegador — reemplazo manual de caracteres

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

Codificación de Archivos y Respuestas de API en JavaScript

En el código de producción, la codificación Base64 se aplica con mayor frecuencia a archivos que se transmiten y a respuestas de APIs externas que entregan contenido binario. Los patrones difieren entre el navegador y Node.js, y los datos binarios requieren un cuidado especial.

Navegador — codificar un archivo desde un elemento 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}`

    // Preview the image inline
    const img   = document.getElementById('preview') as HTMLImageElement
    img.src     = dataUri
    img.hidden  = false

    console.log(`Encoded ${file.name} (${file.size} bytes) → ${encoded.length} Base64 chars`)
  } catch (err) {
    console.error('Encoding failed:', err)
  }
})

Obtención de datos binarios codificados en Base64 desde una 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}`)

Cuando solo necesitas inspeccionar una respuesta codificada durante la depuración de APIs sin configurar un script, pega el valor Base64 directamente en el Codificador Base64 — también decodifica, con salida inmediata. Útil para inspeccionar respuestas de la API de GitHub, payloads JWT y firmas de webhooks.

Codificación Base64 en Línea de Comandos con Node.js y Shell

Para scripts de CI/CD, targets de Makefile o depuración puntual, rara vez necesitas un script completo. Tanto las herramientas del sistema como los one-liners de Node.js cubren la mayoría de los casos multiplataforma.

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=="
Nota:En macOS, base64 envuelve la salida en 76 caracteres por defecto. Esto rompe el análisis posterior. Siempre añade -b 0 (macOS) o --wrap=0 (Linux) cuando necesites un resultado en una sola línea — por ejemplo, al escribir en una variable de entorno o en un campo de configuración.

Alternativa de Alto Rendimiento: js-base64

Las APIs integradas son suficientes para la mayoría de los casos de uso. La razón principal para recurrir a una librería es la consistencia entre entornos: si publicas un paquete que se ejecuta tanto en el navegador como en Node.js, usar Buffer requiere detección de entorno o configuración del bundler, mientras que btoa() requiere el truco de Unicode. js-base64 (más de 100M de descargas semanales en npm) maneja ambos de forma transparente.

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

Internamente, js-base64 usa Buffer nativo cuando está disponible y recurre a una implementación en JS puro en el navegador. Es 2–3× más rápido que el enfoque TextEncoder+btoa para cadenas Unicode largas, y la API simétrica (toBase64 / fromBase64) elimina la carga mental de recordar en qué dirección van btoa y atob.

Codificación de Archivos Binarios Grandes con Streams de Node.js

Cuando necesitas codificar archivos de más de ~50 MB, cargar el archivo completo en memoria con readFileSync() se convierte en un problema. Los streams de Node.js te permiten procesar los datos en fragmentos — pero la codificación Base64 tiene una restricción: debes alimentar el codificador en múltiplos de 3 bytes para evitar un relleno incorrecto en los límites de los fragmentos.

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('Stream encoding complete')
Nota:El tamaño del fragmento debe ser múltiplo de 3 bytes para evitar el relleno espurio = en el medio de la salida. El ejemplo usa 3 * 1024 * 256 = 786.432 bytes (768 KB) — ajusta highWaterMark según tu presupuesto de memoria. Para archivos de menos de 50 MB, readFile() + Buffer.toString('base64') es más simple y suficientemente rápido.

Errores Comunes

He revisado muchas bases de código JavaScript con codificación Base64, y estos cuatro errores aparecen de forma consistente — a menudo sin descubrirse hasta que un carácter no-ASCII o un archivo binario alcanza la ruta de codificación en producción.

Error 1 — Pasar Unicode directamente a btoa()

Problema: btoa() solo acepta caracteres con puntos de código 0–255. Caracteres como ñ, emojis o ideogramas CJK causan una DOMException inmediata. Solución: codifica primero con TextEncoder, o usa Buffer.from(text, 'utf8').toString('base64') en Node.js.

Before · JavaScript
After · JavaScript
// ❌ DOMException: The string to be encoded contains
//    characters outside of the Latin1 range
const username = 'Ana Torres'
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('Ana Torres')
// QW5hIFRvcnJlcw==

Error 2 — Olvidar restaurar el relleno antes de atob()

Problema: El Base64 URL-safe elimina el relleno =. Pasar la cadena sin relleno directamente a atob() produce una salida incorrecta o lanza una excepción según la longitud de la cadena. Solución: restaura + y / y vuelve a añadir la cantidad correcta de relleno antes de llamar a 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"}

Error 3 — Concatenar fragmentos codificados en lugar de buffers crudos

Problema: Cada llamada a btoa() o .toString('base64') añade su propio relleno. Concatenar dos cadenas Base64 con relleno produce una salida inválida porque el relleno solo debe estar al final. Solución: concatena los datos crudos antes de codificar.

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

Error 4 — Usar response.text() para leer datos binarios de API antes de codificar

Problema: response.text() interpreta los bytes crudos como UTF-8 y reemplaza las secuencias de bytes no reconocidas con el carácter de reemplazo U+FFFD. Cualquier contenido binario — imágenes, PDFs, audio — se corrompe silenciosamente antes de llegar a btoa(). Solución: usa response.arrayBuffer() para obtener los bytes crudos.

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

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

MétodoUnicodeDatos binariosURL-safeEntornosRequiere instalación
btoa() / atob()❌ Latin-1❌ workaround necesario❌ reemplazo manualBrowser, Node 16+, Bun, DenoNo
TextEncoder + btoa()✅ UTF-8✅ vía Uint8Array❌ reemplazo manualBrowser, Node 16+, DenoNo
Buffer.from().toString()✅ utf8✅ nativo✅ base64url (Node 18+)Node.js, BunNo
Uint8Array.toBase64() (TC39)✅ binario✅ nativo✅ opción alphabetChrome 130+, Node 22+No
js-base64✅ siempre✅ Uint8Array✅ integradoUniversalnpm install

Elige btoa() solo cuando la entrada es comprobadamente solo ASCII — resúmenes hexadecimales, IDs numéricos o cadenas Latin-1 prevalidadas. Para texto proporcionado por el usuario en un navegador, usa TextEncoder + btoa(). Para todo el código del lado del servidor en Node.js, Buffer es el predeterminado correcto. Para librerías que necesitan ejecutarse en ambos entornos sin configuración del bundler, js-base64 elimina todos los casos límite.

Preguntas Frecuentes

¿Por qué btoa() lanza "InvalidCharacterError" en mi cadena?
btoa() solo acepta caracteres con puntos de código en el rango 0–255 (Latin-1 / ISO-8859-1). Cualquier carácter por encima de U+00FF — incluyendo la mayoría del cirílico, árabe, ideogramas CJK y muchos emojis — causa una DOMException. La solución depende de tu entorno: en el navegador, codifica a bytes UTF-8 con TextEncoder primero, convierte cada byte a un carácter con String.fromCharCode(), luego llama a btoa(). En Node.js, usa Buffer.from(text, 'utf8').toString('base64') que maneja Unicode de forma nativa.
¿Está btoa() disponible en Node.js sin ninguna importación?
Sí, desde Node.js 16.0. Tanto btoa() como atob() están registradas como funciones globales — no se necesita ninguna importación. Se comportan de forma idéntica a sus equivalentes en el navegador, incluyendo la restricción de Latin-1. Para el código del servidor en Node.js, Buffer.from() sigue siendo preferible a btoa() porque maneja UTF-8 de forma nativa, admite datos binarios sin trucos y tiene la opción de codificación 'base64url' añadida en Node.js 18.
¿Cuál es la diferencia entre Base64 estándar y Base64 URL-safe?
El Base64 estándar (RFC 4648 §4) usa + para el valor 62, / para el valor 63 y = para el relleno. Estos caracteres tienen significado especial en las URLs: + se interpreta como espacio en las cadenas de consulta, y / es un separador de rutas. El Base64 URL-safe (RFC 4648 §5) sustituye - por + y _ por /, y normalmente omite el relleno = por completo. Los JWTs usan Base64 URL-safe para los tres segmentos. En Node.js 18+, Buffer.from(text).toString('base64url') produce el formato URL-safe directamente.
¿Cómo codifico una imagen en Base64 para una URI de datos CSS en JavaScript?
En un navegador: usa file.arrayBuffer() para leer el binario, conviértelo a Uint8Array, luego llama a btoa(Array.from(bytes, b => String.fromCharCode(b)).join('')). Construye la URI de datos como 'data:' + file.type + ';base64,' + encoded. En Node.js: const encoded = fs.readFileSync('./image.png').toString('base64') y antepone el tipo MIME. Para archivos SVG a menudo puedes omitir Base64 por completo y usar una URI de datos codificada por URL en su lugar, que es más legible y ligeramente más pequeña.
¿Puedo codificar y decodificar en Base64 sin ninguna librería npm en el navegador?
Sí. Para entrada solo ASCII, btoa() y atob() funcionan directamente. Para Unicode, el par TextEncoder / TextDecoder te proporciona el conjunto de herramientas completo — ambos están integrados en todos los navegadores modernos y Node.js 16+. El único caso en que una librería agrega valor genuinamente es la consistencia entre entornos: si escribes una utilidad que debe funcionar de forma idéntica tanto en el navegador como en Node.js sin configuración del bundler, js-base64 elimina la lógica de detección de entorno.
¿Cómo decodifico contenido Base64 de la API de GitHub?
La API de Contenidos de GitHub devuelve el contenido del archivo como Base64 con saltos de línea incrustados (la API envuelve la salida en 60 caracteres). Elimínalos antes de decodificar: const clean = data.content.replace(/\n/g, ''); const text = atob(clean);. En Node.js: const text = Buffer.from(data.content.replace(/\n/g, ''), 'base64').toString('utf8');. GitHub siempre usa Base64 estándar (no URL-safe), por lo que no se necesita sustitución de + → - o / → _.

Herramientas Relacionadas

Para codificar o decodificar con un clic sin escribir ningún código, pega tu cadena o binario directamente en el Codificador Base64 — maneja los modos estándar y URL-safe al instante en tu navegador.

También disponible en: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 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.