Retool's Temporary State resets on every page refresh — it is session-scoped within a single app load. For cross-session persistence (user preferences, saved filters, last-viewed record), use window.localStorage in JS Queries. Retool manages the actual auth session — you don't build login flows. Session timeout is configured in Settings → Authentication.
| Fact | Value |
|---|---|
| Tool | Retool |
| Difficulty | Beginner |
| Time required | 15-20 min |
| Compatibility | Retool Cloud and Self-hosted |
| Last updated | March 2026 |
Session State in Retool: What Persists and What Doesn't
Understanding state persistence in Retool requires distinguishing three layers: (1) Retool's auth session — managed by Retool, persists until token expiry or logout, not accessible to app code. (2) Temporary State variables — available within a single app load, reset on page refresh. (3) localStorage — browser-stored key-value pairs that persist across page refreshes and browser sessions.
For most apps, Temporary State is sufficient. But for user preferences (selected language, saved table filters, dashboard date range) that should survive page refreshes, localStorage is the right tool.
This tutorial covers localStorage patterns in Retool, session property access via current_user, and session cleanup on logout.
Prerequisites
- A Retool app with user-configurable settings that should persist across refreshes
- Understanding of Retool Temporary State variables
- Basic JavaScript knowledge (localStorage API)
Step-by-step guide
Understand Temporary State vs localStorage lifecycle
Temporary State variables in Retool are initialized with their default values every time the page loads. A user selects a date range filter, then refreshes the page — the filter resets to its default. This is by design for most app state, but it creates poor UX for preferences that should persist. localStorage survives page refreshes, browser restarts (until cleared), and even Retool session refreshes. Access it in JS Queries using window.localStorage.setItem(key, value) and window.localStorage.getItem(key). The data is stored in the user's browser, not on Retool's servers — each user has their own localStorage, making it suitable for per-user preferences.
Expected result: Understanding of when to use Temporary State (short-lived, session-scoped) vs localStorage (persistent, cross-session).
Save user preferences to localStorage
Create a JS Query that runs whenever the user changes a preference (e.g., language, theme, dashboard date range). Call window.localStorage.setItem() with a namespaced key to avoid collisions with other apps. Use the user's email as part of the key to keep preferences per-user even on shared devices.
1// JS Query: saveUserPreferences2// Run on change of relevant filter components34const userId = current_user.email.replace('@', '_').replace('.', '_');56const preferences = {7 language: languageSelector.value,8 dateRange: [9 dateRangePicker1.startValue,10 dateRangePicker1.endValue,11 ],12 pageSize: pageSizeSelect.value,13 sidebarCollapsed: sidebarState.value,14};1516window.localStorage.setItem(17 `retool_prefs_${userId}`,18 JSON.stringify(preferences)19);2021console.log('Preferences saved:', preferences);Expected result: User preferences are saved to localStorage under a user-specific key.
Load preferences from localStorage on page load
Create a JS Query named 'loadUserPreferences' set to 'Run on page load'. This query reads from localStorage and updates Temporary State variables with the saved values. This restores the user's previous preferences when they return to the app. Important: setValue() is async — call each setValue() with await and don't read the value immediately after.
1// JS Query: loadUserPreferences2// Set to 'Run on page load'34const userId = current_user.email.replace('@', '_').replace('.', '_');5const stored = window.localStorage.getItem(`retool_prefs_${userId}`);67if (!stored) {8 // No saved preferences — use defaults9 return;10}1112try {13 const prefs = JSON.parse(stored);1415 // Restore each preference16 // Note: setValue is async — don't read the value immediately after17 if (prefs.language) {18 await currentLang.setValue(prefs.language);19 }20 if (prefs.pageSize) {21 await pageSizeState.setValue(prefs.pageSize);22 }23 if (prefs.sidebarCollapsed !== undefined) {24 await sidebarState.setValue(prefs.sidebarCollapsed);25 }26 // Note: dateRangePicker default values are set differently27 // — they can't be set via setValue() on the component directly2829 console.log('Preferences restored:', prefs);30} catch (err) {31 // Corrupted localStorage — clear and start fresh32 window.localStorage.removeItem(`retool_prefs_${userId}`);33 console.log('Failed to parse preferences, cleared storage');34}Expected result: On page load, previously saved preferences are restored from localStorage.
Access current_user session properties
The current_user object is always available in {{ }} expressions and in JS Queries. Its key properties for session management: current_user.email (unique identifier), current_user.id (Retool internal ID), current_user.fullName, current_user.firstName, current_user.lastName, current_user.groups (array of group names). These values are populated from Retool's authentication session and cannot be modified by the app.
1// Access current_user in JS Queries:2console.log('Logged in as:', current_user.email);3console.log('Groups:', current_user.groups.join(', '));4console.log('User ID:', current_user.id);56// In {{ }} expressions:7// current_user.email — 'alice@company.com'8// current_user.fullName — 'Alice Smith'9// current_user.groups.includes('Managers') — true/false10// current_user.groups[0] — first group name1112// Use in SQL to filter by current user:13// WHERE assigned_to = {{ current_user.email }}Expected result: App can access and display current user identity properties from the authenticated session.
Clear session data on logout
If your app stores sensitive data in localStorage (like cached API responses or user-specific data), you should clear it when the user logs out. Add a 'Logout' button to your app. Its click handler runs a JS Query that clears app-specific localStorage keys before redirecting to the Retool logout URL. Do not clear all localStorage (it may contain other non-Retool data) — only clear your app's namespaced keys.
1// JS Query: handleLogout2// Triggered by 'Logout' button click34const userId = current_user.email.replace('@', '_').replace('.', '_');56// Clear app-specific localStorage keys for this user7const keysToRemove = [8 `retool_prefs_${userId}`,9 `retool_cache_${userId}`,10 `retool_draft_${userId}`,11];1213keysToRemove.forEach(key => window.localStorage.removeItem(key));1415// Redirect to Retool logout16window.location.href = '/auth/logout';17// For custom domain: window.location.href = 'https://yourcompany.retool.com/auth/logout';Expected result: Clicking Logout clears user-specific localStorage and redirects to the Retool logout page.
Complete working example
1// JS Query: sessionManager2// A utility that handles all session/localStorage operations3// Call specific functions from other JS Queries45const namespace = `retool_${current_user.id}`;67// Load a preference with a default fallback8const getPreference = (key, defaultValue = null) => {9 try {10 const stored = window.localStorage.getItem(`${namespace}_${key}`);11 return stored !== null ? JSON.parse(stored) : defaultValue;12 } catch {13 return defaultValue;14 }15};1617// Save a preference18const setPreference = (key, value) => {19 window.localStorage.setItem(`${namespace}_${key}`, JSON.stringify(value));20};2122// Clear all app preferences for this user23const clearAll = () => {24 const keysToRemove = [];25 for (let i = 0; i < window.localStorage.length; i++) {26 const k = window.localStorage.key(i);27 if (k && k.startsWith(namespace)) keysToRemove.push(k);28 }29 keysToRemove.forEach(k => window.localStorage.removeItem(k));30};3132// Example usage:33// const lang = getPreference('language', 'en');34// setPreference('lastVisitedPage', 'orders');35// clearAll(); // On logout3637// Return the interface38return { getPreference, setPreference, clearAll };Common mistakes
Why it's a problem: Using Temporary State to store user preferences, then wondering why they reset after each page refresh
How to avoid: Temporary State is session-scoped and resets on refresh. Use localStorage for preferences that should survive page refreshes and browser restarts.
Why it's a problem: Reading localStorage in a JS Query and immediately setting Temporary State with setValue(), then reading the state
How to avoid: setValue() is async — reading the Temporary State variable immediately after setValue() in the same JS Query function will return the old value. Load preferences on page load and let the reactive bindings update the UI naturally.
Why it's a problem: Storing full query result datasets in localStorage for caching
How to avoid: localStorage has a 5-10MB limit. Storing large datasets causes localStorage quota exceeded errors that silently fail or break other storage. Use Retool's built-in query caching (Advanced tab → Cache the results) for data caching instead.
Best practices
- Namespace all localStorage keys with a user identifier to prevent cross-user data leakage on shared devices
- Store only non-sensitive preferences in localStorage — never API tokens, personal data, or query results
- Always wrap localStorage reads in try/catch — corrupted JSON will throw and break your page load logic
- Clear localStorage keys on logout for apps where users share devices
- For preferences that don't need to persist across browsers or devices, Temporary State is simpler and more appropriate
- Check localStorage size before storing large objects — browsers typically allow 5-10MB per origin
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to implement user preference persistence in a Retool app. Users configure: (1) a language selection stored in Temporary State 'currentLang', (2) a page size preference for tables, (3) sidebar collapsed state. These should survive page refreshes. Write: a 'loadUserPreferences' JS Query (run on page load) that reads from localStorage using current_user.id for namespacing, a 'saveUserPreferences' JS Query called on each preference change, and a 'handleLogout' query that clears localStorage and redirects to /auth/logout.
Create Retool JS Queries for session management: loadUserPreferences (run on page load) reads window.localStorage.getItem('retool_prefs_' + current_user.id) and calls await currentLang.setValue(prefs.language). saveUserPreferences writes JSON.stringify({language: languageSelector.value}) to localStorage. handleLogout calls localStorage.removeItem and window.location.href = '/auth/logout'. Include try/catch for corrupted localStorage.
Frequently asked questions
Does Retool have a built-in session timeout mechanism?
Yes — Retool sessions timeout based on your organization's settings under Settings → Authentication → Session duration. On Retool Cloud the default is 24 hours. For self-hosted, this is configurable. When a session expires, users are redirected to the login page on their next request. Apps handle this gracefully if they use query error handlers for 401 responses.
What is the difference between Temporary State and global variables in Retool?
Temporary State variables are scoped to a single page within an app — they reset on refresh and are not accessible from other pages. Global variables (in multipage apps) persist across page navigation within the app but still reset on refresh. localStorage persists across refreshes and browser sessions. Choose based on how long you need the data to survive.
Can I store the current user's data in a database table for cross-device preference sync?
Yes — create a user_preferences table with columns (user_email, preference_key, preference_value). On page load, run a SELECT query for {{ current_user.email }} to load preferences. On change, run an UPSERT. This gives cross-device sync but requires database round-trips versus the instant localStorage approach.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation