Custom Events

Track signups, purchases, and any other action that matters with one function call.

The track() API

Import track from the same entry point you use to install the tracker:

any-client-component.tsxtsx
import { track } from 'produl-tracker/next'
// or 'produl-tracker/react', 'produl-tracker/vue', 'produl-tracker/remix'

track('cta_click', { location: 'hero', variant: 'blue' })

The signature is:

ts
track(eventName: string, properties?: Record<string, unknown>): void

Calls are buffered via a command queue (window.ma), so track() is safe to call before the tracker script has finished loading. Queued events flush as soon as the tracker is ready.

Framework examples

Next.js (App Router)

Mark the component as a Client Component and import track from produl-tracker/next.

components/SignupButton.tsxtsx
'use client'
import { track } from 'produl-tracker/next'

export default function SignupButton({ plan }: { plan: string }) {
  return (
    <button
      onClick={async () => {
        await createAccount(plan)
        track('signup', { plan })
      }}
    >
      Get started
    </button>
  )
}

React (Vite / CRA)

src/components/CheckoutButton.tsxtsx
import { track } from 'produl-tracker/react'

export default function CheckoutButton({ productId }: { productId: string }) {
  return (
    <button
      onClick={() => {
        track('add_to_cart', { product_id: productId })
        openCheckout(productId)
      }}
    >
      Buy now
    </button>
  )
}

Vue 3

Import track from produl-tracker/vue and call it anywhere in your component logic.

src/components/UpgradeButton.vuevue
<script setup lang="ts">
import { track } from 'produl-tracker/vue'

function handleUpgrade(plan: string) {
  track('upgrade_clicked', { plan })
  router.push('/checkout')
}
</script>

<template>
  <button @click="handleUpgrade('pro')">Upgrade to Pro</button>
</template>

Remix

app/routes/pricing.tsxtsx
import { track } from 'produl-tracker/remix'

export default function PricingPage() {
  return (
    <button
      onClick={() => track('pricing_cta_clicked', { section: 'hero' })}
    >
      Start free trial
    </button>
  )
}

Plain HTML (script tag)

When using the plain script tag, call window.ma.track() directly. The queue handles calls made before the script has loaded.

html
<!-- Queued call: safe even if called before tracker.min.js loads -->
<button onclick="window.ma('event', 'cta_click', { location: 'footer' })">
  Get started
</button>

<script>
  document.getElementById('upgrade-btn').addEventListener('click', function () {
    window.ma('event', 'upgrade_clicked', { plan: 'pro' })
  })
</script>

Script-tag queue

The plain-HTML snippet uses window.ma('event', name, props)as a command queue. Calls made before the tracker loads are buffered and flushed once it is ready. The npm SDK's track() export writes to the same queue, so both approaches are interchangeable.

Auto-tracked events

Produl automatically fires three event types without any extra code — they appear in your dashboard alongside your manual events.

File downloads — file_download

Fired whenever a visitor clicks a link that points to a downloadable file. Recognised extensions: .pdf, .zip, .doc, .docx, .xls, .xlsx, .csv, .pptx, .mp3, .mp4, .dmg, .exe.

file_download propertiesjson
{
  "url":      "https://example.com/docs/guide.pdf",
  "filename": "guide.pdf",
  "type":     "pdf"
}

Outbound clicks — outbound_click

Fired when a visitor clicks a link to an external domain (any hostname different from the current page).

outbound_click propertiesjson
{
  "url":  "https://github.com/your-org/repo",
  "text": "View on GitHub"
}

404 errors — 404

Fired 500 ms after page load if the page title contains the string "404" or "not found" (case-insensitive). Use this to monitor broken links without any backend instrumentation.

404 propertiesjson
{
  "path":     "/old/broken-url",
  "referrer": "https://external-site.com/article"
}

Auto-tracked events count against your plan quota

file_download, outbound_click, and 404 events are billed the same as manual events. If you have a high-traffic site with many outbound links, check your monthly usage in the dashboard.

Naming conventions

Event names are arbitrary strings, but dashboards group events by exact name — so consistency matters. We recommend:

  • snake_case: sign_up, cta_click, form_submit
  • Present tense verbs: subscribe, not subscribed
  • Noun + verb for context: video_played, invoice_paid

Renames split your history

Once an event name is live in production, renaming it means older data shows under the old name and newer data shows under the new name — they won't merge automatically.

Event properties

Properties are an optional second argument — any JSON-serializable object. Keys should be short and snake_case. Values can be strings, numbers, booleans, or nested objects/arrays.

ts
track('video_played', {
  video_id: 'onboarding-01',
  duration_seconds: 12,
  autoplay: false,
  user_plan: 'pro',
})

Properties are fully queryable in the Ask Produl natural-language interface and custom dashboards. For example: "How many pro users played the onboarding video this week?"

Do not include PII

Don't put email addresses, user IDs that identify real people, credit card numbers, or other PII in event properties. Use stable pseudonymous identifiers (plan names, cohort IDs, A/B test variants) instead.

Viewing events

Custom events appear in the Custom Events widget on your site dashboard. The widget shows each event name ranked by total count, with a secondary column showing how many unique visitors triggered it. The top 10 events are shown; use Ask Produl to query the full event list or filter by property values.

The widget is available on all plans. Events are grouped by exact name and aggregated for the selected date range — the same date picker that controls your pageview charts also controls the event counts.

Server-side events

Some events happen server-side — webhook deliveries, Stripe subscriptions, cron job outcomes. For those, use the Server SDK which POSTs events directly to /api/collect with an authenticated server key.

See Server-Side Tracking for install and auth details.

Best practices

  • Track outcomes, not implementation details. checkout_completed is more durable than click_stripe_submit_v2.
  • Keep property keys stable. Adding new keys is fine; renaming or removing them will break existing dashboards and alerts.
  • Use property values, not event names, for variants. Prefer track('signup', { plan: &quot;pro&quot; }) over track('signup_pro').
  • Don't over-track. You only have so many events per month on each plan. Track things you would plausibly make a decision from.
  • Verify with the debug overlay. Add debug to your <Analytics> component temporarily — it shows a live counter of events sent and lets you confirm each track() call fires correctly before deploying.