Skip to main content
Custom code lets you go beyond the visual editor’s capabilities. Calculate values based on user input, integrate with your backend, or add tracking scripts - the Fox API gives you programmatic control over your funnel.
Required element: All custom code must be added through the Raw HTML element. This is the only way to add JavaScript to your funnels.

Quick Start

Add a Raw HTML element to any screen and include your JavaScript in <script> tags:
<script>
// Calculate BMI from user inputs
const weight = parseInt(fox.inputs.get('weight'));
const height = parseInt(fox.inputs.get('height'));
const bmi = weight / ((height/100) * (height/100));

// Store result as new variable
fox.inputs.set('bmi', bmi.toFixed(1));

// Now you can use {{bmi}} anywhere in your funnel
</script>
The Fox API is available globally as window.fox (or just fox) as soon as the screen loads.

Common Use Cases

Capture Email from URL

Automatically associate a visitor with their email when they land from an email campaign or app notification. This binds the email to the FunnelFox profile and sets it as a user property that’s forwarded to connected integrations (Stripe, Amplitude, etc.) so purchases from a paywall are attributed correctly.
<script>
const url = new URL(window.location.href);
const emailParamValue = url.searchParams.get('email');
if (!!emailParamValue) {
  fox.inputs.setEmail(emailParamValue);
}
</script>

Calculate Values from User Input

Transform and combine user responses to create new data:
<script>
// Get user inputs (using element IDs)
const age = parseInt(fox.inputs.get('age'));
const income = parseInt(fox.inputs.get('income'));
const familySize = parseInt(fox.inputs.get('family-size'));

// Calculate affordability score
const score = (income / familySize) - (age * 100);

// Save for use as {{affordability-score}}
fox.inputs.set('affordability-score', score);

// Navigate based on calculation
if (score > 5000) {
  fox.navigation.goToId('premium-offer');
} else {
  fox.navigation.goToId('standard-offer');
}
</script>

Custom Backend Integration

Handle authentication or validation with your own servers:
<script>
// Add to screen with email input and button with "Do Nothing" action
document.getElementById('register-button')
  .children[0] // Access the actual button element
  .addEventListener('click', async () => {
    const email = fox.inputs.get('email');
    const name = fox.inputs.get('name');
    
    try {
      const response = await fetch('https://your-api.com/register', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, name })
      });
      
      if (response.ok) {
        const data = await response.json();
        // Store user ID from your backend
        fox.inputs.set('user-id', data.userId);
        fox.navigation.goNext();
      } else {
        // Show error (make sure error-message element exists)
        document.getElementById('error-message').style.display = 'block';
      }
    } catch (error) {
      console.error('Registration failed:', error);
    }
  });
</script>

Dynamic Navigation Logic

Create complex flows based on multiple conditions:
<script>
const age = parseInt(fox.inputs.get('age'));
const hasKids = fox.inputs.get('has-kids') === 'yes';
const income = fox.inputs.get('income-range');

// Multi-condition routing
if (age < 25 && !hasKids) {
  fox.navigation.goToId('young-singles');
} else if (age < 25 && hasKids) {
  fox.navigation.goToId('young-families');
} else if (income === 'high' && age > 40) {
  fox.navigation.goToId('premium-segment');
} else {
  fox.navigation.goToId('standard-flow');
}
</script>

Third-Party Tracking

Add custom tracking beyond standard integrations:
<script>
// Custom analytics event
fox.trackCustom('QuizCompleted', {
  score: fox.inputs.get('quiz-score'),
  time_spent: Date.now() - startTime,
  answers: fox.inputs.getAll()
});

// Third-party pixel
if (typeof fbq !== 'undefined') {
  fbq('trackCustom', 'LeadQualified', {
    value: fox.inputs.get('estimated-value'),
    currency: 'USD'
  });
}

// Google Tag Manager
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  event: 'funnel_milestone',
  milestone: 'pricing_viewed',
  user_segment: fox.inputs.get('segment')
});
</script>

Fox API Reference

Core Properties

PropertyTypeDescription
fox.sandboxbooleanTrue in preview mode, false in production
fox.localestringUser’s browser language code (e.g., “en-US”)
Control funnel flow programmatically:
// Navigate to next screen in sequence
fox.navigation.goNext();

// Go back to previous screen
fox.navigation.goBack();

// Jump to specific screen by ID
fox.navigation.goToId('checkout');

