npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@behindthescenes/analytics

v0.0.12

Published

Browser analytics SDK for BTS external-site event tracking and attribution.

Downloads

1,464

Readme

@behindthescenes/analytics

Browser SDK for BTS external-site analytics. Track visitor behavior, capture conversion events, and maintain attribution across the customer journey.

Overview

The BTS Analytics SDK provides:

  • Automatic event tracking - Page views, clicks, form submissions, and content views captured without code changes
  • Conversion tracking - Standard e-commerce and lead events (purchase, checkout, sign_up, lead)
  • Cross-site attribution - Journey tokens preserve visitor context across domains
  • Attribution preservation - UTM parameters and click IDs captured and persisted
  • GoHighLevel integration - Contact form submissions directly to GHL
  • Google Analytics compatibility - Detects existing GA installations or loads tags in proxy mode

Installation

npm/yarn/pnpm/bun

npm install @behindthescenes/analytics
pnpm add @behindthescenes/analytics
yarn add @behindthescenes/analytics
bun add @behindthescenes/analytics

Browser Bundle (Hosted)

<script type="module">
  import { createBTSAnalytics } from "https://behindthescenes.com/sdk/analytics/latest/browser/browser.js";

  window.btsAnalytics = createBTSAnalytics({
    siteKey: "your-public-site-key"
  });
</script>

Quick Start

import { createBTSAnalytics } from "@behindthescenes/analytics";

const analytics = createBTSAnalytics({
  siteKey: "your-public-site-key",
});

Setup in BTS

Before using the SDK, configure your site in the BTS platform:

1. Get Your Site Key

  • In BTS, navigate to your creator dashboard
  • Go to Settings > Website Integration
  • Copy your Public Site Key

2. Configure GoHighLevel (Optional)

For contact form submissions:

  • Go to Settings > Integrations > GoHighLevel
  • Connect your GHL account
  • Note your Location ID for use with submitContactForm()

3. Enable External Site Tracking

Ensure your domain is added to the allowed domains list in your BTS space settings.

Framework Setup

React

// BTSAnalyticsProvider.tsx
import { createBTSAnalytics, BTSAnalytics } from "@behindthescenes/analytics";
import { createContext, PropsWithChildren, useContext, useMemo } from "react";

const BTSAnalyticsContext = createContext<BTSAnalytics | null>(null);

export function BTSAnalyticsProvider({ children }: PropsWithChildren) {
  const analytics = useMemo(() =>
    createBTSAnalytics({
      siteKey: "your-public-site-key",
      autoPageviews: true,
    }),
    []
  );

  return (
    <BTSAnalyticsContext.Provider value={analytics}>
      {children}
    </BTSAnalyticsContext.Provider>
  );
}

export function useBTSAnalytics() {
  const analytics = useContext(BTSAnalyticsContext);
  if (!analytics) throw new Error("useBTSAnalytics must be used inside BTSAnalyticsProvider");
  return analytics;
}

Usage:

const analytics = useBTSAnalytics();
analytics.track("cta_clicked", { placement: "hero" });

Next.js

// app/providers.tsx
"use client";

import { createBTSAnalytics, BTSAnalytics } from "@behindthescenes/analytics";
import { createContext, PropsWithChildren, useContext, useMemo } from "react";

const BTSAnalyticsContext = createContext<BTSAnalytics | null>(null);

export function Providers({ children }: PropsWithChildren) {
  const analytics = useMemo(() =>
    createBTSAnalytics({
      siteKey: "your-public-site-key",
      autoPageviews: true,
    }),
    []
  );

  return (
    <BTSAnalyticsContext.Provider value={analytics}>
      {children}
    </BTSAnalyticsContext.Provider>
  );
}

export function useBTSAnalytics() {
  const analytics = useContext(BTSAnalyticsContext);
  if (!analytics) throw new Error("useBTSAnalytics must be used inside Providers");
  return analytics;
}

Remix

// app/root.tsx
import { createBTSAnalytics } from "@behindthescenes/analytics";
import { useEffect, useMemo } from "react";
import { Outlet, useLocation } from "react-router";

export const analytics = createBTSAnalytics({
  siteKey: "your-public-site-key",
  autoPageviews: false,
});

export function Layout() {
  const location = useLocation();
  const path = useMemo(() => location.pathname + location.search, [location]);

  useEffect(() => {
    analytics.page(path);
  }, [path]);

  return <Outlet />;
}

Vue

// src/plugins/bts-analytics.ts
import { createBTSAnalytics } from "@behindthescenes/analytics";
import type { App } from "vue";

export const analytics = createBTSAnalytics({
  siteKey: "your-public-site-key",
  autoPageviews: true,
});

export function installBTSAnalytics(app: App) {
  app.provide("btsAnalytics", analytics);
}
<script setup lang="ts">
import { inject } from "vue";
import type { BTSAnalytics } from "@behindthescenes/analytics";

const analytics = inject<BTSAnalytics>("btsAnalytics")!;

