Base64 in JSON — When It's Necessary and When It's Overkill
JSON was designed for structured text data — objects, arrays, strings, numbers. It was not designed for binary files. But APIs need to transfer images, PDFs, encrypted blobs, and serialized objects alongside regular JSON fields.
The industry-standard solution is wrapping binary data in a Base64 string inside a JSON field:
{
"userId": 123,
"avatar": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
}
This works. But it's overused. I've seen teams Base64-encode integers, short strings, and small configuration objects — cases where the overhead adds nothing useful.
When Base64 in JSON Is Necessary
Binary File Uploads Through Text-Based APIs
This is the canonical use case. If your API accepts file uploads over REST and you're not using multipart/form-data, Base64 is the only way to embed binary content in JSON:
async function uploadFile(file, apiUrl) {
const base64 = await fileToBase64(file);
const response = await fetch(apiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
filename: file.name,
mimeType: file.type,
content: base64
})
});
return response.json();
}
This pattern is especially common in serverless environments where multipart parsing isn't available or adds unnecessary complexity.
Encrypted Data Passed Between Services
When one service encrypts data and passes it to another, the output of the encryption algorithm is raw bytes. JSON can't represent raw bytes, so you Base64-encode the ciphertext:
{
"encryptedData": "7xLqGm9XzP3R...",
"keyId": "kms-key-2024-01",
"iv": "A1b2C3d4E5f6G7h8"
}
This is correct. The raw ciphertext bytes must be encoded for transport, and Base64 is the simplest reliable option.
JWT Tokens in API Responses
Every JWT token is three Base64URL-encoded JSON segments joined by dots:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
The header and payload are Base64URL-encoded JSON objects. They're readable by anyone who decodes them, and that's by design — JWT only promises integrity (via the signature), not confidentiality. For more detail, see How JWT Uses Base64 Encoding.
When Base64 in JSON Is Overkill
Small Numeric IDs or Short Strings
I've seen this pattern in production code:
{
"userId": "MTIz"
}
That's the Base64 encoding of "123". Why? The original developer said "all our API fields should be Base64-encoded for consistency." The "consistency" argument falls apart when you realize the decoder adds latency, the payload is larger (4 bytes instead of 3 for a three-character string), and there's no security benefit.
Don't Base64-encode plaintext identifiers or short strings. JSON handles them natively.
Data You Need to Query or Search
If your database stores a field as Base64 and you need to search it:
-- Bad: searching a Base64-encoded field
SELECT * FROM users WHERE base64_decode(avatar_url) LIKE '%example%';
-- This kills index usage and forces a full table scan
Base64 encoding destroys the ability to index or pattern-match on the original data. If the consumer needs to read or query the data by value, either keep it as native JSON or use a dedicated binary storage mechanism.
Small Objects That Could Be Nested JSON
{
"metadata": "eyJuYW1lIjoiSm9obiIsInJvbGUiOiJhZG1pbiJ9"
}
Decoded: {"name":"John","role":"admin"}
Why not just nest the object as JSON?
{
"metadata": {
"name": "John",
"role": "admin"
}
}
Double-encoding JSON inside JSON adds parsing complexity, hides the schema from API documentation generators, and makes debugging harder. Every layer of encoding is another chance for bugs.
The 33% Overhead Problem
Base64 increases binary data size by approximately 33%. For a 1 MB file, that's 1.33 MB of JSON payload. On a slow mobile connection, those extra 330 KB have real impact.
const original = Buffer.alloc(1024 * 1024, "A"); // 1 MB of 'A's
const encoded = original.toString("base64");
console.log(encoded.length); // 1,396,264 characters — ~36% larger
This matters for:
- Mobile APIs where bandwidth is expensive
- High-throughput systems where 33% more bytes means 33% more network time
- Serverless functions where request/response size affects billing (AWS Lambda charges $0.09/GB for Lambda-to-internet data transfer)
Alternatives to Reduce Overhead
If your API sends large files frequently, consider:
multipart/form-data — Separate the JSON metadata from the binary content:
POST /api/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="metadata"
Content-Type: application/json
{"userId": 123}
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg
[binary data here]
------WebKitFormBoundary--
This avoids Base64 entirely and keeps the binary data as raw bytes.
Separate upload endpoints — Upload the file to a blob store (S3, GCS), return a URL, and reference the URL in the JSON:
{
"userId": 123,
"avatarUrl": "https://cdn.example.com/uploads/photo.jpg"
}
This is the most performant approach for large files. The 33% Base64 overhead is eliminated, and the CDN handles caching and edge delivery.
Performance Benchmark: Base64 vs Multipart
Scenario: Upload 5 MB image to API endpoint
Base64 in JSON:
- Encode time: ~80ms
- Payload size: ~6.7 MB (5 MB × 1.33)
- Upload time (10 Mbps): ~5.4 seconds
- Decode time: ~60ms
- Total: ~5.5 seconds
Multipart/form-data:
- Encode time: 0ms (no encoding needed)
- Payload size: ~5 MB
- Upload time (10 Mbps): ~4.0 seconds
- Decode time: 0ms (no decoding needed)
- Total: ~4.0 seconds
Savings with multipart: ~27% faster, ~25% less bandwidth
For production APIs handling large file uploads, the numbers are clear. Use multipart for big files and Base64+JSON for small payloads or when multipart parsing isn't available.
FAQ
Can I put binary data directly in JSON?
No. JSON strings must be valid UTF-8. Binary data often contains bytes that are invalid UTF-8 or that break JSON parsing (unescaped control characters). Base64 encoding is the standard workaround.
Does Base64 encoding make my JSON payloads secure?
No. Base64 is encoding, not encryption. Anyone who intercepts the payload can decode it without a key. If confidentiality is required, encrypt the data before encoding it to Base64. See Base64 Is Not Encryption for a full explanation.
Should I use Base64 for all JSON string fields?
No. Only Base64-encode data that was originally binary — images, PDFs, encrypted blobs, serialized protobufs. Plaintext strings and numbers are smaller and more readable as native JSON values.
What's the size overhead of Base64 in JSON?
Approximately 33%. For every 3 bytes of binary data, Base64 produces 4 ASCII characters. In JSON, those characters are stored as UTF-8, so a 3 MB file becomes roughly 4 MB in the JSON payload.
How do I decode Base64 from a JSON response in JavaScript?
const response = await fetch("/api/file/123");
const data = await response.json();
const decoded = atob(data.content);
For Unicode or binary content, use TextEncoder/TextDecoder or Buffer (Node.js) as shown in Base64 Encoding in JavaScript.
For debugging Base64-encoded JSON payloads, the Base64 Encoder & Decoder tool lets you paste and decode Base64 strings instantly. If you're working with JWTs embedded in JSON responses, the JWT Decoder handles token splitting and signature verification automatically.