Skip to main content
← All Articles · · 11 min read

Base64 Encode/Decode Explained: When and How to Use It

Understand when to use Base64 encoding and when to avoid it. This guide explains the Base64 algorithm, practical use cases, code examples in JavaScript, Python, Go, and more.

base64encodingdeveloper-toolsweb-developmentapisecurity

Base64 is one of those things every developer uses but few fully understand. You’ve seen it in JWT tokens, CSS data URIs, email attachments, and API responses. You know it makes binary data look like random letters, but when should you actually use it — and when is it the wrong tool?

This guide explains the Base64 algorithm clearly, covers every practical use case, shows how to encode and decode in the most common languages, and points out the mistakes developers make when reaching for Base64 out of habit.


What Is Base64?

Base64 is a binary-to-text encoding scheme. It converts binary data (bytes) into a string of printable ASCII characters using a 64-character alphabet:

  • Uppercase letters: A–Z (26 characters)
  • Lowercase letters: a–z (26 characters)
  • Digits: 0–9 (10 characters)
  • Two additional characters: + and /
  • Padding character: =

That’s 64 characters — hence the name.

Why 64?

Because every ASCII character that’s “safe” to transmit through text-only systems is representable with a small, stable set. Original email systems, HTTP headers, and many protocols were designed for ASCII text, not arbitrary bytes. Base64 bridges the gap: take any binary data, turn it into a safe text string.

How It Works

Base64 processes input in 3-byte groups and converts each group into 4 Base64 characters.

  1. Take 3 bytes (24 bits)
  2. Split into four 6-bit groups
  3. Map each 6-bit group to one Base64 character using a lookup table

Example: encoding Man

ASCII values: M=77, a=97, n=110
Binary:       01001101 01100001 01101110

Split into 6-bit groups:
010011 | 010110 | 000101 | 101110

Map to Base64 alphabet:
010011 = 19 = T
010110 = 22 = W
000101 = 5  = F
101110 = 46 = u

Result: TWFu

If the input isn’t a multiple of 3 bytes, padding (=) fills the output to a multiple of 4 characters.


Base64 Is Not Encryption

This is the most important thing to understand: Base64 encoding is not encryption and provides no security.

Encoding: reversible transformation, no key required. Anyone can decode it. Encryption: transformation that requires a key to reverse. Without the key, the data is unreadable.

If you Base64 encode a password before storing it, that password is exposed. If you Base64 encode a credit card number in an API response, that number is exposed to anyone who intercepts the response.

Base64 is for format transformation, not protection. Use it to make binary data text-safe. Use proper encryption (AES-256, RSA, etc.) when you need to protect data.


When to Use Base64

Base64 is the right choice in these situations:

1. Embedding Images in CSS or HTML

Data URIs let you embed small images directly in your CSS without a separate HTTP request:

.icon {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABmJLR0QA/wD/AP+gvaeTAAAADklEQVQI12NgQAIABQABNjN9GQAAAALaA==');
}

This is useful for small icons (under 10KB) where the request overhead would cost more than the encoding overhead. For larger images, always use separate files — the 33% size increase and browser caching limitations make data URIs counterproductive.

2. Encoding Binary Data in JSON APIs

JSON only supports Unicode strings. If your API needs to return binary data (an image thumbnail, a cryptographic signature, raw bytes), you have two options: Base64 encode it, or use a binary-safe format like MessagePack.

{
  "filename": "avatar.png",
  "data": "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs...",
  "encoding": "base64"
}

Always include an encoding field to make it explicit that the value is Base64 encoded.

3. HTTP Basic Authentication

The Authorization: Basic header encodes credentials as Base64(username:password):

Authorization: Basic dXNlcjpwYXNzd29yZA==

Decoded: user:password

Important: this is only “safe” because HTTPS encrypts the entire HTTP exchange. Over plain HTTP, Basic Auth is trivially intercepted. The Base64 encoding is not security — the TLS layer is.

4. JWT Tokens

JWT tokens use Base64url encoding (a URL-safe variant) for the header and payload:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoxNzExMTQ0ODAwfQ.signature

Decoded header: {"alg":"HS256","typ":"JWT"} Decoded payload: {"sub":"user123","exp":1711144800}

The payload is Base64url encoded, not encrypted. Any JWT can be decoded and the payload read without the signing secret. Never put sensitive data (passwords, SSNs, private keys) in a JWT payload unless the token is also encrypted (JWE, not just JWS).

5. Email Attachments (MIME)

Email protocols were designed for ASCII text. Binary attachments (PDFs, images, Office documents) are Base64 encoded in MIME messages. You see this in raw email source or when parsing email programmatically.

6. Cryptographic Data Exchange

