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

@grainql/tag

v4.7.1

Published

Grain Tag - Lightweight analytics SDK for Grain

Readme


Installation

Script tag

<script src="https://tag.grainql.com/v4/your-tenant-id.js"></script>

The Cloudflare Worker adds your tenant config to the script. The SDK starts on its own. No setup is necessary.

npm

npm install @grainql/tag

Quick start

Script tag

<script src="https://tag.grainql.com/v4/your-tenant-id.js"></script>
<script>
  // The SDK is ready. Track an event.
  GrainTag.track('button_clicked', { button: 'signup' });

  // Identify a user after login.
  GrainTag.identify('user-123');
</script>

npm (React, Next.js, Vue, Svelte, and more)

import { init, track, identify } from '@grainql/tag';

// Call init() one time.
init({ tenantId: 'your-tenant-id' });

// Events sent before init() go into a queue. The SDK sends them after init().
track('page_viewed', { page: '/home' });

// Identify a user after login.
identify('user-123');

SSR (server-side rendering)

import { init, track } from '@grainql/tag';

// On the server, init() returns a stub that does nothing. It does not crash.
const grain = init({ tenantId: 'your-tenant-id' });
track('page_viewed'); // Ignored on the server. Works in the browser.

Config

init({
  tenantId: 'your-tenant-id',               // Required
  apiUrl: 'https://clientapis.grainql.com', // Default
  debug: false,                             // Write debug logs to the console
  consentMode: 'auto',                      // 'auto' | 'opt-in' | 'opt-out'
  enablePageViews: true,                    // Track page views
  enableHeatmaps: true,                     // Track clicks and scrolls
  enableSnapshots: true,                    // Capture DOM snapshots for heatmap overlays
  enableRescue: true,                       // Show exit-intent rescue popups
  batchSize: 50,                            // Events per batch
  flushInterval: 5000,                      // Time between batches, in ms
  retryAttempts: 3,                         // Retries after a failed request
});

API reference

Events

track(eventName, properties?)        // Send a custom event

Identity

identify(userId)                     // Set the user ID

Consent

grain.consent.grant(['analytics'])   // Grant consent for the given categories
grain.consent.revoke()               // Revoke consent
grain.consent.status()               // Get the current consent state

Lifecycle

getInstance()                        // Get the current SDK instance
isInitialized()                      // True if init() has run
destroy()                            // Stop the SDK. You can call init() again after this.
grain.flush()                        // Send all queued events now
grain.isReady()                      // True if the SDK runs in a browser

Consent modes

| Mode | Before consent | After consent | |------|----------------|---------------| | auto | Cookieless. IDs rotate daily. | Permanent IDs | | opt-in | Cookieless. IDs rotate daily. | Permanent IDs | | opt-out | Permanent IDs | Cookieless after the user revokes consent |

How it works

  1. track() puts the event in a queue in memory. It does not block.
  2. Every 5 seconds, or when the queue has 50 events, the SDK sends the batch to the Grain API.
  3. If a request fails, the SDK retries up to 3 times with exponential backoff.
  4. When the page unloads or the tab is hidden, the SDK sends the remaining events with the Beacon API.
  5. By default, the user ID rotates daily and no cookie is set. After consent, the SDK stores a permanent ID in localStorage.

The SDK adds device, browser, os, UTM parameters, first-touch attribution, and session data to every event.

Build from source

npm run build:sdk       # Build the SDK (IIFE, ESM, CJS, and type declarations)
npm run build:worker    # Build the Cloudflare Worker
npm run build           # Build both
npm run build:dev       # Development build (not minified)
npm run typecheck       # TypeScript type check

Grain SDKs

| Use case | SDK | |----------|-----| | Browser analytics, heatmaps, snapshots | @grainql/tag (this package) | | Script tag delivery (Cloudflare Workers) | @grainql/tag (this package) | | Remote config | @grainql/analytics-web | | Server-side events (Node.js) | @grainql/analytics-web |

Migrate from @grainql/analytics-web

If you use @grainql/analytics-web in the browser, change your code as follows:

// Before
import { createGrainAnalytics } from '@grainql/analytics-web';
const grain = createGrainAnalytics({ tenantId: '...' });
grain.track('event');

// After
import { init, track } from '@grainql/tag';
init({ tenantId: '...' });
track('event');

| Before | After | |--------|-------| | createGrainAnalytics() | init() | | grain.setUserId() | identify() | | grain.grantConsent() | grain.consent.grant() |

Contributing

Read CONTRIBUTING.md.

License

MIT