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

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