The Swift Foundation Data Mental Model
Swift Base64 encoding is a method on the Data type — not a free function, not a separate library. If you have any bytes at all (a string, a file, an image, a network payload), you convert them to Data first, then call .base64EncodedString() or .base64EncodedData(). The whole API is two methods with an optional Base64EncodingOptionsparameter.
The two output methods:
base64EncodedString(options:)— returns a SwiftString. Use for JSON, headers, logging.base64EncodedData(options:)— returns aDatavalue with the Base64 bytes. Use for file writes, HTTP bodies, streams.
The options you actually need:
[](default) — single-line output, no wrapping. What 99% of use cases want..lineLength64Characters— MIME-style line wrapping at 64 chars..lineLength76Characters— RFC 2045 line wrapping at 76 chars (email attachments)..endLineWithCarriageReturn,.endLineWithLineFeed— line-ending styles.
Encoding a String — The Standard Pattern
import Foundation
let text = "Hello 世界 👋"
// Step 1: String → Data (UTF-8 is the default and standard choice)
guard let utf8Data = text.data(using: .utf8) else {
fatalError("String not UTF-8 convertible (basically never happens in Swift)")
}
// Step 2: Data → Base64 string
let encoded = utf8Data.base64EncodedString()
print(encoded) // SGVsbG8g5LiW55WMIPCfkYs=
// One-liner (safe for any valid Swift String):
let result = text.data(using: .utf8)!.base64EncodedString()
print(result)
// Decode back:
if let decodedData = Data(base64Encoded: encoded),
let decodedText = String(data: decodedData, encoding: .utf8) {
print(decodedText) // Hello 世界 👋
}Encoding a File
import Foundation
// For small files — load entire file into memory
func encodeSmallFile(path: String) throws -> String {
let url = URL(fileURLWithPath: path)
let data = try Data(contentsOf: url)
return data.base64EncodedString()
}
// For files bundled with your app
func encodeBundledFile(name: String, ext: String) -> String? {
guard let url = Bundle.main.url(forResource: name, withExtension: ext),
let data = try? Data(contentsOf: url) else {
return nil
}
return data.base64EncodedString()
}
// Mapped file — memory-efficient for large files
func encodeLargeFile(path: String) throws -> String {
let url = URL(fileURLWithPath: path)
let data = try Data(contentsOf: url, options: [.mappedIfSafe])
return data.base64EncodedString()
}Encoding a UIImage (iOS) or NSImage (macOS)
import UIKit
// UIImage → JPEG → Base64
func encodeImageJPEG(_ image: UIImage, quality: CGFloat = 0.8) -> String? {
guard let jpegData = image.jpegData(compressionQuality: quality) else {
return nil
}
return jpegData.base64EncodedString()
}
// UIImage → PNG → Base64 (lossless, larger)
func encodeImagePNG(_ image: UIImage) -> String? {
guard let pngData = image.pngData() else {
return nil
}
return pngData.base64EncodedString()
}
// Build a data URL for a <img src="..."> tag in a WKWebView
func dataURL(for image: UIImage) -> String? {
guard let jpegData = image.jpegData(compressionQuality: 0.8) else {
return nil
}
return "data:image/jpeg;base64,\(jpegData.base64EncodedString())"
}
// Decoding: Base64 string → UIImage
func decodeImage(from base64: String) -> UIImage? {
guard let data = Data(base64Encoded: base64) else { return nil }
return UIImage(data: data)
}URL-Safe Base64 (JWT-Style)
Foundation does not ship a URL-safe Base64 encoder. You post-process the standard output to make it JWT- and URL-friendly:
import Foundation
extension Data {
/// RFC 4648 §5 URL-safe encoding with padding
func base64URLEncodedString() -> String {
return base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
}
/// JWT-style: URL-safe + no padding
func base64URLEncodedStringNoPadding() -> String {
return base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
}
extension String {
/// Decode a JWT-style URL-safe Base64 string (adds padding back)
func base64URLDecoded() -> Data? {
var s = self
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
while s.count % 4 != 0 { s += "=" }
return Data(base64Encoded: s)
}
}
// Usage:
let payload = "{\"user_id\":42,\"role\":\"admin\"}".data(using: .utf8)!
let jwtPart = payload.base64URLEncodedStringNoPadding()
print(jwtPart) // eyJ1c2VyX2lkIjo0Miwicm9sZSI6ImFkbWluIn0Encoding a Codable Struct via JSON
import Foundation
struct User: Codable {
let id: Int
let email: String
let roles: [String]
let joined: String
}
func encodeUser(_ user: User) throws -> String {
let jsonData = try JSONEncoder().encode(user)
return jsonData.base64EncodedString()
}
func decodeUser(_ encoded: String) throws -> User {
guard let jsonData = Data(base64Encoded: encoded) else {
throw NSError(domain: "Base64", code: 0)
}
return try JSONDecoder().decode(User.self, from: jsonData)
}
// Usage
let user = User(id: 42, email: "[email protected]",
roles: ["admin", "editor"], joined: "2026-01-15")
let encoded = try encodeUser(user)
print(encoded)
let roundTripped = try decodeUser(encoded)
print(roundTripped)Building an HTTP Basic Auth Header in Swift
import Foundation
func basicAuthHeader(user: String, password: String) -> String {
let credentials = "\(user):\(password)"
let encoded = credentials.data(using: .utf8)!.base64EncodedString()
return "Basic \(encoded)"
}
func callProtectedAPI() async throws -> Data {
var request = URLRequest(url: URL(string: "https://api.example.com/protected")!)
request.setValue(basicAuthHeader(user: "admin", password: "secret123"),
forHTTPHeaderField: "Authorization")
let (data, _) = try await URLSession.shared.data(for: request)
return data
}Background-Thread Encoding for Large Data
import Foundation
// GCD pattern — dispatch heavy encoding off the main thread
func encodeInBackground(_ data: Data,
completion: @escaping (String) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
let encoded = data.base64EncodedString()
DispatchQueue.main.async {
completion(encoded)
}
}
}
// Modern Swift Concurrency (Swift 5.5+)
func encodeAsync(_ data: Data) async -> String {
await Task.detached(priority: .userInitiated) {
data.base64EncodedString()
}.value
}
// Usage in async context:
// let encoded = await encodeAsync(largeImageData)Common Pitfalls in Swift Base64 Code
- Force-unwrapping .data(using: .utf8)! — Safe for any valid Swift String (UTF-8 encoding never fails for Swift strings). But if you use
.asciior.utf16, unwrap fails on non-representable characters. Prefer.utf8unless you have a specific reason. - Using base64EncodedString for URLs — Standard Base64 contains
+,/, and=, all of which get percent-encoded in URLs. Use the URL-safe extension above for JWTs and query parameters. - Encoding on main thread — For images larger than 1MB, main-thread encoding blocks UI for 50-300ms depending on device. Always dispatch to a background queue for images and files.
- Forgetting padding on decode — When decoding user-supplied or JWT-style Base64 (which may lack padding), Foundation returns
nil. Add padding back with thewhile s.count % 4 != 0pattern before callingData(base64Encoded:). - Ignoring image encoding failures —
jpegData(compressionQuality:)returnsnilfor empty images or invalid contexts. Always guard unwrap and provide a fallback path (log, show error UI, or default image).
Command Line Alternative
For quick one-offs without opening Xcode, use the standard base64CLI (present on macOS by default) or a Swift script via swift:
# Standard base64 CLI on macOS
echo -n "Hello" | base64
# Output: SGVsbG8=
# Decode
echo "SGVsbG8=" | base64 -d
# Swift script (create hello.swift with these two lines)
import Foundation
print("Hello".data(using: .utf8)!.base64EncodedString())
# Run: swift hello.swiftKey Facts
- Framework:
- Foundation (built-in on iOS/macOS, import on server-side Swift)
- Method:
- Data.base64EncodedString(options:) / Data.base64EncodedData(options:)
- Decode:
- Data(base64Encoded: string) → Data? (returns nil on invalid input)
- URL-safe:
- Manual post-processing (replace + → -, / → _, strip =)
- Input type:
- Data (convert String with .data(using: .utf8))
- Output type:
- String or Data (choose based on downstream use)
- Thread safety:
- Data operations are thread-safe; dispatch large jobs to background queue
- Line wrapping:
- .lineLength64Characters or .lineLength76Characters options
Related Base64 Tools
- Base64 Encode Online — general-purpose browser encoder
- Base64 Encode in Python — Python 3 equivalent
- Base64 Encode in JavaScript — Node.js and browser
- Base64 Encode in Go — Go encoding/base64
- Base64 Encode in Java — java.util.Base64
- URL-Safe Base64 — cross-language URL encoding
- JWT Debugger — inspect JWT tokens