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

@g2crowd/buyer-intent-provider-sdk

v0.4.1

Published

Buyer intent tracking SDK with pageview defaults

Readme

Buyer Intent JS SDK

Buyer intent tracking for partner sites. Drop custom HTML elements into any page — no framework required. Events are sent via sendBeacon for reliable delivery during navigation.

npm install @g2crowd/buyer-intent-provider-sdk

Quick Start

Import the SDK, wrap your page in a session, and you're tracking:

<script type="module">
  import '@g2crowd/buyer-intent-provider-sdk';
</script>

<buyer-intent-session
  origin="yoursite.com"
  activity-endpoint="/api/activity/events"
>

  <buyer-intent-view tag="products.show">
    <buyer-intent-subject product-id="123"></buyer-intent-subject>

    <h1>Acme CRM</h1>
    <p>Product details here.</p>

    <buyer-intent-click event-name="/leads/create">
      <button>Get a Demo</button>
    </buyer-intent-click>
  </buyer-intent-view>

</buyer-intent-session>

That's it. When the page loads, a $view event fires with product_ids: [123] and tag: "products.show". When the user clicks "Get a Demo", a /leads/create event fires. Both events inherit the session's origin and activity-endpoint automatically.

Elements

<buyer-intent-session>

Wraps a page (or your entire app) and provides config to all elements inside it. Does not fire any events itself.

| Attribute | Description | | -------------------- | -------------------------------------------- | | origin | Partner hostname | | activity-endpoint | URL to POST events to | | user-type | Visitor role (default: "standard") | | distinct-id | Override the auto-generated visitor ID |

| User Type | Meaning | | ---------------- | ------------------------------------------------- | | guest | Logged-out visitor | | standard | Logged-in user (default) | | vendor-admin | Vendor role viewing their own content | | observer | Internal employee / superuser |

<buyer-intent-session origin="yoursite.com" activity-endpoint="/api/activity/events">
  <!-- everything inside inherits origin and endpoint -->
</buyer-intent-session>

<buyer-intent-view>

Fires a $view event when the element connects to the DOM. One event per page navigation.

| Attribute | Description | | ----------------- | ------------------------------------------- | | tag | View type identifier (optional) | | source-location | Controller/action string (optional) | | product-id | Single product ID (simple pages) | | category-id | Single category ID (simple pages) |

For pages with one product, you can put the ID directly on the view:

<buyer-intent-view tag="products.show" product-id="123">
  <h1>Acme CRM</h1>
</buyer-intent-view>

For pages with multiple products, use <buyer-intent-subject> children instead (see below).

<buyer-intent-subject>

Declares that a product or category is a subject of the current view. Nest any number of these inside a <buyer-intent-view> — their IDs accumulate into the view's event automatically. Does not render anything visible.

| Attribute | Description | | -------------- | ---------------------------------------------------- | | product-id | Single product ID | | product-ids | Multiple product IDs (JSON array or comma-separated) | | category-id | Single category ID | | category-ids | Multiple category IDs (JSON array or comma-separated)|

A comparison page that references two products:

<buyer-intent-view tag="comparisons.show">
  <buyer-intent-subject product-id="101"></buyer-intent-subject>
  <buyer-intent-subject product-id="201"></buyer-intent-subject>

  <h1>Acme CRM vs. Rival CRM</h1>
</buyer-intent-view>

The resulting event has product_ids: [101, 201]. If the view also has an inline product-id, they merge together without duplicates.

Subjects can live anywhere inside the view — they don't need to be direct children. This makes them easy to place next to the content they describe:

<buyer-intent-view tag="categories.show">
  <buyer-intent-subject category-id="45"></buyer-intent-subject>
  <h1>CRM Software</h1>

  <div class="product-grid">
    <div class="product-card">
      <buyer-intent-subject product-id="101"></buyer-intent-subject>
      <h2>Acme CRM</h2>
    </div>

    <div class="product-card">
      <buyer-intent-subject product-id="201"></buyer-intent-subject>
      <h2>Rival CRM</h2>
    </div>
  </div>
</buyer-intent-view>

This fires one event: category_ids: [45], product_ids: [101, 201], tag: "categories.show".

<buyer-intent-click>

Fires an event when the user clicks anything inside it.

| Attribute | Description | | ------------ | ------------------------ | | event-name | Click event type |

<buyer-intent-click event-name="/leads/create">
  <button>Get a Demo</button>
</buyer-intent-click>

A click element inside a view automatically inherits the view's tag, product IDs, and category IDs. A click inside a session inherits the session's config. You don't need to repeat anything.

Full Example

Putting it all together — a product page with a session, view, subjects, and two click actions:

<script type="module">
  import '@g2crowd/buyer-intent-provider-sdk';
</script>

<buyer-intent-session
  origin="yoursite.com"
  activity-endpoint="/api/activity/events"
  user-type="standard"
>

  <buyer-intent-view
    tag="products.show"
    source-location="ProductsController#show"
  >
    <buyer-intent-subject product-id="123"></buyer-intent-subject>
    <buyer-intent-subject category-id="45"></buyer-intent-subject>

    <h1>Acme CRM</h1>
    <p>The best CRM for small teams.</p>

    <buyer-intent-click event-name="/leads/create">
      <button>Get a Demo</button>
    </buyer-intent-click>

    <buyer-intent-click event-name="/ad/clicked">
      <a href="https://acme.example.com">Visit Acme</a>
    </buyer-intent-click>
  </buyer-intent-view>

</buyer-intent-session>

React Support

Import from /react and use the same element hierarchy with camelCase props:

import { BuyerIntent } from '@g2crowd/buyer-intent-provider-sdk/react';

