Subir un archivo
  1. base64hub
  2. Aprende Base64
  3. Uint8Array.toBase64()
Guía

Uint8Array.toBase64 y fromBase64 en JavaScript

JavaScript por fin tiene una API Base64 que trabaja con bytes, admite Base64URL y te deja elegir lo estricta que es la decodificación. Sustituye a la mayoría de los usos de btoa, atob y los bucles escritos a mano.

Codificar y decodificar en una línea

La API añade tres métodos para Base64 y tres para hex:

MétodoQué hace
bytes.toBase64(options)De Uint8Array a una cadena Base64
Uint8Array.fromBase64(string, options)De una cadena Base64 a un nuevo Uint8Array
bytes.setFromBase64(string, options)Decodifica en un Uint8Array existente
bytes.toHex()De Uint8Array a una cadena hex en minúsculas
Uint8Array.fromHex(string)De una cadena hex a un nuevo Uint8Array
bytes.setFromHex(string)Decodifica hex en un Uint8Array existente

El texto tiene que convertirse antes en bytes. TextEncoder siempre produce UTF-8, así que Unicode funciona sin trucos:

const bytes = new TextEncoder().encode("Grüße, 世界");
const b64 = bytes.toBase64();
console.log(b64); // "R3LDvMOfZSwg5LiW55WM"

const back = new TextDecoder().decode(Uint8Array.fromBase64(b64));
console.log(back); // "Grüße, 世界"

Un ArrayBuffer, por ejemplo de fetch().arrayBuffer() o crypto.subtle.digest(), necesita una vista: new Uint8Array(buffer).toBase64(). Los demás arrays tipados no tienen estos métodos. Para codificar la memoria en bruto de un Uint16Array, envuélvela: new Uint8Array(u16.buffer, u16.byteOffset, u16.byteLength).

Opciones: alphabet, omitPadding, lastChunkHandling

alphabet

"base64" (el valor por defecto) usa + y /. "base64url" usa - y _. La opción se aplica tanto a la codificación como a la decodificación, y un decodificador configurado con un alfabeto rechaza los caracteres del otro.

const data = new Uint8Array([251, 255]);
data.toBase64();                                         // "+/8="
data.toBase64({ alphabet: "base64url" });                // "-_8="
data.toBase64({ alphabet: "base64url", omitPadding: true }); // "-_8"

Uint8Array.fromBase64("-_8", { alphabet: "base64url" }); // Uint8Array [251, 255]
Uint8Array.fromBase64("-_8");                            // SyntaxError

omitPadding

toBase64 añade el relleno = por defecto, incluso con el alfabeto Base64URL. Pasa omitPadding: true para obtener una salida al estilo JWT. Al decodificar, el relleno lo controla lastChunkHandling.

lastChunkHandling

Esta opción decide qué hacen fromBase64 y setFromBase64 con el último grupo de caracteres. Los tres modos, probados con una entrada que se decodifica en los bytes de Hi?>:

Entrada"loose" (por defecto)"strict""stop-before-partial"
SGk/Pg==Hi?>Hi?>Hi?>
SGk/Pg (sin relleno)Hi?>SyntaxErrorHi? (se detiene antes de Pg)
SGk/Ph== (bits de relleno distintos de cero)Hi?>SyntaxErrorHi?>
SGk/P (un carácter sobrante)SyntaxErrorSyntaxErrorHi?
  • loose se comporta como atob: el relleno es opcional y se ignoran los bits sobrantes del último carácter.
  • strict solo acepta entradas canónicas y con relleno, tal como las describe RFC 4648. Úsalo cuando la forma codificada deba ser única, por ejemplo cuando se comparan o se firman cadenas Base64.
  • stop-before-partial decodifica todos los grupos completos de 4 caracteres y deja sin tocar una cola incompleta. Está pensado para streaming, donde el resto del grupo llega en el siguiente trozo.

En todos los modos se ignoran los espacios en blanco ASCII (espacios, tabulaciones, saltos de línea), así que la entrada dividida en líneas al estilo MIME se decodifica directamente. Cualquier otro carácter fuera del alfabeto lanza un SyntaxError.

Escribir en un búfer existente

setFromBase64 y setFromHex decodifican en un Uint8Array que ya tienes y devuelven cuánto han usado:

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

Hello world son 11 bytes y el destino admite 8. El decodificador solo escribe grupos completos de 3 bytes que quepan, así que se detiene tras 6 bytes e indica que leyó 8 caracteres. Vuelve a llamarlo con el resto de la cadena y un subarray del destino para continuar. Combinado con stop-before-partial, esto te permite decodificar un flujo trozo a trozo en un búfer reservado de antemano:

const out = new Uint8Array(1024);
const { read, written } = out.setFromBase64("SGk/Pg", {
  lastChunkHandling: "stop-before-partial",
});
// read: 4, written: 3. Keep "Pg" and prepend it to the next chunk.

Los métodos hex siguen el mismo patrón. toHex() siempre escribe en minúsculas, fromHex() acepta ambos casos y un número impar de dígitos es un SyntaxError:

