Docs

Install the browser SDK, record your deploys from CI, and ask Claude what people did.

Quick start

  1. Install the SDK

    Terminal
    npm install @ffanalytics/sdk
  2. Call init once, as your app loads

    app.ts
    import { init } from "@ffanalytics/sdk";
    
    init({ key: "pk_..." });

    pk_... is your project key, on the Projects page once you sign in. It is safe to ship in browser code: it can send events but not read them. From here, page views, clicks and errors are recorded with no other code.

    Add release: commitSha to line up every event with the change that shipped it.

  3. Connect Claude Code

    Terminal
    claude mcp add --transport http analyticsff \
      https://mcp.analyticsff.com/mcp

    Run /mcp in Claude Code to sign in, then ask about your product in plain words.

Browser SDK

init options

OptionDefault
keyrequiredYour project key.
releasenoneThe commit SHA of the running build. Every event carries it, so Claude can line events up with the change that shipped them.
autocapturetruefalse turns off automatic events. { pageviews, clicks, errors } turns them off one at a time.
replayoff{ sampleRate } turns on session replay.
hosthttps://us.i.analyticsff.comWhere events are sent.
flushIntervalMs5000How often queued events are sent.
maxBatchSize20Events are sent right away once this many are queued.
debugfalseLogs every event to the browser console.

Calling init again returns the first instance, so it is safe in code that runs more than once.

Functions

app.ts
import { identify, reset, track } from "@ffanalytics/sdk";

identify(user.id, { email: user.email, plan: "pro" });
track("league_created", { size: 12 });

// on logout
reset();
Function
track(event, properties?)Records an event of your own.
identify(userId, traits?)Call after someone logs in. What they did before logging in joins their account. Pass email to see it in the people list. Safe to call on every page load: it only sends when the id changes.
register(properties)Adds these properties to every event from now on, automatic ones included. Kept in memory, so a page reload clears them.
unregister(key)Stops adding one registered property.
reset()Call on logout. The next person on this browser starts with a new anonymous id and a new session.
flush()Sends queued events now. The SDK already does this when the tab is hidden or closed.

Automatic events

These are recorded as soon as init runs. Event names starting with $ are the SDK's own.

EventWhenProperties
$pageviewOn load and on every page change, including navigation in single-page apps.$title, $referrer
$clickClicks on links, buttons, role="button" and anything with data-ff-event.$text, $label (for icon-only buttons), $tag, $selector, $id, $classes, $href
$exceptionUncaught errors and unhandled promise rejections, up to 50 per page.$message, $stack, $filename, $lineno, $colno
$identifyWhen identify() is called with a new id.$anon_distinct_id and your traits
$recordingWhen a session starts being recorded, with replay on.none

Every event, yours included, also carries $pathname, $host, $screen_width, $screen_height, $viewport_width, $language, $user_agent, $lib, $lib_version, and utm_source, utm_medium, utm_campaign, utm_term, utm_content when the URL has them.

Sessions and storage

A session ends after 30 minutes with no events. The SDK keeps the visitor's id and current session in localStorage, under ff_distinct_id and ff_session.

What is never read

The SDK never reads what people type into inputs. A click on a submit button records the button's label and nothing else.

Naming clicks

page.html
<button data-ff-event="signup_cta" data-ff-plan="pro">
  Start trial
</button>

<nav data-ff-ignore>
  ...
</nav>
  • data-ff-event names a click. This button sends signup_cta instead of $click.
  • Any other data-ff-* attribute becomes a property: this click carries plan: "pro".
  • data-ff-ignore stops clicks from being recorded anywhere inside that element. It does not hide anything from session replay; use ff-block for that.

Session replay

Session replay records the page so a visit can be watched back. It is off until you turn it on.

app.ts
init({ key: "pk_...", replay: { sampleRate: 0.25 } });
  • sampleRate is the share of sessions to record, from 0 to 1. It defaults to 1. Each session is picked once, so it is recorded on every page or on none.
  • Everything typed into inputs is masked, always.
  • Nothing is recorded while the tab is hidden.
  • The recorder only downloads when replay is on, so it adds nothing to your page otherwise.
  • Each recorded session adds a $recording event to that person's timeline.

Hiding parts of the page

page.html
<p class="ff-mask">Shown as asterisks</p>
<div class="ff-block">Not recorded at all</div>

ff-mask replaces the text inside with asterisks. ff-block records an empty box the same size instead of the element.

Deploys and CI

Send a deploy event after each deploy and it sits on the timeline next to what people did, so Claude can tell you whether a change moved the numbers.

Terminal
FF_KEY=pk_... npx @ffanalytics/cli deploy --env production

GitHub Actions

Add a step after your deploy step, with your project key saved as a repository secret:

.github/workflows/deploy.yml
- name: Record deploy
  run: npx @ffanalytics/cli deploy --env production
  env:
    FF_KEY: ${{ secrets.FF_KEY }}

The command also picks up the branch, repository, commit message, who ran it, and a link to the Actions run. If sending fails it prints a warning and exits normally, so it never breaks a deploy.

Other events

The first word is the event name, so the same command records anything from a pipeline:

Terminal
npx @ffanalytics/cli ci_run --prop status=success --prop duration_s=312
Flag
--env <name>The environment you deployed to.
--sha <sha>The commit. Defaults to $GITHUB_SHA, then the current git commit.
--source <source>deploy for the deploy event, ci for anything else, unless you set it.
--prop key=valueAdds a property. Repeat it for more. Numbers and true/false are sent as numbers and booleans.
--key <pk_...>Your project key, if FF_KEY is not set.
--host <url>Where the event is sent, if FF_HOST is not set.
--strictExits with an error when sending fails.

Asking Claude

Terminal
claude mcp add --transport http analyticsff \
  https://mcp.analyticsff.com/mcp

Run /mcp in Claude Code to sign in. Claude then sees the projects your account owns. Ask the question you actually have:

  • Did yesterday's deploy change how long people stay?
  • Who signed up this week, and what did they do first?
  • Which errors started after the last release?
  • Pin a chart of daily signups to my dashboard.

What Claude can look up

  • Your projects, and the events in each with counts
  • The properties found on any event
  • Any event over time, by hour, day or week, split by a property
  • Everyone who used your product, most recent first
  • One person's full timeline, grouped by session
  • Your dashboards, which it can read, create and add to

Dashboards

Ask Claude to pin a chart, table or number to a dashboard. A tile keeps the question, not the numbers, so it runs again every time you open the page.

Ask for a written answer, like a nightly recap, and Claude saves it as text. It stays as written until you ask again, then Claude rewrites it for the new dates. Sign in at analyticsff.com to see your dashboards.

Sending events over HTTP

For events from your server, or anywhere the browser SDK does not run, POST them to the same address the SDK uses.

Terminal
curl -X POST https://us.i.analyticsff.com -d '{
  "key": "pk_...",
  "events": [{
    "event": "invoice_paid",
    "source": "server",
    "distinct_id": "user_123",
    "properties": { "amount": 49 }
  }]
}'
Field
eventRequired. Up to 200 characters.
distinct_idUse the same id you pass to identify(), so the event lands on that person's timeline.
tsISO time. Missing, or more than a day off, and the time it arrives is used instead.
sourceapp, server, git, vcs, ci, deploy or agent. Anything else is stored as app.
propertiesAny JSON object under 32 KB. Larger ones are replaced with { "$truncated": true }.
session_id, release, urlOptional, the same as the browser SDK sends.

Up to 500 events and 1 MB per request. The reply is 202 with how many events were accepted and rejected, or 401 for a key that is not recognized.