function trackEvent() {
  analytics.track("button_clicked", { buttonId: "cta" });
}
</script>

Plain JavaScript

<script async src="https://api.bts.it.com/v2/website/analytics/sdk/analytics/latest/browser/browser.js"></script>
<script>
  window.btsDataLayer = window.btsDataLayer || [];
  function bts(){window.btsDataLayer.push(arguments);}
  bts("js", new Date());
  bts("config", "your-public-site-key", {
    autoPageviews: true
  });
</script>

See the docs folder for complete framework guides:

Auto-Tracked Events

The SDK automatically captures these events without additional code:

| Event | Trigger | Description | |-------|---------|-------------| | page_view | Page load, SPA navigation | Initial load and history changes | | outbound_click | External link clicks | Links to different origins | | button_click | Button interactions | button, [role="button"], or [data-bts-track-click] | | form_submit | Form submissions | Any form submit event | | search | GET search forms | Forms with query params q, query, or search | | view_content | Element visibility | Elements with data-bts-view-content |

Content View Tracking

Opt-in to automatic content-view tracking by marking elements:

<article
  data-bts-view-content="offer_123"
  data-bts-content-type="offer"
  data-bts-content-title="Creator Accelerator"
>
  Creator Accelerator
</article>

Standard Events

Use trackStandard() for built-in conversion and engagement events:

// Engagement
analytics.trackStandard("page_view");
analytics.trackStandard("view_content", { contentId: "offer-42" });
analytics.trackStandard("search", { query: "pricing" });

// Lead & Account
analytics.trackStandard("lead", { formId: "newsletter" });
analytics.trackStandard("sign_up", { method: "email" });

// E-commerce
analytics.trackStandard("begin_checkout", { checkoutId: "offer_123" });
analytics.trackStandard("add_payment_info", { checkoutId: "offer_123" });
analytics.trackStandard("purchase", {
  orderId: "order_123",
  monetaryValue: 149,
  currency: "USD"
});

// Engagement
analytics.trackStandard("outbound_click", { url: "https://partner.com" });
analytics.trackStandard("button_click", { buttonId: "cta-primary" });
analytics.trackStandard("form_submit", { formId: "contact" });

Legacy Event Aliases

These aliases are automatically normalized to standard events:

| Legacy Name | Standard Event | |-------------|----------------| | $pageview | page_view | | checkout_started | begin_checkout | | lead_capture | lead | | purchase_completed | purchase | | registration_complete | sign_up |

Custom Events

Track arbitrary events with custom properties:

analytics.track("video_played", {
  videoId: "intro-01",
  duration: 120,
  autoplay: false
});

analytics.track("cta_clicked", {
  ctaId: "hero-primary",
  placement: "hero",
  variant: "blue"
});

User Identification

Associate events with a known user:

analytics.identify("user_123", {
  email: "[email protected]",
  name: "John Doe",
  plan: "pro"
});

Contact Form Submission

Submit contact enquiries directly to GoHighLevel:

await analytics.submitContactForm({
  locationId: "your-ghl-location-id",
  email: "[email protected]",
  subject: "Partnership enquiry",
  body: "Tell us more about working together.",
  name: "Jane Smith",
  source: "website_contact",
  tags: ["partnership", "high-value"],
  customFields: {
    company: "Acme Inc"
  },
  metadata: {
    page: window.location.pathname,
    referrer: document.referrer
  }
});

Required Fields

  • locationId: Your GoHighLevel location ID
  • At least one of email or phone

Optional Fields

  • subject, body: Message content (stored as bts_meta_subject and bts_meta_body)
  • name, firstName, lastName: Contact identity
  • source: Defaults to behind_the_scenes
  • tags: Array of GHL tags to attach
  • customFields: Direct GHL custom field mappings
  • metadata: Additional context forwarded as bts_meta_<key> fields

Journey Tracking (Cross-Site Attribution)

Track visitors across external sites and BTS properties:

// Start a funnel journey
const { journeyToken, journeyId, expiresAt } = await analytics.startFunnel("/landing");

// Decorate BTS URLs with journey context
const checkoutUrl = analytics.decorateUrl(
  "https://behindthescenes.com/checkout",
  journeyToken
);

// Get the persisted token for later use
const token = analytics.getPersistedJourneyToken();

// Notify handoff after visitor reaches BTS
await analytics.notifyHandoff(journeyToken, {
  source: "external_landing_page",
  campaign: "summer_2024"
});

Configuration Options