Public keys, certificates, and signatures are often distributed in PEM format, which is Base64 encoded DER data wrapped in header lines:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----

When NOT to Use Base64

For Large Binary Files

A 1MB file becomes ~1.33MB Base64 encoded. For file upload APIs, use multipart/form-data instead:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

This streams the binary directly without the 33% overhead.

For URLs

Standard Base64 uses +, /, and = — characters that have special meaning in URLs. If you need to put Base64 in a URL, use Base64url instead (- instead of +, _ instead of /, no padding).

Never URL-encode standard Base64 to put it in a URL — the double encoding creates maintenance headaches.

As “Obfuscation”

Encoding a password in Base64 and calling it “encrypted” is a security vulnerability. It is not obfuscation — it’s a well-known, standardized format. Security auditors check for this specifically.


How to Encode and Decode Base64

Browser (JavaScript)

The browser provides btoa() and atob() for ASCII strings:

// Encode
const encoded = btoa('Hello, World!');
// "SGVsbG8sIFdvcmxkIQ=="

// Decode
const decoded = atob('SGVsbG8sIFdvcmxkIQ==');
// "Hello, World!"

For Unicode strings (anything outside ASCII), you need to handle the encoding manually:

// Encode Unicode string to Base64
function encodeUnicode(str) {
  return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) =>
    String.fromCharCode('0x' + p1)
  ));
}

// Decode Base64 to Unicode string
function decodeUnicode(str) {
  return decodeURIComponent(atob(str).split('').map(c =>
    '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
  ).join(''));
}

encodeUnicode('日本語');
// "5pel5pys6Kqe"

Node.js

Node.js uses Buffer for Base64 operations:

// Encode string to Base64
const encoded = Buffer.from('Hello, World!').toString('base64');
// "SGVsbG8sIFdvcmxkIQ=="

// Decode Base64 to string
const decoded = Buffer.from('SGVsbG8sIFdvcmxkIQ==', 'base64').toString('utf8');
// "Hello, World!"

// Encode binary file to Base64
const fs = require('fs');
const imageData = fs.readFileSync('image.png');
const imageBase64 = imageData.toString('base64');

// Base64url (no padding, URL-safe characters)
const urlSafe = Buffer.from('Hello+World/Test=').toString('base64url');

Python

import base64

# Encode bytes to Base64
encoded = base64.b64encode(b'Hello, World!')
# b'SGVsbG8sIFdvcmxkIQ=='

# Decode Base64 to bytes
decoded = base64.b64decode('SGVsbG8sIFdvcmxkIQ==')
# b'Hello, World!'

# String encoding: encode the string to bytes first
text = 'Hello, World!'
encoded_str = base64.b64encode(text.encode('utf-8')).decode('utf-8')
# 'SGVsbG8sIFdvcmxkIQ=='

# Base64url (URL-safe variant)
url_safe = base64.urlsafe_b64encode(b'Hello+World')
url_decoded = base64.urlsafe_b64decode(url_safe)

# Encode a file
with open('image.png', 'rb') as f:
    image_base64 = base64.b64encode(f.read()).decode('utf-8')

Go

import (
    "encoding/base64"
    "fmt"
)

// Encode
encoded := base64.StdEncoding.EncodeToString([]byte("Hello, World!"))
fmt.Println(encoded) // SGVsbG8sIFdvcmxkIQ==

// Decode
decoded, err := base64.StdEncoding.DecodeString("SGVsbG8sIFdvcmxkIQ==")
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(decoded)) // Hello, World!

// URL-safe Base64 (for JWTs, URLs)
urlEncoded := base64.URLEncoding.EncodeToString([]byte("Hello, World!"))
urlDecoded, _ := base64.URLEncoding.DecodeString(urlEncoded)

// URL-safe without padding
rawEncoded := base64.RawURLEncoding.EncodeToString([]byte("Hello, World!"))

Rust

use base64::{Engine, engine::general_purpose};

// Encode
let encoded = general_purpose::STANDARD.encode(b"Hello, World!");
println!("{}", encoded); // SGVsbG8sIFdvcmxkIQ==

// Decode
let decoded_bytes = general_purpose::STANDARD.decode("SGVsbG8sIFdvcmxkIQ==").unwrap();
let decoded = String::from_utf8(decoded_bytes).unwrap();
println!("{}", decoded); // Hello, World!

// URL-safe (no padding)
let url_safe = general_purpose::URL_SAFE_NO_PAD.encode(b"Hello, World!");

Bash / Command Line

# Encode
echo -n "Hello, World!" | base64
# SGVsbG8sIFdvcmxkIQ==

# Decode
echo "SGVsbG8sIFdvcmxkIQ==" | base64 -d
# Hello, World!

