Everything that connects, and what each one gets you.
The tests run on the CI you already have, against the preview URL your host already builds. Nothing is installed on your repositories and nothing is written into them.
PostHog, GA4, Mixpanel and Plausible stay exactly where they are, still collecting: we write and maintain the tracking inside them. The connections after that feed the three things they cannot see — your repository, your ships and your revenue.
39 connections in the tables below, and where one is not yours to switch on the row says so. The three that are set up for you rather than by you have their own section at the foot of the page. The third column is what each row has to earn: not that it connects, but what reading it gets you.
- ci
- GitHub Actions · any CI that can run npx and read an exit code
- preview urls
- Vercel · Netlify · Render · Fly · staging · a tunnel to localhost
- analytics
- PostHog · Mixpanel · Amplitude · Umami · GA4 · Plausible
- code
- GitHub · your repo, read in your editor
- ships
- GitHub Actions · Vercel · Netlify · Railway · Render · Cloudflare · Fly
- money
- Stripe · Lemon Squeezy · Polar · Dodo · your own track() call
- alerts
- Slack · Discord · Mattermost · Rocket.Chat · signed JSON · email
- editors
- Claude Code · Cursor · VS Code · Windsurf · Cline · any MCP client
- apps
- web · Next.js · SvelteKit · Vite · Swift · Kotlin · React Native · Flutter
If your app is a mobile app, the agent drives web browsers only, so there is nothing here for you. If you need us to build a preview environment per pull request, we do not: we run against a URL you give us. If you need pixel-perfect session replay, that is not here and not coming. If you are all-in on PostHog and happy there, PostHog Scouts is real, good and free to you — the half of this you would still want is the instrumentation, not a second reader. If you want a customer-data platform that fans your events out to twenty destinations, that is a different product and building it would mean maintaining a hundred connectors instead of making the reading better. And if you want an answer from your event data today, a step change needs a baseline: what works on day one is the testing, and the checks that read code and HTML rather than events.
your CI, and whatever gives you a URL
A test needs two things: somewhere to run and something to point at. You already have both, which is why there is nothing here to install. Everything in this table is a file you copy or a URL you already get.
| what | how you connect it | what it gets you |
|---|---|---|
| GitHub Actions | copy the workflow template | The suite on every pull request, as one comment edited in place. It runs on your own runner and comments with the GITHUB_TOKEN Actions hands every job, so the only thing you add is ANTHROPIC_API_KEY in that repository's secrets. Permissions are contents: read and pull-requests: write, and nothing else. There is no GitHub App for this and nothing is written to your repository. |
| Vercel · Netlify · Render | the preview URL they already build | Each builds a preview per pull request. The template ships the Vercel wait-for-preview step; Netlify and Render have equivalent actions, and all three set the same output so the run step does not change. This is not a partnership or a marketplace listing, which is the point: nobody's permission is involved, because a URL is the entire input. |
| Fly · a staging box · anything reachable | test --url https://staging.example.com | Hosts without a per-PR preview are one line in the workflow instead: point at staging, or build the app and serve it inside the job. Locally, a tunnel to localhost works the same way. |
| Your Claude API key | ANTHROPIC_API_KEY in the repo's secrets | The agent runs on your key, so the model spend is yours and visible where you already watch it. A replay needs no key at all, which is most runs after the first. |
| The recordings cache | two actions/cache steps, in the template | A runner starts empty every time, so without a cache every test on every pull request is a fresh agent run forever. The save step is if: always() on purpose: the run with a failing test is the one that re-recorded the most, and a post-step that only saves on success throws exactly that work away. |
| Your project page | SMOLANALYTICS_PROJECT + SMOLANALYTICS_WRITE_KEY | Optional. Set both in the job and each verdict is recorded against the project: the suite, what each test last did, and how many runs needed a model. It authenticates with the write key because CI has no cookie, and a failed delivery never changes the verdict or the exit code. |
| Other CI (GitLab, CircleCI, Buildkite) | run the same command | The runner is one npx command and an exit code (0 passed, 1 a test failed, 2 our runner could not finish), so any CI can run the suite. The one thing that is GitHub-only today is --comment, which posts to a GitHub pull request. |
Week one the template runs with continue-on-error: the comment is posted, the merge is not blocked, and you delete that line once the suite has been right often enough to stop one. Forks and dependabot are skipped, because Actions withholds repository secrets from both and every test would fail on an empty key.
the analytics you already run
You remove nothing and migrate nothing. PostHog can be read where it already is, on a schedule you switch on yourself. The rest of this table is for the optional tracking a project can hold when you run no analytics at all: the only thing that ever moves is a copy of your history, one keyed pull, run once, by your agent, so that option has a quarter behind it on day one.
| what | how you connect it | what it gets you |
|---|---|---|
| PostHog, read on a schedule | paste a read-scoped key in project setup | The one vendor we can read live, and you switch it on yourself: region, project id and a read-scoped personal API key in your project's setup page. We prove the key against a real query before storing it and never show green over a key that cannot read. It then asks HogQL for daily counts per event, plus dimension marginals and money counters only for the events that produced a finding, watermarked so a re-run resumes instead of re-reading. Bounded queries rather than paging their raw-events endpoint, which their own docs tell connectors not to do. Your events never move. |
| PostHog history | migrate_from over MCP | A quarter of real history on day one, so backtest has something to read before you have shipped anything. Your agent pulls straight from PostHog's API with a personal API key and your project id, no export file. PostHog's own event ids come across, so running the same range twice replaces rather than doubles. |
| Mixpanel | migrate_from over MCP | The same call with vendor mixpanel and the project's API secret. $insert_id comes across, so a re-run after an interrupted import is safe. Both vendors require a date range and refuse an unbounded pull, and the key is used for that one request and never stored. |
| Amplitude | your export, converted by your agent | Your Export API file (gzipped JSON) becomes events here. $insert_id is preserved, so it dedupes on re-run. This is a file, not a keyed pull: Amplitude and Umami do not get the one-call treatment and the page will not pretend they do. |
| Umami | your export, converted by your agent | The website_event CSV, mapped to events with original timestamps. Umami rows carry no event id, so this one is not idempotent: import the file once, because a second run of it duplicates. |
| CSV / JSONL | any per-event file | Anything with an event name, a distinct id and a timestamp lands. JSONL is this instance's own export shape, so a file that came out of /v1/export goes straight back in with its ids intact. |
| GA4 · Plausible | nothing to import | Neither exports a per-event log you can carry, so nothing is imported and nothing is claimed here. Leave them running: they keep counting what they count, and the log here starts the day you paste the snippet. If you want history on day one, it comes from PostHog or Mixpanel. |
Every import previews first. Your agent runs it dry, shows you the parsed count and three sample events, and only then writes. Rows that cannot be mapped are skipped and counted by reason, so one bad line never silently truncates an import.
your repository
The reader runs in your editor, where your agent already has the repo open. Nothing leaves the machine.
| what | how you connect it | what it gets you |
|---|---|---|
| The code, against the data | event_source | The file and line every event fires from, which track() calls exist but never arrive (deployed wrong, unreachable, SDK not initialised), and which events are still arriving from a call site that was deleted. A count that dropped is a symptom. A missing call site is a cause. |
| What nothing is measuring | instrumentation_coverage | The list of things your product does that no event covers: form submissions, auth flows, payments, mutating API handlers. Absence is invisible in event data, because an action nobody instrumented looks exactly like one nobody performed. |
| The tracking, written for you | propose_instrumentation, then verify_instrumentation | The exact edits at the exact call sites, applied by your own agent, then a green and red table proving each event is FIRING, WIRED or MISSING. You never write tracking code and you never have to trust that it worked. |
your deploys
About 20 hours after a marked commit, once there is a real after-window, the metric that actually moved is posted as a comment on the pull request that shipped it. No markers, no comment.
| what | how you connect it | what it gets you |
|---|---|---|
| GitHub App | install it once, when it is enabled for you | Every commit on your default branch becomes a marker, with no CI edit and no config. Markers are keyed by sha and upserted, so syncing repeatedly is safe. Same gate as the instrumentation PR below: until the App is enabled on your account, use the row under this one, which needs nobody. |
| GitHub Actions | one step that POSTs /v1/deploys | A marker per push, on your terms rather than per merged PR. Set to continue-on-error in the docs on purpose: analytics that can fail a release is analytics that gets deleted after the first outage. |
| Vercel · Netlify · Railway · Render · Cloudflare · Fly | one curl in the build command | Works on every host, needs nobody's permission, and reads the sha and branch out of the host's own build variables. Documented this way because native deploy webhooks are mostly paywalled: of those six only Netlify and Railway offer one free and self-serve, and Fly has none at all. |
| Anything else you ship | record_deploy over MCP | Tell your agent what you shipped and it records the marker. Useful for a config change, a copy edit or a migration, which are ships that never appear in a build log. |
| Feature-flag flips | automatic | Flipping a flag is a ship, and it is the only kind detectable with no setup at all. Recorded as a marker the moment the state actually changes; a no-op flip is not a release and is not recorded. |
Once markers exist, deploy_impact lines each one up against the metric and reports before, after and direction, and the cron that writes the PR comment scores every candidate metric in your own events rather than a default one, preferring a significant 8% over an unproven 40%. It is correlation and the copy says so everywhere: a marker tells you what to look at first, not what to blame.
your revenue
Without money a finding is sized in people: 480 people a month. With it the same finding reads $14,400 a month across 480 people, and the expensive problem ranks above the loud one. The two paths below differ in how you switch them on.
| what | how you connect it | what it gets you |
|---|---|---|
| Your own track() call | track("checkout", { amount: 29 }) | The path that needs nothing from anyone: put an amount on the event you already fire and findings on that metric are priced from the next event onward. Works on any plan, today, with no webhook and no secret. |
| Stripe | POST /v1/revenue/stripe | checkout.session.completed, invoice.paid and payment_intent.succeeded land as ordinary payment events, signature-verified, with cents converted once so no revenue figure is ever a hundred times too large. |
| Lemon Squeezy | POST /v1/revenue/lemonsqueezy | order_created and subscription_payment_success. Retries dedupe on the provider's own event id, so a flaky minute cannot double a day's revenue. |
| Polar | POST /v1/revenue/polar | order.created, read from net_amount rather than gross, so the number matches what you were actually paid. |
| Dodo Payments | POST /v1/revenue/dodo | payment.succeeded, read from settlement_amount for the same reason. |
All four webhooks are authenticated by the provider's signature rather than by your write key, and the signing secret lives in the instance environment rather than in a settings file, so it never travels inside a backup or an export. On the hosted plan that means the webhook rows are configured on your instance rather than pasted by you: see the section below, which says exactly what that involves.
where the answer lands
Your agent wires any of these itself: add_webhook registers the endpoint, test_webhook fires a real delivery and reports the status it answered with, and create_alert defaults to anomaly detection so you need not already know what the number should be.
| what | how you connect it | what it gets you |
|---|---|---|
| Slack | paste an incoming-webhook URL | Findings and alert fires as plain text in the channel. hooks.slack.com URLs are detected from the host, so there is no format to choose. |
| Discord | paste a channel webhook URL | Same, in the place this audience actually is. Discord rejects Slack's payload shape outright, so it is detected separately, and a digest over the 2,000 character limit is clipped rather than dropped. |
| Mattermost · Rocket.Chat | paste the URL, pick Slack format | Both implement Slack's incoming-webhook contract on your own domain, which means the host cannot give them away. Choosing the format explicitly is the whole setup. |
| Anything else | signed JSON | Every non-chat endpoint gets JSON plus X-Smolanalytics-Signature, an HMAC-SHA256 of the exact body. The secret is shown once, when you add the endpoint. |
| on by default | The weekly brief, with each finding tagged verified, acted, recovered or needs you. It is the outcome ledger, not a digest: something you marked acted appears again only to tell you whether the metric actually recovered. |
Every delivery records its status and its error, retries with backoff on a 5xx or a 429, and disables itself after four consecutive failures with the reason on the row. A definite refusal, a 400 or a 404, is not retried, because the receiver understood and said no. An endpoint that starts failing says so instead of going quiet.
your editor
The MCP server carries the tools behind the testing and instrumentation half — instrumentation_coverage, propose_instrumentation, verify_instrumentation, event_source and the deploy markers — so the work happens in the editor where your agent already has the repo open. Your own model does the reasoning, so there is no per-call meter anywhere in the pricing.
| what | how you connect it | what it gets you |
|---|---|---|
| Claude Code · Cursor · VS Code · Windsurf · Cline · Claude Desktop | npx smolanalytics connect | Writes the MCP entry into whichever of those it finds installed: the tools behind the testing and instrumentation half, set up once. |
| Any other MCP client | stdio, or POST /mcp | One organization token reaches every project. Pass a project by name on any tool, or scope the connection to one project so the argument disappears, or scope it read only so it can change nothing. |
| Lovable · Bolt · Replit · v0 | paste the install line | The builder's own agent fetches the guide and wires it up, which is the only path that works when there is no terminal to run a command in. |
The editor path is fully live on day one and runs on your own model, so it is the trial-safe path: the features that spend OUR model key are off on a zero-dollar plan. A coverage table is what the code contains and a verdict is what the browser did — the model phrases the answer, it never invents either.
where the events come from
Writing events down is a solved problem, so this section is deliberately the shortest one on the page.
| what | how you connect it | what it gets you |
|---|---|---|
| Any website | one script tag | Pageviews including pushState route changes, clicks, scroll depth, form submits, rage clicks, dead clicks and uncaught exceptions, with no build step and no npm install. |
| Next.js · SvelteKit · Vite · Create React App · static HTML | npx smolanalytics init | Detects the framework and edits the one file that needs it. Nuxt and Astro are recognised and deliberately not edited: their real install is not a script tag in an HTML file, so the command prints the correct one and changes nothing rather than leaving you a page that looks instrumented and sends nothing. |
| Swift · Kotlin · React Native · Flutter | Swift Package Manager · JitPack · npm · pub.dev | Native SDKs with an offline queue, sessions and screen() tracking, which is what funnels and paths are built from on mobile. |
| Any backend, any language | POST /v1/events | No SDK to install: JSON and a write key, one event or up to ten thousand. Client and server key off the same distinct id, so a mobile session and a webhook join into one funnel without any stitching work. |
switched on for you, not by you
All three are shipped and working. None can be switched on by you today: two are configured where your instance runs, and the GitHub App has to be enabled on your account first. They sit here rather than in a table above pretending to be a URL you paste, because a 501 from inside Stripe's dashboard — or a "shipping very soon" where the repo field should be — is a worse way to learn it.
Point it at a repo and it opens a PR that adds the SDK and wires your key events; you read the diff and merge. The App has to be enabled on your account before the repo picker appears, so today the new-project screen shows this as coming rather than a field you can fill. The MCP path does the same job from your editor right now: npx smolanalytics connect, then /instrument-my-app. Tests need none of this either way — they run from a workflow file you copy in, and nothing is written to your repository.
The endpoint refuses unsigned revenue outright, which is correct: without a signing secret anyone on the internet could invent your revenue. The secret is an instance environment variable, so on the hosted plan it is set on your instance rather than pasted by you. Ask and it gets set, usually the same day. If you would rather not wait, the amount on your own track() call needs nobody.
Top queries with clicks, impressions, CTR and position, the biggest movers against the previous period, and money pages: quick wins sitting at position 4 to 15, snippets that rank but do not earn the click, and one query split across competing pages. It runs on an OAuth client and a consent step performed on the instance, so this is the same situation as above: real, and switched on for you rather than by you.
questions
Do I have to install a GitHub App to get tests on my pull requests?
No. The CI half is a workflow file you copy into .github/workflows/. It runs on your own Actions runner and comments with the GITHUB_TOKEN Actions already gives that job, with contents: read and pull-requests: write and nothing else. We are not granted access to your repositories and nothing is written into them. The GitHub App on this page belongs to the separate tracking-restore feature, is off by default, and testing never touches it.
My host does not build a preview per pull request. Can I still use this?
Yes. The URL is the input, not the host. Point the run at staging, or build the app and serve it inside the job (the template ships that variant commented out), or run it locally against a tunnel. Vercel, Netlify and Render each publish a preview the template can wait for; Fly does not, so it is the staging or build-it-here shape.
Whose model runs the tests?
Yours. The agent reads ANTHROPIC_API_KEY from the environment, so in CI it is a repository secret and the spend is on your own account, visible where you already watch it. A replay calls no model at all, which is what most runs after the first one are.
Does this read my PostHog live?
Yes, and you switch it on yourself: region, project id and a read-scoped personal API key in your project's setup page, proved against a real query before it is stored. That is the scheduled read, and it is a different thing from migrate_from, which is a one-time keyed pull of history your agent runs. PostHog is the only vendor implemented; nothing here reads a live GA4, Mixpanel, Amplitude, Umami or Plausible.
PostHog Scouts does this and it is free. Why would I connect anything here?
Scouts is real, it is good, and if you are all-in on PostHog you should use it. It has one boundary that is structural rather than a roadmap gap: it reads PostHog. It cannot read your GA4, your Amplitude, your Mixpanel or your Plausible, and it will not, because a reader owned by a collector exists to keep you on the collector. If you are not on PostHog then free is not a price you can pay; the price is a migration, which is a bigger bill and a much bigger week. The whole comparison, including where Scouts is genuinely better, is on the PostHog page.
Do I have to turn my current analytics off?
No, and there is no advantage to doing it. They keep collecting, your dashboards keep working, and nothing about this depends on them being gone. The two logs are independent, so if this does not earn its place in a fortnight you delete one snippet and you are exactly where you started.
Does everything here work during the trial?
The connections do, with two exceptions named on this page rather than discovered later: the revenue webhooks and Search Console are set on the instance, not by you. Separately, the features that spend OUR model key are off on a zero-dollar plan. The tests are unaffected either way, because they run on your own Anthropic key, and migrate_from and backtest run in your editor on your own model from the first hour.
Can I send events from a platform that is not listed?
Yes. Everything above is a thin wrapper over one open endpoint, POST /v1/events, which takes JSON and a write key. If your platform can make an HTTP request it can send events, and there is nothing to install.
What does NOT come across when I bring my history?
Saved reports, dashboards and cohort definitions. No analytics tool can move those, because each one defines them in its own internal format, and you should assume any vendor promising otherwise is moving raw events and calling it more. Raw events replay; the rest you rebuild, which is quicker here because you ask for a report in English instead of assembling it.
Is there anything you deliberately do not integrate with?
Anything that needs someone's permission, and anything that would make this a customer-data platform. Nothing on this page required a marketplace listing or a partner programme, which is also why the Stripe App and the Vercel Marketplace integration are absent: both are review-gated. It is the same reason the test runner takes a URL instead of building you an environment. And routing your events onward to twenty other destinations is a different product; building it would mean maintaining a hundred connectors instead of making the reading better.
One sentence, against a URL you already have.
npx smolanalytics test --url … --test "…" needs no account and connects to nothing. When it has earned it, copy the workflow template and the same suite reports on every pull request. Nothing you already run has to be turned off for either.
14 days, no card. Then $19/mo with 100 tested pull requests, 10c each after. keep the analytics you already have; we keep its tracking correct.