# How to Manage Sessions in Retool Applications

- Tool: Retool
- Difficulty: Beginner
- Time required: 15-20 min
- Compatibility: Retool Cloud and Self-hosted
- Last updated: March 2026

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

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

## Before you start

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

```
// JS Query: saveUserPreferences
// Run on change of relevant filter components

const userId = current_user.email.replace('@', '_').replace('.', '_');

const preferences = {
  language: languageSelector.value,
  dateRange: [
    dateRangePicker1.startValue,
    dateRangePicker1.endValue,
  ],
  pageSize: pageSizeSelect.value,
  sidebarCollapsed: sidebarState.value,
};

window.localStorage.setItem(
  `retool_prefs_${userId}`,
  JSON.stringify(preferences)
);

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

```
// JS Query: loadUserPreferences
// Set to 'Run on page load'

const userId = current_user.email.replace('@', '_').replace('.', '_');
const stored = window.localStorage.getItem(`retool_prefs_${userId}`);

if (!stored) {
  // No saved preferences — use defaults
  return;
}

try {
  const prefs = JSON.parse(stored);

  // Restore each preference
  // Note: setValue is async — don't read the value immediately after
  if (prefs.language) {
    await currentLang.setValue(prefs.language);
  }
  if (prefs.pageSize) {
    await pageSizeState.setValue(prefs.pageSize);
  }
  if (prefs.sidebarCollapsed !== undefined) {
    await sidebarState.setValue(prefs.sidebarCollapsed);
  }
  // Note: dateRangePicker default values are set differently
  // — they can't be set via setValue() on the component directly

  console.log('Preferences restored:', prefs);
} catch (err) {
  // Corrupted localStorage — clear and start fresh
  window.localStorage.removeItem(`retool_prefs_${userId}`);
  console.log('Failed to parse preferences, cleared storage');
}
```

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

```
// Access current_user in JS Queries:
console.log('Logged in as:', current_user.email);
console.log('Groups:', current_user.groups.join(', '));
console.log('User ID:', current_user.id);

// In {{ }} expressions:
// current_user.email — 'alice@company.com'
// current_user.fullName — 'Alice Smith'
// current_user.groups.includes('Managers') — true/false
// current_user.groups[0] — first group name

// Use in SQL to filter by current user:
// 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.

```
// JS Query: handleLogout
// Triggered by 'Logout' button click

const userId = current_user.email.replace('@', '_').replace('.', '_');

// Clear app-specific localStorage keys for this user
const keysToRemove = [
  `retool_prefs_${userId}`,
  `retool_cache_${userId}`,
  `retool_draft_${userId}`,
];

keysToRemove.forEach(key => window.localStorage.removeItem(key));

// Redirect to Retool logout
window.location.href = '/auth/logout';
// 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 code example

File: `JS Query: sessionManager`

```javascript
// JS Query: sessionManager
// A utility that handles all session/localStorage operations
// Call specific functions from other JS Queries

const namespace = `retool_${current_user.id}`;

// Load a preference with a default fallback
const getPreference = (key, defaultValue = null) => {
  try {
    const stored = window.localStorage.getItem(`${namespace}_${key}`);
    return stored !== null ? JSON.parse(stored) : defaultValue;
  } catch {
    return defaultValue;
  }
};

// Save a preference
const setPreference = (key, value) => {
  window.localStorage.setItem(`${namespace}_${key}`, JSON.stringify(value));
};

// Clear all app preferences for this user
const clearAll = () => {
  const keysToRemove = [];
  for (let i = 0; i < window.localStorage.length; i++) {
    const k = window.localStorage.key(i);
    if (k && k.startsWith(namespace)) keysToRemove.push(k);
  }
  keysToRemove.forEach(k => window.localStorage.removeItem(k));
};

// Example usage:
// const lang = getPreference('language', 'en');
// setPreference('lastVisitedPage', 'orders');
// clearAll(); // On logout

// Return the interface
return { getPreference, setPreference, clearAll };
```

## Common mistakes

- **Using Temporary State to store user preferences, then wondering why they reset after each page refresh** — undefined Fix: Temporary State is session-scoped and resets on refresh. Use localStorage for preferences that should survive page refreshes and browser restarts.
- **Reading localStorage in a JS Query and immediately setting Temporary State with setValue(), then reading the state** — undefined Fix: 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.
- **Storing full query result datasets in localStorage for caching** — undefined Fix: 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

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

---

Source: https://www.rapidevelopers.com/retool-tutorial/how-to-manage-sessions-in-retool-applications
© RapidDev — https://www.rapidevelopers.com/retool-tutorial/how-to-manage-sessions-in-retool-applications