# Encode a file
base64 image.png > image.b64

# Decode a file
base64 -d image.b64 > image.png

Base64 vs Base64url: Which to Use?

FeatureBase64 (Standard)Base64url
CharactersA-Z, a-z, 0-9, +, /A-Z, a-z, 0-9, -, _
PaddingRequired (=)Optional
URL-safeNo (+, / need escaping)Yes
Used inMIME email, PEM files, data URIsJWTs, OAuth, URL parameters

Use standard Base64 for: email, PEM/certificate files, data URIs, HTTP Basic Auth headers.

Use Base64url for: anything appearing in a URL, JWT tokens, OAuth state parameters, filename-safe identifiers.


Performance: How Much Does Base64 Increase Size?

Base64 encodes 3 bytes into 4 characters — a 33% size increase. This matters at scale:

Original SizeBase64 SizeIncrease
1 KB~1.37 KB+370 bytes
10 KB~13.7 KB+3.7 KB
100 KB~137 KB+37 KB
1 MB~1.37 MB+370 KB

Impact on HTTP Requests

When you embed Base64-encoded images in CSS, you remove an HTTP request but increase the CSS file size. The tradeoff:

  • Worth it: Small icons under 5-10KB where the HTTP request overhead (TCP connection, headers, DNS, latency) exceeds the size cost
  • Not worth it: Photos, illustrations, or any image over ~10KB — the size increase outweighs the request savings

Modern HTTP/2 connection multiplexing reduces the “too many requests” problem significantly. With HTTP/2, multiple resources are served over one connection, making the “save a request” argument for Base64 data URIs weaker than it was in the HTTP/1.1 era.

Impact on JavaScript Bundles

Bundlers (webpack, Rollup, esbuild) can inline small assets as Base64 data URIs automatically:

// webpack config — inline assets under 8KB as Base64
module.exports = {
  module: {
    rules: [{
      test: /\.(png|jpg|gif|svg)$/,
      type: 'asset',
      parser: {
        dataUrlCondition: {
          maxSize: 8 * 1024 // 8KB
        }
      }
    }]
  }
};

For Vite:

// vite.config.js
export default {
  build: {
    assetsInlineLimit: 4096 // 4KB — inline as Base64 if smaller
  }
};

These settings handle the tradeoff automatically based on file size.

Using DevPlaybook Base64 Tool

For quick encoding and decoding without writing code, DevPlaybook’s Base64 tool handles:

  • Text strings (including Unicode)
  • File uploads (encode small files to Base64)
  • Paste-and-decode for instant inspection
  • Base64url variant support
  • Copy-to-clipboard output

Useful scenarios: inspecting a JWT payload, checking what an API is returning in a Base64 field, encoding a small image for a CSS data URI, or verifying that your encoding implementation matches expected output.


Debugging Base64 Issues

”Invalid character in Base64 string”

You’re using standard Base64 where Base64url is expected (or vice versa). Check for +/- and //_ characters.

Output doesn’t match expected value

Check the input encoding. btoa('hello') in JavaScript operates on Latin-1 characters. If your string contains characters above U+00FF, you’ll get a DOMException. Use the Unicode-safe wrapper shown earlier.

Decoding produces garbled text

The Base64 might be correct, but you’re decoding as the wrong character encoding. If the original was UTF-8, decode as UTF-8:

const text = new TextDecoder('utf-8').decode(
  Uint8Array.from(atob(encoded), c => c.charCodeAt(0))
);

Missing padding errors

Base64url often omits the = padding. Before decoding standard Base64, add padding:

function addPadding(base64) {
  return base64 + '='.repeat((4 - base64.length % 4) % 4);
}

Conclusion

Base64 is a practical, widely-used encoding that solves a specific problem: making binary data safe to transmit through text-only channels. Use it for embedding images in CSS, encoding binary data in JSON APIs, HTTP Basic Auth credentials, JWT tokens, and PEM files. Avoid it for large files (use multipart instead), URL parameters (use Base64url), and anything where you need actual security (use encryption).

For quick encode/decode operations, DevPlaybook’s Base64 tool handles any input in the browser without sending data anywhere. For programmatic use, every major language has built-in support — use the examples above as a reference.


Working with JWT tokens that contain Base64url-encoded payloads? The JWT Decoder shows the full decoded structure at a glance. Formatting the JSON output? Try the JSON Formatter.

Free Download

50 CLI Commands That Save Hours Every Week

The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.

  • Git mastery (10 cmds)
  • File & search wizardry
  • Docker & DevOps tricks
  • Process & network tools
  • Productivity boosters
  • macOS & Linux install guide

No spam. 1 email/week max. Unsubscribe anytime.

Found this guide useful? Check out our free developer tools.