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

@customyai/site

v0.2.0

Published

Load the tags and pixels a Customy workspace configured for your site (GA4, Tag Manager, Meta, Clarity, Hotjar…) with consent gating, CSP helpers and server-side dedupe identifiers. Zero dependencies.

Readme

@customyai/site

Load the tags and pixels your Customy workspace configured for your site — Google Analytics 4, Tag Manager, Meta Pixel, Microsoft Clarity, Hotjar, LinkedIn Insight, TikTok, Plausible, or any external script — with consent gating, CSP helpers and the identifiers Customy needs to forward the same events server-side without double counting.

Zero dependencies. Works in any browser bundle; the manifest fetch also runs in Node 18+ for server-side rendering.

npm install @customyai/site

1. Fetch the manifest (server-side)

The manifest is compiled by Customy Data for your source and read with its write key. Fetch it where the key lives — a server component, middleware, an API route — and cache it for a minute.

import { fetchTagManifest } from "@customyai/site";

const manifest = await fetchTagManifest({
  url: "https://data.customy.ai/v1/collect/tags",
  writeKey: process.env.CUSTOMY_DATA_WRITE_KEY!,
});

fetchTagManifest honours etag (pass the previous revision) and returns null on 304.

2. Keep your CSP strict

The manifest lists the origins each tag needs. Merge them into your policy instead of loosening it:

import { mergeCsp } from "@customyai/site";

response.headers.set(
  "Content-Security-Policy",
  mergeCsp("default-src 'self'; script-src 'self' 'nonce-…'; connect-src 'self'", manifest),
);

3. Mount in the browser

import { createSiteTags } from "@customyai/site";

const tags = createSiteTags({
  manifest,                                   // from step 1, passed to the client
  nonce,                                      // your CSP nonce, if any
  consent: () => ({ analytics: consent === "granted", marketing: false, functional: true }),
});

await tags.mount();                           // loads only what is consented
tags.page();                                  // on every route change
tags.track("lead_submitted", { value: 1 });   // gtag / dataLayer / fbq / ttq / plausible…
await tags.updateConsent();                   // after the visitor changes their choice

Nothing is injected inline: vendor bootstraps (gtag, dataLayer, fbq, ttq, clarity, hj, lintrk) are implemented in this package, and every <script src> carries your nonce.

React

import { useSiteTags } from "@customyai/site/react";

const tags = useSiteTags({ manifest, nonce, consent: () => consent, consentKey: JSON.stringify(consent) });
useEffect(() => { tags?.page(); }, [tags, pathname]);

4. Let Customy forward events server-side

When a tag runs in cloud mode, Customy Data forwards your collected events to GA4 (Measurement Protocol) and Meta (Conversions API). Add the browser identifiers to each event's context so both sides deduplicate:

sdk.send({
  type: "track",
  event: "lead_submitted",
  messageId,                 // Meta uses it as event_id
  anonymousId,
  context: { ...tags.context(), application: "my-site" },
  consent: { analytics: true, marketing: false },
});

tags.context() returns { ga: { clientId, sessionId }, meta: { fbp, fbc }, tiktok: { ttclid, ttp }, linkedin: { liFatId }, google: { gclid }, page: { url, title, referrer } }. Customy Data forwards to GA4, Meta, TikTok (Events API) and LinkedIn (Conversions API); each forwarder reads its own identifiers from there.

Consent categories

Every tag has a category — analytics, marketing or functional — chosen in the workspace. A tag mounts only when its category is true; GA4 additionally receives Consent Mode v2 defaults and updates. Cloud forwarding applies the same rule to the consent object of each event.

Withdrawal

updateConsent() after the visitor withdraws a category does what they asked: the vendor is told to stop (ga-disable-<id>, fbq('consent','revoke'), clarity('consent', false), ttq.disableCookie()), page()/track() stop bridging to it, and its cookies on your domain are expired on the host and every parent domain. The result lists them under revoked. Set purgeCookiesOnRevoke: false if you purge yourself.

Regional defaults (Consent Mode v2)

import { EEA_REGIONS } from "@customyai/site";

createSiteTags({
  manifest,
  consent,
  consentDefaults: {
    regions: [{ regions: EEA_REGIONS, consent: {} }], // denied in EEA/UK/CH until the visitor decides
    waitForUpdate: 500,
  },
});

The global default is whatever the visitor already granted (denied otherwise); each regions entry becomes another gtag('consent','default', { region }). ads_data_redaction follows the marketing decision and url_passthrough is on.

5. Serve vendor loaders from your own origin (optional)

// app/tags/js/route.ts (Next.js) — any Fetch-API runtime works
import { createTagScriptProxy } from "@customyai/site";
export const GET = createTagScriptProxy({ manifest: () => loadManifest() });

// browser
createSiteTags({ manifest, consent, scriptProxy: scriptProxyVia("/tags/js") });

Only URLs that appear in the manifest are proxied (404 otherwise), no cookies travel either way, and the response is cacheable. Vendor scripts still talk to their own endpoints afterwards, so keep the connect-src origins from mergeCsp.

License

MIT