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

# Custom Events

> Track specific user actions on your website using scanova('track', ...) — button clicks, sign-ups, video plays, and any other interaction you care about.

Custom events let you track specific user actions that matter to your business — beyond what auto-tracking captures. You define the event name and the metadata, and the SDK sends it with full QR attribution attached automatically.

## Syntax

```javascript theme={null}
scanova('track', 'event_name', { key: 'value' });
```

* **`event_name`** — a string that describes the action. Use `snake_case`.
* **`metadata`** — an optional object with any key-value data relevant to the event. Max 10 KB.

You can call `scanova('track', ...)` at any point after `scanova('init', ...)` runs — from event listeners, async callbacks, or any other JavaScript context.

## Examples

### Button click

Track when a user clicks a specific call-to-action:

```javascript theme={null}
document.getElementById('start-trial-btn').addEventListener('click', () => {
  scanova('track', 'cta_click', {
    button_text: 'Start Free Trial',
    section: 'hero',
    plan: 'pro'
  });
});
```

### Sign-up completed

Send this after your sign-up form submission is confirmed (client-side):

```javascript theme={null}
scanova('track', 'signup_completed', {
  method: 'email',
  plan: 'free'
});
```

<Note>
  For purchase or sign-up confirmations that happen **server-side**, use [Server Events](/conversion-tracking/server/send-events) instead. Server events are more reliable and cannot be blocked by browser extensions.
</Note>

### Video engagement

```javascript theme={null}
video.addEventListener('play', () => {
  scanova('track', 'video_play', { video_id: 'intro-tour' });
});

video.addEventListener('ended', () => {
  scanova('track', 'video_complete', { video_id: 'intro-tour' });
});
```

### Product interaction

```javascript theme={null}
document.querySelectorAll('.product-card').forEach(card => {
  card.addEventListener('click', () => {
    scanova('track', 'product_click', {
      product_id: card.dataset.productId,
      product_name: card.dataset.productName,
      position: card.dataset.position
    });
  });
});
```

### Tab or accordion interaction

```javascript theme={null}
document.querySelectorAll('.tab-btn').forEach(btn => {
  btn.addEventListener('click', () => {
    scanova('track', 'tab_click', { tab_name: btn.textContent.trim() });
  });
});
```

### Link click to external site

```javascript theme={null}
document.querySelectorAll('a[href^="http"]').forEach(link => {
  link.addEventListener('click', () => {
    scanova('track', 'external_link_click', { destination: link.href });
  });
});
```

## Choosing between custom events and auto-tracking

| Situation                                                | Use                     |
| -------------------------------------------------------- | ----------------------- |
| Track all link and button clicks with minimal setup      | `autoClicks: true`      |
| Track a specific button with custom metadata             | `scanova('track', ...)` |
| Track all form submissions                               | `autoForms: true`       |
| Track a specific form with field-level context (non-PII) | `scanova('track', ...)` |
| Any interaction the auto-tracker does not cover          | `scanova('track', ...)` |

You can use both together — `autoClicks: true` plus selective custom events for high-value actions.

## Event naming best practices

* Use `snake_case`: `cta_click`, not `ctaClick` or `CTAClick`
* Be specific enough to be self-explanatory in reports: `signup_completed` is better than `completed`
* Be consistent: pick one name and stick to it. Changing event names later fragments your historical data
* Do not include user-identifying information in the event name

## Metadata best practices

* Keep metadata flat where possible — deeply nested objects are harder to query
* Max object size: **10 KB**
* Max nesting depth: **5 levels**
* **Never include raw email addresses, phone numbers, or other PII** — use server events with `user_identifiers` for that
* Use stable keys — changing key names later fragments your data

```javascript theme={null}
// Good
scanova('track', 'download_click', {
  file_name: 'product-guide.pdf',
  section: 'resources'
});

// Avoid
scanova('track', 'download_click', {
  user_email: 'john@example.com',  // never include PII
  data: { nested: { too: { deep: { for: 'queries' } } } }
});
```

## When to use server events instead

Use [Server-Side Events](/conversion-tracking/server/send-events) rather than custom browser events when:

* The action happens on your server (payment confirmation, CRM creation, email verification)
* You need to pass a conversion value (order total, subscription price)
* You want to include hashed user identifiers for identity matching
* You need guaranteed delivery (server events cannot be blocked by ad blockers)
