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
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>
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
true in preview mode, false in production. Use to run test-only logic.
The user’s browser language code (e.g., "en-US").
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.subscribe((name, value) => {
console.log(`${name} changed to: ${value}`);
})
fox.inputs.subscribeAll((name, value) => {
// React to any input change
})
Navigation
Control which screen the user sees next.
Go to next screen
Go to previous screen
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