Skip to main content
The no-browser-storage rule prevents usage of browser storage APIs (localStorage and sessionStorage).

Rule details

UI extensions run in a sandboxed web worker environment where browser storage APIs (localStorage and sessionStorage) are intentionally unavailable for security and isolation. This prevents extensions from accessing storage data from the host application, storing sensitive data without proper security controls, or interfering with other extensions. Storage APIs don’t exist at runtime and will throw errors if accessed. Instead, use React state for temporary data or backend APIs for persistent data.

Storage alternatives

Use the following UI extension alternatives instead of browser storage APIs:
Use CaseAlternativePurpose
Temporary session dataReact state (useState, useReducer)Store component state
Persistent user datahubspot.fetch() + backend APIStore data across sessions
Complex app stateReact Context + useReducerManage shared state
API response cachingReact state with custom hooksCache data in memory
See the next section for an example of each alternative.

Examples

Temporary data

Instead of using sessionStorage for temporary form data:
// Incorrect
function cacheApiResponse(endpoint, data) {
  sessionStorage.setItem(`cache:${endpoint}`, JSON.stringify(data));
}

const Extension = () => {
  const cached = sessionStorage.getItem('formData');
  return <Text>{cached}</Text>;
};
Use React state:
import { useState } from 'react';
import { Input, Button, Flex } from '@hubspot/ui-extensions';

const Extension = () => {
  const [formData, setFormData] = useState({ name: '', email: '' });

  return (
    <Flex direction="column" gap="small">
      <Input
        label="Name"
        value={formData.name}
        onChange={(value) => setFormData({ ...formData, name: value })}
      />
      <Input
        label="Email"
        value={formData.email}
        onChange={(value) => setFormData({ ...formData, email: value })}
      />
      <Button onClick={() => console.log(formData)}>Submit</Button>
    </Flex>
  );
};

Persistent data

Instead of using localStorage for persistent user preferences:
// Incorrect
const savePreferences = (theme) => {
  localStorage.setItem('userPrefs', JSON.stringify({ theme }));
};

const Extension = () => {
  const [theme, setTheme] = useState(() => {
    const saved = localStorage.getItem('userPrefs');
    return saved ? JSON.parse(saved).theme : 'light';
  });

  return <Text>Theme: {theme}</Text>;
};
Use hubspot.fetch() to persist data via your backend:
import { useState, useEffect } from 'react';
import { hubspot, logger } from '@hubspot/ui-extensions';
import { Button, Text, LoadingSpinner, Flex } from '@hubspot/ui-extensions';

const Extension = () => {
  const [settings, setSettings] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const loadSettings = async () => {
      try {
        const response = await hubspot.fetch('/api/user/preferences');
        const data = await response.json();
        setSettings(data);
      } catch (error) {
        logger.error('Failed to load settings', error);
      } finally {
        setLoading(false);
      }
    };

    loadSettings();
  }, []);

  const updateSetting = async (key, value) => {
    try {
      await hubspot.fetch('/api/user/preferences', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ [key]: value }),
      });
      setSettings({ ...settings, [key]: value });
    } catch (error) {
      logger.error('Failed to update setting', error);
    }
  };

  if (loading) return <LoadingSpinner />;

  return (
    <Flex direction="column" gap="small">
      <Text>Theme: {settings?.theme || 'default'}</Text>
      <Button onClick={() => updateSetting('theme', 'dark')}>
        Switch to Dark
      </Button>
    </Flex>
  );
};

Complex state management

Instead of using localStorage for app-wide state:
// Incorrect
const AppState = {
  save: (key, value) => {
    const state = JSON.parse(localStorage.getItem('appState') || '{}');
    state[key] = value;
    localStorage.setItem('appState', JSON.stringify(state));
  },
  get: (key) => {
    const state = JSON.parse(localStorage.getItem('appState') || '{}');
    return state[key];
  }
};
Use React Context with useReducer:
import { createContext, useContext, useReducer } from 'react';
import { hubspot } from '@hubspot/ui-extensions';
import { Button, Text } from '@hubspot/ui-extensions';

const AppStateContext = createContext();

const initialState = {
  cache: {},
  settings: {},
};

function stateReducer(state, action) {
  switch (action.type) {
    case 'CACHE_DATA':
      return { ...state, cache: { ...state.cache, [action.key]: action.data } };
    case 'UPDATE_SETTINGS':
      return { ...state, settings: { ...state.settings, ...action.payload } };
    default:
      return state;
  }
}

const AppStateProvider = ({ children }) => {
  const [state, dispatch] = useReducer(stateReducer, initialState);

  return (
    <AppStateContext.Provider value={{ state, dispatch }}>
      {children}
    </AppStateContext.Provider>
  );
};

const DataComponent = () => {
  const { state, dispatch } = useContext(AppStateContext);

  const cacheData = (key, data) => {
    dispatch({ type: 'CACHE_DATA', key, data });
  };

  return (
    <>
      <Text>Cached items: {Object.keys(state.cache).length}</Text>
      <Button onClick={() => cacheData('user', { name: 'John' })}>
        Cache User Data
      </Button>
    </>
  );
};

hubspot.extend(() => (
  <AppStateProvider>
    <DataComponent />
  </AppStateProvider>
));
Last modified on February 19, 2026