Frontend Development 12 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Customizing localStorage and sessionStorage with Proxy Methods in Frontend Development

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.

frontendJavaScriptproxyvueStoragelocalStoragesessionStorage
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.