Mastering Custom Implementations of localStorage and sessionStorage
In front-end development, localStorage and sessionStorage are widely used for storing client-side data.
However, in more advanced applications, you may need custom logic on top of their native behavior—such as encryption, logging, key protection, analytics, or multi-layered validation.
This guide shows how to elegantly rewrite storage methods without breaking native functionality, using proxies, hooks, and encapsulated patterns.
Why Override localStorage and sessionStorage?
You might need custom wrappers when:
- Business Logic — Special handling for certain keys (e.g., formatting, validation, encryption).
- Global Monitoring — Log every storage access for analytics or debugging.
- Security — Protect sensitive keys from modification or unauthorized reads.
- Cross‑Team Governance — Enforce consistent storage behavior across large applications.
Core Idea: Wrap, Don’t Replace
The goal is preserving original behavior while adding extra processing.
Step 1: Save the Native Methods
const _setItem = localStorage.setItem;
const _getItem = localStorage.getItem;Step 2: Inject Custom Logic via Wrapper
localStorage.setItem = function (key, value) {
if (key === "protectedKey") return; // Prevent modifications
_setItem.call(this, key, value);
};
localStorage.getItem = function (key) {
return key === "protectedKey" ? "Access denied" : _getItem.call(this, key);
};This pattern keeps native functionality intact but adds rules for specific keys.
Flexible Hook-Based Proxy System
A more scalable approach uses configurable hooks.
function proxyStorage(storage, config = {}) {
const {
beforeSetItem = (k, v) => [k, v],
afterGetItem = (k, v) => v,
} = config;
const _setItem = storage.setItem;
const _getItem = storage.getItem;
storage.setItem = function (key, value) {
const [newKey, newValue] = beforeSetItem(key, value);
_setItem.call(this, newKey, newValue);
};
storage.getItem = function (key) {
const value = _getItem.call(this, key);
return afterGetItem(key, value);
};
}Example Use Cases
1. Encrypt Storage Data
proxyStorage(localStorage, {
beforeSetItem: (key, value) => [key, btoa(value)],
afterGetItem: (key, value) => (value ? atob(value) : value),
});2. Log Every Access
proxyStorage(sessionStorage, {
beforeSetItem: (k, v) => {
console.log("SET", k, v);
return [k, v];
},
afterGetItem: (k, v) => {
console.log("GET", k, v);
return v;
},
});3. Prevent Access to Sensitive Keys
proxyStorage(localStorage, {
afterGetItem: (key, value) =>
key === "secret" ? null : value,
});Encapsulation with a Singleton Class
This makes the proxy reusable and organized.
class StorageProxy {
constructor(storage, config) {
if (StorageProxy.instance) return StorageProxy.instance;
this.init(storage, config);
StorageProxy.instance = this;
}
init(storage, config) {
this.storage = storage;
this.originalSetItem = storage.setItem;
this.originalGetItem = storage.getItem;
this.config = config;
this.proxy();
}
proxy() {
const {
storage,
config: { beforeSetItem, afterGetItem },
} = this;
storage.setItem = (key, value) => {
const [k, v] = beforeSetItem(key, value);
this.originalSetItem.call(storage, k, v);
};
storage.getItem = key => {
const value = this.originalGetItem.call(storage, key);
return afterGetItem(key, value);
};
}
unproxy() {
this.storage.setItem = this.originalSetItem;
this.storage.getItem = this.originalGetItem;
}
}Usage
const customStorage = new StorageProxy(localStorage, {
beforeSetItem: (k, v) => [k, v.toUpperCase()],
afterGetItem: (k, v) => v,
});Summary
Rewriting localStorage and sessionStorage opens the door to:
- Custom behavior
- Security enhancements
- Access monitoring
- Data validation
- Encryption and transformation
With wrappers, hooks, and patterns like singletons, you can tailor storage APIs while preserving the native browser functionality — making your front‑end architecture more secure, maintainable, and adaptable.