Skip to main content
Custom code lets you go beyond the visual editor. Use the Fox API to read user answers, store computed values, and control navigation — all from inside a Raw HTML element.

How it works

Your custom JavaScript lives inside a Raw HTML element on a screen. There is no other way to add JavaScript to your funnel. The code runs immediately when the screen loads — and again every time the user navigates back to that screen. The Fox API (fox) is available globally from the moment the screen opens, no setup needed. Inputs are a persistent key/value store shared across all screens. When a user picks an option or fills a field, FunnelFox saves that answer to fox.inputs under the element’s ID. You can read those values, compute new ones, and write them back — they’ll be available on every screen.
Input values are always strings. Use parseInt(), parseFloat(), or Number() to convert them before doing math.
Only values you store with fox.inputs.set() persist across screens. Regular JavaScript variables (const, let) are reset every time the screen reloads.

Quickstart

1

Add a Raw HTML element

Add a Raw HTML element to the screen where you want your code to run.
2

Use Fox API

Inside the HTML content editor, add a <script> tag and use the Fox API. See the Fox API reference below for all available methods.
Use the Copy for LLM button at the top of this page to copy the Fox API reference in plain markdown, then paste it into any LLM (ChatGPT, Claude, etc.) and ask it to write the code for you.
Here’s a simple example that sends a custom analytics event and fires a Facebook pixel on quiz completion:
<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'
  });
}
</script>

Use cases

Capture email from URL

When a user lands from an email campaign or app notification, read their email from the URL and bind it to their FunnelFox profile. This sets it as a user property forwarded to connected integrations like Stripe and Amplitude, so purchases 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

Read user answers, run calculations, and store the result as a new variable. You can then use that variable anywhere in your funnel with {{variable-name}}, or use it to route users to different screens.
<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

Intercept a button click to call your own API before navigating. Store the response in fox.inputs so it’s available on later screens, or block navigation and show an error if the request fails.
<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('_USERID_', 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

Route users to different screens based on multiple conditions evaluated at once. Useful when a single answer isn’t enough to decide the next step.
<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

Fire custom events to your analytics tools and ad pixels from any screen. Use fox.inputs.getAll() to include all collected answers in the payload.
<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

Properties

fox.sandbox
boolean
true in preview mode, false in production. Use to run test-only logic.
fox.locale
string
The user’s browser language code (e.g., "en-US").

Inputs

Read and write the key/value store shared across all screens.

Get a value

fox.inputs.get('element-id')

Set a value

fox.inputs.set('variable-name', value)

Get all values

fox.inputs.getAll()
// Returns: { email: "user@example.com", age: "25-34", ... }

Set email

fox.inputs.setEmail('user@example.com')
setEmail() saves the email to the user’s profile, not just as a regular input. Use this for primary email collection.

Get email

fox.inputs.getEmail()

Subscribe to a specific input

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

Subscribe to all inputs

fox.inputs.subscribeAll((name, value) => {
  // React to any input change
})
Control which screen the user sees next.

Go to next screen

fox.navigation.goNext()

Go to previous screen

fox.navigation.goBack()

Go to screen by ID

fox.navigation.goToId('screen-id')

Go to screen by index

fox.navigation.goToIndex(3)
Don’t use navigateToScreen(), navigateToId(), navigateToNext(), or navigateToBack(). These are deprecated and exist only for backwards compatibility.

Tracking

Send custom events to connected analytics tools.

Track a custom event

fox.trackCustom('EventName', { key: 'value' })
Example
fox.trackCustom('QuizCompleted', {
  score: fox.inputs.get('quiz-score')
})

Stripe

Force a Stripe Checkout session to use a specific customer. If the customer ID does not exist, the Checkout session will not appear.

Set customer ID

fox.stripe.setCustomerId('customer-id')

Event listeners

React to funnel lifecycle events.

On restore replies

fox.onRestoreReplies(() => {
  // User data restored from a previous session
})

Next steps