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

# EF API (window.ef)

> Use the global ef object in client scripts and on the page — tags, visibility, next step, and delays.

The **`window.ef`** object is available on every funnel page. Use it in **Client script** nodes, in inline scripts, or anywhere on the page to work with tags, visibility, flow control, and timing — without loading the full funnel engine.

<CardGroup cols={2}>
  <Card title="Tags" icon="cookie" href="#tags">
    Add, check, or remove tags (same as Add tag / Has tag nodes).
  </Card>

  <Card title="Flow control" icon="code-branch" href="#flow-control">
    Advance to the next step from a client script with <code>ef.nextNode()</code>.
  </Card>

  <Card title="Visibility" icon="eye" href="#visibility">
    Check if an element is visible or wait until it appears.
  </Card>

  <Card title="Timing" icon="clock" href="#timing">
    Delay execution with <code>ef.wait()</code>.
  </Card>

  <Card title="Video" icon="video" href="#video">
    Check video progress (seconds or %) and run a callback when the CTA is shown.
  </Card>

  <Card title="Cart" icon="cart-shopping" href="#cart">
    Run a callback when a product is added to the cart with <code>ef.onAddToCart()</code>.
  </Card>
</CardGroup>

***

## Where you can use it

* **Client script** node — Your script runs in the page; you can call `ef.addTag()`, `ef.nextNode()`, `ef.waitForVisible()`, etc. The flow does **not** auto-advance after a client script; call `ef.nextNode()` when you want to continue.
* **Any inline or page script** — `window.ef` is defined early, so you can use it in `onclick`, in a `<script>` block, or from other JS.
* **Click tracking** — Buttons or links with `data-ef-add-tag="tagname"` will add that tag when clicked (no client script needed).

***

## Tags

Tags are stored in cookies (same as the **Add tag** and **Has tag** nodes). Use the same tag name in the builder and in `ef` so conditions and scripts stay in sync.

### ef.addTag(name, expirationMinutes)

Sets a tag (stored in a cookie). Use after a conversion, CTA click, or in a client script.

* **name** — Tag name (same as in the **Add tag** node and **Has tag** condition).
* **expirationMinutes** — Optional. If omitted or `0`, the tag is a **session cookie** (cleared when the browser closes). If set to a positive number, the tag expires after that many minutes (e.g. `60` = 1 hour, `10080` = 7 days).

```javascript theme={null}
// Session cookie (cleared when browser closes)
ef.addTag('saw_popup');
ef.addTag('newsletter_signup');

// Expires after 60 minutes (1 hour)
ef.addTag('saw_popup', 60);

// Expires after 7 days (10080 minutes)
ef.addTag('campaign_click', 10080);
```

### ef.hasTag(name)

Returns `true` if the tag is set, `false` otherwise.

```javascript theme={null}
if (ef.hasTag('saw_popup')) {
  // Already saw the popup, skip
} else {
  // Show popup, then ef.addTag('saw_popup')
}
```

### ef.removeTag(name)

Removes the tag (deletes the cookie).

```javascript theme={null}
ef.removeTag('saw_popup');
```

***

## Flow control

### ef.nextNode()

Only meaningful inside a **Client script** node. Advances the funnel to the next step. You can call it immediately or later (e.g. after `ef.waitForVisible` or `ef.wait`).

<Note>
  In the client script you write <code>ef.nextNode()</code> with no arguments. The funnel engine replaces this with a call that knows the current node, so each script gets the correct "next" step.
</Note>

```javascript theme={null}
// Continue right away
ef.nextNode();
```

```javascript theme={null}
// Continue after something is visible
ef.waitForVisible('.thank-you-message', function() {
  ef.nextNode();
});
```

```javascript theme={null}
// Continue after a delay
ef.wait(2000, function() {
  ef.nextNode();
});
```

***

## Visibility

### ef.isVisible(selectorOrElement)

Returns `true` if the element exists and is visible (non-zero size, not `display: none`, not `visibility: hidden`, opacity > 0). Accepts a CSS selector string or a DOM element.

```javascript theme={null}
if (ef.isVisible('.banner')) {
  // Banner is on screen
}

var el = document.querySelector('#my-div');
if (ef.isVisible(el)) {
  // Element is visible
}
```

### ef.waitForVisible(selector, callback)

Waits until an element matching the selector is in the DOM and visible, then runs the callback once. Works on refresh if the element is already visible. Returns a **cancel** function so you can stop waiting.

```javascript theme={null}
ef.waitForVisible('.thank-you-message', function() {
  ef.nextNode();
});
```

```javascript theme={null}
// Optional: cancel if needed
var cancel = ef.waitForVisible('.popup', function() {
  ef.nextNode();
});
// cancel(); // to stop waiting
```

***

## Timing

### ef.wait(ms, callback)

Runs the callback after `ms` milliseconds. Returns a **cancel** function (clears the timeout).

```javascript theme={null}
ef.wait(3000, function() {
  ef.nextNode();
});
```

```javascript theme={null}
var cancel = ef.wait(5000, function() {
  doSomething();
});
// cancel(); // to clear the timeout
```

***

## Video

These helpers use the same video state as the **Video progress check** and **On CTA** nodes (Vidalytics, TrackPlay, SmartPlayer, or main page video).

### ef.videoProgressCheck(opts)

Returns `true` if the video has reached the given position, `false` otherwise. **opts** is an object with either **`seconds`** or **`percent`** (or both).

* **seconds** — Target playback time in seconds (e.g. `30` = 30 seconds).
* **percent** — Target percentage of video length (e.g. `50` = 50%).

