v4 Migration Guide
February 20, 2026 ยท View on GitHub
As expected with a major release, JWTDecode.swift v4 contains breaking changes. Please review this guide thoroughly to understand the changes required to migrate your application to v4.
Table of Contents
Swift Concurrency Support
All public types now conform to Sendable for full Swift 6 concurrency compatibility. This ensures safe usage across concurrency boundaries, actors, and async contexts.
Types Changed
JWT Protocol
The JWT protocol now requires Sendable conformance.
Before (v3):
public protocol JWT {
var header: [String: Any] { get }
var body: [String: Any] { get }
// ...
}
After (v4):
public protocol JWT: Sendable {
var header: [String: any Sendable] { get }
var body: [String: any Sendable] { get }
// ...
}
Claim Struct
The Claim struct now conforms to Sendable and uses any Sendable for value types.
Before (v3):
public struct Claim {
let value: Any?
public var rawValue: Any? { /* ... */ }
}
After (v4):
public struct Claim: Sendable {
let value: (any Sendable)?
public var rawValue: (any Sendable)? { /* ... */ }
}
Properties Changed
JWT Header and Body
If you directly access header or body properties, update the type annotations:
Before (v3):
let header: [String: Any] = jwt.header
let body: [String: Any] = jwt.body
After (v4):
let header: [String: any Sendable] = jwt.header
let body: [String: any Sendable] = jwt.body
Claim Raw Value
If you directly access rawValue, update the type annotation:
Before (v3):
let customClaim: Any? = jwt["custom"].rawValue
After (v4):
let customClaim: (any Sendable)? = jwt["custom"].rawValue
Most Common Usage (No Changes Required)
For most users, no changes are needed:
// These continue to work without changes
let jwt = try decode(jwt: token)
let email = jwt["email"].string
let userId = jwt["sub"].string
let exp = jwt.expiresAt
let isExpired = jwt.expired
New Features
Claim Data Property
A new data property is available for complex claims (arrays and dictionaries only):
// Returns Data for arrays and dictionaries
let data = jwt["custom_object"].data
// Returns nil for primitives (use .string, .integer, etc. instead)
let primitiveData = jwt["email"].data // nil
Claim Decode Method
Decode complex claims directly to Decodable types:
struct Address: Decodable {
let street: String
let city: String
}
// Decode a custom claim
let address = try jwt["address"].decode(Address.self)
// With custom decoder configuration
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let userInfo = try jwt["user_info"].decode(UserInfo.self, using: decoder)
Error Types
New Error Case
A new error case claimDecodingFailed has been added for claim decoding failures:
public enum JWTDecodeError: LocalizedError, CustomDebugStringConvertible, Sendable {
case invalidBase64URL(String)
case invalidJSON(String)
case invalidPartCount(String, Int)
case claimDecodingFailed(String) // New in v4
}
This error is thrown by the Claim.decode<T: Decodable>() method when:
- A claim is not found or has a nil value
- A claim value is a primitive type when an object/array is expected (use
.string,.integer,.boolean, etc. instead) - Claim serialization to JSON data fails
Example:
do {
let address = try jwt["address"].decode(Address.self)
} catch JWTDecodeError.claimDecodingFailed(let message) {
print("Decoding failed: \(message)")
}