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

How to Use Caching Mechanisms in Retool

Retool's query caching stores results server-side and serves them for subsequent runs with the same parameters within the TTL window. Enable it in the query's Advanced tab under 'Cache the results' and set a TTL in seconds. The cache key is based on the query's resource, SQL/config, and parameter values. Use query.invalidateCache() to bust the cache when you need fresh data.

What you'll learn

  • Understand how Retool query-level caching works and how cache keys are constructed
  • Read the query.servedFromCache property to know when cached data is being displayed
  • Call query.invalidateCache() to force a fresh fetch when data must be current
  • Understand that cached results are shared across all users running the same query with the same parameters
  • Choose the right TTL for different query types based on data freshness requirements
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced7 min read20-25 minRetool Cloud and Self-hostedLast updated March 2026RapidDev Engineering Team
TL;DR

Retool's query caching stores results server-side and serves them for subsequent runs with the same parameters within the TTL window. Enable it in the query's Advanced tab under 'Cache the results' and set a TTL in seconds. The cache key is based on the query's resource, SQL/config, and parameter values. Use query.invalidateCache() to bust the cache when you need fresh data.

Quick facts about this guide
FactValue
ToolRetool
DifficultyAdvanced
Time required20-25 min
CompatibilityRetool Cloud and Self-hosted
Last updatedMarch 2026

How Retool Query Caching Works Under the Hood

Retool's query caching is a server-side cache stored on Retool's infrastructure (for cloud) or your server (for self-hosted). When caching is enabled on a query, Retool stores the query result with a cache key derived from the resource name, query SQL/configuration, and the values of all {{ }} parameters at the time of execution.

Subsequent query runs — by any user — with matching cache keys return the stored result immediately without hitting your database. This makes caching particularly powerful for expensive aggregate queries that run frequently and don't change often (e.g., daily sales totals, static lookup tables).

The important implication: Retool's cache is shared across users. If user A runs a query and user B runs the same query within the TTL, user B gets user A's cached result. This is a feature for shared data, but a bug for user-specific queries.

Prerequisites

  • A Retool app with at least one slow or frequently-run query
  • Understanding of SQL query parameters using {{ }} syntax
  • Familiarity with the query Advanced settings tab

Step-by-step guide

1

Enable caching on a query

Open a query (e.g., a slow aggregation query named 'getDailySummary'). Click the 'Advanced' tab in the query editor. Find the 'Cache the results' toggle and enable it. A 'Cache TTL (seconds)' field appears — set the duration you want results to be cached. A TTL of 300 (5 minutes) means the query result is served from cache for up to 5 minutes after the first run. For slow aggregate queries (taking 2-5 seconds), caching dramatically reduces load — subsequent runs return in milliseconds.

typescript
1// Query: getDailySummary
2// Advanced tab settings:
3// Cache the results: ON
4// Cache TTL (seconds): 300
5
6-- The underlying SQL (expensive aggregation)
7SELECT
8 DATE_TRUNC('day', created_at) AS day,
9 COUNT(*) AS orders,
10 SUM(amount) AS revenue
11FROM orders
12WHERE created_at >= NOW() - INTERVAL '90 days'
13GROUP BY 1
14ORDER BY 1;

Expected result: Query runs slowly the first time, then returns in milliseconds for subsequent runs within the TTL.

2

Understand how cache keys work

Retool builds the cache key from three components: (1) the resource name (e.g., 'production-postgres'), (2) the query SQL/configuration template, and (3) the resolved values of all {{ }} parameters. If any parameter changes, the cache key changes and Retool makes a fresh database request. This means a query filtered by {{ select1.value }} will have separate cache entries for each distinct value of select1.value. Changing the date filter creates a new cache entry — the old TTL-valid entry is not reused.

Expected result: Understanding that different parameter values create different cache entries.

3

Check query.servedFromCache to show staleness indicators

After a query runs, check {{ queryName.servedFromCache }} — it's a boolean that is true when the result came from cache, false when fresh from the database. Use this to show a staleness indicator to users. Add a Text component near your chart or table: '{{ getDailySummary.servedFromCache ? "Cached data" : "Live data" }}'. You can also display the cache age by tracking when the query last ran using a Temporary State variable that stores Date.now() on each non-cached run.

typescript
1// Text component value for cache status indicator
2{{ getDailySummary.servedFromCache
3 ? '⚠️ Showing cached data'
4 : '✓ Live data' }}
5
6// CSS class for visual distinction
7{{ getDailySummary.servedFromCache
8 ? 'text-amber-600'
9 : 'text-green-600' }}

Expected result: A cache status indicator shows users whether they're seeing live or cached data.

4

Invalidate the cache when data must be fresh

After a write operation (INSERT, UPDATE, DELETE), the cached data is stale. Call await queryName.invalidateCache() to clear the cache entry and force the next run to fetch fresh data. Chain this in your save button's event handler: first run the update query, then invalidate the read query's cache, then trigger the read query again. This ensures users see updated data immediately after a write.

