# Widget events

> Listen for what the widget does, from actions shown to CTA clicks, dismissals and redirects, and forward them to your own analytics.

## `onReady`

```ts
GeoEngage.onReady(callback: (ge: GeoEngage) => void): void
```

Calls `callback` once, when the widget has started and applied your rules, and hands it `GeoEngage` itself: the same object as `window.GeoEngage`. If the widget is already ready, it runs straight away, so it can't be missed however late your code asks. It always runs just after the `onReady(...)` line rather than inside it, so the code you write after that line runs first either way.

```js
GeoEngage.onReady((ge) => {
  const { country } = ge.getLocation();
  console.log(`GeoEngage ${ge.version} for a visitor in ${country ?? 'an unknown country'}`);
});
```

- `getLocation()` is never `null` here. When nobody could place the visitor, it returns `{ source: 'unknown', timezone }`.
- In consent mode it runs once the visitor has accepted, and not at all if they don't.
- It runs once per page load. In a single-page app a route change doesn't run it again; [`geoengage:route-change`](#dom-events) is for that.
- A callback that throws doesn't stop the others, or the widget. With `data-debug="true"` the error is logged.

## `onAction`

```ts
GeoEngage.onAction(callback): () => void
GeoEngage.onAction(type: string, callback): () => void
GeoEngage.onAction(filter: { type?, rule?, actionType? }, callback): () => void
```

Calls `callback` whenever a rule's action does something: appears, gets clicked, closes. Narrow it down by event type, by a rule's name as it appears in your dashboard, by action type, or any combination. Returns a function that stops listening.

```js
// Everything
GeoEngage.onAction((event) => console.log(event.type, event.detail));

// One event type
GeoEngage.onAction('cta-click', ({ detail }) => {
  console.log(`${detail.ruleName}: ${detail.ctaText}`);
});

// One rule, by its name in the dashboard
GeoEngage.onAction({ rule: 'Black Friday popup' }, ({ type, detail }) => {
  if (type === 'rating-submitted') console.log('Rated', detail.ratingValue);
});

// Stop listening
const stop = GeoEngage.onAction({ type: 'dismissed', actionType: 'show_popup' }, handleDismiss);
stop();
```

> [!TIP]
> Subscribe in your start-up code, right after the tag, to hear the first page load as well. A subscription made before the widget has loaded is handed to it before any rule runs. One made later hears only what happens from then on.

### Event types

| `type` | When |
| --- | --- |
| `shown` | Something appears: a popup, banner, sticky bar, notification, rating, countdown and so on. |
| `cta-click` | The visitor clicks the main button. |
| `secondary-cta-click` | The visitor clicks the secondary button. |
| `dismissed` | The visitor closes it, with the close button, a click outside it or Escape. |
| `auto-closed` | It closes itself when its display time runs out. |
| `rating-submitted` | The visitor submits a rating. |
| `redirect` | A *Redirect to URL* action is about to leave the page. Fired before leaving, so your own tracking still runs. |
| `replaced-text` | A *Replace Text* action changed an element's text. |
| `swapped-image` | A *Swap Image* action changed an image. |
| `inserted-html` | An *Insert HTML* action added content. |
| `set-class` | A *Set Class* action changed an element's classes. |
| `set-style` | A *Set Style* action changed an element's styles. |
| `form-prefilled` | A *Prefill Form* action filled in form fields. |
| `hidden-element` | A *Hide Element* action hid an element. |
| `custom-js-executed` | A *Custom JavaScript* action ran. |

### The event object

```js
{
  type: 'cta-click',
  detail: {
    ruleId: '3f1c2a…',
    ruleName: 'Black Friday popup',
    actionType: 'show_popup',
    variant: null,
    timestamp: 1767225600000,
    // Extra fields for this type:
    ctaText: 'Shop now',
    ctaUrl: 'https://example.com/sale',
  },
}
```

`variant` is the variant's name for a rule with variants, and `null` otherwise. Some types add fields:

| Type | Extra fields |
| --- | --- |
| `cta-click`, `secondary-cta-click` | `ctaText`, and `ctaUrl` when the button links somewhere |
| `rating-submitted` | `ratingValue`, `ratingType`, `maxValue`, `feedback` |
| `redirect` | `redirectUrl` |
| `inserted-html` | `target`, `position` |
| `form-prefilled` | `target`, `filled` (how many fields were filled) |
| `replaced-text`, `swapped-image`, `set-class`, `set-style`, `hidden-element` | `target`, the element's selector |

The compliance banner's **Accept** button is a `cta-click` with `consent: 'accepted'`.

### Sending actions to Google Analytics

Forward every action to Google Tag Manager's data layer, from the first page load on:

```html
<script>
  window.addEventListener('geoengage:action', (event) => {
    const { type, detail } = event.detail;
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
      event: `geoengage_${type.replace(/-/g, '_')}`,
      geoengage_rule: detail.ruleName,
      geoengage_action: detail.actionType,
    });
  });
</script>
```

## `onEvent`

```ts
GeoEngage.onEvent(callback: (event) => void): () => void
```

Calls `callback` with every analytics event the widget records: page views, clicks, scroll depth, your custom events. It fires often, so keep the callback light. Returns a function that stops listening.

## `onExitIntent`

```ts
GeoEngage.onExitIntent(callback: () => void): () => void
```

Calls `callback` when the visitor's pointer leaves the window through the top edge, the usual sign that they're heading for another tab or the address bar. Useful for a last-chance offer from your own code rather than from a rule. Desktop only: phones have no pointer to leave. Returns a function that stops listening.

## DOM events

The widget also dispatches these events on `window`, for tools that can't call the methods, such as a tag manager, and for code that runs before the tag. An event only reaches the listeners that exist when it happens: a `geoengage:ready` listener added after the widget is ready never runs. Use the methods above wherever you can.

| Event | `event.detail` | When |
| --- | --- | --- |
| `geoengage:ready` | `GeoEngage` itself, as `onReady` gets it | The widget has started and applied your rules. |
| `geoengage:action` | `{ type, detail }` | The same events as `onAction`. |
| `geoengage:event` | The event | The same events as `onEvent`. |
| `geoengage:exit-intent` | — | The same moment as `onExitIntent`. |
| `geoengage:route-change` | `{ url }` | A single-page-app route change was handled. |
| `geoengage:awaiting-consent` | `{ version }` | Consent mode is waiting for the visitor. |
| `geoengage:consent-unavailable` | `{ reason, version }` | Consent mode has no way to ask. See [Consent mode](https://geoengage.app/docs/widget/consent#when-consent-cant-be-asked). |
| `geoengage:consent-granted` | `{ version }` | `grantConsent()` has started the widget. |
| `geoengage:consent-revoked` | `{ version }` | `revokeConsent()` was called. |

```js
// In a script that may run before the tag. Anywhere else, use GeoEngage.onReady.
window.addEventListener('geoengage:ready', (event) => {
  const ge = event.detail; // GeoEngage itself
  console.log(`GeoEngage ${ge.version} is running for a visitor in ${ge.getLocation().country ?? 'an unknown country'}`);
});
```

---

Source: https://geoengage.app/docs/widget/events
