Install with AI

Install with AI (Claude Code / Cursor / Copilot)

This page is written for AI coding assistants. Paste this URL (https://docs.iwanttotrysomething.site/install-with-ai) into Claude Code, Cursor, or any agent and ask it to install TrackCrumb. The page is structured so the agent can follow it end-to-end — install, wire up, and verify — without further prompting.

UAT preview build (temporary). The SDK is not yet on the public npm registry. Until then it is distributed as a tarball hosted on this docs site (URL below) and the npm install commands you may see elsewhere in the docs (npm install @trackcrumb/sdk) will return 404. Use the instructions on this page — they are the source of truth during UAT.

Current SDK version: 0.2.0 (2026-04-30). Adds auto-rendering in-app messages, multi-step campaigns with Next/Done flow, score-based audience targeting, frequency caps, and a 5-minute message cache. Backwards-compatible with 0.1.x integrations — no code changes required to upgrade. See §6. In-app messages below for what changed.


Instructions for the AI assistant

You are integrating TrackCrumb (product analytics: events, autocapture, session replay, feature flags) into the user’s website(s). Follow the steps below in order. Do not skip the verification step.

0. Inputs you need from the user

Before editing any files, confirm you have:

  1. API key(s) — format st_live_... or st_test_.... Found in the dashboard at Settings → API Keys. If the user hasn’t signed up, direct them to app.iwanttotrysomething.site first (UAT environment).
  2. Which site(s) the SDK should load on.
  3. Integration style — pick based on the site:
    • Plain HTML / static site / WordPress / Webflow / Shopify → Script tag (UMD)
    • React / Next.js / Vue / SvelteKit / any bundled app → npm package

If the user wants to track multiple sites, ask whether each site needs its own workspace (full data isolation, separate API key) or one shared workspace (one key, differentiate via a site property on events). Default to one shared workspace unless the user says otherwise — it’s simpler.

1. Install

Option A — Script tag (no build step)

Insert immediately before </head> on every page you want to track:

<script src="https://docs.iwanttotrysomething.site/sdk/sdk.min.js" async></script>
<script>
  (function () {
    function boot() {
      window.TrackCrumb.init({
        apiKey: "REPLACE_WITH_API_KEY",
        apiHost: "https://ingest.iwanttotrysomething.site"
      });
      // Optional: tag events with the current site when sharing one workspace across multiple domains.
      window.TrackCrumb.track("page_view", { site: location.hostname });
    }
    if (window.TrackCrumb) boot();
    else document.addEventListener("DOMContentLoaded", function () {
      var t = setInterval(function () {
        if (window.TrackCrumb) { clearInterval(t); boot(); }
      }, 50);
    });
  })();
</script>

Replace REPLACE_WITH_API_KEY with the actual key the user provides. Do not commit the key into a public template; for static hosts where env vars aren’t available, inject it at build/deploy time.

Option B — npm / bundler (UAT tarball)

The SDK is not on the public npm registry yet. Install the UAT tarball directly from the docs host. The package name inside the tarball is @trackcrumb/sdk-js (this is intentional during UAT; it will become @trackcrumb/sdk once published to npm).

npm install https://docs.iwanttotrysomething.site/sdk/trackcrumb-sdk-js-0.2.0.tgz
# or: bun add https://docs.iwanttotrysomething.site/sdk/trackcrumb-sdk-js-0.2.0.tgz
# or: yarn add https://docs.iwanttotrysomething.site/sdk/trackcrumb-sdk-js-0.2.0.tgz
# or: pnpm add https://docs.iwanttotrysomething.site/sdk/trackcrumb-sdk-js-0.2.0.tgz

Create a single module, import it once at app entry:

// src/lib/tracker.ts
import { TrackCrumb } from "@trackcrumb/sdk-js";
 
export const tracker = new TrackCrumb({
  apiKey: process.env.NEXT_PUBLIC_TRACKCRUMB_KEY!, // or VITE_, etc.
  apiHost: "https://ingest.iwanttotrysomething.site",
  autocapture: true,         // default: true — captures clicks + page views
  replayEnabled: false,      // set true to record session replay
});

Import tracker wherever you need track() / identify():

import { tracker } from "@/lib/tracker";
 
tracker.identify("user_123", { email: "[email protected]", plan: "pro" });
tracker.track("checkout_started", { cart_value: 49 });

When a new UAT build is published, the tarball URL gets a new version (e.g. …-0.1.1.tgz). Update the dependency in package.json to the new URL and re-run install. The previous URL keeps working as long as the file is still hosted, so rollbacks are trivial.

For React apps, the @trackcrumb/sdk-react package is not yet packaged for UAT — use raw @trackcrumb/sdk-js for now and instantiate new TrackCrumb(...) once at app entry as shown above.

2. Multi-site / multi-product setup

If the user is installing on multiple domains:

StrategyWhen to useHow
One key, tag eventsRelated sites, unified funnelSame apiKey, add { site: location.hostname } to track() calls
One key per workspaceIndependent products, isolated dashboardsUser signs up a second workspace, copies its key, init with that key on site 2

There is no domain allowlist — the ingest endpoint (/e) accepts any origin. Access is gated by the API key only, so never ship a test key in production.

3. Verify the integration

This step is required. Do not report the task as done until events are visible.

  1. Open the site in a browser with devtools → Network tab open.
  2. Reload the page. You should see a POST to https://ingest.iwanttotrysomething.site/e returning 202 Accepted.
  3. In the dashboard, open Trends (or Live events if available). The page_view event should appear within 30 seconds.
  4. If using the script tag, run this in the devtools console to confirm the global is present:
    window.TrackCrumb && window.TrackCrumb.track("ai_install_test", { ok: true });
    Then check Trends for ai_install_test.

If verification fails, walk through the Troubleshooting table below before asking the user for help.

4. Troubleshooting

SymptomLikely causeFix
No network request to /eScript blocked by adblocker, or init never ranTest in an incognito window with extensions off; check console for errors
401 responseWrong / revoked API key, or test key on prod domainRe-copy key from dashboard Settings → API Keys
403 responseKey is disabledRe-enable in dashboard, or generate a new one
429 responseRate limit (default 1000 req/key/min)Back off, or request a higher limit
Request to wrong hostCustom apiHost typoDefault to https://ingest.iwanttotrysomething.site unless self-hosting
Events send but don’t show upWorkspace mismatch — key belongs to a different workspaceConfirm the active workspace in the dashboard matches the key’s workspace
CORS error in consoleScript is calling an internal-only pathThe public SDK paths (/e, /api/v1/config, /api/v1/messages/active, /api/v1/messages/*/track, /api/v1/groups/*/*, /api/v1/surveys/*/respond) are CORS-open. Other paths are dashboard-only.
cta_clicked event missing after clicking a linkEvent was buffered but the page navigated before the SDK flushedCall window.TrackCrumb.flush() in the onClick handler right after track(...). flush() synchronously beacons the queue so the event survives the navigation.

5. Report back to the user

After verification succeeds, summarise to the user in three lines:

  1. Which file(s) you edited (paths).
  2. Which API key you used (prefix only, e.g. st_live_ab12…).
  3. A link or instruction to open the dashboard’s Trends view to see live events.

5b. Reliable tracking on CTAs that navigate

When a track() call is followed by an immediate page navigation (<a href="...">, window.location = ..., form submit), the SDK’s default requestIdleCallback-scheduled batch flush can be canceled by the navigation — the event silently drops. There’s a visibilitychange fallback that catches most cases, but same-origin navigations to fast-responding pages can race the listener.

Fix: call TrackCrumb.flush() right after track() in any navigation handler. flush() synchronously posts the queue via navigator.sendBeacon, which survives unload.

<a href="/pricing" onClick={() => {
  window.TrackCrumb.track("cta_clicked", { cta: "pricing" });
  window.TrackCrumb.flush();   // ← guarantees delivery before navigation
}}>
  See pricing
</a>

Don’t bother for clicks that don’t navigate (button toggles, hash links, in-page state changes) — the normal flush works fine for those.

6. In-app messages auto-render (since 0.2.0)

The SDK now auto-fetches and renders in-app message campaigns (tooltips, modals, banners) that the user creates in the dashboard. You do not need to wire anything up in code — the same new TrackCrumb({...}) call from §1 enables it.

What you should mention to the user after install:

  1. Auto-render is off by default at the product level. Even with the SDK installed, no campaigns will render until they flip Settings → Products → [product] → “Auto-render in-app messages” to on. This is intentional — prevents draft campaigns from leaking onto production sites.
  2. Campaigns are configured in the dashboard, not in code. Tell the user to visit /messages to create one. They set the trigger event, URL pathname regex, audience, frequency cap, and step content there.
  3. The trigger event matches what you track(). Auto-captured events ($pageview, $click, $form_submit) are valid triggers, so a “show on /pricing” campaign Just Works without any new tracking calls. Custom events (signup_started, etc.) need to be passed through tracker.track(...) as usual.
  4. Call tracker.identify() early after login. Audience targeting (e.g. “show this to users likely to churn”) relies on the user’s distinct_id. The SDK refetches the active campaign list on identify so audience eligibility is re-evaluated; in-session dedupe survives the refetch.

Reserved event names emitted by the renderer (do not use these for your own custom events):

EventFires when
message_impressionFirst step of a campaign renders (once per campaign, not per step)
message_dismissUser clicks × on any step (terminal — flow ends)
message_cta_clickUser clicks the CTA button (terminal — flow ends)
message_completeUser clicks Done on the final step of a multi-step campaign (terminal — flow ends successfully)

You don’t need to do anything special for these — they flow through the same /e ingest path as ordinary events. The SDK also dual-writes terminal events to POST /api/v1/messages/:id/track so the server-side frequency cap can read them.

7. A/B experiments and tracker.flags.getVariant() (since 0.2.0)

If the user has created an experiment in the dashboard (at /experiments), they need two things in their code: a call to bucket the user into a variant, and a conversion-event fire when something meaningful happens. The SDK handles attribution automatically.

When to mention this to the user: After install completes and the user asks about A/B testing, feature flags, or the Experiments page, offer the following snippet.

Script tag integration

<script>
  // After TrackCrumb.init() has run:
  var variant = window.TrackCrumb.flags.getVariant("my_flag_key");
  // variant is "control" | "treatment" (or whatever keys you defined)
  if (variant === "treatment") {
    showNewExperience();
  } else {
    showOriginalExperience();
  }
 
  // Later, when the user does the thing you're measuring:
  window.TrackCrumb.track("trial_started", { converted: 1 });
  // experiment_id and variant are attached automatically — no extra code needed.
</script>

npm / bundler integration

import { tracker } from "@/lib/tracker"; // the singleton from §1
 
// Bucket the user — deterministic by distinct_id, cached 60 s
const variant = tracker.flags.getVariant("my_flag_key");
 
if (variant === "treatment") {
  showNewExperience();
} else {
  showOriginalExperience();
}
 
// Fire the conversion event — attribution is automatic
tracker.track("trial_started", { converted: 1 });

What getVariant() does internally:

  1. Fetches /config from the API (cached 5 minutes per session via sessionStorage).
  2. Deterministically assigns a variant from the flag’s rollout percentage using the user’s distinct_id — the same user always gets the same variant.
  3. Checks the experiment’s holdout — a configurable fraction of users (max 25%) always returns "control" regardless of rollout, used for long-term regression detection.
  4. Stores experiment_id + variant in memory and auto-attaches them to all subsequent track() calls.

The user does not need to pass experiment_id or variant to track() manually.

The flag key is visible on the Flags page (/flags) or was auto-generated when the experiment was created (slug of the experiment name, e.g. experiment “Pricing copy test” → flag key pricing_copy_test).

Fallback value: If the flag doesn’t exist or the API is unreachable, getVariant() returns "control" by default so the base experience always renders.

Multi-arm experiments (since 0.2.0): getVariant() returns "control" or "treatment" for the common 2-arm case, but the engine supports up to 4 arms with custom keys. If the user’s experiment has more than 2 arms, getVariant() returns the matching arm key (e.g. "variant_b"). Always wire your code with explicit arm-key checks (if (variant === "treatment_b")) — never default-branch on !== "control".

Auto-pause on SRM: if traffic significantly deviates from configured weights (chi-square p < 0.001), the experiment is automatically paused and the dashboard shows a banner. There is nothing to fix in the customer’s code when this happens — it usually indicates a bucketing bug (e.g. one variant is being skipped due to a logic error in the integration) or a traffic source bias. Tell the user to investigate before resuming.

Apply-winner: when the customer clicks “Apply winner →” in the dashboard, the linked flag’s rollout flips to 100%. The customer’s code can then safely remove the if (variant === "treatment") branch in their next release.

7.1 In-app message A/B (since 0.2.0)

If the customer creates A/B variants of a message campaign (via the Create variant button on the message editor), the SDK handles variant selection automatically:

  • The customer does not need to call getVariant() manually for messages — MessageManager does it internally based on the linked experiment.
  • A user who consumes any variant (dismiss, CTA click, complete) will not see other variants of the same campaign — frequency cap aggregates across variants.
  • After the customer clicks Apply winner, only the winning variant renders.

This means the integration the AI assistant ships in §1 is enough — no code changes when the customer adds A/B testing to their messages.

8. What not to do

  • Do not put the API key in a public GitHub repo. For static sites, inject at deploy time.
  • Do not call init() more than once per page load.
  • Do not wrap track() calls in try/catch swallowing errors — the SDK already swallows transport errors internally; extra handlers hide real bugs.
  • Do not disable autocapture unless the user explicitly asks — it’s the main value of a drop-in install.
  • Do not enable replayEnabled: true without asking the user — session replay captures DOM and has privacy implications.
  • Do not emit your own message_* or $-prefixed custom events — both prefixes are reserved by the SDK and dashboard.
  • Do not manually add experiment_id, variant, or converted to track() calls while getVariant() is active — the SDK auto-attaches experiment_id and variant; manually adding them will overwrite the correct values and break experiment attribution. Only add converted: 1 on the conversion event itself.
  • Do not instantiate MessageRenderer or MessageManager directly in user code — they’re wired automatically by new TrackCrumb(...). Direct use is only for the dashboard preview surface.
  • Do not assume campaigns will render immediately after the user flips status to Active. The SDK has a 5-minute sessionStorage cache; in-flight sessions can take up to 5 min (or a reload) to pick up changes.

Human-readable reference