```javascript theme={null}
if (ef.videoProgressCheck({ percent: 50 })) {
  // Video is at or past 50%
}

if (ef.videoProgressCheck({ seconds: 30 })) {
  // Video is at or past 30 seconds
}
```

### ef.onCta(callback)

Runs your **callback** when the video CTA is shown (when `video-cta-shown` fires, e.g. from `getStarted()`). The callback receives one argument: the event detail object (e.g. `{ focus_on_cta, timestamp }`). Returns an **unsubscribe** function to remove the listener.

```javascript theme={null}
ef.onCta(function(detail) {
  console.log('CTA shown', detail.focus_on_cta, detail.timestamp);
  ef.nextNode();
});

// Or cancel the listener later:
var unsubscribe = ef.onCta(function() { ef.nextNode(); });
// unsubscribe();
```

### ef.getVideoProgress()

Returns the current video state: **`{ currentTime, duration, percent }`**. Same sources as the funnel video nodes. Useful when you need raw values instead of a threshold check.

```javascript theme={null}
var progress = ef.getVideoProgress();
console.log(progress.currentTime, progress.duration, progress.percent);
```

***

## Cart

### ef.onAddToCart(callback)

Runs your **callback** whenever a product is added to the cart. It fires for the client-side cart (`ef.addToCart`, `.ef-add-to-cart` elements, and cart **+ / −** buttons) **and** for `/b` buy links. Returns an **unsubscribe** function.

<Note>
  Post-purchase one-click upsells (`/ef-u`) are a separate purchase event and do **not** trigger this callback.
</Note>

<Tip>
  Safe to call from custom head code or a tracking script that runs before the page bundle finishes loading — early `ef.onAddToCart(...)` calls are queued and registered automatically once `window.ef` is ready.
</Tip>

The callback receives a single event object:

* **source** — `'cart'` or `'buy-link'`.
* **products** — array of `{ id, name, price, currency, brand, quantity }`.
* **cart** — full cart snapshot (client cart only; `undefined` for buy links).

```javascript theme={null}
var unsubscribe = ef.onAddToCart(function(event) {
  event.products.forEach(function(product) {
    console.log('Added:', product.id, product.price, 'from', event.source);

    // Example: fire a Meta Pixel AddToCart event
    // fbq('track', 'AddToCart', {
    //   content_ids: [product.id],
    //   value: product.price,
    //   currency: product.currency,
    // });
  });
});

// Stop listening later:
// unsubscribe();
```

The same payload is also dispatched as an `ef:add-to-cart` **CustomEvent** on `window`. This is handy for Google Tag Manager or pixels that listen for DOM events instead of calling `window.ef`:

```javascript theme={null}
window.addEventListener('ef:add-to-cart', function(e) {
  var products = (e.detail && e.detail.products) || [];
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({ event: 'add_to_cart', ecommerce: { items: products } });
});
```

<Note>
  `onAddToCart` fires only when an item is **added**. To react to any cart change (add, remove, quantity, clear), listen for the `cart-updated` window event instead — see the [Cart](/frontend-template-engine/cart) reference.
</Note>

***

## Client script examples

**Wait for an element, then continue:**

```javascript theme={null}
ef.waitForVisible('.order-confirmation', function() {
  ef.nextNode();
});
```

**Delay, then continue:**

```javascript theme={null}
ef.wait(2000, function() {
  ef.nextNode();
});
```

**Set a tag and continue:**

```javascript theme={null}
ef.addTag('completed_survey');
ef.nextNode();
```

**Branch in script (then continue when ready):**

```javascript theme={null}
if (ef.hasTag('saw_offer')) {
  ef.nextNode(); // go to next step
} else {
  ef.addTag('saw_offer');
  ef.waitForVisible('#offer-cta', function() {
    ef.nextNode();
  });
}
```

***

## Adding a tag from a click (no script)

Add `data-ef-add-tag="tagname"` to a link or button (or to a parent component with `data-cid`). When the user clicks, the tag is set automatically. Useful for "clicked CTA" or "opened modal" without a client script.

```html theme={null}
<button data-ef-add-tag="clicked_cta">Get started</button>
```

***

## Summary

| Method                                  | Description                                                                                                                                              |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ef.addTag(name, expirationMinutes?)`   | Set a tag. Omit or use `0` for session cookie; use a positive number for expiration in minutes.                                                          |
| `ef.hasTag(name)`                       | Returns whether the tag is set.                                                                                                                          |
| `ef.removeTag(name)`                    | Remove the tag.                                                                                                                                          |
| `ef.nextNode()`                         | Advance to the next step (use inside Client script).                                                                                                     |
| `ef.isVisible(selectorOrElement)`       | Returns true if the element is visible.                                                                                                                  |
| `ef.waitForVisible(selector, callback)` | Run callback when the element is visible; returns cancel.                                                                                                |
| `ef.wait(ms, callback)`                 | Run callback after ms milliseconds; returns cancel.                                                                                                      |
| `ef.videoProgressCheck(opts)`           | Returns true if video reached `opts.seconds` and/or `opts.percent`.                                                                                      |
| `ef.onCta(callback)`                    | Run callback when video CTA is shown; returns unsubscribe.                                                                                               |
| `ef.getVideoProgress()`                 | Returns `{ currentTime, duration, percent }` for the main video.                                                                                         |
| `ef.onAddToCart(callback)`              | Run callback when a product is added to the cart (client cart or `/b` buy links); returns unsubscribe. Also dispatches an `ef:add-to-cart` window event. |
