Skip to main content
Free20 minutesIntermediate

Custom Framework Integration

Integrate Zenovay with any JavaScript framework - vanilla JS, Svelte, Angular, and custom implementations. Explore integration setup and best practices.

integrationjavascriptcustomvanilla-jssvelte
Last updated:

Integrate Zenovay analytics into any JavaScript framework or vanilla JavaScript application using our flexible tracking API.

Universal Script Installation

Basic Script Tag

Works with any framework:

<script
  defer
  data-tracking-code="YOUR_TRACKING_CODE"
  src="https://api.zenovay.com/z.js"
></script>

Script Attributes

AttributeDescription
data-tracking-codeRequired. Your website tracking code
deferRecommended. Load script without blocking page render

Standard Configuration

<script
  defer
  data-tracking-code="YOUR_TRACKING_CODE"
  src="https://api.zenovay.com/z.js"
></script>

Vanilla JavaScript

Basic Usage

// Track an event
window.zenovay('track', 'button_click', {
  button_id: 'signup',
  location: 'hero'
});

// Track page view (if auto-track disabled)
window.zenovay('page');

// Identify a user
window.zenovay('identify', 'user-123', {
  email: '[email protected]',
  plan: 'pro'
});

// Track a goal
window.zenovay('goal', 'purchase', {
  value: 99.99
});

// Track revenue
window.zenovay('revenue', 149.99, 'USD', {
  order_id: 'ORD-001'
});

Wait for Script Load

function waitForZenovay(callback, maxWait = 5000) {
  const startTime = Date.now();

  function check() {
    if (window.zenovay) {
      callback(window.zenovay);
    } else if (Date.now() - startTime < maxWait) {
      requestAnimationFrame(check);
    }
  }

  check();
}

// Usage
waitForZenovay((zenovay) => {
  zenovay('track', 'app_ready');
});

Event Queue Pattern

// Queue function (works before script loads)
window.zenovay = window.zenovay || function() {
  (window.zenovay.q = window.zenovay.q || []).push(arguments);
};

// Now you can call zenovay() immediately, even before the script loads
window.zenovay('track', 'early_event', { source: 'inline' });
window.zenovay('page');

Svelte Integration

Setup in App.svelte

<script>
  import { onMount } from 'svelte';
  import { page } from '$app/stores';

  onMount(() => {
    // Track initial page view
    if (window.zenovay) {
      window.zenovay('page');
    }
  });

  // Track route changes
  $: if ($page && window.zenovay) {
    window.zenovay('page');
  }
</script>

Svelte Store

// stores/analytics.js
import { writable } from 'svelte/store';

function createAnalyticsStore() {
  const { subscribe } = writable(null);

  return {
    subscribe,
    track: (event, data) => {
      if (typeof window !== 'undefined' && window.zenovay) {
        window.zenovay('track', event, data);
      }
    },
    identify: (userId, properties) => {
      if (typeof window !== 'undefined' && window.zenovay) {
        window.zenovay('identify', userId, properties);
      }
    },
    trackGoal: (name, value) => {
      if (typeof window !== 'undefined' && window.zenovay) {
        window.zenovay('goal', name, { value });
      }
    }
  };
}

export const analytics = createAnalyticsStore();

Svelte Component Usage

<script>
  import { analytics } from '../stores/analytics';

  function handleClick() {
    analytics.track('button_click', { button: 'cta' });
  }
</script>

<button on:click={handleClick}>
  Click Me
</button>

SvelteKit Integration

// hooks.client.js
import { page } from '$app/stores';

page.subscribe(($page) => {
  if (typeof window !== 'undefined' && window.zenovay && $page) {
    window.zenovay('page');
  }
});

Angular Integration

Service Creation

// analytics.service.ts
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';

declare global {
  interface Window {
    zenovay: {
      (command: 'track', event: string, data?: Record<string, any>): void;
      (command: 'page'): void;
      (command: 'goal', name: string, data?: Record<string, any>): void;
      (command: 'revenue', amount: number, currency: string, meta?: Record<string, any>): void;
      (command: 'identify', userId: string, properties?: Record<string, any>): void;
    };
  }
}

@Injectable({
  providedIn: 'root'
})
export class AnalyticsService {
  constructor(private router: Router) {
    this.setupRouteTracking();
  }