new Uint8Array([0xde, 0xad, 0xbe, 0xef]).toHex(); // "deadbeef"
Uint8Array.fromHex("CAFE");                       // Uint8Array [202, 254]

Por qué btoa y atob son incómodos

btoa y atob vienen de los primeros tiempos de los navegadores y trabajan con «cadenas binarias»: cadenas de JavaScript en las que cada código de carácter es un valor de byte de 0 a 255. Eso provoca tres problemas.

Unicode o lanza un error o falla sin avisar. Los caracteres por encima de U+00FF hacen que btoa lance un InvalidCharacterError (Chrome: “The string to be encoded contains characters outside of the Latin1 range”). Los caracteres entre U+0080 y U+00FF son peores: se codifican sin error, como Latin-1. btoa("Grüße") devuelve R3L832U=, mientras que la codificación UTF-8 que espera cualquier otro sistema es R3LDvMOfZQ==.

Decodificar devuelve una cadena, no bytes. atob("Q2Fmw6k=") devuelve "Café". Para obtener el texto real hay que convertir la cadena en bytes y luego decodificarlos como UTF-8, lo que requiere un bucle o Uint8Array.from(atob(s), c => c.charCodeAt(0)).

Las entradas grandes requieren cuidado. El típico btoa(String.fromCharCode(...bytes)) pasa cada byte como argumento de una función, y con un array de 1 MB lanza RangeError: Maximum call stack size exceeded. Hay que construir la cadena por trozos.

Tampoco hay soporte para Base64URL ni control sobre el relleno o el grado de rigor. El viejo apaño btoa(unescape(encodeURIComponent(text))) sí produce Base64 de UTF-8, pero depende de unescape, que está obsoleto. Con la nueva API, todo esto se reduce a new TextEncoder().encode(text).toBase64().

Soporte en navegadores y Node.js

Los métodos vienen de la propuesta de TC39 «Uint8Array to/from Base64», que llegó a la fase 4 en julio de 2025 y forma parte de ECMAScript 2026.

EntornoPrimera versión con los seis métodos
Chrome, Edge140
Firefox133
Safari (macOS e iOS)18.2
Node.js25.0.0

Con Chrome 140, la función pasó a ser Baseline en septiembre de 2025, así que todos los navegadores principales actuales la tienen. En Node.js probamos los métodos directamente. Node 25.0.0 y 26.12.1 tienen los seis. Node 24.22.1, una versión LTS, no tiene ninguno por defecto. Su V8 acepta una opción experimental --js-base-64, pero en nuestra prueba esa opción exponía toBase64, fromBase64, toHex y fromHex sin setFromBase64 ni setFromHex, así que considera Node 24 como no compatible. En Node 24 y anteriores, usa Buffer.from(b64, "base64") y buf.toString("base64"), que también aceptan "base64url". Deno 2.5 y Bun 1.1.22 también admiten la API, según los datos de compatibilidad de MDN.

Una alternativa para entornos antiguos

Detecta la función y recurre a btoa/atob por trozos. Esta versión se probó en Node 24 (ruta alternativa) y Node 26 (ruta nativa) con texto UTF-8 y un array de 1 MB:

function bytesToBase64(bytes) {
  if (typeof bytes.toBase64 === "function") return bytes.toBase64();
  let bin = "";
  for (let i = 0; i < bytes.length; i += 0x8000) {
    bin += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
  }
  return btoa(bin);
}

function base64ToBytes(b64) {
  if (typeof Uint8Array.fromBase64 === "function") return Uint8Array.fromBase64(b64);
  return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
}

La alternativa solo cubre las opciones por defecto. Si necesitas base64url, strict o setFromBase64 en todas partes, usa un polyfill completo: core-js incluye uno como es.uint8-array.to-base64, es.uint8-array.from-base64 y módulos relacionados.

Para probar codificaciones sin escribir código, usa el codificador Base64 y el decodificador, o Base64URL para el alfabeto URL-safe. Hay más ejemplos de JavaScript, incluido el Buffer de Node, en Base64 en JavaScript y Base64 en Node.js.

Preguntas frecuentes

¿Se puede usar ya la nueva API Base64 en producción?

En navegadores, sí: está en Chrome y Edge 140, Firefox 133 y Safari 18.2. En Node.js necesita la versión 25 o posterior. Mantén una alternativa si das soporte a navegadores antiguos o a Node 24 e inferiores.

¿Uint8Array.fromBase64 acepta Base64URL?

Sí, con { alphabet: "base64url" }. Sin esa opción, - y _ provocan un SyntaxError. En el modo permisivo por defecto, el relleno es opcional con ambos alfabetos.

¿Cómo codifico una cadena con toBase64?

Conviértela antes en bytes UTF-8: new TextEncoder().encode(text).toBase64(). Para decodificar, haz lo inverso con new TextDecoder().decode(Uint8Array.fromBase64(b64)).

¿Por qué toBase64 no es una función en mi Buffer o Uint16Array?

Los métodos solo existen en Uint8Array y sus subclases. El Buffer de Node los tiene a partir de Node 25. Para otros arrays tipados, envuelve la memoria: new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength).

Sigue leyendo