Custom JavaScript
The Custom JS system lets you inject JavaScript into your funnels for advanced behavior that goes beyond the visual editor. Your code interacts with the funnel through the window.appfunnel API.
Open the Code tab (code icon) in the left sidebar to access the editor.
Three Scopes
Global JS
Code that runs on every page of the funnel. Subscriptions and state persist across page navigation. Use this for analytics, global event handlers, or initialization code.
// Track every page view
appfunnel.on('page.view', (e) => {
analytics.track('page_view', { pageId: e.pageId });
});Page JS
Code that runs only on the current page. All event subscriptions are automatically cleaned up when the user navigates away — no manual teardown needed.
// Validate a field on this page only
appfunnel.on('variable.update', (e) => {
if (e.variableName === 'user.email') {
const isValid = e.newValue.includes('@');
appfunnel.setVariable('data.emailValid', isValid);
}
});Preload
Preload external resources (stylesheets, scripts, images, fonts, fetch requests) so they’re ready before the user needs them. Add a URL, select its type, and the browser fetches it in the background.
| Type | Use case |
|---|---|
stylesheet | External CSS files |
script | External JavaScript |
image | Hero images, backgrounds |
font | Custom web fonts |
fetch | API data to prefetch |
The appfunnel API
When your funnel runs, window.appfunnel is available with the following interface.
Methods
setVariable(variableId, value, operation?)
Set or modify a variable.
appfunnel.setVariable('user.name', 'Jane') // set
appfunnel.setVariable('user.count', null, 'increment') // increment
appfunnel.setVariable('user.count', null, 'decrement') // decrement
appfunnel.setVariable('user.active', null, 'toggle') // toggle boolean| Param | Type | Description |
|---|---|---|
variableId | string | e.g. user.myVariable |
value | any | The value to set (ignored for increment/decrement/toggle) |
operation | string? | 'set' (default), 'increment', 'decrement', 'toggle' |
toggleArrayItem(variableId, item)
Add or remove an item from a stringArray variable.
appfunnel.toggleArrayItem('user.selections', 'option1')goToNextPage()
Navigate forward by evaluating the current page’s routing rules.
goBack()
Navigate to the previous page in the session history.
callEvent(eventName, eventData?)
Emit a custom event. Integrations (Meta Pixel, GTM, webhooks) receive it, and you can subscribe to it via appfunnel.on().
appfunnel.callEvent('quiz_completed', {
goal: appfunnel.getVariable('answers.goal'),
});selectProduct(productId)
Select a product by its UUID.
appfunnel.selectProduct('product-uuid-here')openUrl(url)
Open a URL in a new tab.
appfunnel.openUrl('https://example.com')Getters
getVariable(variableId)
Returns the current value of a variable.
const name = appfunnel.getVariable('user.name')getVariables()
Returns an object with all variable IDs as keys and their current values.
const allVars = appfunnel.getVariables()getCurrentPageId()
Returns the current page ID, or null.
Event Subscriptions
Subscribe to events with appfunnel.on(eventType, callback). It returns an unsubscribe function.
const unsub = appfunnel.on('page.view', (e) => {
console.log('Page:', e.pageName);
});
// Later: unsub()You can also unsubscribe via appfunnel.off(eventType, callback).
Built-in Events
| Event | Fires when | Event data |
|---|---|---|
page.view | Page finishes loading | pageId, pageName |
page.exit | Before leaving a page | pageId, pageName |
variable.update | Any variable changes | variableId, variableName, oldValue, newValue |
navigation.next | goToNextPage() is called | fromPageId, fromPageName |
navigation.prev | goBack() is called | fromPageId, fromPageName |
You can also subscribe to custom events emitted via callEvent():
appfunnel.on('my_custom_event', (data) => {
console.log(data);
});Debugging
Enable debug logging to see all internal operations in the browser console:
appfunnel.debug = trueThis logs script execution, event emission, subscription changes, variable updates, navigation, and errors.
Examples
Track time on page
// Global JS
let start = null;
appfunnel.on('page.view', (e) => {
start = Date.now();
});
appfunnel.on('page.exit', (e) => {
appfunnel.callEvent('time_on_page', {
pageId: e.pageId,
duration: Date.now() - start,
});
});Auto-navigate when a score reaches a threshold
// Page JS
appfunnel.on('variable.update', (e) => {
if (e.variableName === 'user.score' && e.newValue >= 100) {
appfunnel.goToNextPage();
}
});Log all variable changes (debug helper)
// Global JS
appfunnel.on('variable.update', (e) => {
console.log(`${e.variableName}: ${e.oldValue} → ${e.newValue}`);
});Best Practices
- Use Page JS for page-specific logic — subscriptions are auto-cleaned, preventing memory leaks.
- Use Global JS sparingly — only for truly cross-page behavior like analytics.
- Wrap code in try-catch — an unhandled error in your JS won’t break the funnel, but it’s good practice.
- Don’t block navigation — avoid long-running synchronous operations.
- Test with debug mode —
appfunnel.debug = truesurfaces issues quickly.