Ship internal tools in hours, not weeks. Real auth, users, jobs, audit logs, and cohesive UI included. Early access $249 $499 → [Get it now]

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.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to loop through a 2D array in JavaScript
How to loop through a 2D array in JavaScript

What are the three dots `...` in JavaScript do?
What are the three dots `...` in JavaScript do?

How to replace all occurrences of a string in JavaScript?
How to replace all occurrences of a string in JavaScript?

How to Remove Underline from Link in CSS
How to Remove Underline from Link in CSS

Answers by CoreUI Core Team