  private setupRouteTracking() {
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd)
    ).subscribe((event: NavigationEnd) => {
      this.trackPageview(event.urlAfterRedirects);
    });
  }

  track(event: string, data?: Record<string, any>) {
    if (window.zenovay) {
      (window as any).zenovay('track', event, data);
    }
  }

  trackPageview() {
    if (window.zenovay) {
      (window as any).zenovay('page');
    }
  }

  trackGoal(name: string, value?: number) {
    if (window.zenovay) {
      (window as any).zenovay('goal', name, { value });
    }
  }

  identify(userId: string, properties?: Record<string, any>) {
    if (window.zenovay) {
      (window as any).zenovay('identify', userId, properties);
    }
  }
}

Component Usage

// signup.component.ts
import { Component } from '@angular/core';
import { AnalyticsService } from './analytics.service';

@Component({
  selector: 'app-signup',
  template: `<button (click)="handleSignup()">Sign Up</button>`
})
export class SignupComponent {
  constructor(private analytics: AnalyticsService) {}

  handleSignup() {
    this.analytics.track('signup_click', {
      source: 'header'
    });
  }
}

Module Setup

// app.module.ts
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { AnalyticsService } from './analytics.service';

export function initAnalytics(analytics: AnalyticsService) {
  return () => {
    // Initialize tracking
  };
}

@NgModule({
  providers: [
    AnalyticsService,
    {
      provide: APP_INITIALIZER,
      useFactory: initAnalytics,
      deps: [AnalyticsService],
      multi: true
    }
  ]
})
export class AppModule {}

Ember.js Integration

Service Creation

// app/services/analytics.js
import Service from '@ember/service';
import { inject as service } from '@ember/service';

export default class AnalyticsService extends Service {
  @service router;

  constructor() {
    super(...arguments);
    this.setupRouteTracking();
  }

  setupRouteTracking() {
    this.router.on('routeDidChange', () => {
      this.trackPageview(this.router.currentURL);
    });
  }

  track(event, data) {
    if (window.zenovay) {
      window.zenovay('track', event, data);
    }
  }

  trackPageview() {
    if (window.zenovay) {
      window.zenovay('page');
    }
  }

  identify(userId, properties) {
    if (window.zenovay) {
      window.zenovay('identify', userId, properties);
    }
  }
}

Alpine.js Integration

<div x-data="{ analytics: $store.analytics }">
  <button @click="analytics.track('click', { button: 'cta' })">
    Click Me
  </button>
</div>

<script>
document.addEventListener('alpine:init', () => {
  Alpine.store('analytics', {
    track(event, data) {
      if (window.zenovay) {
        window.zenovay('track', event, data);
      }
    },
    identify(userId, properties) {
      if (window.zenovay) {
        window.zenovay('identify', userId, properties);
      }
    }
  });
});
</script>

jQuery Integration

// Initialize tracking helper
$.zenovay = {
  track: function(event, data) {
    if (window.zenovay) {
      window.zenovay('track', event, data);
    }
  },
  identify: function(userId, properties) {
    if (window.zenovay) {
      window.zenovay('identify', userId, properties);
    }
  }
};

// Usage
$(document).ready(function() {
  $('#signup-btn').click(function() {
    $.zenovay.track('signup_click', {
      button: $(this).data('name')
    });
  });
});

Web Components

// analytics-tracker.js
class AnalyticsTracker extends HTMLElement {
  connectedCallback() {
    this.addEventListener('click', this.handleClick.bind(this));
  }

  handleClick() {
    const event = this.getAttribute('data-event');
    const dataAttrs = {};

    for (const attr of this.attributes) {
      if (attr.name.startsWith('data-prop-')) {
        const key = attr.name.replace('data-prop-', '');
        dataAttrs[key] = attr.value;
      }
    }

    if (window.zenovay && event) {
      window.zenovay('track', event, dataAttrs);
    }
  }
}

customElements.define('analytics-tracker', AnalyticsTracker);

Usage:

<analytics-tracker
  data-event="cta_click"
  data-prop-button="hero"
  data-prop-page="home"
>
  <button>Get Started</button>
</analytics-tracker>

