Guides

Retool beforeunload: Show Unsaved Changes Warning

OTC Team··4 min read
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 variable named isDirty with a default value of false.
  • On each input component's onChange event handler, add a Set Variable action that sets isDirty to true.
  • On your Save button's onClick handler, after your save query runs successfully, reset isDirty to false.

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 modal component named confirmLeaveModal with 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 localStorage variable using a Run Script action: localStorage.setItem('formDraft', JSON.stringify(formData.value));
  • On app load, checking localStorage for 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.

Ready to ship your first tool?