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

flowgrid-sdk

v1.8.2

Published

A TypeScript SDK for tracking user events, feature usage, experiments, and feature flags with Flowgrid.

Readme

Flowgrid SDK

npm license types

A production-grade, framework-agnostic TypeScript SDK for product analytics, e-commerce, experiments, and enterprise insights — with a single unified entry point that works in any JavaScript environment (Node, browsers, edge runtimes, React, Vue, Next, Nuxt, Svelte, Angular, etc.).

Start for free: [https://www.flow-grid.xyz/]

import { FlowGrid } from "flowgrid-sdk";

// One line. Sane defaults. Auto-instruments sessions, page views,
// performance, heatmaps, and attribution out of the box.
const fg = FlowGrid.init({
  webId: "wx_123",
  apiKey: "pk_xxx",
  // Opt in to the extras you want at init.
  autoTrack: {
    replay: true,
    engagement: true,
    performance: true,
    passiveIdentity: true,
  },
});

await fg.track("signup_completed", { plan: "pro" });
await fg.track("feature_used", { featureId: "export", featureName: "Data Export", userId: "u_1" });
await fg.track("experiment_exposure", { experimentId: "checkout_v2", variantId: "B", userId: "u_1" });
await fg.track("add_to_cart", { productId: "sku_1", name: "T-Shirt", price: 29, currency: "USD", quantity: 1 });

Highlights

  • One class, every featurenew FlowGrid(...) exposes analytics, ecommerce, experiments, and enterprise modules as lazy properties.
  • Feature usage, explicitfg.track("feature_usage", { featureId, featureName }). You say what happened; the SDK just sends it.
  • Passive identity — opt in at init with autoTrack: { passiveIdentity: true } (or later via fg.autoTrack({ passiveIdentity: true })) to recognise anonymous visitors from any form (PII hashed in the browser).
  • Framework-agnostic — zero peer dependencies. Works with React, Vue, Next.js, Nuxt, Svelte, Astro, plain Node, Cloudflare Workers, etc.
  • Privacy & consent built-in — DNT/GPC support, ConsentManager, bot filtering.
  • Resilient transport — retry with exponential backoff, sendBeacon fallback, offline localStorage buffering, 10KB payload guard.
  • Strict TypeScript — strong types for every config and response.

Installation

npm install flowgrid-sdk
# or
pnpm add flowgrid-sdk
# or
yarn add flowgrid-sdk

Requires Node >= 18.12. No peer dependencies.


Quick Start

FlowGrid is one platform with two runtimes. Initialise once per runtime — the browser client for everything a visitor does on the page, the server client for what happens in your backend — and both feed the same dashboard under the same identity:

// lib/flowgrid.client.ts — BROWSER: init once, import everywhere client-side
import { FlowGrid } from "flowgrid-sdk";

export const fg = FlowGrid.init({
  webId: process.env.NEXT_PUBLIC_FLOWGRID_WEB_ID!,   // public site id (wx_…)
  apiKey: process.env.NEXT_PUBLIC_FLOWGRID_API_KEY!, // public API key

  // Who is this? (optional — identifies up-front so the very first events attribute)
  user: { userId: "u_1", email: "[email protected]", name: "Alice" },

  // What gets auto-tracked? engagement + replay are on by default;
  // pageviews/sessions/heatmaps/performance come from the script tag install.
  autoTrack: {
    engagement: true,
    replay: { sampleRate: 1.0, maskAllInputs: true },
    passiveIdentity: true, // recognise visitors from forms (PII hashed in-browser)
  },

  // What is the visitor consenting to? (see “Cookie Consent” below —
  // pair with renderConsentBanner() for an opt-in banner in one call)
  consent: { analytics: true, marketing: true },
});
// lib/flowgrid.server.ts — SERVER (Node ≥ 18 / edge): identity, feature usage, revenue, errors
import { FlowGridServer } from "flowgrid-sdk/server";

export const fgServer = FlowGridServer.init({
  webId: process.env.NEXT_PUBLIC_FLOWGRID_WEB_ID!, // same site id
  apiKey: process.env.FLOWGRID_API_KEY!,           // private key — server env only
});

That's the whole setup. Browser calls (fg.track, fg.identifyUser, fg.cart.add, …) and server calls (fgServer.trackAuth, fgServer.trackFeature, fgServer.trackPurchase, fgServer.trackError, …) speak the same wire contract, so a user identified in an OAuth callback on the server and the same user clicking around in the browser resolve to one identity in the dashboard.

Unified client (browser)

import { FlowGrid } from "flowgrid-sdk";

const fg = FlowGrid.init({
  webId: "web_123",
  apiKey: "key_xxx",
  autoTrack: { heatmaps: { movement: true } },
});

// Plain custom events still work.
await fg.track("cta_click", { id: "hero" }, { userId: "u_1" });

// Feature usage — a plain event.
fg.track("feature_usage", { featureId: "data_export", featureName: "Data Export" });

// Recognized event names route to the matching feature module.
await fg.track("feature_used", { featureId: "export", featureName: "Data Export", userId: "u_1" });
await fg.track("prompt_submitted", { promptId: "p_1", promptType: "chat", userId: "u_1" });
await fg.track("experiment_conversion", { experimentId: "checkout_v2", variantId: "B", userId: "u_1", metricName: "purchase" });
await fg.track("purchase", { order });
await fg.track("support.ticket_created", { ticketId: "t_1", userId: "u_1", subject: "Help", category: "billing", priority: "medium", channel: "email" });

Available namespaces on FlowGrid:

| Group | Properties | | ----------- | ----------------------------------------------------------------------------------------------------------------------- | | Core | activation, features, prompts, experiments | | Analytics | pageViews, sessions, events, identify, funnels, retention, attribution, heatmaps, performance, replay | | E-commerce | products, cart, checkout, purchases, refunds, promotions, wishlist, ltv, search, subscriptions | | Enterprise | engagement, cohorts, churn, monetization, multiPathFunnels, support, acquisition, paths, alerts, security, forecasting |

Browser script tag (CMS / no-build)

<script src="https://cdn.flow-grid.xyz/flowgrid.min.js"></script>
<script>
  FlowGrid.init({
    webId: "web_abc123",
    apiKey: "key_xxx",
  });

  FlowGrid.track("signup_clicked", { placement: "hero" });
  FlowGrid.identify("user_123", { plan: "pro" });
  FlowGrid.track("feature_usage", { featureId: "ai_agent_builder", featureName: "AI Agent Builder" });
  FlowGrid.productView({ productId: "sku_1", name: "T-Shirt", price: 29 });
  FlowGrid.addToCart({ productId: "sku_1", name: "T-Shirt", price: 29 }, 1);
</script>

The script-tag API is intentionally small: init, track, page, identify, feature, experiment, productView, addToCart, purchase, autoTrack, consent helpers (setConsent / hasConsent), reset, and instance() for the full SDK. FlowGrid.init() auto-instruments sessions, page views, SPA route changes, scroll depth, time on page, performance, heatmaps, and attribution by default; session replay is opt-in with autoTrack: { replay: true }, and passive identity capture with autoTrack: { passiveIdentity: true }.


Feature Usage

Track product feature usage with a plain event — featureId and featureName are the only required props:

fg.track("feature_usage", { featureId: "ai_agent_builder", featureName: "AI Agent Builder" });

// Optional: an action ("viewed" | "used" | "completed" | "abandoned"), a category, a known user.
fg.track("feature_usage", {
  featureId: "ai_agent_builder",
  featureName: "AI Agent Builder",
  action: "completed",
  category: "automation",
  userId: "u_1",
});
// script tag: FlowGrid.track("feature_usage", { featureId: "ai_agent_builder", featureName: "AI Agent Builder" })

action defaults to "used". No userId or category is required — it works for anonymous visitors out of the box (the backend defaults userId to the visitor and category to "general"). The event routes through the unified .track() router, so it shares routing/consent/transport with every other event.

Helpers

Don't want to hand-write track("feature_usage", …) and repeat the id/name? Bind a feature once with fg.defineFeature and call the verb that describes what happened — each maps to a lifecycle stage the dashboard funnel understands (a viewed feature is Discovered, a used one is Adopted):

const feature = fg.defineFeature("ai-agent-builder", "AI Agent Builder", { category: "automation" });

feature.viewed();                 // action: "viewed"    → Discovered + Viewed
feature.used({ model: "opus" });  // action: "used"      → Adopted
feature.completed();              // action: "completed" → Completed
feature.abandoned();              // action: "abandoned" → Abandonment
// script tag: FlowGrid.defineFeature("ai-agent-builder", "AI Agent Builder").used()

Each call is a thin, explicit wrapper over track("feature_usage", …) — no hidden state, no lifecycle guessing; you send exactly the action you name. The same verbs exist on fg.features.viewed/used/completed/abandoned(id, name) for one-off calls, and fg.features.record(id, name, action) for a dynamic action.


Passive Identity Capture

Recognize anonymous visitors automatically: when a visitor types their email / name / phone into any form, FlowGrid classifies the field, hashes PII in the browser, and links the identity to their visitor_id — so the dashboard shows them by name (and, with a CRM connected, matches them to the contact). No per-form instrumentation.

It's opt-in in the SDK (the flowgrid.js snippet runs it by default) and requires analytics consent:

const fg = FlowGrid.init({ webId: "...", apiKey: "..." });
FlowGrid.setConsent({ analytics: true });

const stop = fg.autoTrack({ passiveIdentity: true });
// or with options:
fg.autoTrack({ passiveIdentity: { debounceMs: 800, excludePath: /\/(checkout|admin)/i } });

Never reads password/hidden/card/cvv/ssn/pin fields, anything marked data-analytics="ignore" or .no-track, free-text (message/comment/search), or sensitive paths (/checkout, /account/settings, /admin). Raw email goes only to the server-side identity store; the event stream sees hashes.


Server-side / Edge Usage

Use the dedicated flowgrid-sdk/server entry point on the server. It is a Node/edge-safe client (global fetch only — no window, document, or storage) scoped to what makes sense off-browser: identity, feature usage, ecommerce / revenue, and API / server error tracking. It speaks the same wire contract as the browser SDK, so events land in the same pipes. Works in Node ≥ 18, Bun, Deno, Cloudflare Workers, and Vercel Edge/serverless.

// lib/flowgrid-server.ts — init once, import anywhere server-side
import { FlowGridServer } from "flowgrid-sdk/server";

export const fg = FlowGridServer.init({
  webId: process.env.NEXT_PUBLIC_WEB_ID!,
  apiKey: process.env.FLOWGRID_API_KEY!, // required on the server
});

Identity — auth-level events (identify a visitor server-side, e.g. in an OAuth callback or session endpoint). Pass the browser's visitor id when you have the request cookies so server events link to the web session; without it, a stable synthetic visitor is derived from the userId:

import { visitorIdFromCookies } from "flowgrid-sdk/server";
import { cookies } from "next/headers";

const visitorId = visitorIdFromCookies(await cookies());

await fg.trackAuth(
  { event: "login", userId: user.id, email: user.email, method: "google" },
  { visitorId },
); // identify + login_completed + active_user ping, in one call

// Or the individual operations:
await fg.identifyUser(user.id, { email: user.email, plan: "pro" }, { visitorId });
await fg.updateTraits(user.id, { plan: "enterprise" });
await fg.pingActiveUser(user.id, { email: user.email }); // DAU/WAU/MAU by identity
await fg.logout(user.id);

Feature usage from API routes, jobs, and webhooks:

await fg.trackFeature({ featureId: "csv_export", featureName: "CSV Export", userId: user.id });

// Or bind once and use semantic verbs (server twin of fg.defineFeature):
const teamUpdate = fg.defineFeature("team_update", "Team update", { category: "teams" });
await teamUpdate.used({ userId: user.id, teamId });

Ecommerce / revenue — record purchases, refunds, order lifecycle, and subscription MRR events from your payment webhooks, where the money is actually confirmed. Browser purchase events are structurally lossy (ad blockers, closed tabs on the checkout redirect); the server is the authoritative source for revenue:

// Stripe webhook: checkout.session.completed
await fg.trackPurchase({
  order: {
    orderId: session.id,
    items,                              // CartItem[] — same shape as the browser SDK
    subtotal, discountTotal: 0, shippingTotal: 0, taxTotal,
    total: session.amount_total / 100,
    currency: "USD",
    payment: { method: "credit_card", provider: "stripe" },
  },
  userId: session.client_reference_id,
});

// charge.refunded — keeps net revenue accurate
await fg.trackRefund({
  orderId, refundId: refund.id,
  amount: refund.amount / 100, currency: "USD",
  reason: "customer_request", isFullRefund: true, userId,
});

// Fulfilment webhooks
await fg.trackOrderStatus(orderId, "processing", "pending", { userId });
await fg.trackOrderShipped(orderId, { method: "express", cost: 5, trackingNumber });
await fg.trackOrderDelivered(orderId);

// Subscription / MRR lifecycle — one method, discriminated on `event`:
await fg.trackSubscription({
  event: "start", // "change" | "cancel" | "renew" | "trial_converted" | "trial_expired"
  subscriptionId: sub.id,
  userId,
  plan: "professional",
  mrr: { amount: 9900, currency: "USD" }, // smallest currency unit
  billingCycle: "monthly",
  trialDays: 14,
});

Revenue attribution — payment webhooks arrive with no cookies, so the visitor/session ids ride through the provider's checkout metadata and come back out in the webhook. Two legs:

// LEG 1 — checkout creation: embed the ids from the request cookies.
import { checkoutAttributionFromCookies } from "flowgrid-sdk/server";
import { cookies } from "next/headers";

// Stripe
const session = await stripe.checkout.sessions.create({
  // …
  metadata: { ...checkoutAttributionFromCookies(await cookies()) },
});

// Lemon Squeezy
const checkout = await createCheckout(storeId, variantId, {
  checkoutData: { custom: { ...checkoutAttributionFromCookies(await cookies()) } },
});

// (Creating the checkout client-side instead? Use fg.checkoutAttribution()
//  from the browser SDK — same keys.)
// LEG 2 — webhook: read the ids back and attach them to the revenue event.
import { attributionFromMetadata } from "flowgrid-sdk/server";

// Stripe checkout.session.completed — unwraps `.metadata` automatically
const { visitorId, sessionId } = attributionFromMetadata(event.data.object);

// Lemon Squeezy order_created — unwraps `.meta.custom_data` automatically
// const { visitorId, sessionId } = attributionFromMetadata(payload);

await fg.trackPurchase({ order, userId }, { visitorId, sessionId });

With the real visitorId + sessionId on the purchase, revenue joins the visitor's full journey — the session, landing page and UTM campaign that earned it — instead of a synthetic server visitor.

API + server-side error tracking — errors become error_events with route/method/status context; FlowGrid-internal noise is filtered out:

try { … } catch (err) {
  await fg.trackError(err, { route: "/api/teams/edit/[teamId]", method: "PATCH", statusCode: 500, userId });
  throw err;
}

// Or wrap a handler — tracks and rethrows, framework behaviour unchanged:
export const PATCH = fg.withErrorTracking(handler, { route: "/api/teams/edit/[teamId]", method: "PATCH" });

// Opt-in process-level capture (Node only; observes, never swallows):
const detach = fg.captureUncaught();

Like the browser transport, the server client never throws for delivery problems — every method resolves { ok, status?, reason? }.


Cookie Consent

What you're asking consent for

When a visitor accepts cookies, this is exactly what each category permits FlowGrid to do (this is what the SDK enforces, not aspirational copy — each category maps to a hard gate in the transport).

Default posture: implied consent. Every category starts granted, and tracking runs out of the box — the "declined" behaviour below only applies when you've installed a consent gate (requireExplicitConsent: true or the opt-in banner) and the visitor actively rejects a category:

| Category | Can the visitor decline? | What it covers | | ------------- | ------------------------ | -------------- | | necessary | No — always on | The visitor/session identifiers (visitor_id cookie, fg_session_id) and the consent cookies themselves (fg_consent, fg_tracking_consent). Required for the service to function; no behavioural profiling. | | analytics | Yes | All event tracking. Page views, sessions, clicks, forms, searches, feature usage, funnels, performance/Web Vitals, engagement, identity events (identifyUser, active-user pings), and session replay recordings — plus the device/page context attached to each event (screen size, device type, language, URL, path, title, referrer). Declined → no events leave the browser. | | marketing | Yes | Campaign attribution. The utm_source/medium/campaign/term/content and ref/via/source parameters read from the URL and the __flowgrid_* landing-page cookies, attached to events as _utm_*/_ref properties. Declined → events (if analytics is granted) are sent without any campaign attribution. | | preferences | Yes | UI preferences your own app stores (locale, theme). FlowGrid itself stores nothing in this category — it exists so your banner can offer it. |

Independent of the banner, visitors sending Do-Not-Track or Global Privacy Control are treated as having declined non-essential tracking by default (respectDNT: true), and localhost traffic is never tracked unless explicitly enabled.

Server-side events (flowgrid-sdk/server) are outside the browser consent gates by design: they represent your backend's own records (auth events, API feature usage, server errors) under your contractual/legitimate-interest basis. If you want browser consent to extend to server calls, check your stored consent state before calling the server client.

No-code (one tag) — recommended

Drop a single element on the page and FlowGrid renders a styled, opt-in, GDPR/CCPA-friendly consent banner that blocks analytics, marketing and session-replay tracking until the visitor accepts — with zero JavaScript:

<script src="https://cdn.flow-grid.xyz/flowgrid.min.js" data-site="YOUR_WEBSITE_ID"></script>
<div data-cookie-ccbanner></div>

The banner auto-initializes ahead of (and independently of) tracking, so consent gates engage before the first event fires. It re-scans for late-mounted markers (page builders / SPA routes) and is idempotent.

Tier 1 — attributes (no CSS). Tune copy, colours, position via data-cc-*:

<div data-cookie-ccbanner
     data-cc-position="bottom-right"
     data-cc-primary="#EBF212"
     data-cc-radius="16px"
     data-cc-privacy-url="/privacy"></div>

Common attributes: data-cc-mode (opt-in|opt-out), data-cc-title, data-cc-description, data-cc-accept-label, data-cc-reject-label, data-cc-position, data-cc-primary / -bg / -text, data-cc-radius, data-cc-categories (analytics,marketing), data-cc-cookie-name / -days / -domain, data-cc-respect-dnt, data-cc-theme (flowgrid|light|dark), data-cc-manage-selector (re-open hook). Add data-cc-manual to a tag to skip auto-init.

Tier 2 — your CSS. Add data-cc-unstyled to drop the default look and style the stable class hooks (.fg-cc-banner, .fg-cc-card, .fg-cc-btn--accept, …) and CSS variables (--fg-cc-bg, --fg-cc-primary, …) from your own stylesheet.

Tier 3 — bring your own markup. Put your own HTML inside the marker; FlowGrid binds your buttons by data-role and never injects DOM:

<div data-cookie-ccbanner style="display:none">
  <h2>Cookies?</h2>
  <button data-cc-accept>Sure</button>
  <button data-cc-reject>No thanks</button>
</div>

Global defaults can be set on window.FlowGridConfig.consentBanner before the tag; per-element data-cc-* attributes override them. SPA apps can re-scan after a client-side navigation with FlowGrid.wireConsentBanner() (or import wireDeclarativeBanners() directly).

Programmatic banner

Render the same banner from code with renderConsentBanner():

import { renderConsentBanner } from "flowgrid-sdk";

const banner = renderConsentBanner({
  mode: "opt-in",
  title: "We value your privacy",
  privacyPolicyUrl: "/privacy",
  theme: { position: "bottom-right", primaryColor: "#EBF212" },
});

document.getElementById("manage-cookies")?.addEventListener("click", () => banner?.show());

Core ConsentManager

The SDK ships a framework-agnostic ConsentManager. Render your own banner UI in whatever framework you use, and call into the manager to persist preferences.

import { ConsentManager, FlowGridTransport } from "flowgrid-sdk";

const consent = new ConsentManager({
  cookieDomain: ".example.com",
  cookieExpiry: 365,
  respectDNT: true,
  onChange: (prefs) => {
    FlowGridTransport.setConsent({
      analytics: prefs.analytics,
      marketing: prefs.marketing,
    });
  },
});

consent.acceptAll();
consent.rejectNonEssential();
consent.update({ analytics: true, marketing: false, preferences: true });
consent.hasCategory("analytics"); // boolean
consent.reset();

CookieBannerConfig and CookieBannerTheme types are exported from flowgrid-sdk/consent if you want a typed config object to pass to your own banner component.

You can also gate the transport directly without ConsentManager:

import { FlowGridTransport } from "flowgrid-sdk";

FlowGridTransport.setConsent({ analytics: true, marketing: false });
FlowGridTransport.hasConsent("analytics"); // true

Package Exports

| Import path | Description | | -------------------------- | ------------------------------------------------ | | flowgrid-sdk | Unified FlowGrid class + every feature module | | flowgrid-sdk/analytics | Analytics modules only | | flowgrid-sdk/ecommerce | Ecommerce modules only | | flowgrid-sdk/core | Core modules (activation, experiments, prompts) | | flowgrid-sdk/consent | ConsentManager + types | | flowgrid-sdk/server | FlowGridServer — Node/edge tracking (identity, feature usage, revenue, errors) | | flowgrid-sdk/types | Shared TypeScript type definitions |


Documentation

Full reference: https://flow-grid.xyz/documentation

License

MIT © Flowgrid

Contact Support [email protected]