Single Page Application (SPA) Patterns

History API Tracking

// Track history changes
(function() {
  const pushState = history.pushState;
  const replaceState = history.replaceState;

  history.pushState = function() {
    pushState.apply(history, arguments);
    trackPageChange();
  };

  history.replaceState = function() {
    replaceState.apply(history, arguments);
    trackPageChange();
  };

  window.addEventListener('popstate', trackPageChange);

  function trackPageChange() {
    if (window.zenovay) {
      // Small delay to ensure DOM updated
      setTimeout(() => {
        window.zenovay('page');
      }, 100);
    }
  }
})();

Hash-Based Routing

window.addEventListener('hashchange', function() {
  if (window.zenovay) {
    window.zenovay('page');
  }
});

TypeScript Definitions

// types/zenovay.d.ts
interface ZenovayEventData {
  [key: string]: string | number | boolean | undefined;
}

interface ZenovayRevenueData {
  order_id: string;
  value: number;
  currency?: string;
  items?: Array<{
    id: string;
    name: string;
    price: number;
    quantity: number;
  }>;
}

interface Zenovay {
  (command: 'track', event: string, data?: ZenovayEventData): void;
  (command: 'page'): void;
  (command: 'goal', name: string, data?: { value?: number }): void;
  (command: 'revenue', amount: number, currency: string, meta?: Record<string, unknown>): void;
  (command: 'identify', userId: string, properties?: ZenovayEventData): void;
}

declare global {
  interface Window {
    zenovay?: Zenovay;
  }
}

export {};

Wrapper Library Pattern

Create a reusable wrapper:

// lib/analytics.ts
class Analytics {
  private queue: Array<() => void> = [];
  private ready = false;

  constructor() {
    this.waitForScript();
  }

  private waitForScript() {
    const check = () => {
      if (window.zenovay) {
        this.ready = true;
        this.processQueue();
      } else {
        requestAnimationFrame(check);
      }
    };
    check();
  }

  private processQueue() {
    this.queue.forEach(fn => fn());
    this.queue = [];
  }

  private execute(fn: () => void) {
    if (this.ready) {
      fn();
    } else {
      this.queue.push(fn);
    }
  }

  track(event: string, data?: Record<string, any>) {
    this.execute(() => (window.zenovay as any)('track', event, data));
  }

  pageview() {
    this.execute(() => (window.zenovay as any)('page'));
  }

  identify(userId: string, properties?: Record<string, any>) {
    this.execute(() => (window.zenovay as any)('identify', userId, properties));
  }

  goal(name: string, value?: number) {
    this.execute(() => (window.zenovay as any)('goal', name, { value }));
  }

  revenue(amount: number, currency: string, meta?: Record<string, any>) {
    this.execute(() => (window.zenovay as any)('revenue', amount, currency, meta));
  }
}

export const analytics = new Analytics();

Testing Your Integration

Debug Mode

Turn on debug logging to see tracker activity in the browser console. There are three ways to enable it:

<!-- 1. Add the data-debug attribute to the script tag -->
<script
  defer
  data-tracking-code="YOUR_TRACKING_CODE"
  data-debug="true"
  src="https://api.zenovay.com/z.js"
></script>
2. Append ?zenovay_debug=true to any page URL (handy for production).
3. Debug logging is on automatically when running on localhost.

With debug enabled, the tracker logs each event it sends to the console.

Verification Steps

  1. Open browser DevTools
  2. Go to Network tab
  3. Filter by "zenovay" or "analytics"
  4. Perform actions
  5. Verify requests sent

Test Event

// Quick test
if (window.zenovay) {
  window.zenovay('track', 'test_event', {
    test: true,
    timestamp: Date.now()
  });
  console.log('Test event sent');
} else {
  console.error('Zenovay not loaded');
}

Troubleshooting

Script Not Loading

Check:

  • Script URL correct
  • No ad blocker interference
  • Website ID valid
  • Network requests visible

Events Not Tracking

Verify:

  • window.zenovay exists
  • Data format correct
  • No JavaScript errors
  • Domain allowed

SPA Navigation Issues

Ensure:

  • Route changes detected
  • trackPageview called on navigation
  • No duplicate tracking

Next Steps

Was this article helpful?