Hi everyone,
I’m trying to implement session timeout behaviour in my Bubble app. Specifically, I’d like to automatically log out users if:
- They close the tab or browser without explicitly logging out
- They remain inactive on the page for a certain amount of time (e.g., 15–20 minutes), warning is shown first indicating that session is about to expire
What’s the best way to handle this in Bubble? Are there reliable methods (native or via plugin) to track user inactivity and log them out accordingly?
Would appreciate any guidance or examples
Thanks in advance!
You can manage both inactivity and closing-the-app logouts with a single JavaScript to Bubble element + a tiny bit of client-side code:
-
Add a “JavaScript to Bubble” element
- bubble_fn_suffix:
runLogout
- Trigger event:
checked
- Publish value:
checked
-
Drop an HTML element (e.g. in your header) and paste this script:
<script>
// —— CONFIG ——
const INACTIVITY_LIMIT_MS = 5 * 60 * 1000; // 5 minutes
// —— IDLE TIMER ——
let idleTimeout;
function resetIdleTimer() {
clearTimeout(idleTimeout);
idleTimeout = setTimeout(() => {
bubble_fn_runLogout(); // inactivity logout
}, INACTIVITY_LIMIT_MS);
}
['mousemove','keydown','mousedown','touchstart','scroll']
.forEach(evt => document.addEventListener(evt, resetIdleTimer));
resetIdleTimer();
// —— RELOAD DETECTION ——
// flag true if user initiates a reload via F5, Ctrl+R, or ⌘+R
let isReload = false;
window.addEventListener('keydown', e => {
if (
e.key === 'F5' ||
(e.key.toLowerCase() === 'r' && (e.ctrlKey || e.metaKey))
) {
isReload = true;
}
});
// —— CLOSE-ONLY LISTENER ——
window.addEventListener('beforeunload', e => {
if (!isReload) {
bubble_fn_runLogout(); // page close logout only
}
// no need to preventDefault or set returnValue
});
</script>
-
In your Bubble editor, create one workflow:
- Trigger: When JavaScript to Bubble event
runLogout is fired
- Action: Account → Log the user out
How this works
- Inactivity: any user action resets the timer; after X minutes of silence, it calls
bubble_fn_runLogout().
- Close-only: we watch for reload keypresses (F5, Ctrl+R, ⌘+R) and set
isReload. On beforeunload, we only fire the logout if isReload is still false, so page refreshes won’t log them out.
Tweak INACTIVITY_LIMIT_MS or the key rules as needed!
I hope it works out 
1 Like
Thank You, carlosvsk.edits
I will try it out
Test and say me if worked out!
There are no conditionals right
You don’t need to have conditionals there, that will only be a trigger event. The “conditionals” are in the script I sent you, which will run this event when the user becomes inactive or closes the page.
Not working for me
I closed the session without logging out, waited for about 15 minutes but when I clicked on preview again, I was still showing as logged in
Should I change something?
There are 4 pages on my app, should I enter the code on every page and try again?
I stored the login time on the Current User (the authentication happens somewhere else, so I’m not sure if Bubble stores the login time somewhere else but the concept is the same) and created a reusable element that checks if the login time of Current User + 24hrs passed or not. If passed, logs the user out. I added that reusable element to every page with 1x1 pixel.
Hi there!
Sorry it took me a while to come back. My first idea looked fine on paper, but after testing I realised it just didn’t work reliably. I spent some extra time, did more experiments, and came up with a version that actually does the job.
The new approach (BroadcastChannel
)
BroadcastChannel keeps a live counter of open tabs only in memory, so the user can’t edit a value on disk. Every tab tells the others when it opens, closes or detects activity, and they agree on when to log out.
<script>
(function () {
/* ––– SETTINGS ––– */
const INACTIVITY_MS = 5 * 60 * 1000; // 5 min
const CHECK_EVERY_MS = 15 * 1000; // idle check
const bc = new BroadcastChannel('myapp-tabs'); // change if you share a domain
let openTabs = 1;
/* ––– UTIL ––– */
const now = () => Date.now();
const logout = () =>
typeof bubble_fn_runLogout === 'function' && bubble_fn_runLogout();
/* ––– TAB COUNTER ––– */
bc.postMessage({ t:'open' });
bc.onmessage = ({ data }) => {
if (data.t === 'open') openTabs++;
if (data.t === 'close') openTabs--;
if (data.t === 'ping') lastActivity = now();
};
/* ––– GLOBAL ACTIVITY ––– */
let lastActivity = now();
const touch = () => { lastActivity = now(); bc.postMessage({ t:'ping' }); };
['mousemove','keydown','mousedown','touchstart','scroll','visibilitychange']
.forEach(evt => addEventListener(evt, touch, { passive:true }));
/* ––– INACTIVITY TIMER ––– */
const idleLoop = setInterval(() => {
if (now() - lastActivity >= INACTIVITY_MS) {
logout(); clearInterval(idleLoop);
}
}, CHECK_EVERY_MS);
/* ––– TAB CLOSE ––– */
addEventListener('pagehide', () => {
bc.postMessage({ t:'close' });
if (openTabs - 1 === 0) logout(); // last tab ⇒ logout
});
})();
</script>
Bubble steps (only three)
-
JavaScript to Bubble element
- Function name →
runLogout
- Just “Trigger Event” → checked.
-
Workflow
- Event → “JavaScript to Bubble is triggered” (
runLogout)
- Action → Account → Log the user out.
-
HTML element on every page where you want auto-logout → paste the script above.
(If you run multiple apps on the same domain, use a unique channel name instead of "myapp-tabs".)
Tip: turn this into a Reusable Element (the JS-to-Bubble + the HTML script). Drop that reusable on every page and you’re done — one place to maintain, works everywhere.
Why this is safer
- Nothing is stored in
localStorage, so changing a value in DevTools no longer “frees” the user from logout.
- Tabs talk to each other in real time; when the very last one closes, the session ends.
- Front-end code is never 100 % untouchable, but this raises the bar a lot.
Hope this helps and again, sorry for the delay!
Let me know if you hit any edge-cases and I’ll be happy to adjust.
1 Like
I hope it works this time. I tried it on my app and it worked!