# How to Set Up Data Caching in Retool

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

## TL;DR

To set up query caching in Retool: open the query editor → Advanced tab → toggle 'Cache the results' ON → set TTL in seconds. Start with slow aggregate queries (>500ms) and reference data tables. Use the Debug Panel Queries tab to confirm cache hits (look for 'served from cache' in the query metadata). Always pair caching with invalidateCache() calls after writes.

## A Practical Setup Walkthrough for Retool Query Caching

Caching in Retool is configured at the individual query level — each query independently decides whether to cache its results and for how long. There is no global cache policy, which means you need to evaluate each query and apply caching selectively.

This tutorial is the setup walkthrough companion to the 'How to use caching mechanisms in Retool' conceptual guide. It focuses on the practical decisions: which queries to cache, what TTL to choose, how to verify the cache is working, and how to plan your invalidation strategy.

The primary audience is Retool developers who know they have slow queries and want a concrete process for identifying cache candidates and configuring them correctly.

## Before you start

- At least one Retool app with queries taking more than 300ms to execute
- Access to the query editor and Advanced settings tab
- Understanding of your data's change frequency (how often does the underlying data update?)
- Familiarity with the Debug Panel in Retool

## Step-by-step guide

### 1. Identify cache candidates using Debug Panel timing

Before enabling caching, identify which queries are slowest using the Debug Panel. Click the bug icon to open the Debug Panel. Switch to the Queries tab. Run your app and observe query execution times in the waterfall. Queries taking more than 300ms that run frequently are the best cache candidates. Sort the list by duration — the longest-running queries at the top of the list are your highest-impact cache opportunities.

**Expected result:** List of queries sorted by execution time. Queries over 300ms are identified as cache candidates.

### 2. Assess data change frequency for each candidate

For each slow query identified, ask: 'How often does this data change, and how stale is too stale?' Create a simple TTL selection guide: data that changes real-time (order status, inventory) → no cache. Data that changes hourly (daily aggregates, reports) → 5-15 minute TTL. Data that changes daily (monthly summaries, customer stats) → 1-hour TTL. Reference data (lookup tables, country lists) → 24-hour TTL. Match the TTL to the acceptable staleness window for your business use case.

**Expected result:** Each candidate query has an assessed TTL value based on data change frequency.

### 3. Configure caching in the query Advanced tab

Open the slow query identified in Step 1. Click the 'Advanced' tab in the query editor. Scroll to find 'Cache the results' toggle — enable it. In the 'Cache TTL (seconds)' field, enter the TTL you determined in Step 2. For a daily summary query, enter 300 (5 minutes). For a reference data query, enter 3600 (1 hour). Save the query. The cache takes effect on the next run — the first run after enabling caching will always execute against the database and populate the cache.

```
-- Example query with caching enabled
-- Query name: getProductCatalog
-- Advanced tab: Cache the results = ON, TTL = 3600 (1 hour)

SELECT
  id,
  name,
  sku,
  category,
  unit_price,
  is_active
FROM products
WHERE is_active = true
ORDER BY category, name;
-- This query rarely changes (product catalog updates are infrequent)
-- 1-hour cache drastically reduces load without meaningful staleness
```

**Expected result:** Caching is configured on the query. Next run populates the cache; subsequent runs within TTL are served from cache.

### 4. Verify cache is working using Debug Panel

Run your app again. Open the Debug Panel → Queries tab. Find your cached query in the list. After the first run (which populates the cache), trigger the query again. Observe that the second execution is near-instant (< 10ms). In the query detail, you should see metadata indicating 'served from cache'. You can also check programmatically with {{ queryName.servedFromCache }} — it returns true when the result came from cache.

**Expected result:** Second and subsequent query runs within the TTL window complete in under 10ms.

### 5. Set up cache invalidation for write operations

Identify all write queries (INSERT, UPDATE, DELETE) that affect the same data as your cached queries. For each write query, add cache invalidation to its success handler. The cleanest approach is a dedicated JS Query that runs all invalidations and refreshes after any write. Wire this query to the 'On success' event handler of each write query.

