Skip to main content
Exit popups appear after users navigate away from a page, making them perfect for “before you go” feedback flows that capture insights without interrupting the user’s current journey.

How Exit Triggers Work

Unlike traditional exit-intent popups that try to catch the user before they leave, the MagicFeedback SDK’s exit triggers are designed for a better user experience:
  1. User visits a matching route (e.g., /pricing)
  2. User navigates away to a different route
  3. Exit trigger is queued in sessionStorage
  4. After a configurable delay on the destination route, the popup appears
Exit popups appear after navigation, not before. This avoids interrupting the user’s intent to navigate while still capturing valuable feedback.

Defining Exit Triggers

const popupDefinitions = [
  {
    id: 'popup-pricing-exit',
    title: 'Feedback on pricing',
    message: '<p>Help us understand your pricing concerns.</p>',
    triggers: {
      type: 'exit',
      value: 3, // Delay in seconds after navigation
      condition: [{ answered: false, cooldownDays: 14 }],
    },
    surveyId: 'survey-pricing-exit',
    productId: 'product-main',
    segments: {
      path: ['/pricing'], // Show when leaving /pricing
    },
    actions: {
      accept: {
        label: 'Share Feedback',
        surveyId: 'survey-pricing-exit',
      },
    },
  },
];

const popups = new DeepdotsPopups();
popups.init({
  mode: 'client',
  popups: popupDefinitions,
});
popups.autoLaunch();

Configuration Options

Delay Value

The value field specifies how many seconds to wait after navigation before showing the popup:
triggers: {
  type: 'exit',
  value: 3, // Wait 3 seconds on the destination route
}
  • Short delay (1-3s): Good for high-intent pages where users are actively browsing
  • Medium delay (5-10s): Balanced approach for general exit feedback
  • Long delay (15-30s): For detailed feedback after users have settled on the new page
A 3-5 second delay typically works well for most exit popups. It gives users time to orient themselves on the new page without feeling interrupted.

Route Targeting

Use segments.path to specify which routes should trigger the exit popup when users navigate away:
segments: {
  path: [
    '/pricing',           // Exit from pricing page
    '/features',          // Exit from features page
    '/checkout',          // Exit from checkout
  ]
}
Learn more in the Route Targeting guide.

The Exit Popup Queue

How the Queue Works

The SDK uses sessionStorage to persist exit popups across navigation:
  1. When a user leaves a matching route, the popup is queued
  2. The queue stores: popup ID, survey ID, source URL, and due time
  3. After the delay expires, the SDK shows the popup
  4. Shown popups are removed from the queue
// Queue structure in sessionStorage
interface DeferredExitPopup {
  id: string;         // Popup definition ID
  surveyId: string;   // Survey to show
  sourceUrl: string;  // URL where exit occurred
  dueAt: number;      // Timestamp when popup should appear
}

Queue Persistence

Exit popups survive page reloads and navigation:
// User flow:
// 1. Visit /pricing
// 2. Navigate to /home (exit trigger queued)
// 3. Reload page
// 4. Popup still appears after delay ✓
The queue uses sessionStorage, so exit popups are cleared when:
  • The browser tab is closed
  • The session ends
  • Storage is manually cleared
Exit triggers work with all navigation methods:
<a href="/home">Go Home</a>
<!-- Exit trigger fires when clicking the link -->
<a href="#/dashboard">Dashboard</a>
<!-- Exit trigger fires for hash route changes -->
// Exit trigger fires for pushState/replaceState
history.pushState(null, '', '/new-page');
history.replaceState(null, '', '/different-page');
Works with modern frameworks:
// React Router
navigate('/new-page');

// Vue Router
router.push('/new-page');

// Next.js
router.push('/new-page');

Complete Example

Here’s a real-world example with multiple exit triggers:
import { DeepdotsPopups } from '@magicfeedback/popup-sdk';

