Documentation Index
Fetch the complete documentation index at: https://developers.hubspot.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
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 Case | Alternative | Purpose |
|---|
| Temporary session data | React state (useState, useReducer) | Store component state |
| Persistent user data | hubspot.fetch() + backend API | Store data across sessions |
| Complex app state | React Context + useReducer | Manage shared state |
| API response caching | React state with custom hooks | Cache 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>
));