@growth-loop/sdk
v0.1.6
Published
Growth SDK for browser, Node, and edge runtimes — drop-in product analytics for vibe-coded SaaS. Pairs with @growth-loop/mcp-server to give Claude Code an AI growth engineer for your product.
Maintainers
Readme
@growth-loop/sdk
Browser / Node / edge SDK for growth-loop.dev — Claude Code-native product analytics for vibe-coded SaaS.
Pairs with @growth-loop/mcp-server so Claude Code can ask
your funnels, retention, and revenue questions directly with citations.
Install
pnpm add @growth-loop/sdk
# or
npm i @growth-loop/sdk
# or
yarn add @growth-loop/sdkQuick start — Browser (Next.js, Vite, anything web)
// src/lib/growth.ts
'use client';
import { instrument } from '@growth-loop/sdk';
import { createBrowserClient } from '@growth-loop/sdk/browser';
export const growth = instrument(
createBrowserClient({
apiKey: process.env.NEXT_PUBLIC_GROWTH_KEY!,
host: process.env.NEXT_PUBLIC_GROWTH_HOST ?? 'https://api.growth-loop.dev',
}),
);// any client component
import { growth } from '@/lib/growth';
<button onClick={growth.button('cta_clicked', onClick, { location: 'hero' })}>
Sign up
</button>createBrowserClient enables autoCapture by default — page views, clicks,
rage-clicks, JS errors, and navigation timing all flow without ceremony.
Disable with autoCapture: false if you prefer manual control.
Quick start — Node / server actions / route handlers
// src/lib/growth.server.ts
import { instrument } from '@growth-loop/sdk';
import { createServerClient } from '@growth-loop/sdk/node';
export const growth = instrument(
createServerClient({
apiKey: process.env.GROWTH_KEY!,
host: process.env.GROWTH_HOST ?? 'https://api.growth-loop.dev',
environment: (process.env.NODE_ENV ?? 'development') as
| 'production' | 'preview' | 'development',
release: process.env.GIT_COMMIT_SHA,
}),
);// in a route handler / server action
await growth.identify({ distinctId: user.id, properties: { plan: user.plan } });
growth.step('signup_completed', { source: 'organic' });
await growth.flush(); // important on serverless — request can finish before queue drainsQuick start — Edge / Cloudflare Workers / Deno
The default transport uses fetch, which works in any modern runtime. For
queue-backed delivery (e.g. Cloudflare Queues), pass a custom transport:
import { createClient } from '@growth-loop/sdk';
const growth = createClient({
apiKey: env.GROWTH_KEY,
transport: {
async send(url, payload, headers) {
await env.GROWTH_QUEUE.send({ url, payload, headers });
},
},
});Core API
Growth / createClient(options)
The low-level client. Use createClient for a factory or new Growth(options)
directly.
interface GrowthOptions {
apiKey: string;
host?: string; // default: 'https://api.growth-loop.dev'
flushInterval?: number; // default: 5000ms
batchSize?: number; // default: 50
environment?: string;
release?: string; // git commit SHA — annotates events for `/diagnose`
transport?: Transport;
autoCapture?: boolean; // browser only; default: true
}Methods:
| Method | What it does |
|---|---|
| track(name, properties?, options?) | Enqueue one event. Non-blocking. |
| identify({ distinctId, properties? }) | Set the current distinct ID + emit $identify. Subsequent events inherit the ID. |
| flush() | Force-send any queued events. Returns a Promise. Always await on serverless. |
| shutdown() | Flush + stop the worker. Called automatically on beforeunload / process.exit. |
| setDistinctId(id) | Set the ID without emitting $identify. |
Helpers — instrument(client)
Wraps a GrowthClient and adds three high-leverage helpers used by
/init (the MCP slash-prompt that auto-instruments your repo):
growth.span(name, fn, options?)
Wraps an async function. Emits <name>.started, <name>.completed (with
duration_ms), or <name>.failed (with error_message, error_name). Use
on anything > 500ms or with non-trivial failure rate.
const checkout = growth.span('checkout', async (items: Item[]) => {
return await stripe.charge(items);
});growth.step(name, properties?)
Single funnel-step event. Use for activation milestones.
growth.step('onboarding.connected_repo', { provider: 'github' });growth.button(name, onClick, properties?)
Returns a wrapped click handler — emits the event, then runs the original. Built for React onClick props.
<button onClick={growth.button('cta_clicked', signUp, { location: 'hero' })}>
Sign up
</button>Recommended event taxonomy
The MCP server's /diagnose and /weekly prompts know these names natively.
Use them when they fit.
| Stage | Events |
|---|---|
| Identity | signup_completed, login_completed, $identify |
| Activation | onboarding.started, onboarding.completed, first_<thing>_created |
| Revenue | checkout_started, checkout_completed, subscription_created, subscription_canceled, payment_failed |
| Engagement | wrap key clicks with growth.button(...) — limit to 5–10 |
| Performance | growth.span('checkout', ...), growth.span('ai.generate', ...) |
Privacy
- Never put PII in
properties(email, raw IP, full address, payment details). UsedistinctIdfor identity. The dashboard explicitly does not decrypt or display anything inpropertiesas identity. - ClickHouse TTL is 12 months by default — events older than that are dropped automatically.
- Browser SDK respects DNT (Do Not Track) and skips ingestion when set.
Environment variables read by the SDK
| Var | Where | Default |
|---|---|---|
| GROWTH_KEY / NEXT_PUBLIC_GROWTH_KEY | Node / Browser | required |
| GROWTH_HOST / NEXT_PUBLIC_GROWTH_HOST | both | https://api.growth-loop.dev |
| GIT_COMMIT_SHA | Node | unset (passed via release) |
Going deeper
The SDK is the ingest layer. The agent layer (Claude Code + MCP) is what makes growth-loop different — install the MCP server too:
claude mcp add growth-loop \
-e GROWTH_API_KEY=pk_live_<your-key> \
-e GROWTH_HOST=https://api.growth-loop.dev \
-- npx -y @growth-loop/mcp-serverThen in Claude Code: /growth-init (auto-instrument), /growth-diagnose <metric>,
/growth-weekly, /growth-pmf, /growth-icp, /growth-strategy, /growth-pivot.
(The growth- prefix is intentional — it keeps our prompts out of the way
of Claude Code's built-in /init and any other editor's defaults.)
License
MIT
