Base64 pojawia się wszędzie, gdy zaczniesz szukać — tokeny JWT, URI danych, załączniki e-mail,
ładunki API przenoszące pliki binarne. Samo kodowanie jest zdefiniowane w
RFC 4648
i jest martwe proste w koncepcji: pobierz dowolne bajty i reprezentuj je używając tylko 64 drukowalnych
znaków ASCII. Co przysparza problemów, to implementacja w JavaScript — różne API w przeglądarce
i Node.js, pułapka Unicode, przez którą btoa() rzuca wyjątek, oraz wariant bezpieczny dla URL,
od którego zależą JWT. Ten przewodnik omawia wszystko z działającym kodem.
btoa() i atob() w przeglądarce
Przeglądarki mają od dawna
btoa()
i
atob().
Nazwy są mylące (binary to ASCII i z powrotem), ale użycie jest
proste dla prostych ciągów:
// Encode a plain ASCII string
const encoded = btoa('hello world');
console.log(encoded); // "aGVsbG8gd29ybGQ="
// Decode it back
const decoded = atob('aGVsbG8gd29ybGQ=');
console.log(decoded); // "hello world"
// A more realistic example — encoding a simple auth token
const credentials = 'apiuser:s3cr3tkey';
const basicAuth = 'Basic ' + btoa(credentials);
// "Basic YXBpdXNlcjpzM2NyM3RrZXk="
// This is exactly what HTTP Basic Authentication usesbtoa() obsługuje tylko ciągi, w których
każdy znak ma punkt kodowy ≤ 255 (zakres Latin-1). Przekazanie ciągu zawierającego
emoji lub znak nielacińskie spowoduje natychmiastowy InvalidCharacterError.
To jeden z najczęstszych błędów Base64 w kodzie przeglądarkowym.// ❌ This throws — emoji is outside Latin-1
btoa('Hello 🌍');
// Uncaught DOMException: Failed to execute 'btoa' on 'Window':
// The string to be encoded contains characters outside of the Latin1 range.
// ❌ This also throws — any non-ASCII character will do it
btoa('café');
// Uncaught DOMException: ...Bezpieczna obsługa Unicode w przeglądarce
Naprawą jest najpierw zakodowanie ciągu do bajtów UTF-8, a następnie zakodowanie tych bajtów w Base64.
Klasyczne podejście używa encodeURIComponent i sztuczki z dekodowaniem procentowym. Nowoczesne
podejście używa TextEncoder, który jest
dostępny we wszystkich nowoczesnych przeglądarkach
i Node.js 11+:
// ✅ Unicode-safe encode using TextEncoder
function encodeBase64(str) {
const bytes = new TextEncoder().encode(str); // UTF-8 byte array
const binString = Array.from(bytes, byte =>
String.fromCodePoint(byte)
).join('');
return btoa(binString);
}
// ✅ Unicode-safe decode using TextDecoder
function decodeBase64(base64Str) {
const binString = atob(base64Str);
const bytes = Uint8Array.from(binString, char =>
char.codePointAt(0)
);
return new TextDecoder().decode(bytes);
}
// Now emojis and international text work fine
console.log(encodeBase64('Hello 🌍')); // "SGVsbG8g8J+MjQ=="
console.log(decodeBase64('SGVsbG8g8J+MjQ==')); // "Hello 🌍"
console.log(encodeBase64('Héllo café')); // "SMOpbGxvIGNhZsOp"
console.log(decodeBase64('SMOpbGxvIGNhZsOp')); // "Héllo café"Zachowaj te dwie funkcje narzędziowe gdzieś w swojej bazie kodu i zapomnij o gołym
btoa(). Para TextEncoder/TextDecoder to
właściwe narzędzie do wszystkiego poza czystym ASCII. Możesz to teraz wypróbować za pomocą narzędzia
Koder Base64.
Buffer.from() w Node.js
Node.js ma własne API do tego przez klasę Buffer, która obsługuje kodowanie/dekodowanie czyściej. Nie ma tu pułapki Unicode, ponieważ jawnie określasz kodowanie wejścia:
// Encode string → Base64
const encoded = Buffer.from('Hello 🌍', 'utf8').toString('base64');
console.log(encoded); // "SGVsbG8g8J+MjQ=="
// Decode Base64 → string
const decoded = Buffer.from('SGVsbG8g8J+MjQ==', 'base64').toString('utf8');
console.log(decoded); // "Hello 🌍"
// Practical example — encoding a JSON payload to embed in a config file
const config = {
apiKey: 'sk-prod-abc123',
projectId: 'proj_x9f2k',
region: 'us-east-1'
};
const encodedConfig = Buffer.from(JSON.stringify(config), 'utf8').toString('base64');
// eyJhcGlLZXkiOiJzay1wcm9kLWFiYzEyMyIsInByb2plY3RJZCI6InByb2pfeDlmMmsiLCJyZWdpb24iOiJ1cy1lYXN0LTEifQ==
// Decode and parse it back
const decodedConfig = JSON.parse(
Buffer.from(encodedConfig, 'base64').toString('utf8')
);
console.log(decodedConfig.region); // "us-east-1"Pamiętaj, że btoa() i atob() są dostępne w Node.js 16+ jako
zmienne globalne (dla kompatybilności z przeglądarką), ale API Buffer jest bardziej idiomatyczne w
Node.js i istnieje od
Node.js v0.1.
Do kodowania specyficznego dla JSON narzędzie JSON do Base64 jest przydatne
do szybkich ręcznych konwersji.
Base64 bezpieczne dla URL — co naprawdę używają JWT
Standardowe Base64 używa + i / w swoim alfabecie. Oba te znaki
są specjalne w URL — + oznacza spację w ciągach zapytania, a /
to separator ścieżki. Gdy potrzebujesz Base64 w URL lub jako segment JWT, używasz wariantu bezpiecznego dla URL:
zastąp + znakiem - i / znakiem _,
a następnie usuń dopełnienie =. Jest to znormalizowane w
RFC 4648 §5
i jest tym, czego każda biblioteka JWT używa wewnętrznie:
// Convert standard Base64 to URL-safe Base64
function toBase64Url(base64Str) {
return base64Str
.replace(/+/g, '-')
.replace(///g, '_')
.replace(/=+$/, ''); // strip padding
}
// Convert URL-safe Base64 back to standard Base64
function fromBase64Url(base64UrlStr) {
// Restore padding — length must be a multiple of 4
const padded = base64UrlStr + '==='.slice((base64UrlStr.length + 3) % 4);
return padded
.replace(/-/g, '+')
.replace(/_/g, '/');
}
// Encode a string to URL-safe Base64
function encodeBase64Url(str) {
return toBase64Url(btoa(str));
}
// Decode URL-safe Base64 to a string
function decodeBase64Url(str) {
return atob(fromBase64Url(str));
}
// Example: manually inspect a JWT payload
const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjQyLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE3MTM0MDAwMDB9.signature';
const [header, payload] = jwt.split('.');
console.log(decodeBase64Url(header));
// {"alg":"HS256","typ":"JWT"}
console.log(decodeBase64Url(payload));
// {"userId":42,"role":"admin","iat":1713400000}Dlatego widzisz ciągi Base64 takie jak
eyJhbGciOiJIUzI1NiJ9 w JWT — bez dopełnienia, myślniki zamiast plusów.
Przy wysyłaniu zakodowanych danych jako parametr zapytania URL, zawsze używaj wariantu bezpiecznego dla URL,
aby uniknąć uszkodzonych URL. Narzędzie Dekoder Base64 obsługuje zarówno
standardowe, jak i bezpieczne dla URL Base64 automatycznie.
Kodowanie pliku za pomocą API FileReader
Typowe zadanie w przeglądarce: użytkownik wybiera obraz lub dokument i musisz wysłać go
do API jako Base64.
API FileReader
ma readAsDataURL() dokładnie do tego — daje pełny URI danych z dołączonym typem MIME:
// Wrap FileReader in a Promise for easier async usage
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
// result is "data:image/png;base64,iVBORw0KGgo..."
// Strip the data URI prefix to get just the Base64 string
const base64 = reader.result.split(',')[1];
resolve(base64);
};
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsDataURL(file);
});
}
// Hook it up to a file input
const fileInput = document.getElementById('avatarUpload');
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
try {
const base64 = await fileToBase64(file);
console.log(`File size: ${file.size} bytes`);
console.log(`Base64 length: ${base64.length} chars`);
// Send to your API
await fetch('/api/users/42/avatar', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ image: base64, mimeType: file.type })
});
} catch (err) {
console.error('Upload failed:', err.message);
}
});Jeśli potrzebujesz pełnego URI danych (z prefiksem MIME) zamiast tylko
surowego Base64, pomiń .split(',')[1] i użyj bezpośrednio reader.result.
Do masowej konwersji plików narzędzie Image to Base64 obsługuje
obrazy bez pisania żadnego kodu.
Kodowanie danych binarnych i Uint8Array
Czasami nie zaczynasz od ciągu ani pliku — masz surowe bajty z operacji WebCrypto,
eksportu canvas lub modułu WebAssembly. Oto jak przejść z
Uint8Array do Base64 i z powrotem w obu środowiskach:
// --- Browser ---
// Uint8Array → Base64 (browser)
function uint8ToBase64(bytes) {
const binString = Array.from(bytes, byte =>
String.fromCodePoint(byte)
).join('');
return btoa(binString);
}
// Base64 → Uint8Array (browser)
function base64ToUint8(base64Str) {
const binString = atob(base64Str);
return Uint8Array.from(binString, char => char.codePointAt(0));
}
// Example: export a canvas as raw PNG bytes → Base64
const canvas = document.getElementById('myCanvas');
canvas.toBlob(blob => {
blob.arrayBuffer().then(buffer => {
const bytes = new Uint8Array(buffer);
const encoded = uint8ToBase64(bytes);
console.log('PNG as Base64:', encoded.slice(0, 40) + '...');
});
}, 'image/png');
// --- Node.js ---
// Uint8Array / Buffer → Base64 (Node.js)
function uint8ToBase64Node(bytes) {
return Buffer.from(bytes).toString('base64');
}
// Base64 → Buffer (Node.js)
function base64ToBufferNode(base64Str) {
return Buffer.from(base64Str, 'base64');
}
// Example: hash a password and encode the result
const crypto = require('crypto');
const hash = crypto.createHash('sha256').update('mySecretPassword').digest();
// hash is a Buffer (which extends Uint8Array)
console.log(hash.toString('base64'));
// "XohImNooBHFR0OVvjcYpJ3NgxxxxxxxxxxxxxA=="Osadzanie obrazów jako URI danych
Jednym z najbardziej praktycznych zastosowań Base64 w tworzeniu stron internetowych jest osadzanie obrazów bezpośrednio w HTML lub CSS, eliminując żądanie HTTP. Prawdopodobnie widziałeś URI danych w wbudowanych SVG lub szablonach e-mail. Oto wzorzec:
<!-- Inline image in HTML — no separate network request -->
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
alt="1x1 transparent pixel"
width="1"
height="1"
/>/* Inline background image in CSS — commonly used for small icons and loading spinners */
.spinner {
width: 32px;
height: 32px;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEyIDJhMTAgMTAgMCAxIDAgMCAyMCAxMCAxMCAwIDAgMCAwLTIweiIvPjwvc3ZnPg==");
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}// Generate a data URI from a fetched image (Node.js)
const fs = require('fs');
const path = require('path');
function imageFileToDataUri(filePath) {
const ext = path.extname(filePath).slice(1).toLowerCase();
const mimeMap = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
gif: 'image/gif', svg: 'image/svg+xml', webp: 'image/webp' };
const mimeType = mimeMap[ext] ?? 'application/octet-stream';
const fileData = fs.readFileSync(filePath);
const base64 = fileData.toString('base64');
return `data:${mimeType};base64,${base64}`;
}
const dataUri = imageFileToDataUri('./logo.png');
// "data:image/png;base64,iVBORw0KGgo..."
// Drop this into an <img src> or CSS background-imageKompaktowy moduł narzędziowy dla obu środowisk
Zamiast rozrzucać wywołania btoa() po całej bazie kodu, warto
mieć jeden moduł narzędziowy, który obsługuje Unicode, warianty bezpieczne dla URL i działa zarówno
w przeglądarce, jak i Node.js. Oto taki, który robi to wszystko:
// base64.js — drop into any project
const isNode = typeof process !== 'undefined' && process.versions?.node;
export function encode(str) {
if (isNode) {
return Buffer.from(str, 'utf8').toString('base64');
}
// Browser: encode to UTF-8 bytes first, then Base64
const bytes = new TextEncoder().encode(str);
const binString = Array.from(bytes, b => String.fromCodePoint(b)).join('');
return btoa(binString);
}
export function decode(base64Str) {
if (isNode) {
return Buffer.from(base64Str, 'base64').toString('utf8');
}
// Browser: Base64 → bytes → UTF-8 string
const binString = atob(base64Str);
const bytes = Uint8Array.from(binString, c => c.codePointAt(0));
return new TextDecoder().decode(bytes);
}
export function encodeUrlSafe(str) {
return encode(str)
.replace(/+/g, '-')
.replace(///g, '_')
.replace(/=+$/, '');
}
export function decodeUrlSafe(str) {
const padded = str + '==='.slice((str.length + 3) % 4);
return decode(padded.replace(/-/g, '+').replace(/_/g, '/'));
}
export function encodeBytes(bytes) {
if (isNode) return Buffer.from(bytes).toString('base64');
const binString = Array.from(bytes, b => String.fromCodePoint(b)).join('');
return btoa(binString);
}
export function decodeToBytes(base64Str) {
if (isNode) return Buffer.from(base64Str, 'base64');
const binString = atob(base64Str);
return Uint8Array.from(binString, c => c.codePointAt(0));
}// Usage examples
import { encode, decode, encodeUrlSafe, decodeUrlSafe } from './base64.js';
encode('Hello 🌍'); // "SGVsbG8g8J+MjQ=="
decode('SGVsbG8g8J+MjQ=='); // "Hello 🌍"
encodeUrlSafe('[email protected]'); // "dXNlckBleGFtcGxlLmNvbQ" (no +, /, or =)
decodeUrlSafe('dXNlckBleGFtcGxlLmNvbQ'); // "[email protected]"Typowe pułapki, na które warto zwrócić uwagę
- btoa() rzuca wyjątek dla znaków spoza Latin — każdy znak powyżej punktu kodowego 255 powoduje
InvalidCharacterError. Zawsze używaj podejścia zTextEncoderlubBuffer.from(str, 'utf8')w Node.js. - Dopełnienie ma znaczenie dla dekodowania — ciągi Base64 muszą mieć długość będącą wielokrotnością 4. Brakujące dopełnienie
=powoduje, żeatob()cicho zwraca śmieci lub rzuca wyjątek, w zależności od przeglądarki. Zawsze przywracaj dopełnienie przed dekodowaniem ciągów bezpiecznych dla URL. - Buffer vs kodowanie ciągów w Node.js —
Buffer.from(str)domyślnie używa UTF-8, aleBuffer.from(str, 'binary')traktuje ciąg jako bajty Latin-1. Używanie złego kodowania podczas dekodowania produkuje zniekształcone wyjście, które może być trudne do debugowania. - Typ MIME URI danych —
data:;base64,...(bez typu MIME) będzie działać w niektórych przeglądarkach, ale nie w innych. Zawsze dodawaj typ MIME:data:image/png;base64,.... - Podziały wierszy w MIME Base64 — RFC 4648 pozwala implementacjom wstawiać podziały wierszy co 76 znaków (jak robią to kodery e-mail).
atob()iBuffer.from()oba to obsługują, ale jeśli sam generujesz Base64, nie dodawaj podziałów wierszy, chyba że docelowy system ich oczekuje.
Podsumowanie
Base64 w JavaScript jest jednym z tych tematów, które wyglądają trywialnie, dopóki cię nie ugryzie.
Krótka wersja: nigdy nie używaj gołego btoa() do czegokolwiek generowanego przez użytkownika — owiń go
TextEncoder do poprawnej obsługi Unicode. W Node.js Buffer.from(str,
'utf8').toString('base64') to właściwy idiom. Gdy zakodowany ciąg trafia do URL lub JWT,
przełącz się na wariant bezpieczny dla URL. Do szybkich eksperymentów lub jednorazowych konwersji, narzędzia
Koder Base64, Dekoder Base64,
i JSON do Base64 oszczędzają czas. Strona
słownika Base64 MDN
ma też solidne materiały referencyjne skupione na przeglądarce, jeśli potrzebujesz drugiej opinii.