How to decode a base64 string in JavaScript
Decoding base64 strings is essential for processing API responses, handling file data, reading encoded content, and implementing features like data import or content restoration in JavaScript applications.
With over 25 years of experience in software development and as the creator of CoreUI, I’ve implemented base64 decoding in components like data processors, file viewers, and API integrations where converting encoded data back to readable format is crucial for functionality.
From my expertise, the most straightforward and browser-native solution is using the built-in atob() function, which provides standard base64 decoding.
This approach is efficient, widely supported, and specifically designed for ASCII-to-binary conversion in web environments.
Use the atob() function to decode a base64-encoded string back to its original format.
const encoded = 'SGVsbG8gV29ybGQ='
const decoded = atob(encoded)
// Result: 'Hello World'
The atob() function (ASCII-to-binary) converts a base64-encoded string back to its original text representation. In this example, atob('SGVsbG8gV29ybGQ=') produces 'Hello World', which is the original string before encoding. The function expects a valid base64 string and will throw a DOMException if the input contains invalid characters. Note that atob() works with strings containing characters in the 0-255 range (Latin-1), so additional steps are needed for Unicode content.
1. Decoding ASCII Strings
For simple ASCII text (English letters, numbers, common punctuation), atob() works directly without any extra steps.
// Basic text decoding
console.log(atob('SGVsbG8gV29ybGQ='))
// Result: 'Hello World'
// Numbers and special characters
console.log(atob('MTIzNDU2Nzg5MA=='))
// Result: '1234567890'
console.log(atob('eyJuYW1lIjoiSm9obiIsImFnZSI6MzB9'))
// Result: '{"name":"John","age":30}'
// Decode and parse JSON from API
const encodedPayload = 'eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiZWRpdG9yIn0='
const jsonString = atob(encodedPayload)
const data = JSON.parse(jsonString)
console.log(data)
// { user: 'admin', role: 'editor' }
// Decode URL-safe base64 (replace - and _ back to + and /)
const urlSafeBase64 = 'SGVsbG8td29ybGQ_'
const standardBase64 = urlSafeBase64.replace(/-/g, '+').replace(/_/g, '/')
console.log(atob(standardBase64))
// Result: 'Hello-world?'
URL-safe base64 uses - and _ instead of + and / to avoid conflicts in URLs. Always convert back to standard base64 before calling atob(). For the inverse operation, see how to encode a string in base64 in JavaScript.
2. Decoding Unicode Strings
The atob() function only handles Latin-1 characters (code points 0-255). Decoding Unicode content (emoji, CJK, accented characters) requires converting the binary string back through TextDecoder.
// atob() alone fails with Unicode that was encoded via btoa()
// The encoding side must have used a special process
// Modern approach: TextDecoder (recommended)
const decodeBase64Unicode = (encoded) => {
const binaryString = atob(encoded)
const bytes = Uint8Array.from(binaryString, char => char.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
// These were encoded with the matching encode function
console.log(decodeBase64Unicode('Y2Fmw6k='))
// Result: 'café'
console.log(decodeBase64Unicode('5L2g5aW9'))
// Result: '你好'
console.log(decodeBase64Unicode('8J+YgA=='))
// Result: '😀'
// Legacy approach (still works but less readable)
const decodeBase64UnicodeLegacy = (encoded) => {
return decodeURIComponent(
atob(encoded)
.split('')
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
)
}
console.log(decodeBase64UnicodeLegacy('Y2Fmw6k='))
// Result: 'café'
The TextDecoder approach is cleaner and more performant than the legacy decodeURIComponent trick. It works by converting the binary string from atob() into a Uint8Array of raw bytes, then decoding those bytes as UTF-8. This correctly handles multi-byte characters like emoji and CJK text.
3. Error Handling for Invalid Base64
User input and API responses may contain malformed base64 strings. Always wrap atob() in error handling to prevent runtime crashes.
// atob() throws on invalid input
try {
atob('not-valid-base64!!!')
} catch (error) {
console.log(error.message)
// 'Failed to execute 'atob' on 'Window': The string to be decoded
// is not correctly encoded.'
}
// Safe decode function
const safeBase64Decode = (encoded) => {
try {
return { success: true, data: atob(encoded) }
} catch {
return { success: false, data: null }
}
}
console.log(safeBase64Decode('SGVsbG8='))
// { success: true, data: 'Hello' }
console.log(safeBase64Decode('!!!invalid!!!'))
// { success: false, data: null }
// Validate before decoding
const isValidBase64 = (str) => {
if (!str || str.length % 4 !== 0) return false
return /^[A-Za-z0-9+/]*={0,2}$/.test(str)
}
console.log(isValidBase64('SGVsbG8=')) // true
console.log(isValidBase64('SGVsbG8gV29y')) // true
console.log(isValidBase64('not valid!')) // false
console.log(isValidBase64('')) // false
// Combined safe Unicode decode
const safeDecode = (encoded) => {
try {
const binaryString = atob(encoded)
const bytes = Uint8Array.from(binaryString, c => c.charCodeAt(0))
return new TextDecoder().decode(bytes)
} catch {
return null
}
}
console.log(safeDecode('Y2Fmw6k=')) // 'café'
console.log(safeDecode('broken!!!')) // null
The isValidBase64 regex checks that the string contains only valid base64 characters (A-Z, a-z, 0-9, +, /) with up to two = padding characters at the end. This is useful for pre-validation before attempting to decode.
4. Decoding JWT Tokens
One of the most common real-world uses of base64 decoding is reading JWT (JSON Web Token) payloads. JWTs use URL-safe base64 encoding for their header and payload sections.
// JWT structure: header.payload.signature
const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiam9obiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxNjMxMjAwMH0.signature'
// Decode JWT payload (second segment)
const decodeJwtPayload = (token) => {
const parts = token.split('.')
if (parts.length !== 3) throw new Error('Invalid JWT format')
// Convert URL-safe base64 to standard base64
let payload = parts[1]
.replace(/-/g, '+')
.replace(/_/g, '/')
// Add padding if needed
while (payload.length % 4 !== 0) {
payload += '='
}
return JSON.parse(atob(payload))
}
const payload = decodeJwtPayload(jwt)
console.log(payload)
// { user: 'john', role: 'admin', exp: 1716312000 }
// Check if JWT is expired
const isTokenExpired = (token) => {
try {
const { exp } = decodeJwtPayload(token)
if (!exp) return false
return Date.now() >= exp * 1000
} catch {
return true
}
}
console.log(isTokenExpired(jwt))
// Result: true or false depending on current time
// Decode JWT header
const decodeJwtHeader = (token) => {
const parts = token.split('.')
let header = parts[0].replace(/-/g, '+').replace(/_/g, '/')
while (header.length % 4 !== 0) header += '='
return JSON.parse(atob(header))
}
console.log(decodeJwtHeader(jwt))
// { alg: 'HS256', typ: 'JWT' }
JWT payloads are not encrypted — they are only base64-encoded. Never trust decoded JWT data on the client without server-side verification. This decoding pattern is commonly used with authentication flows in CoreUI dashboard templates to display user information from stored tokens.
5. Decoding Base64 Images and Files
Base64-encoded images appear in data URIs, API responses, and inline CSS. Decoding them back to binary data is essential for file processing.
// Parse a data URI
const parseDataUri = (dataUri) => {
const match = dataUri.match(/^data:(.+?);base64,(.+)$/)
if (!match) throw new Error('Invalid data URI')
return {
mimeType: match[1],
base64Data: match[2]
}
}
const imgUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
const { mimeType, base64Data } = parseDataUri(imgUri)
console.log(mimeType) // 'image/png'
console.log(base64Data) // 'iVBORw0KGgo...'
// Convert base64 to Blob (for download or display)
const base64ToBlob = (base64, mimeType) => {
const binaryString = atob(base64)
const bytes = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
return new Blob([bytes], { type: mimeType })
}
const blob = base64ToBlob(base64Data, mimeType)
console.log(blob.size) // file size in bytes
console.log(blob.type) // 'image/png'
// Create downloadable link from base64
const downloadBase64File = (base64, mimeType, filename) => {
const blob = base64ToBlob(base64, mimeType)
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
link.click()
URL.revokeObjectURL(url)
}
// Usage: downloadBase64File(base64Data, 'image/png', 'image.png')
The base64ToBlob function is essential when working with file uploads or image processing in CoreUI React components. It converts the base64 string back to raw binary data that can be used with URL.createObjectURL for display or download.
6. Decoding in Node.js with Buffer
In Node.js, the Buffer class provides a more straightforward API for base64 operations. It handles Unicode automatically without the extra steps required by atob().
// Node.js: Buffer handles everything
const encoded = 'SGVsbG8gV29ybGQ='
const decoded = Buffer.from(encoded, 'base64').toString('utf-8')
// Result: 'Hello World'
// Unicode works directly
const unicodeDecoded = Buffer.from('Y2Fmw6k=', 'base64').toString('utf-8')
// Result: 'café'
// Decode to different encodings
const hexString = Buffer.from('SGVsbG8=', 'base64').toString('hex')
// Result: '48656c6c6f'
// Isomorphic decode function (works in both browser and Node.js)
const decode = (encoded) => {
if (typeof Buffer !== 'undefined') {
// Node.js
return Buffer.from(encoded, 'base64').toString('utf-8')
}
// Browser
const binaryString = atob(encoded)
const bytes = Uint8Array.from(binaryString, c => c.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
console.log(decode('SGVsbG8gV29ybGQ='))
// Result: 'Hello World' (in both environments)
// Decode base64 file in Node.js
// const fs = require('fs')
// const fileData = Buffer.from(base64String, 'base64')
// fs.writeFileSync('output.pdf', fileData)
The isomorphic decode function detects the environment automatically, making it ideal for libraries that run in both browser and server contexts. Node.js Buffer.from() is generally preferred over atob() in server-side code because it handles Unicode natively and supports multiple output encodings.
Best Practice Note:
For ASCII-only content, atob() works directly. For Unicode content, always use the TextDecoder approach — it’s cleaner and more reliable than the legacy decodeURIComponent trick. Always wrap decoding in try-catch for untrusted input. In Node.js, prefer Buffer.from(str, 'base64') over atob(). When working with JWTs, remember that decoding is not verification — always validate tokens on the server. For the encoding counterpart, see how to encode a string in base64 in JavaScript. For related string operations, see how to convert a string to an array in JavaScript.