export default function ProductPage() {
  return (
    <BuyerIntent.Session origin="yoursite.com" activityEndpoint="/api/activity/events">
      <BuyerIntent.View tag="products.show" sourceLocation="ProductsController#show">
        <BuyerIntent.Subject productId={123} />
        <BuyerIntent.Subject categoryId={45} />

        <h1>Acme CRM</h1>

        <BuyerIntent.Click eventName="/leads/create">
          <button>Get a Demo</button>
        </BuyerIntent.Click>
      </BuyerIntent.View>
    </BuyerIntent.Session>
  );
}

BuyerIntent.Session, BuyerIntent.View, BuyerIntent.Subject, and BuyerIntent.Click map 1:1 to the HTML elements described above. The session is a good fit for a layout component or _app wrapper so it's set once for every page.

Syntactic Sugar

For common page types, pre-built components hardcode the tag or event-name so you don't have to:

Views

| Component | Tag | Required Prop | | ----------------- | ----------------------- | ------------- | | ProfileView | products.show | productId | | PricingView | products.pricing | productId | | CompetitorsView | products.competitors | productId | | CategoryView | categories.show | categoryId | | CompareView | comparisons.show | productIds | | WriteReviewView | reviewers.take_survey | productId |

Clicks

| Component | Event Name | Required Prop | | ----------------- | --------------- | ------------- | | AdClick | /ad/clicked | productId | | LeadCreateClick | /leads/create | productId |

import { BuyerIntent } from '@g2crowd/buyer-intent-provider-sdk/react';

<BuyerIntent.Session origin="yoursite.com" activityEndpoint="/api/activity/events">
  <BuyerIntent.ProfileView productId={123}>
    <h1>Acme CRM</h1>

    <BuyerIntent.LeadCreateClick productId={123}>
      <button>Get a Demo</button>
    </BuyerIntent.LeadCreateClick>
  </BuyerIntent.ProfileView>
</BuyerIntent.Session>

These components still accept origin, activityEndpoint, sourceLocation, and context props for cases where you aren't using a session wrapper.

Backend

Next.js Route Handler

import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';

const POST = createNextRouteHandler({
  kafkaBrokers: [process.env.KAFKA_BROKER || 'localhost:9092'],
  partnerId: process.env.PARTNER_ID,
});

export { POST };

Standalone Server

PARTNER_ID=partner-a KAFKA_BROKERS=broker1:9092,broker2:9092 \
buyer-intent-server

Environment variables: PORT (default 3000), PARTNER_ID, KAFKA_BROKERS (comma-separated), TOPIC_PREFIX, KAFKA_TOPIC, KAFKA_CLIENT_ID, USE_DEV_LOGGER=true.

Custom Kafka Producer

import { Kafka } from 'kafkajs';
import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';

const kafka = new Kafka({
  clientId: 'partner-app',
  brokers: (process.env.KAFKA_BROKERS || '').split(','),
});

const POST = createNextRouteHandler({
  producer: kafka.producer({ allowAutoTopicCreation: false }),
  partnerId: process.env.PARTNER_ID,
});

export { POST };

Dev Adapter (No Kafka)

import {
  createNextRouteHandler,
  createDevLoggerProducer,
} from '@g2crowd/buyer-intent-provider-sdk/server';

const POST = createNextRouteHandler({
  producer: createDevLoggerProducer(),
  partnerId: 'dev',
});

export { POST };

Event Payload

The client sends a beacon to the activity endpoint. The server handler wraps it into a composite event and writes it to Kafka.

Client → Server (sendBeacon body)

{
  "name": "$view",
  "properties": {
    "product_ids": [123],
    "category_ids": [45],
    "tag": "products.show",
    "url": "https://yoursite.com/products/acme-crm",
    "user_type": "standard",
    "distinct_id": "visitor-uuid",
    "origin": "yoursite.com",
    "source_location": "ProductsController#show",
    "context": {}
  },
  "visit": {
    "properties": {
      "landing_page": "https://yoursite.com/products/acme-crm",
      "referrer": "https://google.com/",
      "user_agent": "Mozilla/5.0 ...",
      "utm_source": "newsletter",
      "utm_medium": "email",
      "utm_campaign": "spring-2026",
      "utm_term": "crm+software",
      "utm_content": "hero-cta"
    }
  }
}

Server → Kafka (composite event)

The server enriches the client payload with server-side tokens, timestamps, and IP before writing to Kafka. This is the shape written to the intent_events_{partnerId} topic:

{
  "event": {
    "id": "e0c1f2a3-...",
    "name": "$view",
    "time": "2026-02-17T14:20:00.000Z",
    "properties": {
      "product_ids": [123],
      "category_ids": [45],
      "tag": "products.show",
      "url": "https://yoursite.com/products/acme-crm",
      "user_type": "standard",
      "distinct_id": "visitor-uuid",
      "origin": "yoursite.com",
      "source_location": "ProductsController#show",
      "context": {}
    }
  },
  "visit": {
    "visit_token": "a1b2c3d4-...",
    "visitor_token": "f5e6d7c8-...",
    "started_at": "2026-02-17T14:20:00.000Z",
    "created_at": "2026-02-17T14:20:00.000Z",
    "properties": {
      "landing_page": "https://yoursite.com/products/acme-crm",
      "referrer": "https://google.com/",
      "user_agent": "Mozilla/5.0 ...",
      "ip": "203.0.113.42",
      "utm_source": "newsletter",
      "utm_medium": "email",
      "utm_campaign": "spring-2026",
      "utm_term": "crm+software",
      "utm_content": "hero-cta"
    }
  }
}

The server sets visit_token and visitor_token as httpOnly cookies so repeat visits from the same browser share stable tokens.