const analytics = createBTSAnalytics({
  siteKey: "required-site-key",

  // Auto-tracking configuration
  autoTrack: {
    pageviews: true,        // Initial page_view + SPA navigation
    history: true,          // pushState/replaceState hooks
    outboundLinks: true,    // External link clicks
    buttonClicks: true,     // Button interactions
    formSubmissions: true,  // Form submit events
    search: true,           // GET search forms
    viewContent: true       // IntersectionObserver content views
  },

  // Or disable all auto-tracking
  autoTrack: false,

  // Legacy option: disable pageviews (also disables history hooks)
  autoPageviews: false,

  // Debug mode (logs to console)
  debug: false,

  // Flush interval in milliseconds (default: 300ms)
  flushIntervalMs: 300,

  // Google Analytics integration
  googleAnalytics: {
    loadTag: true,
    measurementId: "G-XXXXXXXXXX"
  },

  // Custom request signing
  requestHeaders: async ({ bodyText, path, headers }) => {
    return {
      ...headers,
      "X-Signature": await signPayload(bodyText)
    };
  }
});

Auto-Tracking Behavior

  • autoTrack: false - Disables all automatic tracking
  • autoPageviews: false - Disables initial pageview and SPA navigation
  • Setting pageviews: false in autoTrack object also disables history hooks
  • Disabling pageviews prevents duplicate tracking when implementing custom pageview logic

Google Analytics Integration

Detection Mode (Default)

The SDK automatically detects existing GA installations and includes context:

  • ga_client_id: From _ga cookie
  • ga_tag_installed: Boolean if gtag detected
  • ga_measurement_id: From script tag

Proxy Mode

Load GA tag for scanner visibility while forwarding events server-side:

const analytics = createBTSAnalytics({
  siteKey: "your-site-key",
  googleAnalytics: {
    loadTag: true,
    measurementId: "G-XXXXXXXXXX"
  }
});

This loads the Google tag with send_page_view: false so BTS can forward events while GA scanners detect an installed tag.

Attribution & UTM Parameters

The SDK automatically captures and persists:

UTM Parameters:

  • utm_source, utm_medium, utm_campaign, utm_term, utm_content

Click IDs:

  • fbclid, gclid, gbraid, wbraid
  • li_fat_id, msclkid, ttclid

Cookies:

  • _fbp, _fbc (Facebook Pixel)

Attribution data is stored in localStorage and attached to every event, preserving the original landing URL and referrer across the session.

Request Signing

Add custom headers for request validation:

const analytics = createBTSAnalytics({
  siteKey: "your-site-key",
  requestHeaders: async ({ bodyText, headers, path }) => {
    const timestamp = new Date().toISOString();
    const signature = await signAnalyticsPayload(`${timestamp}:${path}:${bodyText}`);

    return {
      ...headers,
      "X-BTS-Analytics-Timestamp": timestamp,
      "X-BTS-Analytics-Signature": signature,
    };
  },
});

When requestHeaders is configured, the SDK uses fetch(..., { keepalive: true }) instead of sendBeacon for page-unload events, enabling the same retry semantics for all requests.

Flush Reliability

The SDK queues events and flushes them in batches:

  • Automatic flush every 300ms (configurable)
  • Immediate flush when queue reaches 50 events
  • Failed batches are requeued and retried
  • Page unload uses sendBeacon (or fetch with keepalive when custom headers are set)

Manual Flush

// Force immediate flush
await analytics.flushNow();

Cleanup

Remove event listeners and flush pending events:

analytics.destroy();

This:

  • Removes click, submit, and navigation listeners
  • Disconnects IntersectionObserver and MutationObserver
  • Flushes any pending events
  • Prevents further event tracking

API Reference

Methods

| Method | Description | |--------|-------------| | track(eventName, properties?) | Track a custom event | | trackStandard(eventName, properties?) | Track a standard event | | identify(userId, traits?) | Identify the current user | | page(path?) | Manually trigger a page view | | startFunnel(entryPath?) | Start a cross-site journey | | decorateUrl(url, journeyToken?) | Append site key and journey to URL | | getPersistedJourneyToken() | Get stored journey token | | notifyHandoff(journeyToken, context?) | Notify BTS of journey handoff | | submitContactForm(input) | Submit to GoHighLevel | | flushNow() | Immediately flush event queue | | listStandardEvents() | Get list of standard event names | | destroy() | Cleanup and remove listeners |

Types

type BTSAnalyticsStandardEventName =
  | "page_view"
  | "view_content"
  | "search"
  | "lead"
  | "sign_up"
  | "begin_checkout"
  | "add_payment_info"
  | "purchase"
  | "outbound_click"
  | "button_click"
  | "form_submit";

type BTSAnalyticsEventType = "page_view" | "custom" | "identify" | "conversion";

Releases

Publishing the npm package bumps the version with standard semver release types:

  • patch: Bug fixes and backwards-compatible corrections (e.g., 1.2.3 to 1.2.4)
  • minor: Backwards-compatible features (e.g., 1.2.3 to 1.3.0)
  • major: Breaking changes (e.g., 1.2.3 to 2.0.0)

Run the GitHub Actions release workflow manually and choose the release type to publish the next package version. The workflow reads the latest published npm version, increments it, validates the package, publishes it, and commits the updated package.json version.

For local publishing, set the release type before running the publish script:

RELEASE_TYPE=minor bun run release:publish