Skip to main content
RapidDev - Software Development Agency
retool-tutorial

How to Manage Sessions in Retool Applications

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.

What you'll learn

  • Persist user preferences across page refreshes using window.localStorage in JS Queries
  • Read current_user session properties: email, fullName, id, and group memberships
  • Detect session expiry and redirect users to re-authenticate
  • Clear session data on logout using a JS Query that clears localStorage keys
  • Understand the lifecycle of Temporary State (resets on refresh) vs localStorage (persists)
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner7 min read15-20 minRetool Cloud and Self-hostedLast updated March 2026RapidDev Engineering Team
TL;DR

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.

Quick facts about this guide
FactValue
ToolRetool
DifficultyBeginner
Time required15-20 min
CompatibilityRetool Cloud and Self-hosted
Last updatedMarch 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

1

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).

2

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.

typescript
1// JS Query: saveUserPreferences
2// Run on change of relevant filter components
3
4const userId = current_user.email.replace('@', '_').replace('.', '_');
5
6const preferences = {
7 language: languageSelector.value,
8 dateRange: [
9 dateRangePicker1.startValue,
10 dateRangePicker1.endValue,
11 ],
12 pageSize: pageSizeSelect.value,
13 sidebarCollapsed: sidebarState.value,
14};
15
16window.localStorage.setItem(
17 `retool_prefs_${userId}`,
18 JSON.stringify(preferences)
19);
20
21console.log('Preferences saved:', preferences);

Expected result: User preferences are saved to localStorage under a user-specific key.

3

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.

typescript
1// JS Query: loadUserPreferences
2// Set to 'Run on page load'
3
4const userId = current_user.email.replace('@', '_').replace('.', '_');
5const stored = window.localStorage.getItem(`retool_prefs_${userId}`);
6
7if (!stored) {
8 // No saved preferences — use defaults
9 return;
10}
11
12try {
13 const prefs = JSON.parse(stored);
14
15 // Restore each preference
16 // Note: setValue is async — don't read the value immediately after
17 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 differently
27 // — they can't be set via setValue() on the component directly
28
29 console.log('Preferences restored:', prefs);
30} catch (err) {
31 // Corrupted localStorage — clear and start fresh
32 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.

4

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.

typescript
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);
5
6// In {{ }} expressions:
7// current_user.email — 'alice@company.com'
8// current_user.fullName — 'Alice Smith'
9// current_user.groups.includes('Managers') — true/false
10// current_user.groups[0] — first group name
11
12// 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.

5

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.

typescript
1// JS Query: handleLogout
2// Triggered by 'Logout' button click
3
4const userId = current_user.email.replace('@', '_').replace('.', '_');
5
6// Clear app-specific localStorage keys for this user
7const keysToRemove = [
8 `retool_prefs_${userId}`,
9 `retool_cache_${userId}`,
10 `retool_draft_${userId}`,
11];
12
13keysToRemove.forEach(key => window.localStorage.removeItem(key));
14
15// Redirect to Retool logout
16window.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

JS Query: sessionManager
1// JS Query: sessionManager
2// A utility that handles all session/localStorage operations
3// Call specific functions from other JS Queries
4
5const namespace = `retool_${current_user.id}`;
6
7// Load a preference with a default fallback
8const 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};
16
17// Save a preference
18const setPreference = (key, value) => {
19 window.localStorage.setItem(`${namespace}_${key}`, JSON.stringify(value));
20};
21
22// Clear all app preferences for this user
23const 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};
31
32// Example usage:
33// const lang = getPreference('language', 'en');
34// setPreference('lastVisitedPage', 'orders');
35// clearAll(); // On logout
36
37// Return the interface
38return { 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.

ChatGPT Prompt

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.

Retool Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Learning is great. Shipping is faster with help.

Our engineers have built 600+ apps on Retool and the tools around it. If your project needs to be live sooner than your learning curve allows — book a free consultation.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.