> ## Documentation Index
> Fetch the complete documentation index at: https://funnelfox.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Per-screen custom code & Fox API

> Add custom JavaScript to individual funnel screens with the Fox API. Calculate values, integrate backends, add tracking scripts, and extend funnel logic.

Per-screen custom code lets you add JavaScript to individual screens using a [Raw HTML element](/elements/raw) and the Fox API. Use it to read user answers, store computed values, and control navigation.

<Warning>Custom code is not reviewed or controlled by FunnelFox. Make sure anything you add is reliable and thoroughly tested before using it in production.</Warning>

## How it works

Your custom JavaScript lives inside a [Raw HTML element](/elements/raw) on a screen. To add code that applies to every screen at once, use [funnel-wide custom code](/editor/coding-funnel-wide) instead.

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.

<Note>
  Input values are always strings. Use `parseInt()`, `parseFloat()`, or `Number()` to convert them before doing math.
</Note>

Only values you store with `fox.inputs.set()` persist across screens. Regular JavaScript variables (`const`, `let`) are reset every time the screen reloads.

## Quickstart

<Steps>
  <Step title="Add a Raw HTML element">
    Add a [Raw HTML element](/elements/raw) to the screen where you want your code to run.
  </Step>

  <Step title="Use Fox API">
    Inside the HTML content editor, add a `<script>` tag and use the Fox API. See the [Fox API reference](#fox-api-reference) below for all available methods.
  </Step>
</Steps>

<Tip>
  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.
</Tip>

Here's a simple example that sends a custom analytics event and fires a Facebook pixel on quiz completion:

```html theme={null}
<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](/integrations/analytics/index#user-properties) forwarded to connected integrations like Stripe and Amplitude, so purchases are attributed correctly.

```html theme={null}
<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.

```html theme={null}
<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.

```html theme={null}
<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.

```html theme={null}
<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.

```html theme={null}
<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

<ParamField path="fox.sandbox" type="boolean">
  `true` in preview mode, `false` in production. Use to run test-only logic.
</ParamField>

<ParamField path="fox.locale" type="string">
  The user's browser language code (e.g., `"en-US"`).
</ParamField>

### Inputs

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

#### Get a value

```javascript theme={null}
fox.inputs.get('element-id')
```

#### Set a value

```javascript theme={null}
fox.inputs.set('variable-name', value)
```

#### Get all values

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

#### Set email

```javascript theme={null}
fox.inputs.setEmail('user@example.com')
```

<Info>
  `setEmail()` saves the email to the user's profile, not just as a regular input. Use this for primary email collection.
</Info>

#### Get email

```javascript theme={null}
fox.inputs.getEmail()
```

#### Subscribe to a specific input

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

#### Subscribe to all inputs

```javascript theme={null}
fox.inputs.subscribeAll((name, value) => {
  // React to any input change
})
```

### Navigation

Control which screen the user sees next.

#### Go to next screen

```javascript theme={null}
fox.navigation.goNext()
```

#### Go to previous screen

```javascript theme={null}
fox.navigation.goBack()
```

#### Go to screen by ID

```javascript theme={null}
fox.navigation.goToId('screen-id')
```

#### Go to screen by index

```javascript theme={null}
fox.navigation.goToIndex(3)
```

<Warning>
  Don't use `navigateToScreen()`, `navigateToId()`, `navigateToNext()`, or `navigateToBack()`. These are deprecated and exist only for backwards compatibility.
</Warning>

### Tracking

Send custom events to connected [analytics tools](/integrations/analytics).

#### Track a custom event

```javascript theme={null}
fox.trackCustom('EventName', { key: 'value' })
```

**Example**

```javascript theme={null}
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

```javascript theme={null}
fox.stripe.setCustomerId('customer-id')
```

### Event listeners

React to funnel lifecycle events.

#### On restore replies

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

## Next steps

* Learn how to add the [Raw HTML element](/elements/raw)
* Add [funnel-wide custom code](/editor/coding-funnel-wide) that applies to every screen
