Guides
Retool beforeunload: Show Unsaved Changes Warning

If you're building a data entry form in Retool and want to warn users before they close the tab or navigate away, you've probably searched for a way to hook into the browser's beforeunload event. The short answer: Retool does not natively support beforeunload, and attempts to register it via custom JavaScript are blocked by Retool's sandboxed iframe environment. But there are practical workarounds that get you close enough — and this guide walks through all of them.
Why Retool's beforeunload Warning Doesn't Work Out of the Box
The browser's native beforeunload event fires when a user attempts to close a tab, reload the page, or navigate away. Normally you'd register it like this:
window.addEventListener('beforeunload', (e) => { e.preventDefault(); e.returnValue = ''; });
The problem is that Retool runs your app inside a sandboxed <iframe>. JavaScript executed inside Retool's Run Script action or a transformer operates within that iframe context — not the top-level browser window. Because of this, window.addEventListener('beforeunload', ...) either fails silently or has no effect on the actual tab-close or reload action the user triggers at the browser level. This is a known limitation that the Retool team has flagged as a feature request, but it is not currently on the active roadmap.
What "Dirty State" Tracking Means and Why You Need It
Before implementing any warning, you need a way to know whether the user has made changes that haven't been saved yet. This is called tracking dirty state. A form is "dirty" when a user has typed something or changed a value but hasn't clicked Save. Without this, you'd be warning users even when there's nothing to lose.
In Retool, you can track dirty state using a variable component:
- Create a
variablenamedisDirtywith a default value offalse. - On each input component's onChange event handler, add a Set Variable action that sets
isDirtytotrue. - On your Save button's onClick handler, after your save query runs successfully, reset
isDirtytofalse.
Now you have a reliable signal for whether there's anything worth warning the user about.
Workaround 1: Use a Custom Confirmation Modal for In-App Navigation
While you can't intercept a browser tab close, you can intercept in-app navigation — for example, if your Retool app uses multiple pages or a sidebar menu. This is where a modal-based confirmation pattern works well.
- Create a
modalcomponent namedconfirmLeaveModalwith a message like "You have unsaved changes. Are you sure you want to leave?" - Add two buttons inside the modal: Stay (closes the modal) and Leave Anyway (triggers navigation and resets
isDirty). - On any navigation button or link in your app, add a Run Script event handler with this logic:
if (isDirty.value) { confirmLeaveModal.open(); } else { // proceed with navigation }
This pattern gives users a clear, intentional prompt before they abandon their work, and it works entirely within Retool's supported feature set.
Workaround 2: Try Injecting beforeunload via a Custom HTML Component
If your Retool instance is self-hosted or you have access to the Custom Component feature, you can attempt to reach the parent window using window.parent. This is not guaranteed to work depending on your deployment's iframe sandboxing policy, but it's worth trying:
- Add a Custom Component to your app.
- Inside the HTML/JS of the custom component, add:
window.parent.addEventListener('beforeunload', function(e) { e.preventDefault(); e.returnValue = ''; });
Some self-hosted Retool deployments allow this because the iframe and parent share the same origin. On Retool Cloud, this will likely be blocked by cross-origin restrictions. Test it in your specific environment before relying on it.
Workaround 3: Auto-Save to Reduce the Stakes
The most resilient solution is removing the problem entirely: auto-save the form data so there's nothing to lose. You can implement this by:
- Triggering your save query on a timer component set to fire every 10–30 seconds when
isDirty.value === true. - Saving form state to a
localStoragevariable using a Run Script action:localStorage.setItem('formDraft', JSON.stringify(formData.value)); - On app load, checking
localStoragefor a saved draft and pre-populating the form if one exists.
This approach is especially useful for long forms where losing progress would be genuinely disruptive to your users.
The Bottom Line on Retool and beforeunload
Native beforeunload support in Retool is currently unsupported and not on the near-term roadmap. For most use cases, combining dirty state tracking with a confirmation modal covers the in-app navigation scenario cleanly. If you need to guard against tab closes and page reloads, the Custom Component approach with window.parent is your best bet on self-hosted instances, and auto-save is the safest fallback for everyone else. Pick the strategy that matches your deployment and form complexity — and stop users from losing their work.
Ready to build?
We scope, design, and ship your Retool app — fast.