Web analytics SDK for Clamp Analytics — a privacy-first product analytics platform with a built-in MCP server, so the same events you track with this SDK are queryable by Claude, Cursor, VS Code, and any other Model Context Protocol client.
Auto-pageviews, sessions, custom events, revenue, and error capture in under 2 KB gzipped. No cookies, no personal data, no consent banner. Works in the browser, on the server, with React and Next.js, and via a one-tag CDN install for hosted platforms.
Hosted at clamp.sh — free tier covers 100k events/month. MIT SDK; the platform itself is hosted by Clamp.
npm install @clamp-sh/analyticsOr paste one script tag for hosted platforms — see Script tag (no build step) below.
Install guides at clamp.sh/docs/install and clamp.sh/docs/integrations:
| Path | Install |
|---|---|
| Next.js (App Router + Pages) | /docs/install/nextjs |
| Vite + React | /docs/install/vite-react |
| Nuxt 3 | /docs/install/nuxt |
| SvelteKit | /docs/install/sveltekit |
| Astro | /docs/install/astro |
| Webflow | /docs/integrations/webflow |
| Shopify | /docs/integrations/shopify |
| WordPress | /docs/integrations/wordpress |
| Squarespace | /docs/integrations/squarespace |
| Ghost | /docs/integrations/ghost |
| Framer | /docs/integrations/framer |
| Wix | /docs/integrations/wix |
| Carrd, Notion, Mintlify, Docusaurus, Hugo, Jekyll, Eleventy | /docs/integrations |
For sites you can't npm install into — Webflow, Shopify, WordPress, Squarespace, Ghost, Framer, Wix, Notion-as-site services, and others — drop one script tag in the page <head>. The SDK reads the project ID from the tag's data attribute and starts tracking pageviews and sessions automatically.
<!-- Clamp Analytics — https://clamp.sh -->
<script
src="https://cdn.clamp.sh/v0/cdn.global.js"
data-clamp-project="proj_xxx"
defer
></script>Replace proj_xxx with your project ID from the Clamp dashboard. Platform-specific install guides live at clamp.sh/docs/integrations.
Add data-clamp-extensions (comma-separated):
<script
src="https://cdn.clamp.sh/v0/cdn.global.js"
data-clamp-project="proj_xxx"
data-clamp-extensions="outbound-links,downloads"
defer
></script>Available: outbound-links, downloads, data-attributes, section-views, web-vitals, not-found. See the extensions reference.
If you need programmatic configuration — endpoint override, excluded paths, error capture toggle — use the manual API instead of the data attribute:
<script src="https://cdn.clamp.sh/v0/cdn.global.js"></script>
<script>
clamp.init("proj_xxx", {
excludePaths: ["/dashboard"],
captureErrors: true,
});
</script>Or import the ESM build directly:
import { init } from "@clamp-sh/analytics";
init("proj_xxx", { excludePaths: ["/dashboard"] });import { init, track, getAnonymousId } from "@clamp-sh/analytics"
init("proj_xxx")
// Custom events
track("signup", { plan: "pro" })
// Get visitor ID (for linking server-side events)
const anonId = getAnonymousId()After init(), pageviews are tracked automatically, including SPA navigations.
import { Analytics } from "@clamp-sh/analytics/react"
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics projectId="proj_xxx" />
</body>
</html>
)
}Add to your root layout. Pageviews are tracked automatically. Use track() from @clamp-sh/analytics anywhere in your app for custom events.
import { init, track } from "@clamp-sh/analytics/server"
init({ projectId: "proj_xxx", apiKey: "sk_proj_..." })
await track("account_created", {
anonymousId: "anon_abc123",
properties: { plan: "pro" },
})Server events require an API key (found in your project settings).
Track any action with track(name, properties). Properties are flat key-value pairs. Each value is a string, finite number, boolean, or Money.
import { track } from "@clamp-sh/analytics"
track("signup", { plan: "pro", source: "pricing_page" })
track("feature_used", { name: "csv_export" })
track("invite_sent", { role: "editor", team: "acme" })On the server:
import { track } from "@clamp-sh/analytics/server"
await track("subscription_created", {
anonymousId: "anon_abc123",
properties: { plan: "pro", interval: "monthly" },
})Pageviews are tracked automatically. Everything else goes through track().
Define your event map once and get autocomplete and type checking across your app. Zero runtime cost.
import type { Money } from "@clamp-sh/analytics"
type Events = {
signup: { plan: string; source: string }
purchase: { plan: string; total: Money; tax: Money }
feature_used: { name: string }
invite_sent: { role: string }
}
init<Events>("proj_xxx")
track("signup", { plan: "pro", source: "homepage" }) // autocomplete
track("signup", { wrong: "field" }) // type error
track("unknown_event") // type errorPast a handful of events, declare them in event-schema.yaml and let the CLI generate the type — same compile-time safety, one source of truth across your codebase and your team.
Works the same way with the server SDK:
import { init, track } from "@clamp-sh/analytics/server"
init<Events>({ projectId: "proj_xxx", apiKey: "sk_proj_..." })
await track("purchase", {
properties: {
plan: "pro",
total: { amount: 49, currency: "USD" },
tax: { amount: 7.35, currency: "USD" },
},
})Attach a Money value to any event property to make it queryable by revenue.sum. Clamp never mixes currencies in a single sum.
import { track } from "@clamp-sh/analytics"
track("purchase", {
plan: "pro",
total: { amount: 29.00, currency: "USD" },
tax: { amount: 4.35, currency: "USD" },
})Server-side (e.g. from a Stripe webhook):
import { track } from "@clamp-sh/analytics/server"
await track("checkout_completed", {
anonymousId: session.client_reference_id,
properties: {
plan: session.metadata.plan,
total: { amount: session.amount_total / 100, currency: session.currency.toUpperCase() },
},
})Auto-tracked clicks can also carry money via data-clamp-money-<key>:
<button
data-clamp-event="purchase"
data-clamp-plan="pro"
data-clamp-money-total="29.00 USD"
data-clamp-money-tax="4.35 USD"
>Buy</button>Your agent can now ask questions like "which source drove the most revenue last month" or "how much did European traffic spend on the Pro plan".
Capture exceptions and unhandled rejections as $error events. Errors live in the same event stream as your custom tracking, so an agent can correlate "errors spiked" with "revenue dropped" in a single MCP call.
Browser, manual:
import { captureError } from "@clamp-sh/analytics"
try {
riskyOperation()
} catch (err) {
captureError(err, { feature: "checkout", retry: 1 })
}Browser, auto-capture (off by default; opt in to forward window.onerror + unhandledrejection):
init("proj_xxx", { captureErrors: true })Browser, explicit subpath import (for tighter bundling control):
import { captureError, installErrorCapture } from "@clamp-sh/analytics/errors"Server:
import { captureError } from "@clamp-sh/analytics/server"
try {
await processWebhook(payload)
} catch (err) {
await captureError(err, { anonymousId, context: { webhook: "stripe" } })
}The error-capture machinery (browser side) lives in a separate chunk that lazy-loads on first use, so users who never capture errors pay zero bytes for it. A per-session client-side rate limit caps duplicate-message captures at 5 to prevent runaway loops from blowing through the event quota; the server adds a stable error.fingerprint at ingest so the same bug groups across occurrences regardless of which session reported it.
Opt-in auto-tracking features. Each extension lives at its own subpath and is dynamic-imported only when enabled, so projects that turn on one extension only download that chunk:
init("proj_xxx", {
extensions: {
outboundLinks: true, // outbound_click events
downloads: true, // download events
downloads: { extensions: ["pdf", "zip"] }, // (override file list)
notFound: true, // 404 events
dataAttributes: true, // any custom event from `data-clamp-event`
webVitals: true, // web_vital events (peer dep: `web-vitals`)
webVitals: { sampleRate: 0.1 }, // (10% sample)
sectionViews: true, // section_viewed events
sectionViews: { threshold: 0.6 }, // (60% visibility)
}
})Each is also importable directly for advanced use:
import { installOutboundLinks } from "@clamp-sh/analytics/extensions/outbound-links"
import { installDownloads } from "@clamp-sh/analytics/extensions/downloads"
import { install404 } from "@clamp-sh/analytics/extensions/not-found"
import { installDataAttributes } from "@clamp-sh/analytics/extensions/data-attributes"
import { installWebVitals } from "@clamp-sh/analytics/extensions/web-vitals"
import { installSectionViews } from "@clamp-sh/analytics/extensions/section-views"See the docs for the per-extension event schema and edge cases.
track("signup", { plan: "pro", source: "pricing_page" })track("feature_used", { name: "csv_export" })Pass the anonymous ID from the browser to your API, then include it in server-side events to connect the two.
// Browser: send anonymous ID with your API call
const anonId = getAnonymousId()
await fetch("/api/checkout", {
method: "POST",
body: JSON.stringify({ plan: "pro", anonId }),
})// Server: include it in the event
await track("checkout_completed", {
anonymousId: req.body.anonId,
properties: { plan: "pro", amount: "49" },
})// app/layout.tsx
import { Analytics } from "@clamp-sh/analytics/react"
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics projectId="proj_xxx" />
</body>
</html>
)
}
// app/pricing/page.tsx (client component)
"use client"
import { track } from "@clamp-sh/analytics"
export default function Pricing() {
return (
<button onClick={() => track("plan_selected", { plan: "growth" })}>
Choose Growth
</button>
)
}// app/actions.ts
"use server"
import { track } from "@clamp-sh/analytics/server"
export async function createTeam(name: string, anonId: string) {
const team = await db.teams.create({ name })
await track("team_created", {
anonymousId: anonId,
properties: { team_id: team.id },
})
return team
}import express from "express"
import { init, track } from "@clamp-sh/analytics/server"
init({ projectId: "proj_xxx", apiKey: "sk_proj_..." })
const app = express()
app.post("/api/subscribe", async (req, res) => {
await track("subscription_started", {
anonymousId: req.body.anonId,
properties: { plan: req.body.plan },
})
res.json({ ok: true })
})Property limits, debug mode, the full API surface, and edge-case behaviour live in the SDK reference docs.
MIT