// Navigate by screen index (0-based)
fox.navigation.goToIndex(3);
Deprecated methods: Don’t use navigateToScreen(), navigateToId(), navigateToNext(), or navigateToBack(). These legacy methods exist for backwards compatibility only.

Input Methods

Read and write user data:
// Get input value by element ID
const email = fox.inputs.get('email');
const age = fox.inputs.get('age-select');

// Set custom input (creates {{variable}})
fox.inputs.set('calculated-price', 99.99);
fox.inputs.set('user-segment', 'premium');

// Get all inputs as object
const allData = fox.inputs.getAll();
// Returns: { email: "[email protected]", age: "25-34", ... }

// Special email method (saves to profile)
fox.inputs.setEmail('[email protected]');
const email = fox.inputs.getEmail();

// Listen to input changes
fox.inputs.subscribe((name, value) => {
  console.log(`Input ${name} changed to:`, value);
});

// Listen to all input changes (same as subscribe)
fox.inputs.subscribeAll((name, value) => {
  // React to any input change
});
Email binding: setEmail() is special - it saves the email to the user’s profile, not just as a regular input. Use this for primary email collection.

Stripe Integration

For Stripe-specific operations:
// Set customer ID for payment processing
fox.stripe.setCustomerId('cus_ABC123xyz');

Custom Tracking

Send events to third-party analytics:
// Track custom event with payload
fox.trackCustom('PricingCalculated', {
  base_price: 99,
  discount: 20,
  final_price: 79,
  currency: 'USD'
});
Note: Custom tracking events are sent to third-party services only. They don’t appear in FunnelFox Analytics dashboard.

Event Listeners

React to funnel events:
// Listen for when saved replies are restored
fox.onRestoreReplies(() => {
  console.log('User data restored from previous session');
  // Update UI based on restored data
});

Working with the Raw HTML Element

Adding Custom Code

  1. Drag a Raw HTML element onto your screen
  2. Add your HTML and JavaScript:
<!-- Custom HTML -->
<div id="custom-calculator">
  <h3>Your Result</h3>
  <p id="result-text">Calculating...</p>
</div>

<script>
// Your JavaScript code
const weight = fox.inputs.get('weight')?.value;
const height = fox.inputs.get('height')?.value;
const bmi = weight / ((height/100) * (height/100));

document.getElementById('result-text').textContent = 
  `Your BMI is ${bmi.toFixed(1)}`;
  
fox.inputs.set('bmi', bmi.toFixed(1));
</script>

Execution Timing

  • Code runs immediately when the screen loads
  • No need for DOMContentLoaded or similar wrappers
  • Fox API is available immediately
  • Code runs every time user navigates to the screen

Accessing Other Elements

You can interact with other elements on the same screen:
<script>
// Hide/show elements based on logic
if (fox.inputs.get('age') < 18) {
  document.getElementById('adult-content').style.display = 'none';
}

// Modify button behavior
document.getElementById('special-button')
  .children[0]
  .addEventListener('click', () => {
    // Custom logic before navigation
    if (validateForm()) {
      fox.navigation.goNext();
    }
  });
</script>

Store Complex Data

<script>
// Create structured data from multiple inputs
const profile = {
  demographics: {
    age: fox.inputs.get('age'),
    location: fox.inputs.get('location'),
    income: fox.inputs.get('income')
  },
  preferences: {
    plan: fox.inputs.get('selected-plan'),
    addons: fox.inputs.get('selected-addons'),
    billing: fox.inputs.get('billing-cycle')
  },
  score: calculateScore()
};

// Store as JSON string
fox.inputs.set('user-profile', JSON.stringify(profile));
</script>

Limitations & Warnings

Here be dragons: Custom code can break your funnel if not properly tested. Always test thoroughly in preview mode before publishing.

Things to Know

  • No sandboxing: Your code has full access to the page
  • No error boundaries: JavaScript errors can break the funnel
  • Performance impact: Heavy scripts can slow down navigation
  • Cross-screen state: Code doesn’t persist between screens
  • Browser compatibility: You’re responsible for compatibility

Testing Your Code

Preview Mode

Use preview mode to test without affecting production:
<script>
if (fox.sandbox) {
  console.log('Running in preview mode');
  // Test-specific code
  fox.inputs.set('test-mode', true);
} else {
  console.log('Running in production');
  // Production code
}
</script>

Next Steps

I