typescript
1// JS Query: saveAndRefresh
2// Triggered by 'Save Changes' button
3
4// 1. Run the update
5await updateRecord.trigger();
6
7// 2. Invalidate the stale cache
8await getDailySummary.invalidateCache();
9
10// 3. Re-run the read query to get fresh data
11await getDailySummary.trigger();
12
13utils.showNotification({
14 title: 'Saved',
15 description: 'Record updated. Dashboard refreshed.',
16 notificationType: 'success',
17});

Expected result: After saving, the dashboard shows fresh data instead of the previously cached result.

5

Never cache user-specific queries

Because the cache is shared across all users, never enable caching on queries that filter by the current user's data using {{ retoolContext.currentUser.email }} or similar. If user A runs the query and it caches, user B will see user A's data when they run the same query within the TTL. Only cache queries returning shared data visible to all users (global aggregates, reference data, system-wide summaries).

Expected result: Understanding of which queries are safe to cache and which must always run fresh.

Complete working example

JS Query: cachePolicyManager
1// JS Query: cachePolicyManager
2// A helper that manages cache invalidation across multiple cached queries
3// Run this after any write operation that affects dashboard data
4
5// List of cached queries that need invalidation after a write
6const cachedReadQueries = [
7 getDailySummary,
8 getTopProducts,
9 getCustomerStats,
10];
11
12// Invalidate all cached queries in parallel
13await Promise.all(
14 cachedReadQueries.map(q => q.invalidateCache())
15);
16
17// Re-run all queries to populate fresh cache entries
18await Promise.all(
19 cachedReadQueries.map(q => q.trigger())
20);
21
22utils.showNotification({
23 title: 'Dashboard refreshed',
24 description: 'All cached data has been updated.',
25 notificationType: 'success',
26});

Common mistakes

Why it's a problem: Enabling caching on a query that uses {{ retoolContext.currentUser.email }} as a filter

How to avoid: The Retool cache is shared across users. A user-filtered query cached for user A will return user A's data to user B if they run the same query within the TTL. Only cache queries returning data that is the same for all users.

Why it's a problem: Forgetting to invalidate the cache after a write operation, leaving users with stale data

How to avoid: Add await queryName.invalidateCache() to your write operation's success handler, followed by await queryName.trigger() to populate a fresh cache entry immediately.

Why it's a problem: Setting a very short TTL (5-10 seconds) on a slow query hoping to get both caching and freshness

How to avoid: A 5-second TTL provides minimal benefit for queries that take 2-3 seconds to run — the cache window is too narrow. If you need near-real-time data, disable caching and use a periodic refresh instead.

Best practices

  • Cache long-running aggregate queries (>500ms) with TTLs matching their data change frequency
  • Never cache queries with user-specific WHERE clauses — the shared cache will leak one user's data to another
  • Always invalidate related caches after write operations using invalidateCache() chained in the write query's success handler
  • Display servedFromCache to users on dashboards so they know data may be stale
  • Use longer TTLs (1 hour+) for reference/lookup data that rarely changes (country lists, category tables, config values)
  • Monitor cache hit rates in the Debug Panel Performance tab to validate caching is providing benefit

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a Retool dashboard with a 'getDailySummary' query that takes 3 seconds and runs frequently. I want to cache it with a 5-minute TTL. Explain: (1) how to configure caching in the query Advanced tab, (2) how the cache key works with my {{ dateRangePicker1.value }} parameter, (3) how to show users whether they're seeing cached data using getDailySummary.servedFromCache, (4) how to invalidate the cache after a user saves an update using invalidateCache(), (5) which of my other queries should NOT be cached because they use {{ retoolContext.currentUser.email }}.

Retool Prompt

Configure Retool query caching for 'getDailySummary': Cache the results ON, TTL 300 seconds. Add a Text component showing {{ getDailySummary.servedFromCache ? 'Cached' : 'Live' }}. Write a JS Query 'invalidateAndRefresh' that calls await getDailySummary.invalidateCache() then await getDailySummary.trigger(). Explain why queries with {{ retoolContext.currentUser.email }} in the WHERE clause must never be cached.

Frequently asked questions

Where is the Retool query cache stored?

For Retool Cloud, the cache is stored on Retool's infrastructure (not in your database). For self-hosted Retool, the cache is stored in your Retool PostgreSQL database's cache table. The cache is per-organization, not per-user.

Can I cache REST API queries in Retool, or is it only for SQL queries?

Caching is available for all Retool query types including REST API, GraphQL, SQL, and custom JavaScript queries. The cache key for REST API queries is based on the endpoint URL, HTTP method, headers, and body parameters.

Does Retool caching work in Preview mode vs Published apps?

Cache settings apply in both preview and published apps. However, the cache is separate between preview and production — a query cached in preview does not serve cached results in the published app. This allows safe testing without affecting production cache state.

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.