const popupDefinitions = [
  // Pricing page exit
  {
    id: 'popup-pricing-exit',
    title: 'Pricing feedback',
    message: '<p>What made you leave the pricing page?</p>',
    triggers: {
      type: 'exit',
      value: 3,
      condition: [{ answered: false, cooldownDays: 7 }],
    },
    surveyId: 'survey-pricing-exit',
    productId: 'product-main',
    segments: {
      path: ['/pricing'],
    },
    actions: {
      accept: {
        label: 'Tell Us Why',
        surveyId: 'survey-pricing-exit',
      },
    },
  },
  // Product page exit
  {
    id: 'popup-product-exit',
    title: 'Product feedback',
    message: '<p>Did you find what you were looking for?</p>',
    triggers: {
      type: 'exit',
      value: 5,
      condition: [{ answered: false, cooldownDays: 14 }],
    },
    surveyId: 'survey-product-exit',
    productId: 'product-main',
    segments: {
      path: ['/product/*'], // Match all product pages
    },
    actions: {
      accept: {
        label: 'Share Feedback',
        surveyId: 'survey-product-exit',
      },
    },
  },
];

const popups = new DeepdotsPopups();

popups.init({
  mode: 'client',
  debug: true,
  popups: popupDefinitions,
});

// Listen for popup events
popups.on('popup_shown', (event) => {
  console.log('Exit popup shown:', event);
});

popups.autoLaunch();

Exit Popup Behavior

When Popups Are Shown

An exit popup will show when:
  1. ✅ User was on a matching route (defined in segments.path)
  2. ✅ User navigated to a different route
  3. ✅ The configured delay has elapsed
  4. ✅ Conditions are met (answered, cooldownDays)
  5. ✅ No other popup is currently showing

When Popups Are Skipped

An exit popup will not show when:
  • ❌ User is still on the same route
  • ❌ Conditions block it (answered: false and survey was completed)
  • ❌ Cooldown period hasn’t elapsed
  • ❌ Route doesn’t match segments.path
  • ❌ User closed the tab before delay elapsed
The SDK checks conditions at render time (after the delay), not when the exit is queued. This ensures popups respect real-time user state.

Source Code Reference

The exit popup implementation is in src/core/deepdots-popups.ts:
  • Queue storage: Lines 307-444 handle sessionStorage persistence
  • Navigation detection: Triggers module detects route changes
  • Path matching: Lines 247-283 evaluate segments.path
  • Deferred rendering: Lines 362-392 manage timers and display logic

API Reference

queueExitPopup(surveyId, delaySeconds, sourceUrl?, popupId?)

Manually queue an exit popup (typically called by trigger handlers):
popups.queueExitPopup(
  'survey-exit-001',  // Survey ID
  5,                  // Delay in seconds
  '/pricing',         // Source URL (optional)
  'popup-exit-001'    // Popup ID (optional)
);
You typically don’t need to call this directly. The SDK automatically queues exit popups when autoLaunch() is enabled and exit triggers are configured.

Best Practices

Match your delay to the user’s context:
// High-value page (pricing, checkout)
triggers: { type: 'exit', value: 3 }

// Content pages
triggers: { type: 'exit', value: 5 }

// Post-navigation surveys
triggers: { type: 'exit', value: 10 }
Prevent survey fatigue with cooldown periods:
condition: [
  {
    answered: false,
    cooldownDays: 14 // Don't show again for 2 weeks
  }
]
Focus on pages where exit feedback is most valuable:
// Good: Specific high-value pages
segments: { path: ['/pricing', '/checkout', '/signup'] }

// Avoid: Too broad
segments: { path: ['*'] }
Verify exit popups work in your app:
popups.init({
  mode: 'client',
  debug: true, // See queue and render logs
  popups: definitions,
});

Debugging Exit Popups

Enable debug mode to see exit popup logs:
popups.init({
  mode: 'client',
  debug: true,
  popups: definitions,
});
Logs include:
  • When exits are queued
  • Queue contents in sessionStorage
  • When delays expire
  • Why popups are skipped
  • When popups are shown

Next Steps

Route Targeting

Learn path matching rules for exit triggers

Event Triggers

Trigger popups from business events

Server Mode

Manage exit popups remotely

Client Mode

Define exit triggers inline

Build docs developers (and LLMs) love