diff --git a/Sources/XXMessengerClient/Utils/Stored.swift b/Sources/XXMessengerClient/Utils/Stored.swift
index 052d638705c6dce4a619fbe18d0d55cf2159733c..ab0de7e1db138d121b2f4a9228f6bb61900d84c4 100644
--- a/Sources/XXMessengerClient/Utils/Stored.swift
+++ b/Sources/XXMessengerClient/Utils/Stored.swift
@@ -1,3 +1,4 @@
+import Foundation
 import XCTestDynamicOverlay
 
 public struct Stored<Value> {
@@ -31,6 +32,26 @@ private final class Memory<Value> {
   var value: Value
 }
 
+extension Stored {
+  public static func userDefaults<T>(
+    key: String,
+    userDefaults: UserDefaults = .standard
+  ) -> Stored<T?> where T: Codable {
+    let encoder = JSONEncoder()
+    let decoder = JSONDecoder()
+    return Stored<T?>(
+      get: {
+        guard let data = userDefaults.data(forKey: key) else { return nil }
+        return try? decoder.decode(T.self, from: data)
+      },
+      set: { newValue in
+        let data = try? encoder.encode(newValue)
+        userDefaults.set(data, forKey: key)
+      }
+    )
+  }
+}
+
 extension Stored {
   public struct MissingValueError: Error, Equatable {
     public init(typeDescription: String) {
diff --git a/Tests/XXMessengerClientTests/Utils/StoredTests.swift b/Tests/XXMessengerClientTests/Utils/StoredTests.swift
index 154f61967627500def1526f2944aeb074792c748..834097ef9e870d7b9e297d3131c246c6a068c48a 100644
--- a/Tests/XXMessengerClientTests/Utils/StoredTests.swift
+++ b/Tests/XXMessengerClientTests/Utils/StoredTests.swift
@@ -1,8 +1,9 @@
+import CustomDump
 import XCTest
 @testable import XXMessengerClient
 
 final class StoredTests: XCTestCase {
-  func testInMemory() throws {
+  func testInMemory() {
     let stored: Stored<String?> = .inMemory()
 
     XCTAssertNil(stored())
@@ -15,4 +16,35 @@ final class StoredTests: XCTestCase {
 
     XCTAssertNil(stored())
   }
+
+  func testUserDefaults() {
+    struct Value: Equatable, Codable {
+      var id = UUID()
+    }
+
+    let key = "key"
+    let userDefaults = UserDefaults(suiteName: "XXMessengerClient_StoredTests")!
+    userDefaults.removeObject(forKey: key)
+    let stored: Stored<Value?> = .userDefaults(
+      key: key,
+      userDefaults: userDefaults
+    )
+
+    XCTAssertNoDifference(stored.get(), nil)
+
+    let value = Value()
+    stored.set(value)
+
+    XCTAssertNoDifference(
+      userDefaults.data(forKey: key).map { data in
+        try? JSONDecoder().decode(Value.self, from: data)
+      },
+      value
+    )
+    XCTAssertNoDifference(stored.get(), value)
+
+    userDefaults.set(nil, forKey: key)
+
+    XCTAssertNoDifference(stored.get(), nil)
+  }
 }