```
// JS Query: invalidateDashboardCache
// Triggered by write operation success events
// Clears and refreshes all related cached queries

await Promise.all([
  getProductCatalog.invalidateCache(),
  getDailySummary.invalidateCache(),
  getCategoryStats.invalidateCache(),
]);

// Immediately re-populate cache with fresh data
await Promise.all([
  getProductCatalog.trigger(),
  getDailySummary.trigger(),
  getCategoryStats.trigger(),
]);

utils.showNotification({
  title: 'Saved and refreshed',
  notificationType: 'success',
});
```

**Expected result:** After any write operation, cached queries are invalidated and immediately re-run to populate fresh cache entries.

### 6. Monitor cache effectiveness over time

Review the Debug Panel Performance tab weekly to confirm caching is reducing query execution times. If a query's average execution time is consistently low after enabling caching, the cache is working. If it's still slow, check whether the cache key is changing too frequently (different parameter values creating many unique cache entries) or whether the TTL is set too short. Adjust TTL values based on observed usage patterns.

**Expected result:** Dashboard shows reduced average query execution times. Database load decreases as cache hit rate increases.

## Complete code example

File: `JS Query: invalidateDashboardCache`

```javascript
// JS Query: invalidateDashboardCache
// Use as the 'On success' handler for all write queries
// Invalidates and refreshes all dashboard cached queries

// Define which cached queries need invalidation
const cachedQueries = [
  { query: getProductCatalog, name: 'Product catalog' },
  { query: getDailySummary, name: 'Daily summary' },
  { query: getCategoryStats, name: 'Category stats' },
];

try {
  // Step 1: Invalidate all cached entries simultaneously
  await Promise.all(cachedQueries.map(({ query }) => query.invalidateCache()));

  // Step 2: Re-run all queries to warm up the cache
  await Promise.all(cachedQueries.map(({ query }) => query.trigger()));

  utils.showNotification({
    title: 'Dashboard refreshed',
    description: `${cachedQueries.length} cached queries updated.`,
    notificationType: 'success',
    duration: 3,
  });
} catch (err) {
  utils.showNotification({
    title: 'Cache refresh failed',
    description: err.message,
    notificationType: 'error',
  });
}
```

## Common mistakes

- **Caching a query with user-specific parameters (like {{ retoolContext.currentUser.email }}) expecting per-user caches** — undefined Fix: Retool uses a single shared cache — different users with the same parameter values share a cache entry. Only cache queries returning data that should be identical for all users.
- **Enabling caching without planning cache invalidation, leading to stale data after writes** — undefined Fix: For every cached read query, identify all write queries that modify the same data, and add invalidateCache() calls to those write queries' success handlers.
- **Setting TTL to 0 or 1 second thinking this creates a 'minimal' cache** — undefined Fix: A TTL of 0 or 1 second effectively disables caching since it expires almost immediately. Either use a meaningful TTL (minimum 60 seconds) or disable caching entirely.

## Best practices

- Start with your 5 slowest queries and enable caching only on those — don't cache everything blindly
- Choose TTL values conservatively: shorter TTLs are safer, longer TTLs give more performance benefit
- Create a central 'invalidateDashboardCache' JS Query that handles all cache invalidations after writes
- Document which queries are cached and their TTLs in the query description field for team awareness
- Test cache behavior in your staging environment before enabling in production
- Re-evaluate cache TTLs quarterly — data change patterns shift as applications evolve

## Frequently asked questions

### Is there a way to see all cached queries in a Retool app in one place?

There is no central cache management UI in Retool. You need to check each query individually in its Advanced tab. For larger teams, document cached queries in a shared resource (Notion, Confluence) and include the TTL and invalidation strategy for each.

### Does caching affect query execution in preview mode vs published apps?

Caching works in both preview and published modes. However, the cache is stored separately — a cache populated in preview mode does not affect the published app's cache. This is intentional, allowing you to test cache behavior safely.

### Can I programmatically warm up the cache on page load for all cached queries?

Yes — set all your cached queries to 'Run on page load'. On first page load, they execute against the database and populate the cache. Subsequent users who load the same page within the TTL window receive cached results immediately. This is the standard cache warming pattern in Retool.

---

Source: https://www.rapidevelopers.com/retool-tutorial/how-to-set-up-data-caching-in-retool
© RapidDev — https://www.rapidevelopers.com/retool-tutorial/how-to-set-up-data-caching-in-retool
