Guides
How to Stop a Running Query in Retool (And Workarounds That Work)
If you've ever needed to stop a running query in Retool — say, a two-minute build process or a query fetching tens of thousands of data points — you've probably already discovered the bad news: Retool has no native query.stop() method. There's no built-in way to programmatically cancel a query that's already in flight. This is a known limitation, actively tracked as a feature request by the Retool team, but until it ships, you need a workaround. This guide covers exactly that.
Why You Can't Cancel a Retool Query Mid-Execution
Retool queries — whether they hit a REST API, a database, or a JavaScript transformer — run to completion once triggered. The query.isFetching property tells you a query is in progress, but there's no corresponding query.stop(), query.cancel(), or query.abort() method exposed in Retool's query object. The Retool team has acknowledged this gap and is tracking demand for it, but it hasn't landed in the product yet.
This becomes a real problem in several common scenarios:
- A user triggers a long-running backend process (like a CI build or data pipeline) and wants to abort it before it finishes.
- A user rapidly clicks through options on a chart or table, firing off multiple queries in quick succession — the UI then renders the first stale response before replacing it with the second, creating a jarring experience.
- A query fetches a massive dataset and the user realizes mid-load they selected the wrong filter.
Workaround 1: Decouple the UI State from query.isFetching
The quickest fix — and the one that requires the least architectural change — is to manually manage a "loading" state variable in Retool instead of relying on query.isFetching directly.
- Create a Retool state variable, for example
isQueryRunning, and set it totruewhen the query fires. - Add a Cancel button in your UI that sets
isQueryRunningtofalsewhen clicked. - Bind all loading spinners, disabled states, and UI feedback to
isQueryRunninginstead ofquery.isFetching. - In your query's success and failure handlers, also set
isQueryRunningback tofalse.
The important caveat: this doesn't actually stop the underlying network request or database query. The query will still run to completion on the backend. What you're doing is faking the cancellation from the user's perspective — the UI acts as if the query was cancelled, even if the server is still chugging away. For many use cases, this is good enough. For others (like a long-running backend job you genuinely want to kill), you'll need the next approach.
Workaround 2: Use a Backend Cancellation Endpoint
If you control the backend, the cleanest solution is to expose a dedicated cancellation endpoint and call it from Retool when the user hits "Stop."
- When your main query fires, have the backend return a job ID or request token immediately (even if the work is still running asynchronously).
- Store that token in a Retool state variable, e.g.
currentJobId. - Wire a Cancel button to a second Retool query — something like
cancelJobQuery— that hits your/jobs/{id}/cancelendpoint with that stored token. - Poll the job status with a separate query using Retool's query trigger interval, and update the UI accordingly.
This is the most robust approach for genuinely long-running processes like background jobs, data exports, or triggered pipelines. It requires more backend work, but it gives users real control.
Workaround 3: Handle Rapid Re-Triggers with a Request Guard
For the "user clicks too fast and gets stale results" problem, a lightweight guard in a JavaScript query can help. Before rendering results, check whether the response still matches the currently selected state:
- Store the "current selection" (e.g. a chart point ID or filter value) in a Retool state variable like
activeSelectionat the time the query is triggered. - In the query's success event handler, compare the returned data's identifier against
activeSelection. - If they don't match, do nothing — discard the stale result and let the newer query's response win.
This is conceptually similar to a cancellation token pattern used in vanilla JavaScript. It won't prevent the network round-trips, but it eliminates the jarring UI flip where an old result flashes before being replaced.
Workaround 4: Offload to a Custom Component with a Worker Thread
For extremely heavy workloads — where the query's runtime is genuinely blocking the Retool app's main thread — consider wrapping the logic in a Retool Custom Component. Custom components run in their own iframe context, which means you can use browser APIs like Web Workers to offload processing to a background thread. This won't help with server-side cancellation, but it can prevent long JS execution from freezing your entire Retool app.
What to Do Until Retool Ships a Native Solution
The honest answer is that none of these workarounds are as clean as a first-class query.stop() method would be. Here's a quick decision guide based on your situation:
- Long-running backend job (2+ minutes): Implement a backend cancel endpoint and poll for status. This is the only real solution.
- User triggering rapid re-queries on a UI element: Use a request guard in the success handler to discard stale responses.
- Just need the UI to stop looking "busy": Decouple your loading state from
query.isFetchingusing a manual state variable. - Heavy JS processing blocking the UI: Move the logic into a Custom Component using a Web Worker.
If this limitation is affecting your team, upvote the feature request in the Retool community thread. The more signal the team sees, the sooner query.stop() becomes a reality. Until then, the workarounds above will keep your internal tools feeling responsive and professional.
Ready to build?
We scope, design, and ship your Retool app — fast.