Customizing localStorage and sessionStorage with Proxy Methods in Frontend Development
This article explains how to override the native localStorage and sessionStorage APIs in JavaScript, providing a flexible proxy layer that supports business‑specific logic, global monitoring, data protection, encryption, and configurable hooks, with practical Vue 3 integration examples.
In modern front‑end projects, the native localStorage and sessionStorage APIs often cannot satisfy special business requirements such as custom encryption, validation, default value filling, global operation monitoring, or protection against accidental key modifications.
Technical Solution
Core Idea
Save the original setItem and getItem methods, then rewrite them on window (or on the specific storage object) to inject custom logic while still being able to call the original implementations via .call(this, …) to preserve the correct context.
const _setItem = localStorage.setItem;
localStorage.setItem = function (...args) {
// custom logic....
// finally call _setItem
};When using a Vue project, the rewrite should happen before the Vue instance is created so that all components use the proxied methods.
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
// proxy localStorage and sessionStorage
function proxyStorage(storage) {
// ...
}
proxyStorage(localStorage);
proxyStorage(sessionStorage);
const app = createApp(App);
app.use(router).use(store);
app.mount('#app');Proxy Storage Method – Simple Intercept
A basic proxy can intercept setItem and getItem for a given storage object, allowing custom logic for specific keys.
function proxyStorage(storage) {
const originalSetItem = storage.setItem;
const originalGetItem = storage.getItem;
storage.setItem = function (key, value) {
// custom logic, e.g., reject "system" key
if (key === 'system') {
return;
}
originalSetItem.call(this, key, value);
};
storage.getItem = function (key) {
if (key === 'system') {
return "Sorry, you have no permission to read user info";
}
return originalGetItem.call(this, key);
};
}
proxyStorage(localStorage);
proxyStorage(sessionStorage);Using .call(this, …) is necessary because the original methods are prototype functions that lose their this binding when invoked directly.
originalSetItem.call(this, key, value);Flexible Configuration with Hooks
To support more scenarios, the proxy can accept a configuration object that provides beforeSetItem and afterGetItem hooks.
function proxyStorage(storage, config = {}) {
const originalSetItem = storage.setItem;
const originalGetItem = storage.getItem;
const beforeSetItem = config.beforeSetItem || ((key, value) => [key, value]);
const afterGetItem = config.afterGetItem || ((key, value) => value);
storage.setItem = function (key, value) {
const [newKey, newValue] = beforeSetItem(key, value) || [key, value];
if (newKey !== undefined && newValue !== undefined) {
originalSetItem.call(this, newKey, newValue);
} else {
originalSetItem.call(this, key, value);
}
};
storage.getItem = function (key) {
const originalValue = originalGetItem.call(this, key);
return afterGetItem(key, originalValue);
};
}Examples of hook usage:
Example 1 – Encrypted storage : encrypt values before saving and decrypt them when reading using crypto-js .
import CryptoJS from 'crypto-js';
const secretKey = 'private-secret-key';
proxyStorage(localStorage, {
beforeSetItem: (key, value) => {
const encrypted = CryptoJS.AES.encrypt(value, secretKey).toString();
return [key, encrypted];
},
afterGetItem: (key, value) => {
try {
const bytes = CryptoJS.AES.decrypt(value, secretKey);
return bytes.toString(CryptoJS.enc.Utf8) || null;
} catch (e) {
return null;
}
},
});
localStorage.setItem('sensitiveData', 'my-secret-data');
console.log(localStorage.getItem('sensitiveData'));Example 2 – Operation monitoring : log every set and get operation.
proxyStorage(localStorage, {
beforeSetItem: (key, value) => {
console.log(`Set: key=${key}, value=${value}`);
return [key, value];
},
afterGetItem: (key, value) => {
console.log(`Get: key=${key}, value=${value}`);
return value;
},
});Example 3 – Intercept specific keys : block or modify operations on privileged keys such as admin .
proxyStorage(localStorage, {
beforeSetItem: (key, value) => {
if (key === 'admin') {
console.warn('You have no permission to operate');
return; // block
}
return [key, value];
},
afterGetItem: (key, value) => {
if (key === 'admin') {
console.warn('You have no permission to operate');
return 'error';
}
return value;
},
});Canceling the Proxy
When a page transition or other scenario requires restoring the original methods, the proxy function can return an unproxy function that reinstates the saved originals.
function proxyStorage(storage, config = {}) {
const originalSetItem = storage.setItem;
const originalGetItem = storage.getItem;
// ... (hook logic as above)
const unproxy = () => {
storage.setItem = originalSetItem;
storage.getItem = originalGetItem;
};
return unproxy;
}
const unproxy = proxyStorage(localStorage, config);
// later
unproxy();Final Encapsulation – StorageProxy Class
The complete solution can be wrapped in a singleton class to guarantee a single instance across the application.
class StorageProxy {
constructor(storage, config = {}) {
if (StorageProxy.instance) return StorageProxy.instance;
this.storage = storage;
this.config = config;
this.originalSetItem = storage.setItem;
this.originalGetItem = storage.getItem;
this.beforeSetItem = config.beforeSetItem || ((k, v) => [k, v]);
this.afterGetItem = config.afterGetItem || ((k, v) => v);
this.proxyMethods();
StorageProxy.instance = this;
}
proxyMethods() {
const { storage, beforeSetItem, afterGetItem, originalSetItem, originalGetItem } = this;
storage.setItem = function (key, value) {
const [newKey, newValue] = beforeSetItem(key, value) || [key, value];
if (newKey !== undefined && newValue !== undefined) {
originalSetItem.call(this, newKey, newValue);
}
};
storage.getItem = function (key) {
const originalValue = originalGetItem.call(this, key);
return afterGetItem(key, originalValue);
};
}
unproxy() {
const { storage, originalSetItem, originalGetItem } = this;
storage.setItem = originalSetItem;
storage.getItem = originalGetItem;
}
static getInstance(storage = localStorage, config = {}) {
if (!StorageProxy.instance) new StorageProxy(storage, config);
return StorageProxy.instance;
}
}
export default StorageProxy;In a Vue 3 project, the singleton can be created once and injected as a global property:
import { createApp } from 'vue';
import App from './App.vue';
import storageProxy from './storageProxy';
const app = createApp(App);
app.config.globalProperties.$storageProxy = storageProxy;
app.mount('#app');Conclusion
The article demonstrates how to proxy localStorage and sessionStorage by overriding setItem and getItem , offering extensible hooks for encryption, monitoring, key‑level interception, and a clean way to restore the original APIs, making the technique ready for real‑world front‑end projects.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.