rybbit Documentation

Rybbit - open-source and privacy-friendly alternative to Google Analytics that is 10x more intuitive.

13,142 stars TypeScript TypeScript 101 files · ~189,196 tokens #analytics#google-analytics#product-analytics#web-analytics
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy rybbit on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name rybbit -p 80:80 --restart always clickhouse/clickhouse-server:26.3.17.4
Configured Services & Ports
⚙️ caddy
image: caddy:2.10.0
🔌 Ports: 80:80, 443:443, 443:443/udp
⚙️ clickhouse
image: clickhouse/clickhouse-server:26.3.17.4
🔌 Ports: 8123:8123, 9000:9000
⚙️ postgres
image: postgres:17.4
🔌 Ports: 5432:5432
⚙️ redis
image: redis:8.6.4-alpine
⚙️ backend
image: build locally
🔌 Ports: 3001:3001
⚙️ client
image: build locally
🔌 Ports: 3002:3002
docker-compose.yml
yaml (docker-compose.yml)
# DON'T USE THIS FILE, USE docker-compose.yml instead

services:
  caddy:
    image: caddy:2.10.0
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp" # Needed for HTTP/3
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile # Mount Caddy config file
      - caddy_data:/data # Mount persistent data volume for certs etc.
      - caddy_config:/config # Mount persistent config volume
    environment:
      # Pass domain name for use in Caddyfile
      # Email is configured via Caddyfile global options
      - DOMAIN_NAME=${DOMAIN_NAME}
    depends_on:
      - backend
      - client

  clickhouse:
    container_name: clickhouse
    image: clickhouse/clickhouse-server:26.3.17.4
    volumes:
      - clickhouse-data:/var/lib/clickhouse
      - /etc/rybbit-backup/clickhouse-s3.xml:/etc/clickhouse-server/config.d/backup-s3.xml:ro
      - /etc/rybbit-backup/clickhouse-backup-user.xml:/etc/clickhouse-server/users.d/backup-user.xml:ro
    configs:
      - source: clickhouse_network
        target: /etc/clickhouse-server/config.d/network.xml
      - source: clickhouse_server_settings
        target: /etc/clickhouse-server/config.d/server_settings.xml
      - source: clickhouse_logging
        target: /etc/clickhouse-server/config.d/logging_rules.xml
      # Profile (user-level) settings are only read from users.d, not config.d
      - source: clickhouse_user_settings
        target: /etc/clickhouse-server/users.d/user_settings.xml
    environment:
      - CLICKHOUSE_DB=${CLICKHOUSE_DB:-analytics}
      - CLICKHOUSE_USER=${CLICKHOUSE_USER:-default}
      - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-frog}
      # Lets the backend provision the least-privilege `rybbit_query` user
      # (custom SQL queries / dashboards) with SQL at startup.
      - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8123/ping"]
      interval: 3s
      timeout: 5s
      retries: 5
      start_period: 10s
    restart: unless-stopped
    ports:
      - "8123:8123"
      - "9000:9000"
    mem_limit: 12g
    mem_reservation: 6g
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "3"

  postgres:
    image: postgres:17.4
    container_name: postgres
    environment:
      - POSTGRES_USER=${POSTGRES_USER:-frog}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-frog}
      - POSTGRES_DB=${POSTGRES_DB:-analytics}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    restart: unless-stopped
    ports:
      - "5432:5432"

  redis:
    image: redis:8.6.4-alpine
    container_name: redis
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-changeme}", "--no-auth-warning", "ping"]
      interval: 3s
      timeout: 5s
      retries: 5
      start_period: 5s
    restart: unless-stopped
    # Backs session tracking, sticky identity, and bot-anomaly counters.
    # noeviction keeps those keys from being dropped under memory pressure;
    # session keys carry a 30-minute TTL so memory stays bounded by active-user
    # count. AOF (everysec) keeps that state durable across restarts.
    command:
      - redis-server
      - --requirepass
      - ${REDIS_PASSWORD:-changeme}
      - --appendonly
      - "yes"
      - --appendfsync
      - everysec
      - --maxmemory-policy
      - noeviction

  backend:
    container_name: backend
    build:
      context: .
      dockerfile: ./server/Dockerfile
    environment:
      - NODE_ENV=production
      - CLICKHOUSE_HOST=${CLICKHOUSE_HOST:-http://clickhouse:8123}
      - CLICKHOUSE_DB=${CLICKHOUSE_DB:-analytics}
      - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-frog}
      - CLICKHOUSE_QUERY_PASSWORD=${CLICKHOUSE_QUERY_PASSWORD:-${CLICKHOUSE_PASSWORD:-frog}}
      - POSTGRES_HOST=postgres
      - POSTGRES_PORT=5432
      - POSTGRES_DB=${POSTGRES_DB:-analytics}
      - POSTGRES_USER=${POSTGRES_USER:-frog}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-frog}
      - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
      - BASE_URL=${BASE_URL}
      - DISABLE_SIGNUP=${DISABLE_SIGNUP}
      # below is only for rybbit cloud
      - CLOUD=${CLOUD}
      - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
      - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
      # AppSumo configuration
      - APPSUMO_CLIENT_ID=${APPSUMO_CLIENT_ID}
      - APPSUMO_CLIENT_SECRET=${APPSUMO_CLIENT_SECRET}
      - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
      - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
      - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
      - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
      - RESEND_API_KEY=${RESEND_API_KEY}
      # R2 storage for session replay (cloud only)
      - R2_ACCOUNT_ID=${R2_ACCOUNT_ID}
      - R2_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID}
      - R2_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY}
      - R2_BUCKET_NAME=${R2_BUCKET_NAME:-rybbit}
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - REDIS_PASSWORD=${REDIS_PASSWORD:-changeme}
      - MAPBOX_TOKEN=${MAPBOX_TOKEN}
      - TURNSTILE_SECRET_KEY=${TURNSTILE_SECRET_KEY}
      - GOOGLE_REDIRECT_URI=${GOOGLE_REDIRECT_URI}
      - OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
      - OPENROUTER_MODEL=${OPENROUTER_MODEL}
      - CLUSTER_WORKERS=${CLUSTER_WORKERS:-4}
      - BETTER_AUTH_API_KEY=${BETTER_AUTH_API_KEY}
    depends_on:
      clickhouse:
        condition: service_healthy
      postgres:
        condition: service_started
      redis:
        condition: service_healthy
    restart: unless-stopped
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"
    ports:
      - "3001:3001"

  client:
    container_name: client
    build:
      context: .
      dockerfile: ./client/Dockerfile
      args:
        NEXT_PUBLIC_BACKEND_URL: ${BASE_URL}
        NEXT_PUBLIC_DISABLE_SIGNUP: ${DISABLE_SIGNUP}
        NEXT_PUBLIC_CLOUD: ${CLOUD}
        NEXT_PUBLIC_MAPBOX_TOKEN: ${MAPBOX_TOKEN}
        NEXT_PUBLIC_TURNSTILE_SITE_KEY: ${TURNSTILE_SITE_KEY}
        NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${STRIPE_PUBLISHABLE_KEY}
    environment:
      - NODE_ENV=production
      - NEXT_PUBLIC_BACKEND_URL=${BASE_URL}
      - NEXT_PUBLIC_DISABLE_SIGNUP=${DISABLE_SIGNUP}
      - NEXT_PUBLIC_CLOUD=${CLOUD}
      - NEXT_PUBLIC_MAPBOX_TOKEN=${MAPBOX_TOKEN}
      - NEXT_PUBLIC_TURNSTILE_SITE_KEY=${TURNSTILE_SITE_KEY}
      - NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${STRIPE_PUBLISHABLE_KEY}
    depends_on:
      - backend
    restart: unless-stopped
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"
    ports:
      - "3002:3002"

volumes:
  clickhouse-data:
  postgres-data:
  redis-data:
  caddy_data: # Persistent volume for Caddy's certificates and state
  caddy_config: # Persistent volume for Caddy's configuration cache (optional but good practice)

configs:
  clickhouse_network:
    content: |
      
          0.0.0.0
      

  # Server-level settings live directly under ; a 
  # wrapper is not a recognized section and gets silently ignored.
  # background_pool_size below ~10 fails the startup sanity check
  # (number_of_free_entries_in_pool_to_execute_mutation defaults to 20),
  # so the merge pool is left at its default.
  clickhouse_server_settings:
    content: |
      
          20
          524288000
      

  clickhouse_logging:
    content: |
      
        
            warning
            true
        
        
        
            system
            query_log
7500 event_date + INTERVAL 7 DAY DELETE
clickhouse_user_settings: content: | 1 1 1 1 0 0 8000000000 4000000000 4000000000 4 8192 0

Technical Documentation: rybbit-io/rybbit

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (README.md)

Rybbit is the modern open source and privacy friendly alternative to Google Analytics. It takes only a couple minutes to setup and is super intuitive to use.


🚀 Getting Started

There are two ways to start using Rybbit:

Option Description
Hosted Service The fastest way to get started
Self-Hosting Deploy and manage Rybbit on your own VPS for complete control

📚 Explore our documentation to learn more about installation, configuration, and usage.

To work on the client and backend locally, see the pnpm workspace setup.


✨ Key Features

  • All key web analytics metrics including sessions, unique users, pageviews, bounce rate, session duration
  • Session replays
  • No cookies & privacy friendly
  • Customizable goals. retention, user journeys, and funnels dashboards
  • Advanced filtering across 15+ dimensions
  • Custom events with JSON properties
  • 3 level location tracking (country -> region -> city) + advanced map visualizations
  • Real time dashboard
  • Support for organizations and unlimited number of sites

📊 Comparison

See how Rybbit compares to other analytics solutions:

Feature Rybbit GA4 Plausible Cloudflare
Open Source ✅ ❌ ✅ ❌
Self-Hosting ✅ ❌ ✅* ❌
Cookieless & Privacy friendly ✅ ❌ ✅ ✅
Advanced Maps ✅ ❌ ❌ ❌
Advanced Filters ✅ ⚠️ Limited ⚠️ Limited ❌
Web Vitals ✅** ❌ ❌ ❌
Session Details ✅ ❌ ❌ ❌
User Profiles ✅ ❌ ❌ ❌
Session Replays ✅ ❌ ❌ ❌
Funnels ✅ ✅ ✅** ❌
User Journeys ✅ ✅ ❌ ❌
Retention Analysis ✅ ✅ ❌ ❌
Goals & Events ✅ ✅ ✅ ❌
Real-time Dashboard ✅ ✅ ✅ ✅
Custom Events (JSON) ✅ ✅ ⚠️ Limited ❌
Error Tracking ✅ ❌ ❌ ❌
Public Dashboards ✅ ❌ ✅ ❌
Organizations ✅ ✅ ✅ ✅
Frog 🐸 ✅ ❌ ❌ ❌

* Plausible's Community Edition has very limited features compared to their cloud version

** Only available on paid tiers


📊 Dashboard Preview

Map

Session Replay

Journeys

Funnels

Goals

Users

Errors

Retention

Web Vitals (Cloud only)


⭐ Star History


README (`docs/README.md`)

docs-v2

This is a Next.js application generated with
Create Fumadocs.

Run development server:

bash
npm run dev
# or
pnpm dev
# or
yarn dev

Open http://localhost:3000 with your browser to see the result.

Explore

In the project, you can see:

  • lib/source.ts: Code for content source adapter, loader() provides the interface to access your content.
  • app/layout.config.tsx: Shared options for layouts, optional but preferred to keep.
Route Description
app/(home) The route group for your landing page and other pages.
app/docs The documentation layout and pages.
app/api/search/route.ts The Route Handler for search.

Fumadocs MDX

A source.config.ts config file has been included, you can customise different options like frontmatter schema.

Read the Introduction for further details.

Learn More

To learn more about Next.js and Fumadocs, take a look at the following
resources:


Content Docs (Docs) Index (`docs/content/docs/(docs)/index.mdx`)


title: Introduction full: true

Rybbit is a powerful open source web and product analytics platform. Check out our live demo using a real production site.

Getting Started

Choose the option that's right for you:

And then learn how to add the tracking script to your website to start collecting data.

Code

Rybbit is open sourced under the AGPL-3.0 license. You can find the code on GitHub.

Community

Join our Discord server or talk to me on X. I'm always looking for feedback and ideas to make Rybbit as awesome as possible.


Content Docs Api Getting Started (`docs/content/docs/api/getting-started.mdx`)


title: Getting Started

import { Callout } from 'fumadocs-ui/components/callout';
import { TypeTable } from 'fumadocs-ui/components/type-table';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

The API is currently in beta. There may be breaking changes as we continue to improve and expand the API.

API Playground

The easiest way to explore and test the API is using the built-in API Playground in your Rybbit dashboard. Access it by navigating to any site and clicking "API Playground" in the sidebar.

The playground provides:

  • Endpoint browser - Browse all available API endpoints
  • Parameter controls - Configure request parameters with a visual interface
  • Exact time ranges - Test date ranges with optional start and end times
  • Live responses - See real API responses with your actual data
  • Code generation - Copy ready-to-use code snippets

This is the recommended way to familiarize yourself with the API before integrating it into your applications.

Guides

Looking for what to build with the API? These end-to-end recipes chain endpoints together to solve a real problem:

Authentication

All API requests must include authentication using one of the following methods:

bash
Authorization: Bearer your_api_key_here

Query Parameter (Testing Only)

bash
?api_key=your_api_key_here
Query parameters expose API keys in server logs and browser history. Use only for quick testing.

Generating API Keys

Rybbit has two kinds of API keys:

  • Organization API keys (recommended for integrations) — the organization's own credential. They can access every site in the organization and keep working when team members leave. Only organization admins and owners can create and manage them.
  • Personal API keys — act as you, with exactly your access, including any site restrictions, across every organization you belong to. Best for personal scripts and for connecting MCP clients.

Organization keys

  1. Navigate to Settings → Organization in your Rybbit dashboard
  2. Scroll to the Organization API Keys section (visible to admins and owners)
  3. Enter a name for the key and click Create
  4. Copy the key immediately (it won't be shown again)

Personal keys

  1. Navigate to Settings → Account in your Rybbit dashboard
  2. Scroll to the Personal API Keys section
  3. Enter a name for the key and click Create
  4. Copy the key immediately (it won't be shown again)

Both kinds support restricting a key to specific permissions when you create it — leave restrictions off for a full-access key.

Rate Limits (Cloud Only)

Requests are limited in two ways: a burst limit that is the same on every plan, and a daily quota that depends on your plan.

Plan Burst Daily quota
Free / Basic API keys not available —
Standard 50 requests per 10 seconds 5,000 requests per day
Pro 50 requests per 10 seconds 25,000 requests per day

The burst limit is a bucket of 50 requests that refills continuously at 5 requests per second. You can spend all 50 at once, then keep going at 5 per second — so short spikes are fine and there is no window boundary to line up with.

The daily quota resets at 00:00 UTC.

Requests refused by the burst limit don't count against your daily quota.

Which budget a request is charged to

Limits apply per credential owner, never per key — creating more keys splits an existing budget rather than adding to it:

Credential Budget
Organization key (rb_org_) The organization's. All of an organization's keys share it.
Personal key Yours. All of your personal keys share one budget, across every organization you belong to.
OAuth token (MCP) Yours, the same budget as your personal keys.

A personal key's quota follows the most generous plan among the organizations you belong to.

Self-hosted instances are not rate limited at all — neither tier applies, and no rate limit headers are sent.

Reading your remaining budget

Analytics and management endpoints report your current budget on every response, so you can pace a client without waiting to be rejected:

Header Meaning
X-RateLimit-Burst-Limit / -Remaining Burst bucket size and tokens left
X-RateLimit-Burst-Reset Seconds until the bucket is full again
X-RateLimit-Daily-Limit / -Remaining Daily quota and requests left today
X-RateLimit-Daily-Reset Seconds until 00:00 UTC
RateLimit-Limit / -Remaining / -Reset Whichever of the two is closest to running out

Treat these as advisory: a few endpoints resolve credentials outside the standard path and omit them. Retry-After on a 429 is always present.

When you hit a limit

A 429 names the limit you hit and tells you how long to wait, in seconds:

json
{
  "error": "Rate limit exceeded",
  "scope": "daily",
  "limit": 5000,
  "retryAfter": 34200
}

scope is burst or daily, and the same value is in the Retry-After header. The difference matters: a burst rejection clears in about a second, while a daily one lasts until the quota resets. Back off accordingly rather than retrying straight away.


Common Parameters

The following parameters are shared across all analytics endpoints.

Time Parameters

All endpoints require date-based, exact datetime, or relative time parameters:

<TypeTable
type={{
start_date: {
type: 'string',
typeDescription: 'Format: YYYY-MM-DD (e.g., "2024-01-15")',
required: false,
},
end_date: {
type: 'string',
typeDescription: 'Format: YYYY-MM-DD (e.g., "2024-01-31")',
required: false,
},
time_zone: {
type: 'string',
typeDescription: 'e.g., "America/New_York", "Europe/London", "Asia/Tokyo". Required with date and exact datetime ranges.',
required: false,
},
start_datetime: {
type: 'string',
typeDescription: 'Format: YYYY-MM-DD HH:mm:ss (e.g., "2024-01-15 13:00:00")',
required: false,
},
end_datetime: {
type: 'string',
typeDescription: 'Format: YYYY-MM-DD HH:mm:ss (e.g., "2024-01-15 15:00:00")',
required: false,
},
past_minutes_start: {
type: 'number',
typeDescription: 'Minutes ago from now. Must be greater than past_minutes_end.',
required: false,
},
past_minutes_end: {
type: 'number',
typeDescription: 'Minutes ago from now. Must be less than past_minutes_start.',
required: false,
},
}}
/>

Note: You must provide either:

  • All three date parameters (start_date, end_date, time_zone), OR
  • All three exact datetime parameters (start_datetime, end_datetime, time_zone), OR
  • Both relative parameters (past_minutes_start, past_minutes_end)

Examples

Date-based query (last 30 days):

text
?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York

Exact datetime query (8 AM to 10 AM in New York on Jan 15, sent as UTC):

text
?start_datetime=2024-01-15%2013:00:00&end_datetime=2024-01-15%2015:00:00&time_zone=America/New_York

Relative query (last 60 minutes):

text
?past_minutes_start=60&past_minutes_end=0

Filter Parameters

The filters parameter accepts a JSON-encoded array of filter objects to narrow down your data.

You can also pass segment_id, the ID of a saved segment, instead of or alongside filters. The segment's filters are expanded on the server and ANDed with any filters you pass. Use the Segments endpoints to find IDs. Public and private-link callers can only use segments marked as shown on the public dashboard.

text
?start_date=2024-01-01&end_date=2024-01-31&time_zone=UTC&segment_id=12

Filter Object Structure

<TypeTable
type={{
parameter: {
type: 'FilterParameter',
typeDescription: 'See Filter Parameters table below',
required: true,
},
type: {
type: 'FilterType',
typeDescription: '"equals" | "not_equals" | "contains" | "not_contains" | "regex" | "not_regex" | "greater_than" | "less_than"',
required: true,
},
value: {
type: 'Array<string | number>',
typeDescription: 'Multiple values are treated as OR conditions',
required: true,
},
}}
/>

Filter Types

<TypeTable
type={{
equals: {
type: 'string',
typeDescription: 'Value must exactly match',
},
not_equals: {
type: 'string',
typeDescription: 'Value must not match',
},
contains: {
type: 'string',
typeDescription: 'Value must contain the substring (uses SQL LIKE %value%)',
},
not_contains: {
type: 'string',
typeDescription: 'Value must not contain the substring',
},
regex: {
type: 'string',
typeDescription: 'Value must match the regex pattern (max 500 characters)',
},
not_regex: {
type: 'string',
typeDescription: 'Value must not match the regex pattern',
},
greater_than: {
type: 'number',
typeDescription: 'For numeric fields like lat, lon',
},
less_than: {
type: 'number',
typeDescription: 'For numeric fields like lat, lon',
},
}}
/>

Available Filter Parameters

Browser & Device

<TypeTable
type={{
browser: {
type: 'string',
typeDescription: 'e.g., "Chrome", "Firefox", "Safari"',
},
browser_version: {
type: 'string',
typeDescription: 'e.g., "Chrome 120"',
},
operating_system: {
type: 'string',
typeDescription: 'e.g., "Windows", "macOS", "Linux"',
},
operating_system_version: {
type: 'string',
typeDescription: 'e.g., "Windows 10/11", "macOS 14"',
},
device_type: {
type: 'string',
typeDescription: '"desktop", "mobile", "tablet"',
},
dimensions: {
type: 'string',
typeDescription: 'e.g., "1920x1080", "390x844"',
},
language: {
type: 'string',
typeDescription: 'e.g., "en-US", "es-ES"',
},
}}
/>

Location

<TypeTable
type={{
country: {
type: 'string',
typeDescription: 'ISO 3166-1 alpha-2 code, e.g., "US", "GB"',
},
region: {
type: 'string',
typeDescription: 'e.g., "California", "England"',
},
city: {
type: 'string',
typeDescription: 'Format: "Region-City", e.g., "CA-San Francisco"',
},
timezone: {
type: 'string',
typeDescription: 'IANA timezone, e.g., "America/New_York"',
},
lat: {
type: 'number',
typeDescription: 'Supports greater_than, less_than, equals (with 0.001 tolerance)',
},
lon: {
type: 'number',
typeDescription: 'Supports greater_than, less_than, equals (with 0.001 tolerance)',
},
}}
/>

Page & Traffic

<TypeTable
type={{
pathname: {
type: 'string',
typeDescription: 'e.g., "/blog/post-title"',
},
page_title: {
type: 'string',
},
hostname: {
type: 'string',
typeDescription: 'e.g., "example.com"',
},
querystring: {
type: 'string',
typeDescription: 'e.g., "?ref=twitter"',
},
referrer: {
type: 'string',
typeDescription: 'e.g., "google.com", "twitter.com"',
},
entry_page: {
type: 'string',
},
exit_page: {
type: 'string',
},
channel: {
type: 'string',
typeDescription: 'e.g., "Direct", "Organic Search", "Social"',
},
}}
/>

UTM Parameters

<TypeTable
type={{
utm_source: {
type: 'string',
typeDescription: 'e.g., "google", "newsletter"',
},
utm_medium: {
type: 'string',
typeDescription: 'e.g., "cpc", "email"',
},
utm_campaign: {
type: 'string',
},
utm_term: {
type: 'string',
},
utm_content: {
type: 'string',
},
}}
/>

User & Events

<TypeTable
type={{
user_id: {
type: 'string',
typeDescription: 'Matches both device fingerprint and custom identified user ID',
},
event_name: {
type: 'string',
typeDescription: 'Filters to sessions containing the event',
},
}}
/>

Filter Examples

<Tabs items={['Single Filter', 'Multiple Filters', 'Contains', 'Regex', 'Numeric', 'URL-encoded']}>

json
[
  {
    "parameter": "country",
    "type": "equals",
    "value": ["US"]
  }
]
Multiple filters use AND logic: ```json [ { "parameter": "country", "type": "equals", "value": ["US", "CA"] }, { "parameter": "device_type", "type": "equals", "value": ["mobile"] } ] ``` Substring match: ```json [ { "parameter": "pathname", "type": "contains", "value": ["/blog"] } ] ``` Pattern match: ```json [ { "parameter": "pathname", "type": "regex", "value": ["^/products/[0-9]+$"] } ] ``` Geolocation comparison: ```json [ { "parameter": "lat", "type": "greater_than", "value": ["37.0"] }, { "parameter": "lat", "type": "less_than", "value": ["38.0"] } ] ``` ``` ?filters=%5B%7B%22parameter%22%3A%22country%22%2C%22type%22%3A%22equals%22%2C%22value%22%3A%5B%22US%22%5D%7D%5D ```

Error Responses

All endpoints return standard HTTP status codes:

  • 200 - Success
  • 400 - Bad Request (invalid parameters)
  • 401 - Unauthorized (missing or invalid API key)
  • 403 - Forbidden (no access to site)
  • 404 - Not Found (site doesn't exist)
  • 429 - Too Many Requests (rate limit exceeded)
  • 500 - Internal Server Error

Error Response Format

json
{
  "error": "Error message describing what went wrong"
}

Content Partials Guides README (`docs/content/partials/guides/README.md`)

Integration guide standard

Every page under content/docs/(docs)/guides/ follows TEMPLATE.mdx in this folder.
npm run lint:guides checks every guide against the rules below and runs in CI.

Rules

  1. Frontmatter: title is the platform name as the vendor spells it. description is
    Add Rybbit analytics to your site (use app, store, docs, blog when
    that is the better noun). category is one of the values in scripts/check-guides.mjs.
    icon is an @icons-pack/react-simple-icons export name (SiWordpress); omit it when
    the brand has no icon.
  2. Skeleton: intro paragraph, then exactly three <Step>s titled
    Get your tracking snippet, Add the snippet to , Verify installation.
    Optional ## Track custom events and ## Troubleshooting, then ## Next steps.
  3. Shared content comes from partials, never retyped:
    • get-snippet.mdx – the canonical snippet and the self-hosting note.
    • verify.mdx – the verification checklist.
    • next-steps.mdx – cross-links.
      Include with ../../../partials/guides/<name>.mdx (one more ../
      from a subfolder such as react/). Includes are inlined at build time, so they also
      reach the .mdx / llms.txt endpoints that AI agents read.
  4. Snippet form: when a guide must show the tag inline (a template file, a config
    value, a JS injector), use the URL form script.js?siteId=YOUR_SITE_ID with defer.
    Never the legacy data-site-id attribute: script optimisers strip data-* attributes.
  5. Only platform-specific content belongs in the guide. Generic explanations of what
    Rybbit is, how events work, GDPR, ad blockers or proxies are linked, not repeated.
  6. Don't invent UI. Menu paths and file names must be real. If you cannot confirm
    where custom head code goes on a platform, say what the setting is usually called and
    point to the vendor's own page rather than guessing a path.
  7. Tone: second person, present tense, imperative steps. No "powerful", "seamless",
    "easily". A guide is typically 40–120 lines; length comes from real platform detail,
    not padding.
  8. Imports: double quotes, Steps before Callout before Tabs, only what is used.
  9. Sidebar: add the slug to guides/meta.json under the matching ---Category---
    separator, alphabetically. The index page (guides/index.mdx) groups by frontmatter
    category automatically.

Adding a guide

text
cp content/partials/guides/TEMPLATE.mdx "content/docs/(docs)/guides/<slug>.mdx"
# fill it in, then
npm run lint:guides

Content Docs (Docs) Guides Index (`docs/content/docs/(docs)/guides/index.mdx`)


title: Integration guides

Rybbit works on any site that can load a script tag. Each guide below shows exactly where the tracking snippet goes on that platform and how to confirm it is working.

Don't see your platform? Paste the snippet from Site Settings → Tracking Script into the <head> of every page, or follow the tracking script reference.


Content Docs (Docs) Mcp Index (`docs/content/docs/(docs)/mcp/index.mdx`)


title: MCP

import { Callout } from "fumadocs-ui/components/callout";
import { Cards, Card } from "fumadocs-ui/components/card";
import { SiClaude } from "@icons-pack/react-simple-icons";

Rybbit includes a hosted Model Context Protocol (MCP) endpoint. It gives an AI assistant read and write access to your analytics: exploring traffic and behavior data, and managing sites, goals, funnels, organization members, teams, and user profiles — through the same API the dashboard uses, with the same access checks, roles, and rate limits.

Your MCP client connects to Rybbit over HTTPS and sends your API key with each protocol request. Rybbit verifies the key before processing any message, and every tool call is authorized against the key user's role and site access.

Connect a client

If your client supports the MCP authorization flow — Claude Code, Codex, Claude Desktop, and opencode all do — you can connect with OAuth and skip API keys entirely. The steps below describe the API-key path, which works in every client and supports scoped credentials.

1. Create an API key

  1. Open Settings → Account in Rybbit.
  2. Create a key in the Personal API Keys section.
  3. Copy it immediately. Rybbit only shows a new key once.

A key created without permissions can do everything its user can do. To hand an agent a narrower credential, create the key with scoped permissions (POST /api/user/api-keys with a permissions object, e.g. {"analytics": ["read"], "goals": ["read", "write"]}): the MCP tool list and every API call are then limited to those resource:action scopes. write implies read on the same resource, and scopes never elevate — org admin/owner requirements still apply. Deleting the key revokes the connection.

2. Add the endpoint

Use this Streamable HTTP URL for Rybbit Cloud:

text
https://app.rybbit.io/api/mcp

For a self-hosted installation, replace the host with your configured BASE_URL:

text
https://analytics.example.com/api/mcp

Configure your client to send this header on every request:

text
Authorization: Bearer <RYBBIT_API_KEY>

MCP clients use different configuration formats. A typical remote-server configuration has this shape:

json
{
  "mcpServers": {
    "rybbit": {
      "url": "https://app.rybbit.io/api/mcp",
      "headers": {
        "Authorization": "Bearer <RYBBIT_API_KEY>"
      }
    }
  }
}

Use your client's secret or environment-variable support when available instead of saving the key in a file. Query-string API keys are not accepted by the MCP endpoint. Per-client walkthroughs — Claude Code, Codex, Claude Desktop, Cursor, VS Code, opencode — are in Client guides below.

Or connect with OAuth

Clients that support the MCP authorization flow can skip API keys entirely: add the endpoint URL with no headers, and the client discovers Rybbit's authorization server through the standard well-known metadata (/.well-known/oauth-protected-resource), registers itself, and opens a browser window for you to log in and approve access. The resulting access token acts with your user's role, exactly like an API key would.

OAuth grants support the same resource:action scopes as API keys (advertised in scopes_supported). A grant that requests only the standard OIDC scopes is unrestricted; a grant that requests custom scopes like analytics:read is limited to them. The requesting client controls which scopes it asks for — consent is currently approve-or-deny as a whole.

3. Start with context

Ask the assistant to call list_sites, or prompt it with something like:

List my Rybbit sites, summarize last month's traffic for the docs site, and create a goal for signups.

list_sites returns the numeric site_id and organization_id required by the other tools, plus the key's role in each organization. It does not return API keys or member email addresses.

Client guides

Coding agents can use the endpoint to pull analytics into your development workflow — checking whether an error you just fixed still occurs in production, or looking up which pages matter before a refactor — and chat clients like Claude Desktop turn it into a conversational analytics interface. Follow the guide for your client:

} title="Claude Code" description="One command with OAuth or an API key, plus a no-secrets team setup" href="/docs/mcp/claude-code" /> } title="Claude Desktop" description="Add a custom connector on claude.ai, desktop, and mobile" href="/docs/mcp/claude-desktop" />

Any other MCP client that supports Streamable HTTP works with the generic configuration from step 2.

Available tools

Analytics (read)

Tool Returns
get_overview Sessions, pageviews, users, pages per session, bounce rate, and session duration
get_overview_timeseries The overview KPIs bucketed over time
get_breakdown Sessions broken down by one dimension (pages, referrers, countries, devices, UTM, ...)
get_live_stats Visitors active on the site right now
get_event_names Custom event names tracked on the site with counts
get_errors JavaScript errors grouped by name/message with occurrence counts
get_web_vitals Core Web Vitals percentiles (LCP, CLS, INP, FCP, TTFB)
get_retention User retention cohort table
get_journeys Most common page-to-page navigation paths
get_annotations Notes pinned to dates on the traffic chart (launches, deploys, campaigns, outages)
list_segments Saved segments: named, reusable filter sets for a site or the whole organization
apply_segment Overview KPIs for visitors matching a saved segment, with its filters for reuse

Search Console (read)

Tool Returns
get_search_console_status Whether Google Search Console is connected for the site, and which property
get_search_console_data Clicks, impressions, CTR, and average position grouped by query, page, country, device, or date

Search Console tools read the Google Search Console property connected to the site under Site settings > Integrations (Rybbit Cloud); no Google Cloud project or separate MCP server is needed. get_search_console_data groups by up to three dimensions (query, page, country, device, date), accepts ANDed filters such as {"dimension": "page", "operator": "contains", "expression": "/pricing"}, and returns 100 rows by default (up to 25,000, paged with start_row). Google's numbers lag about two days; pass data_state: "all" to include fresh, still-changing data. For how this compares with self-run Search Console servers, and prompts that work well, see the Google Search Console MCP guide.

Sites

Tool Does
list_sites List organizations and sites the key can access (call this first)
get_site One site's full configuration
create_site Add a new site to an organization (admin)
update_site_config Change site settings: name, domain, tracking features, exclusions, tags (editor)
delete_site Permanently delete a site and its data (admin, destructive)

Goals & funnels

Tool Does
get_goals Conversion goals with conversion stats
create_goal Create a path, event, or autocapture goal
update_goal Replace a goal's definition
delete_goal Permanently delete a goal (destructive)
get_funnels Saved funnel definitions
analyze_funnel Compute an ad-hoc funnel without saving it
save_funnel Save a funnel, or update one by passing funnel_id
delete_funnel Permanently delete a saved funnel (destructive)

People

Tool Does
get_users Person inventory with per-user aggregates and traits (sortable, searchable)
get_user One person's profile: traits, linked devices, vitals, locations
identify_user Link an anonymous device to your user ID and merge traits
update_user_traits Replace a person's traits wholesale
delete_user GDPR erasure of one person's analytics data (editor, destructive)

Organization & teams

Tool Does
list_members Organization members with roles, site access, and teams
add_member Add an existing Rybbit user to the organization (admin)
update_member_site_access Restrict a member to specific sites (admin)
list_teams Teams with members and site access
create_team Create a team (admin)
update_team Rename a team or replace its members/sites (admin)
delete_team Permanently delete a team (admin, destructive)

Raw data & SQL

Tool Does
get_sessions Recent visitor sessions with full attribution
get_session One session's detail plus its event timeline
get_events Raw recent events, newest first
get_query_schema The ClickHouse schema and rules for run_query
run_query Read-only ClickHouse SQL against the site-scoped scoped_events table

Analytics tools accept optional time inputs (start_date/end_date with an IANA time_zone, or past_minutes) and the same filters as the dashboard. Omitting time inputs queries all time; an unrecognized time_zone is rejected before the call is made. get_sessions, get_events, and get_users accept an optional fields list to return only the named row columns, which keeps large result sets small.

Permissions and roles

The MCP surface mirrors the REST API's permission model:

  • Read tools work for any role with access to the site, including viewers.
  • Write tools need the member role or above on the site (goals, funnels, identify_user, update_user_traits, save_funnel).
  • Tools marked (editor) need the editor role or above on the site.
  • Tools marked (admin) require the API key's user to hold the admin or owner role in the organization. add_member can't grant a role above the key's own, so only an owner's key can grant owner.

A key acts with its user's role on each site, including site-specific roles from site restrictions and teams, and it reaches exactly the sites that user can see. The REST layer enforces all of this: an underprivileged key gets a 403 "Insufficient role" naming the role required.

To give an assistant a read-only or read-mostly view, create a scoped API key (see step 1): grant only the resource:action scopes it needs — e.g. {"analytics": ["read"], "sessions": ["read"]} for a reporting agent. The MCP tool list is filtered to those scopes, and any out-of-scope call returns 403 { "error": "Insufficient scope", "required": "goals:write" }. Alternatively, connect with a viewer's key: it hides nothing but refuses every write.

OAuth clients request scopes through the standard grant. Rybbit advertises the same resource:action scopes in its discovery metadata; a grant carrying no custom scopes (or only the standard OIDC scopes) is treated as full access, for backward compatibility.

Destructive tools

delete_goal, delete_funnel, delete_site, delete_team, and delete_user permanently destroy data and are marked with the MCP destructiveHint annotation, so well-behaved clients ask for confirmation before invoking them. delete_site and delete_user (the GDPR-erasure path) also remove recorded events and replays, with erasure completing asynchronously.

Data and security boundary

  • The API key is verified before any MCP message is processed, and each tool call re-runs the corresponding REST route's own access checks, role requirements, and rate limits.
  • The surface returns whatever the REST API returns for your role — including session-level records and, where a site has IP tracking enabled, visitor IP addresses. Connect only AI clients you trust with that data, and use a separate, revocable API key per integration.
  • Text originating in tracked traffic (page titles, paths, referrers, event names, traits) is stripped of control and bidi-override characters. AI clients should still treat these values as untrusted data, never as instructions.
  • run_query is restricted to a read-only, site-scoped table with row and execution-time caps.
  • The server does not contain an AI model or send analytics to an AI provider. Data only leaves Rybbit in response to the MCP client you configure.
  • The endpoint is stateless. Each HTTP request is independently authenticated.

Troubleshooting

  • 401 Unauthorized: The client did not send a Bearer credential, or the key/token is invalid, expired, or revoked. OAuth-capable clients re-authenticate automatically via the WWW-Authenticate challenge.
  • OAuth login loop or "table does not exist" (self-hosted): Run npm run db:push in server/ to create the OAuth tables, then retry.
  • 403 in tool results: The key's user lacks access to that site or organization, or holds too low a role for the tool ("Insufficient role" names the role required). Check the role field in list_sites.
  • 403 "Insufficient scope": The credential was created without the resource:action scope this tool needs (the required field names it). Use a key or OAuth grant that includes it.
  • 429 Too Many Requests: The API key exceeded its plan's rate limit.
  • 404 "GSC not connected" from Search Console tools: Connect Google Search Console for that site under Site settings > Integrations, then retry.
  • 503 Service Unavailable: Rybbit could not verify the key. Retry the request; do not replace a known-valid key based on this response.
  • 405 Method Not Allowed on GET: This is expected. The stateless endpoint accepts MCP messages over POST.
  • Site access error: Call list_sites again. Membership or site permissions may have changed.

Content Docs (Docs) Proxy Guide Index (`docs/content/docs/(docs)/proxy-guide/index.mdx`)


title: Proxy Setup Guide

import { Cards, Card } from 'fumadocs-ui/components/card';
import { Callout } from 'fumadocs-ui/components/callout';
import {
SiAstro,
SiCloudflare,
SiExpress,
SiLaravel,
SiNextdotjs,
SiNginx,
SiVercel
} from '@icons-pack/react-simple-icons';

Proxying Rybbit's tracking script through your own domain helps bypass ad blockers and improves tracking accuracy by making all analytics requests appear as first-party traffic.

What is Proxy Setup?

When you add Rybbit's tracking script directly to your website using the default installation:

html
<script src="https://app.rybbit.io/api/script.js" async data-site-id="YOUR_SITE_ID"></script>

The browser loads the script from app.rybbit.io, which ad blockers can identify and block as third-party analytics. Even though Rybbit is privacy-focused and doesn't use cookies, some ad blockers use aggressive blocking rules that target any analytics domains.

Proxying solves this by serving the tracking script and handling tracking requests through your own domain (e.g., yourdomain.com/analytics/script.js), making all tracking appear as first-party traffic.

Rybbit is fully compliant with GDPR, CCPA, and other privacy regulations. It uses cookieless tracking based on IP + User Agent hashing with optional daily rotating salts. Proxying is about improving data accuracy, not circumventing privacy laws.

Why Proxy Rybbit?

Improve Tracking Accuracy

Ad blockers are becoming increasingly aggressive and may block analytics scripts regardless of their privacy practices. By proxying through your own domain:

  • Bypass ad blocker detection: Requests appear as first-party traffic to your own domain
  • Increase data completeness: Typical improvement of 10-30% in tracked sessions
  • Maintain accurate metrics: Get a true picture of your website's performance

Stay Privacy-Compliant

Proxying doesn't compromise user privacy:

  • No cookies are used (Rybbit remains cookieless)
  • User data is still processed according to your privacy policy
  • GDPR, CCPA, and other privacy regulations remain fully satisfied
  • Users can still opt out through browser settings or Do Not Track

Better Performance (Optional)

With proper caching configuration, proxying can also improve performance:

  • Serve scripts from your CDN or edge network
  • Reduce DNS lookups (one less domain to resolve)
  • Cache static scripts closer to your users

How It Works

Here's the flow when you proxy Rybbit:

  1. User visits your website: Your HTML includes the tracking script from your domain

    html
    <script src="https://yourdomain.com/analytics/script.js" data-site-id="123"></script>
  2. Script loads from your domain: Your server proxies the request to Rybbit's servers

    • Request: yourdomain.com/analytics/script.js → proxied to → app.rybbit.io/api/script.js
  3. Script auto-discovers analytics host: The Rybbit script automatically detects the domain it was loaded from

    typescript
    // The script extracts the host from its own src attribute
    const analyticsHost = src.split("/script.js")[0];
    // Result: "https://yourdomain.com/analytics"
  4. Tracking data sent through your domain: All analytics events are sent to your domain and proxied to Rybbit

    • Event tracking: yourdomain.com/analytics/track → proxied to → app.rybbit.io/api/track
    • Session replay: yourdomain.com/analytics/session-replay/record/{siteId} → proxied
    • Configuration: yourdomain.com/analytics/site/tracking-config/{siteId} → proxied
  5. Data appears in your Rybbit dashboard: No changes needed in the Rybbit interface

**Bandwidth Consideration**: All tracking requests will flow through your server infrastructure, which will increase bandwidth usage. For most sites this is minimal (typically 10-50KB per session), but consider this when evaluating hosting costs.

Choose Your Framework

Select the framework or platform that matches your setup:

} title="Next.js" href="/docs/proxy-guide/next-js" className="flex items-center gap-2" /> } title="Nginx" href="/docs/proxy-guide/nginx" className="flex items-center gap-2" /> } title="Vercel" href="/docs/proxy-guide/vercel" className="flex items-center gap-2" /> } title="Express.js" href="/docs/proxy-guide/express" className="flex items-center gap-2" /> } title="Laravel" href="/docs/proxy-guide/laravel" className="flex items-center gap-2" /> } title="Astro" href="/docs/proxy-guide/astro" className="flex items-center gap-2" /> } title="Cloudflare Workers" href="/docs/proxy-guide/cloudflare-workers" className="flex items-center gap-2" />

Generic Implementation

If your framework isn't listed above, check out our generic implementation guide that covers the core concepts applicable to any reverse proxy or server setup.

Need Help?

If you encounter issues during setup, check our troubleshooting guide for common problems and solutions.


Content Docs Api Bots Overview (`docs/content/docs/api/bots/overview.mdx`)


title: Get Bot Overview full: true method: GET

import { TypeTable } from 'fumadocs-ui/components/type-table';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

text
GET /api/sites/:site/bots/overview

Returns aggregate bot-traffic metrics with a breakdown by detection layer.

Path Parameters

<TypeTable
type={{
site: {
type: 'string | number',
required: true,
},
}}
/>

Query Parameters

Accepts all Common Parameters (time and filters).

Bot filtering supports a subset of filter parameters: browser, browser_version, operating_system, operating_system_version, country, region, city, device_type, referrer, hostname, pathname, querystring, dimensions, user_id, lat, and lon.

<TypeTable
type={{
layer: {
type: 'string',
typeDescription: '"ua_pattern" | "header_heuristics" | "client_signals" | "bot_asn" | "rate_anomaly"',
required: false,
},
}}
/>

Response

<TypeTable
type={{
bot_requests: {
type: 'number',
},
total_events: {
type: 'number',
},
bot_percentage: {
type: 'number',
typeDescription: 'Range: 0-100',
},
ai_requests: {
type: 'number',
},
ai_agent_requests: {
type: 'number',
},
ai_crawler_requests: {
type: 'number',
},
ua_pattern: {
type: 'number',
},
header_heuristics: {
type: 'number',
},
client_signals: {
type: 'number',
},
bot_asn: {
type: 'number',
},
rate_anomaly: {
type: 'number',
},
}}
/>

<Tabs items={['cURL', 'JavaScript', 'Python', 'PHP', 'Ruby', 'Go', 'Rust', 'Java', '.NET']}>

bash title="request"
curl -X GET "https://app.rybbit.io/api/sites/123/bots/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York" \
  -H "Authorization: Bearer your_api_key_here"
```javascript title="Request" const response = await fetch( 'https://app.rybbit.io/api/sites/123/bots/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York', { headers: { 'Authorization': 'Bearer your_api_key_here' } } );

const data = await response.json();

text
</Tab>
  <Tab value="Python">
```python title="Request"
import requests

response = requests.get(
    'https://app.rybbit.io/api/sites/123/bots/overview',
    params={
        'start_date': '2024-01-01',
        'end_date': '2024-01-31',
        'time_zone': 'America/New_York'
    },
    headers={
        'Authorization': 'Bearer your_api_key_here'
    }
)

data = response.json()
```php title="Request" $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'https://app.rybbit.io/api/sites/123/bots/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer your_api_key_here' ]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

text
</Tab>
  <Tab value="Ruby">
```ruby title="Request"
require 'net/http'
require 'json'

uri = URI('https://app.rybbit.io/api/sites/123/bots/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer your_api_key_here'

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
```go title="Request" req, _ := http.NewRequest("GET", "https://app.rybbit.io/api/sites/123/bots/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York", nil) req.Header.Set("Authorization", "Bearer your_api_key_here")

client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()

var data map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)

text
</Tab>
  <Tab value="Rust">
```rust title="Request"
let client = reqwest::Client::new();
let res = client
    .get("https://app.rybbit.io/api/sites/123/bots/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York")
    .header("Authorization", "Bearer your_api_key_here")
    .send()
    .await?;

let data: serde_json::Value = res.json().await?;
```java title="Request" HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://app.rybbit.io/api/sites/123/bots/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York")) .header("Authorization", "Bearer your_api_key_here") .GET() .build();

HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());

text
</Tab>
  <Tab value=".NET">
```csharp title="Request"
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer your_api_key_here");

var response = await client.GetAsync("https://app.rybbit.io/api/sites/123/bots/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York");
var data = await response.Content.ReadAsStringAsync();
json title="response"
{
  "data": {
    "bot_requests": 1840,
    "total_events": 48933,
    "bot_percentage": 3.76,
    "ai_requests": 940,
    "ai_agent_requests": 180,
    "ai_crawler_requests": 760,
    "ua_pattern": 1200,
    "header_heuristics": 410,
    "client_signals": 95,
    "bot_asn": 120,
    "rate_anomaly": 15
  }
}

Content Docs Api Overview Overview (`docs/content/docs/api/overview/overview.mdx`)


title: Get Overview full: true method: GET

import { TypeTable } from 'fumadocs-ui/components/type-table';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

text
GET /api/sites/:site/overview

Returns high-level analytics metrics for a site.

Path Parameters

<TypeTable
type={{
site: {
type: 'string | number',
required: true,
},
}}
/>

Query Parameters

Accepts all Common Parameters (time and filters).

Response

<TypeTable
type={{
sessions: {
type: 'number',
},
pageviews: {
type: 'number',
},
users: {
type: 'number',
},
pages_per_session: {
type: 'number',
},
bounce_rate: {
type: 'number',
typeDescription: 'Range: 0-100',
},
session_duration: {
type: 'number',
},
}}
/>

<Tabs items={['cURL', 'JavaScript', 'Python', 'PHP', 'Ruby', 'Go', 'Rust', 'Java', '.NET']}>

bash title="request"
curl -X GET "https://app.rybbit.io/api/sites/123/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York" \
  -H "Authorization: Bearer your_api_key_here"
```javascript title="Request" const response = await fetch( 'https://app.rybbit.io/api/sites/123/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York', { headers: { 'Authorization': 'Bearer your_api_key_here' } } );

const data = await response.json();

text
</Tab>
  <Tab value="Python">
```python title="Request"
import requests

response = requests.get(
    'https://app.rybbit.io/api/sites/123/overview',
    params={
        'start_date': '2024-01-01',
        'end_date': '2024-01-31',
        'time_zone': 'America/New_York'
    },
    headers={
        'Authorization': 'Bearer your_api_key_here'
    }
)

data = response.json()
```php title="Request" $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'https://app.rybbit.io/api/sites/123/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer your_api_key_here' ]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

text
</Tab>
  <Tab value="Ruby">
```ruby title="Request"
require 'net/http'
require 'json'

uri = URI('https://app.rybbit.io/api/sites/123/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer your_api_key_here'

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
```go title="Request" req, _ := http.NewRequest("GET", "https://app.rybbit.io/api/sites/123/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York", nil) req.Header.Set("Authorization", "Bearer your_api_key_here")

client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()

var data map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)

text
</Tab>
  <Tab value="Rust">
```rust title="Request"
let client = reqwest::Client::new();
let res = client
    .get("https://app.rybbit.io/api/sites/123/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York")
    .header("Authorization", "Bearer your_api_key_here")
    .send()
    .await?;

let data: serde_json::Value = res.json().await?;
```java title="Request" HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://app.rybbit.io/api/sites/123/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York")) .header("Authorization", "Bearer your_api_key_here") .GET() .build();

HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());

text
</Tab>
  <Tab value=".NET">
```csharp title="Request"
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer your_api_key_here");

var response = await client.GetAsync("https://app.rybbit.io/api/sites/123/overview?start_date=2024-01-01&end_date=2024-01-31&time_zone=America/New_York");
var data = await response.Content.ReadAsStringAsync();
json title="response"
{
  "data": {
    "sessions": 15420,
    "pageviews": 48933,
    "users": 12847,
    "pages_per_session": 3.17,
    "bounce_rate": 42.8,
    "session_duration": 182.5
  }
}

Content Docs Api Performance Overview (`docs/content/docs/api/performance/overview.mdx`)


title: Get Performance Overview full: true method: GET

import { TypeTable } from 'fumadocs-ui/components/type-table';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

text
GET /api/sites/:site/performance/overview

Returns aggregate performance metrics with percentile breakdowns for all Core Web Vitals.

Path Parameters

<TypeTable
type={{
site: {
type: 'string | number',
required: true,
},
}}
/>

Query Parameters

Accepts all Common Parameters (time and filters).

Response

<TypeTable
type={{
data: {
type: 'PerformanceMetrics',
},
}}
/>

PerformanceMetrics Object

Each metric (lcp, cls, inp, fcp, ttfb) includes the following percentiles:

<TypeTable
type={{
lcp_p50: {
type: 'number',
typeDescription: 'milliseconds',
},
lcp_p75: {
type: 'number',
typeDescription: 'milliseconds',
},
lcp_p90: {
type: 'number',
typeDescription: 'milliseconds',
},
lcp_p99: {
type: 'number',
typeDescription: 'milliseconds',
},
total_performance_events: {
type: 'number',
},
}}
/>

<Tabs items={['cURL', 'JavaScript', 'Python', 'PHP', 'Ruby', 'Go', 'Rust', 'Java', '.NET']}>

bash title="request"
curl -X GET "https://app.rybbit.io/api/sites/1/performance/overview?start_date=2024-01-01&end_date=2024-01-31" \
  -H "Authorization: Bearer your_api_key_here"
```javascript title="Request" const response = await fetch( 'https://app.rybbit.io/api/sites/1/performance/overview?start_date=2024-01-01&end_date=2024-01-31', { headers: { 'Authorization': 'Bearer your_api_key_here' } } );

const data = await response.json();

text
</Tab>
  <Tab value="Python">
```python title="Request"
import requests

response = requests.get(
    'https://app.rybbit.io/api/sites/1/performance/overview',
    params={
        'start_date': '2024-01-01',
        'end_date': '2024-01-31'
    },
    headers={
        'Authorization': 'Bearer your_api_key_here'
    }
)

data = response.json()
```php title="Request" $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'https://app.rybbit.io/api/sites/1/performance/overview?start_date=2024-01-01&end_date=2024-01-31'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer your_api_key_here' ]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

text
</Tab>
  <Tab value="Ruby">
```ruby title="Request"
require 'net/http'
require 'json'

uri = URI('https://app.rybbit.io/api/sites/1/performance/overview?start_date=2024-01-01&end_date=2024-01-31')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer your_api_key_here'

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
```go title="Request" req, _ := http.NewRequest("GET", "https://app.rybbit.io/api/sites/1/performance/overview?start_date=2024-01-01&end_date=2024-01-31", nil) req.Header.Set("Authorization", "Bearer your_api_key_here")

client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()

var data map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)

text
</Tab>
  <Tab value="Rust">
```rust title="Request"
let client = reqwest::Client::new();
let res = client
    .get("https://app.rybbit.io/api/sites/1/performance/overview?start_date=2024-01-01&end_date=2024-01-31")
    .header("Authorization", "Bearer your_api_key_here")
    .send()
    .await?;

let data: serde_json::Value = res.json().await?;
```java title="Request" HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://app.rybbit.io/api/sites/1/performance/overview?start_date=2024-01-01&end_date=2024-01-31")) .header("Authorization", "Bearer your_api_key_here") .GET() .build();

HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());

text
</Tab>
  <Tab value=".NET">
```csharp title="Request"
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer your_api_key_here");

var response = await client.GetAsync("https://app.rybbit.io/api/sites/1/performance/overview?start_date=2024-01-01&end_date=2024-01-31");
var data = await response.Content.ReadAsStringAsync();
json title="response"
{
  "data": {
    "lcp_p50": 1850,
    "lcp_p75": 2400,
    "lcp_p90": 3200,
    "lcp_p99": 5100,
    "cls_p50": 0.05,
    "cls_p75": 0.1,
    "cls_p90": 0.18,
    "cls_p99": 0.35,
    "inp_p50": 120,
    "inp_p75": 180,
    "inp_p90": 280,
    "inp_p99": 450,
    "fcp_p50": 1200,
    "fcp_p75": 1600,
    "fcp_p90": 2100,
    "fcp_p99": 3500,
    "ttfb_p50": 450,
    "ttfb_p75": 650,
    "ttfb_p90": 950,
    "ttfb_p99": 1800,
    "total_performance_events": 48521
  }
}

CLAUDE (`docs/CLAUDE.md`)

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

  • Dev: npm run dev (Next.js with Turbopack on port 3003)
  • Build: npm run build
  • Production: npm start
  • Type Check: tsc --noEmit
  • Lint integration guides: npm run lint:guides (rules in content/partials/guides/README.md; copy TEMPLATE.mdx for a new guide)

Project Overview

This is the documentation and marketing website for Rybbit, built with Next.js 16 and Fumadocs. The site includes:

  • Documentation pages (using Fumadocs MDX)
  • Blog (separate content collection)
  • Marketing pages (home, pricing, features, comparison pages)
  • Analytics tools (SEO, ROI calculators, privacy policy builder, etc.)
  • LLM-friendly documentation endpoints
  • The domain is rybbit.com

Code Architecture

Content Management

  • Fumadocs MDX: Documentation system using fumadocs-mdx for MDX processing
  • Source Configuration: source.config.ts defines two content collections:
    • docs: Documentation pages in content/docs/
    • blog: Blog posts in content/blog/ with extended frontmatter (date, author, image, tags)
  • Source Adapters:
    • src/lib/source.ts: Docs source loader (base URL: /docs)
    • src/lib/blog-source.ts: Blog source loader (base URL: /blog)
  • Auto-generated: .source/ directory contains generated TypeScript from MDX files

Route Structure

  • app/(home)/*: Marketing pages (landing, pricing, features, comparison pages, tools)
  • app/docs/[[...slug]]/: Documentation pages using catch-all routing
  • app/blog/: Blog listing and individual posts
  • app/api/search/: Documentation search powered by Orama
  • app/api/tools/: API endpoints for tool functionality (SEO generators, analytics detector, etc.)
  • app/llms.mdx/ and app/llms-full.txt/: LLM-optimized documentation endpoints

Internationalization (i18n)

  • Library: next-intl v4 with experimental extract mode enabled
  • Supported Locales: en (default), de, fr, zh, es, pl, it, ko, pt, ja
  • Message Files: messages/{locale}.json — one JSON file per locale with ~242 keys
  • Config Files in src/i18n/:
    • routing.ts: Locale list and prefix strategy (as-needed — no prefix for default en)
    • request.ts: Server-side locale resolution with fallback to en
    • navigation.ts: Locale-aware Link, redirect, usePathname, useRouter exports
  • Middleware: src/proxy.ts uses next-intl/middleware for automatic locale detection from URL or Accept-Language header
  • URL Structure: Default locale has no prefix (/pricing); others are prefixed (/fr/pricing)
  • Fumadocs i18n: src/lib/i18n.ts configures the Fumadocs doc tree with the same locales
  • Usage in components: const t = useExtracted() from next-intl, then t("key") or t("Welcome {name}", { name })
  • Language Switcher: src/components/LanguageSwitcher.tsx — client component dropdown for all 10 locales
  • Adding translations: Add keys to messages/en.json, then run npm run extract to sync all locale files
  • Automated translation: .github/workflows/translate-docs.yml detects untranslated strings on push to master and opens a PR with AI-generated translations

Path Aliases

  • @/*: Maps to src/*
  • @/.source: Maps to .source/index.ts (generated MDX content)

Code Conventions

  • Fumadocs for documentation infrastructure; MDX components defined in src/mdx-components.tsx
  • Tailwind CSS v4 with dark mode support

Important Notes

  • Content files in content/docs/ and content/blog/ are processed by Fumadocs MDX
  • The .source/ directory is auto-generated and should not be edited manually
  • Next.js rewrites map /docs/:path*.mdx to /llms.mdx/:path* for LLM consumption
  • Remote images allowed from: pbs.twimg.com, abs.twimg.com, ui-avatars.com, cdn.outrank.so, www.google.com

.Design Sync Docs ActivitySlider (`.design-sync/docs/ActivitySlider.md`)


category: Replay

The session-replay scrubber: a Radix slider whose track shows where the visitor was active and whose rail above it plots the meaningful events (clicks, navigations, typing, rage clicks, console errors) as colored 8px dots, so you can jump straight to the interesting moment. It is the "activity" view of the replay player; TimelineSlider is the plain thin transport rail used when there is no event data.

Single part: ActivitySlider. It extends every Radix Slider.Root prop (value/defaultValue as [ms], max, step, onValueChange, disabled) plus three of its own:

  • duration (ms, the recording length; ALSO pass it as max so the thumb and the rail agree),
  • activityPeriods ({ start, end }[] in ms; drawn as neutral-600 segments on the neutral-700 idle track),
  • events (raw rrweb events { timestamp, type, data }; the slider runs them through getMeaningfulEvents from the replay module, so feed it what the player already has: type 4 Meta for session start/navigation, type 3 source 2 for clicks, source 5 for input, type 6 console plugin for logs).

Marker hues: navigation/console log blue-400, click/double click violet-500, right click fuchsia-500, rage click red-500, typing amber-500, resize cyan-500, console warning yellow-500, console error red-500. Each dot has a 2px ring in the panel color (ring-neutral-900 in dark) so overlapping dots stay distinct, and its title shows the label and offset on hover (not capturable statically). Over 160 markers the plain clicks/typing are sampled while every navigation, rage click and console event is kept. The played portion of the track is emerald (accent-500); the thumb is a 16px white disc with an emerald border.

Conventions: mount it inside the player card (neutral-900) with the session identity on the left and m:ss / m:ss on the right in 12px muted tabular figures; drive value from the player's current time.

tsx
import { ActivitySlider } from "@rybbit/ui";

<ActivitySlider
  duration={duration}
  max={duration}
  step={100}
  value={[currentTime]}
  onValueChange={([t]) => player.seek(t)}
  activityPeriods={[{ start: 0, end: 30_000 }, { start: 34_000, end: 78_000 }]}
  events={rrwebEvents}
/>

.Design Sync Docs Alert (`.design-sync/docs/Alert.md`)


category: Feedback

Inline, non-dismissing status message that lives in the page flow (a settings panel, the top of a report, an onboarding step). Use it for state the user needs to see when they arrive: tracking detected, a plan limit approaching, a failed verification. For a transient confirmation after an action use Toaster/toast; for something that must be acknowledged use AlertDialog.

Parts: Alert (root, role="alert", full width, 4.8px radius, 16px/12px padding, tinted fill) → a lucide icon as the first child (absolutely positioned top-left, 16px, tinted to the variant; the text shifts right 28px automatically) → AlertTitle (14px medium, one line) → AlertDescription (14px, wraps; nested `` get relaxed leading, so an action row can follow the text).

Variants (variant on Alert): default (neutral-800 fill, gray icon) · info (blue tint) · success (emerald tint) · warning (yellow tint) · destructive (red tint). There is no border; the hue is carried by the 12-15% tint and the icon only, so the alert sits flat on the panel like everything else in the instrument. Title in sentence case, description says the consequence or the next step; title-only and description-only are both fine for one-liners. Give the icon className="h-4 w-4".

tsx
import { Alert, AlertDescription, AlertTitle } from "@rybbit/ui";
import { AlertTriangle } from "lucide-react";

<Alert variant="warning">
  <AlertTriangle className="h-4 w-4" />
  <AlertTitle>Approaching your event limit</AlertTitle>
  <AlertDescription>You have used 91% of 1M monthly events. Upgrade to keep collecting after the cap.</AlertDescription>
</Alert>

.Design Sync Docs AlertDialog (`.design-sync/docs/AlertDialog.md`)


category: Overlays

Focus-trapped confirmation prompt (Radix AlertDialog). Unlike Dialog, it has no close X, cannot be dismissed by clicking the overlay, and always ends in a choice. Use it before an irreversible or costly action (delete a site, leave an organization, regenerate a key); use Dialog for forms and everything else.

Parts: AlertDialog (root, open/onOpenChange) → AlertDialogTrigger (asChild around a Button) → AlertDialogContent (centered 512px panel) → AlertDialogHeader / AlertDialogTitle / AlertDialogDescription → optional body → AlertDialogFooter with AlertDialogCancel (already styled as an outline Button, closes on click) and AlertDialogAction (accepts Button variant and size; closes on click, so attach the real work to onClick).

Conventions: title is a question naming the object ("Delete tomato.gg?"); description states the consequence and whether it can be undone; the cancel label is a verb ("Keep site"), not "No". The action is variant="destructive" for deletes and variant="accent" for non-destructive confirms. Never put more than two buttons in the footer.

tsx
<AlertDialog>
  <AlertDialogTrigger asChild>
    <Button variant="destructive">Delete site</Button>
  </AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Delete tomato.gg?</AlertDialogTitle>
      <AlertDialogDescription>
        This removes the site and its 2.4M events. This cannot be undone.
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Keep site</AlertDialogCancel>
      <AlertDialogAction variant="destructive" onClick={deleteSite}>Delete site</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

.Design Sync Docs Badge (`.design-sync/docs/Badge.md`)


category: Data display

Small inline status label: 12px medium text, 2px/6px padding, 2.8px radius (rounded-md), inline-flex. One export, Badge, with a variant prop; it renders a div, so it is not clickable — wrap it or use a Button size="xs" when it must act.

Variants:

  • default — neutral-800 fill, neutral-100 text. Plan names, generic labels ("Pro").
  • secondary — one notch quieter (panel fill, neutral-300 text). Counts and low-emphasis tags.
  • success — emerald 20% tint, emerald-400 text. "Tracking", "Enabled", "Verified".
  • warning — yellow tint. "No data", "Paused", thresholds.
  • destructive — red tint. "Bot", "Blocked", "Failed".
  • info — blue tint. "Beta", "New", informational only (never for data-series color).
  • outline — neutral-700 border, transparent fill. Environment / category tags.
  • ghost — no border or fill. Inline keywords.

Conventions: colored variants are semantic, so use them only when the state has that meaning — a status column is the canonical place. Put a 12px lucide icon (className="h-3 w-3") as the first child and add gap-1; add tabular-nums for counts and font-mono for paths. Badges do not change size; if you need a larger pill, it is a Button.

tsx
<Badge variant="success">Tracking</Badge>
<Badge variant="warning">No data</Badge>
<Badge variant="destructive" className="gap-1"><Bot className="h-3 w-3" /> Bot</Badge>
<Badge variant="secondary" className="tabular-nums">2,140</Badge>

.Design Sync Docs BasicTabs (`.design-sync/docs/BasicTabs.md`)


category: Navigation

Underline tabs (Radix), the quiet sibling of Tabs. There is no filled strip: triggers are muted text sitting on a transparent 32px row, and the active one turns bright with a 2px bottom border. Use it inside cards and section headers to switch a breakdown (Countries / Regions / Cities, Channels / Referrers) where a pill strip would be too heavy; use Tabs for top-level view switching.

Parts (exported with the BasicTabs prefix because the source file shares names with tabs.tsx): BasicTabs (root; defaultValue or value / onValueChange) → BasicTabsList (inline-flex row with 12px gaps) → BasicTabsTrigger (value, disabled) and BasicTabsContent (value; adds mt-2).

Conventions: short one-word labels; an optional count after the label as 48; keep the list flush-left with the card's content edge. Never mix BasicTabs and Tabs parts in one tree.

tsx
<BasicTabs defaultValue="countries">
  <BasicTabsList>
    <BasicTabsTrigger value="countries">Countries</BasicTabsTrigger>
    <BasicTabsTrigger value="regions">Regions</BasicTabsTrigger>
    <BasicTabsTrigger value="cities">Cities</BasicTabsTrigger>
  </BasicTabsList>
  <BasicTabsContent value="countries">{/* ranked list */}</BasicTabsContent>
  <BasicTabsContent value="regions">…</BasicTabsContent>
  <BasicTabsContent value="cities">…</BasicTabsContent>
</BasicTabs>

.Design Sync Docs Breadcrumb (`.design-sync/docs/Breadcrumb.md`)


category: Navigation

Location trail for deep pages (a user profile, a funnel, an org settings sub-page). Ancestors are muted links, the current page is bright plain text, and a small chevron separates them. Use it only when the page is two or more levels below a sidebar destination; top-level report pages do not need one.

Parts: Breadcrumb (<nav aria-label="breadcrumb">) → BreadcrumbList (<ol>, 14px muted text, wraps) → BreadcrumbItem (<li>) containing either BreadcrumbLink (href, or asChild to wrap a router Link) or BreadcrumbPage (current page, aria-current="page"). Put a BreadcrumbSeparator between items: with no children it renders a 14px ChevronRight; pass any node (e.g. <Slash />) to replace it. BreadcrumbEllipsis is a 36px "…" placeholder for collapsed middle levels; wrap it in its own BreadcrumbItem.

Conventions: the site domain (tomato.gg) is the first crumb inside a site; keep at most four visible crumbs and collapse the rest with the ellipsis; the last item is always a BreadcrumbPage, never a link. Hover brightening on links is a live state and is not shown statically.

tsx
<Breadcrumb>
  <BreadcrumbList>
    <BreadcrumbItem>
      <BreadcrumbLink href="/tomato.gg">tomato.gg</BreadcrumbLink>
    </BreadcrumbItem>
    <BreadcrumbSeparator />
    <BreadcrumbItem>
      <BreadcrumbLink href="/tomato.gg/users">Users</BreadcrumbLink>
    </BreadcrumbItem>
    <BreadcrumbSeparator />
    <BreadcrumbItem>
      <BreadcrumbPage>usr_8f3a91c2</BreadcrumbPage>
    </BreadcrumbItem>
  </BreadcrumbList>
</Breadcrumb>

.Design Sync Docs Button (`.design-sync/docs/Button.md`)


category: Actions

The primary control. Nine visual variants on one shared shape (36px tall, 4.8px radius, 14px medium text, 4px icon gap).

  • accent — the ONE emerald primary action on a surface ("Add site", "Save", "Publish"). At most one per view.
  • default — the raised gray chrome button (neutral-850 fill, neutral-750 border in dark). The everyday secondary action.
  • secondary — one notch quieter than default (panel-colored fill).
  • outline — bordered, transparent; toolbar and filter chrome.
  • ghost — no border or fill until hover; icon buttons, "Cancel", inline actions.
  • link — text only with underline on hover; navigation-like actions.
  • success / warning / destructive — state-colored actions (emerald, yellow, red). Use for confirmations, never as decoration.

Sizes: lg (40px) · default (36px) · sm (32px) · xs (24px) · icon (36px square) · smIcon (28px square). Put a lucide icon as the first child; the button sizes it to 16px automatically.

asChild renders the styles onto the child element (e.g. an <a>), keeping one DOM node.

tsx
<Button variant="accent"><Plus /> New goal</Button>
<Button variant="outline" size="sm">Export CSV</Button>
<Button size="icon" variant="ghost" aria-label="Settings"><Settings /></Button>

.Design Sync Docs ButtonGroup (`.design-sync/docs/ButtonGroup.md`)


category: Actions

Joins adjacent Buttons (and inputs/select triggers) into one control: inner corners are squared and the shared border is collapsed so a Day/Week/Month switch, a zoom toolbar or a split "range + chevron" trigger reads as a single piece of chrome. Use it whenever two or more related actions sit flush; use plain flex gap-2 for unrelated buttons.

Parts: ButtonGroup (root, role="group", flex w-fit items-stretch; orientation="horizontal" (default) removes left borders/radii between children, "vertical" stacks them and removes the top ones) → children are ordinary Buttons. ButtonGroupSeparator is a hairline Separator (neutral-800, stretches to the group height) for splitting clusters inside one group. ButtonGroupText is a static label cell with the same height and border (neutral-800 fill; asChild to render it as a label or link); put a lucide icon first and it is sized to 16px. Nesting a ButtonGroup directly inside a ButtonGroup inserts an 8px gap between the clusters, which is how you build a filter bar.

Conventions: members share one size (sm for filter chips, icon for tool rails). Use variant="outline" for the members and variant="default" (the raised neutral-850 chrome) for the selected one; accent only for the single primary action at the end of a group. buttonGroupVariants is exported if you need the classes on a custom element.

tsx
import { Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from "@rybbit/ui";
import { Calendar, ChevronDown, Download } from "lucide-react";

<ButtonGroup>
  <Button variant="outline" size="sm">Day</Button>
  <Button variant="default" size="sm">Week</Button>
  <Button variant="outline" size="sm">Month</Button>
</ButtonGroup>

<ButtonGroup>
  <ButtonGroupText><Calendar /> Last 30 days</ButtonGroupText>
  <Button variant="outline" aria-label="Change range"><ChevronDown /></Button>
  <ButtonGroupSeparator />
  <Button variant="outline"><Download /> Export</Button>
</ButtonGroup>

.Design Sync Docs Calendar (`.design-sync/docs/Calendar.md`)


category: Forms

Month-grid date picker (react-day-picker v9) that lives inside the date-range popover and any "pick a day" dialog. One component plus its day cell: Calendar (all DayPicker props pass through) and CalendarDayButton (the ghost/icon Button used per day, swappable via components). Key props: mode="single" with selected: Date / onSelect(date), or mode="range" with selected: { from, to } / onSelect(range); defaultMonth (always set it so the render is deterministic); disabled (a matcher such as { after: new Date() } to block future days); captionLayout="label" (default, static "May 2024") or "dropdown" (month + year selects, needs startMonth/endMonth); numberOfMonths={2} for a side-by-side range picker; showOutsideDays (default true).

Appearance: a w-fit neutral-850 panel with 12px padding and 32px cells (--cell-size), muted weekday letters, the selected day as a light (neutral-200) pill with dark text, today as a neutral-700 pill, range middles as a continuous neutral-700 band with square inner corners, disabled days at 50% opacity. Inside a PopoverContent or CardContent the panel background goes transparent to sit on the host. Keep it for picking days; pair it with the preset range list (Last 7/30/90 days) rather than replacing it, and use Input type="time" for times.

tsx
const [range, setRange] = React.useState<{ from?: Date; to?: Date }>();

<Calendar
  mode="range"
  selected={range}
  onSelect={setRange}
  defaultMonth={new Date(2024, 4, 1)}
  disabled={{ after: new Date() }}
  numberOfMonths={2}
/>

.Design Sync Docs Card (`.design-sync/docs/Card.md`)


category: Data display

The panel. Every dashboard section, stat tile, and settings block sits in a Card: a neutral-900 surface one step off the canvas, 1px neutral-850 border, 4.8px radius, overflow-hidden, no shadow. Flat parts, all optional:

  • Card — the surface (relative, so loaders and absolute children anchor to it).
  • CardHeader — vertical stack with 16px padding; holds CardTitle (16px semibold, tight tracking) and CardDescription (14px, neutral-400 muted).
  • CardContent — 16px padding with the top padding removed (p-4 pt-0) so it butts against the header. Put your rows, table, or chart here.
  • CardFooter — horizontal flex row, same padding rule; put Buttons here (justify-end gap-2 for an action row, ONE accent).
  • CardLoader — an indeterminate progress bar for "refetching in place". It is absolute top-0 left-0 w-full with a -15px top margin, so render it as the first child of a Card (or inside any relative container) and the card's overflow-hidden clips it to a thin line along the top edge. It needs no props; it reads the theme via next-themes and colors itself neutral-400 in dark mode. Use it with Skeleton content underneath on first load, or alone over stale data while refetching.

Conventions: stat tiles do not use CardHeader — they are a Card with px-3 py-2, a 12px text-muted-foreground label, and a 24px font-medium figure (delta in text-emerald-400 / text-red-400). Lists inside CardContent use border-b border-neutral-800 hairlines between rows, not nested cards. Never nest a Card in a Card.

tsx
<Card>
  <CardLoader />
  <CardHeader>
    <CardTitle>Top pages</CardTitle>
    <CardDescription>Last 7 days on tomato.gg</CardDescription>
  </CardHeader>
  <CardContent className="flex flex-col gap-2">
    <Skeleton className="h-4 w-full" />
    <Skeleton className="h-4 w-full" />
  </CardContent>
  <CardFooter className="justify-end gap-2">
    <Button variant="ghost" size="sm">Cancel</Button>
    <Button variant="accent" size="sm">Save changes</Button>
  </CardFooter>
</Card>


category: Data display

Horizontal (or vertical) slide deck driven by Embla, for a row of stat cards, onboarding steps or screenshots that would not fit side by side. In an analytics UI reach for it rarely: a grid or a scrollable row is usually clearer; use a carousel when the slides are peers and only a few need to be visible at once.

Parts: Carousel (root, role="region"; props orientation="horizontal" | "vertical", opts passed straight to Embla, e.g. { loop: true, startIndex: 1, align: "start" }, plugins, setApi to receive the CarouselApi for programmatic scrolling; arrow keys work out of the box) → CarouselContent (the overflow-hidden viewport; its inner flex track has a -16px start margin so items are spaced by their own 16px padding) → CarouselItem (one slide, basis-full by default; set style={{ flexBasis: "33.3333%" }} or a basis-* class for N-up) → CarouselPrevious / CarouselNext (32px round outline icon buttons, absolutely positioned 48px OUTSIDE the track: -left-12 / -right-12, or above/below and rotated when vertical; they disable themselves at the ends unless loop is on). Both buttons accept every Button prop, so pass variant/className to reposition them inside the track if the parent has no gutter.

Conventions: reserve a 64px side gutter in the parent for the arrows; give a vertical CarouselContent an explicit height; slides are Cards with the stat-tile layout (12px muted label, 24px semibold tabular number, emerald/red delta line). Keep the visible slide count at 1-3.

tsx
import { Card, CardContent, Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@rybbit/ui";

<div style={{ padding: "16px 64px" }}>
  <Carousel opts={{ align: "start" }}>
    <CarouselContent>
      {stats.map((s) => (
        <CarouselItem key={s.label} style={{ flexBasis: "33.3333%" }}>
          <Card>
            <CardContent className="p-4">
              <div className="text-xs font-medium text-neutral-400">{s.label}</div>
              <div className="mt-1 text-2xl font-semibold tabular-nums">{s.value}</div>
              <div className="mt-1 text-xs text-emerald-400">{s.delta} vs. previous 30 days</div>
            </CardContent>
          </Card>
        </CarouselItem>
      ))}
    </CarouselContent>
    <CarouselPrevious />
    <CarouselNext />
  </Carousel>
</div>

.Design Sync Docs Checkbox (`.design-sync/docs/Checkbox.md`)


category: Forms

Radix checkbox, 16px square, 4.8px radius. Unchecked: 1px neutral-700 border on a faint neutral-900/30 fill. Checked: emerald (accent-500) border and fill with a white lucide Check. Focus shows a 3px emerald ring. Use it for independent on/off choices in a list (columns to include, sites to notify, filters to apply); use Switch for a single setting that applies immediately, and RadioGroup for mutually exclusive options.

Parts: Checkbox only (indicator is internal). Props: checked / defaultChecked, onCheckedChange, disabled, id, name, className.

Conventions:

  • Always give it an id and a sibling Label htmlFor in a flex items-center gap-2 row; the label toggles it.
  • With a description: flex items-start gap-3, add className="mt-0.5" to the checkbox so it aligns with the first text line, and put the description in text-xs text-neutral-400.
  • Option lists live in a grid gap-2.5 inside a rounded-lg border border-neutral-800 bg-neutral-900 p-4 panel with a text-sm font-medium heading; option labels use font-normal.
  • disabled fades the box to 50%; add opacity-70 to its label. Hover and indeterminate are not shown in the previews.
tsx
<div className="flex items-center gap-2">
  <Checkbox id="exclude-bots" defaultChecked />
  <Label htmlFor="exclude-bots">Exclude known bots</Label>
</div>

<div className="flex items-start gap-3">
  <Checkbox id="salt" className="mt-0.5" />
  <div className="grid gap-1">
    <Label htmlFor="salt">Salt user IDs daily</Label>
    <p className="text-xs text-neutral-400">Returning-visitor stats reset at midnight UTC.</p>
  </div>
</div>

.Design Sync Docs Command (`.design-sync/docs/Command.md`)


category: Overlays

Searchable command list built on cmdk. Typing in CommandInput filters CommandItems by their text (or value / keywords), arrow keys move the highlight, Enter fires onSelect. Use it for the ⌘K palette, site switchers, and any pick-from-many list that needs a search box; for a plain menu of actions use DropdownMenu, and for a form value use Select.

Parts: Command (root; transparent background, so wrap it in your own panel such as rounded-lg border border-neutral-800 bg-neutral-900, or use CommandDialog) → CommandInput (search icon + input, hairline bottom border; placeholder, value / onValueChange) → CommandList (scroll container capped at 300px) containing CommandEmpty (shown only when nothing matches), CommandGroup (heading renders a small muted label), CommandItem (value, keywords, onSelect, disabled; highlighted row is a raised neutral), CommandShortcut (right-aligned key hint) and CommandSeparator. CommandDialog wraps the same children in a Dialog (open / onOpenChange, title / description for screen readers, showCloseButton) with taller 48px rows.

Conventions: put a 16px lucide icon with mr-2 size-4 text-neutral-400 before item text; group by kind (Sites / Go to / Actions) with a separator between groups; keep the first item selected by default (cmdk does this). Keyboard navigation and the filtered state are live behaviors and are not shown statically.

tsx
<CommandDialog open={open} onOpenChange={setOpen} title="Command palette">
  <CommandInput placeholder="Search sites, pages, or commands…" />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Sites">
      <CommandItem onSelect={() => go("/tomato.gg")}>
        <Globe className="mr-2 size-4 text-neutral-400" /> tomato.gg
      </CommandItem>
    </CommandGroup>
    <CommandSeparator />
    <CommandGroup heading="Go to">
      <CommandItem>
        <Users className="mr-2 size-4 text-neutral-400" /> Sessions
        <CommandShortcut>⌘2</CommandShortcut>
      </CommandItem>
    </CommandGroup>
  </CommandList>
</CommandDialog>

.Design Sync Docs Dialog (`.design-sync/docs/Dialog.md`)


category: Overlays

Modal dialog (Radix). Compose the flat parts: Dialog (root, controls open) → DialogTrigger (wraps the opener, asChild for a Button) → DialogContent (portalled panel, 512px max, close X built in; hideClose removes it) → DialogHeader / DialogTitle / DialogDescription → body → DialogFooter (actions, right-aligned on desktop). DialogClose wraps any element that should dismiss. DialogContentFullScreen is the edge-to-edge editor surface with no overlay or padding.

Rules of thumb: title in sentence case; description explains the consequence; footer holds a ghost/outline cancel and ONE accent (or destructive) confirm. For screen-size-adaptive dialogs use ResponsiveDialog (dialog on desktop, drawer on mobile). For a plain confirm/cancel prompt with focus trapping use AlertDialog.

tsx
<Dialog>
  <DialogTrigger asChild><Button variant="accent">Add site</Button></DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Add a website</DialogTitle>
      <DialogDescription>Enter the domain you want to track.</DialogDescription>
    </DialogHeader>
    <Input placeholder="example.com" />
    <DialogFooter>
      <DialogClose asChild><Button variant="ghost">Cancel</Button></DialogClose>
      <Button variant="accent">Add site</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

.Design Sync Docs Drawer (`.design-sync/docs/Drawer.md`)


category: Overlays

Touch-first panel built on vaul: slides up from the bottom with a drag handle and swipe-to-dismiss. Use it on mobile layouts for short forms and detail cards (a goal editor, a session summary, a filter list). On desktop prefer Dialog or Sheet; ResponsiveDialog switches between the two for you.

Parts: Drawer (root, open/onOpenChange; direction="bottom" | "top" | "left" | "right", default bottom) → DrawerTrigger (asChild around a Button) → DrawerContent (portalled panel with overlay; the drag handle is rendered automatically for bottom) → DrawerHeader / DrawerTitle / DrawerDescription → your content → DrawerFooter (stacked, full-width actions pushed to the bottom with mt-auto). DrawerClose wraps any element that should dismiss.

Conventions: header and footer carry their own p-4, so pad body content with px-4 to line up. Footer buttons stack vertically at full width — put the accent action first and a ghost cancel below it. Bottom drawers center their header text; side drawers (left/right) behave like a Sheet and are capped at max-w-sm. Keep bottom drawers under ~60% of the viewport; anything taller belongs in a page.

tsx
<Drawer>
  <DrawerTrigger asChild><Button variant="accent">New goal</Button></DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>New goal</DrawerTitle>
      <DrawerDescription>Count a conversion whenever a visitor reaches this page.</DrawerDescription>
    </DrawerHeader>
    <div className="grid gap-3 px-4">
      <Label htmlFor="path">Path</Label>
      <Input id="path" defaultValue="/welcome" />
    </div>
    <DrawerFooter>
      <Button variant="accent">Create goal</Button>
      <DrawerClose asChild><Button variant="ghost">Cancel</Button></DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>

.Design Sync Docs DropdownMenu (`.design-sync/docs/DropdownMenu.md`)


category: Overlays

Contextual action menu (Radix) that opens from a trigger and closes on select or outside click. Use it for per-row/per-site actions, column toggles, and quick single-choice settings; use Select when the value is a form field, and Popover when the content is not a list of commands.

Parts: DropdownMenu (root, open / onOpenChange) → DropdownMenuTrigger (renders as a Button and accepts Button variant / size; pass asChild to supply your own element, or unstyled to opt out of button styling) → DropdownMenuContent (portalled panel, align="start" | "end", sideOffset 4 by default; give it a width like className="w-56"). Inside: DropdownMenuLabel (section heading), DropdownMenuGroup, DropdownMenuItem (a leading lucide icon is auto-sized to 16px; inset indents to align with checkbox rows; disabled dims it), DropdownMenuShortcut (right-aligned key hint), DropdownMenuSeparator, DropdownMenuCheckboxItem (checked / onCheckedChange, emerald check indicator), DropdownMenuRadioGroup + DropdownMenuRadioItem (value, emerald dot), and DropdownMenuSub + DropdownMenuSubTrigger (chevron built in) + DropdownMenuSubContent for a nested level. DropdownMenuPortal is only needed when you portal sub-content yourself.

Conventions: one label per section, separators between sections, the destructive action last and colored with text-red-400. Keep menus to ~8 rows; put value pickers with many options in Select. Hover/focus highlight is a raised neutral, never emerald: emerald appears only on the check/radio indicator.

tsx
<DropdownMenu>
  <DropdownMenuTrigger variant="outline" size="sm">
    tomato.gg <ChevronDown className="size-4" />
  </DropdownMenuTrigger>
  <DropdownMenuContent align="start" className="w-56">
    <DropdownMenuLabel>tomato.gg</DropdownMenuLabel>
    <DropdownMenuSeparator />
    <DropdownMenuItem>
      <Settings /> Site settings
      <DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
    </DropdownMenuItem>
    <DropdownMenuCheckboxItem checked>Show bounce rate</DropdownMenuCheckboxItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem className="text-red-400 focus:text-red-400">
      <Trash2 /> Delete site
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

.Design Sync Docs Form (`.design-sync/docs/Form.md`)


category: Forms

The react-hook-form wiring layer for every settings and create dialog. Form is react-hook-form's FormProvider: spread the object from useForm() into it, then put a real <form onSubmit={form.handleSubmit(fn)}> inside. Each field is FormField (control={form.control}, name, and a render={({ field }) => ...} that spreads field onto the input) → FormItem (a space-y-2 stack that owns the ids) → FormLabel (turns red on error) → FormControl (a Slot that stamps id, aria-describedby, aria-invalid on the single child control) → FormDescription (muted 12.8px helper) → FormMessage (red 12.8px; renders the field's error message automatically, or its children, or nothing). useFormField() exposes the ids and error inside custom controls.

Conventions: one FormItem per field, grid gap-5 (or space-y-4) between them, actions in a right-aligned flex justify-end gap-2 row with a ghost cancel and ONE accent submit. Bind non-input controls via their own props: a Select takes value={field.value} onValueChange={field.onChange} with FormControl wrapping the SelectTrigger; a Switch/Checkbox takes checked={field.value} onCheckedChange={field.onChange}. Show errors from validation, or set them with form.setError("domain", { message }). Use plain Label + Input only for a single uncontrolled field outside a form.

tsx
const form = useForm({ defaultValues: { domain: "" } });

<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-5">
    <FormField
      control={form.control}
      name="domain"
      rules={{ required: "Domain is required." }}
      render={({ field }) => (
        <FormItem>
          <FormLabel>Domain</FormLabel>
          <FormControl><Input placeholder="example.com" {...field} /></FormControl>
          <FormDescription>The hostname the tracking script will report from.</FormDescription>
          <FormMessage />
        </FormItem>
      )}
    />
    <div className="flex justify-end gap-2">
      <Button type="button" variant="ghost">Cancel</Button>
      <Button type="submit" variant="accent">Add site</Button>
    </div>
  </form>
</Form>

.Design Sync Docs Input (`.design-sync/docs/Input.md`)


category: Forms

Single-line text field. Transparent fill, 1px neutral-800 border, 4.8px radius, placeholder in neutral-400, and a 1px neutral-300 ring on focus. Use it for every free-text, number, URL and password field in settings and dialogs; pair it with Label above via htmlFor.

Parts: Input only. It forwards every native `` prop (type, placeholder, defaultValue, disabled, aria-invalid, ref).

Conventions:

  • inputSize="default" (36px, 14px text) for forms; inputSize="sm" (28px, 12px text) for toolbar filters and table headers.
  • isSearch renders a leading lucide Search icon and pads the text past it. Use it for every "Search / Filter …" box.
  • aria-invalid="true" turns the border red; put the error text in a text-xs text-red-400 line below.
  • disabled drops the field to 50% opacity. Width comes from the container: it is w-full, so constrain with className="w-32" or a grid.
  • Stack Label + Input in a grid gap-1.5; use grid grid-cols-2 gap-3 for side-by-side fields. Prefer Textarea for multi-line, Select for enumerations.
tsx
<div className="grid gap-1.5">
  <Label htmlFor="domain">Domain</Label>
  <Input id="domain" placeholder="example.com" defaultValue="tomato.gg" />
</div>

<Input isSearch inputSize="sm" placeholder="Search pages…" />

<div className="grid gap-1.5">
  <Input aria-invalid="true" defaultValue="not a domain" />
  <p className="text-xs text-red-400">Enter a valid domain, like tomato.gg</p>
</div>

.Design Sync Docs InputWithSuggestions (`.design-sync/docs/InputWithSuggestions.md`)


category: Forms

A text Input that shows a filterable list of known values underneath while typing: page paths, event names, referrers, UTM values. Free text is always allowed; the suggestions are hints, not constraints. One component, no sub-parts: InputWithSuggestions. Props: suggestions: { value, label?, count? }[] (count renders right-aligned in compact form, e.g. 6.1K), value + onValueChange(text) for controlled use (uncontrolled works too), plus every Input prop (placeholder, disabled, onChange, className). Forwards its ref to the input element.

Conventions: the list opens on focus and on every keystroke, filters by case-insensitive substring of the current value, closes on outside click or when a row is picked, and is portalled (into the nearest role="dialog" or body) so it escapes overflow clipping. Sort suggestions by count descending so the most common value is first. Prefer it over Select when the value space is open-ended (any path could be typed) and over MultiSelect when only one value is being entered.

tsx
const pages = [
  { value: "/pricing", count: 6120 },
  { value: "/docs/script", count: 4890 },
  { value: "/signup", count: 1975 },
];
const [path, setPath] = React.useState("");

<InputWithSuggestions
  value={path}
  onValueChange={setPath}
  suggestions={pages}
  placeholder="/checkout"
/>

.Design Sync Docs Label (`.design-sync/docs/Label.md`)


category: Forms

Form label built on Radix Label: 14px, medium weight, leading-none, inherits the foreground color. Always wire it to its control with htmlFor + id so clicking the text focuses the input or toggles the checkbox/switch. It is the only text style for field names; helper text below a field is a plain p.text-xs.text-neutral-400.

Parts: Label only. Forwards every native <label> prop and className.

Conventions:

  • Above a field: grid gap-1.5 with Label first, then Input / Textarea / Select.
  • Beside a toggle: flex items-center gap-2 with the Checkbox / RadioGroupItem / Switch first, then the Label. For lists of options use className="font-normal" so only the group heading is medium.
  • When the control has peer and is disabled, the label auto-fades to 70% (peer-disabled:opacity-70); otherwise add className="opacity-70" yourself.
  • Required marker: * inside the label. A trailing hint sits on the same row with flex items-baseline justify-between.
tsx
<div className="grid gap-1.5">
  <Label htmlFor="site-name">Site name</Label>
  <Input id="site-name" defaultValue="rybbit.com" />
  <p className="text-xs text-neutral-400">Shown in the site switcher.</p>
</div>

<div className="flex items-center gap-2">
  <Checkbox id="exclude-bots" defaultChecked />
  <Label htmlFor="exclude-bots">Exclude known bots</Label>
</div>

.Design Sync Docs MultiSelect (`.design-sync/docs/MultiSelect.md`)


category: Forms

Searchable multi-value picker for filters (countries, browsers, pages, sites): an outline combobox button that grows to hold the chosen values as secondary badges with an X to remove, and a Popover + Command list with a search box and check marks. One component, no sub-parts: MultiSelect. Props: options: { value, label, disabled? }[], value: string[] + onValueChange(values) (controlled; keep the array in state), placeholder (empty-state text, default "Select items..."), searchPlaceholder, emptyText ("No items found."), disabled, className (trigger), badgeClassName (chips).

Conventions: the trigger is w-full and at least 36px tall; chips wrap onto extra rows rather than truncating, so give it a bounded width (form column or w-96). The list opens on click only (no open prop) and filters by label substring; selecting toggles membership and keeps the list open. Use it when more than one value can be active at once; for a single choice use Select; for free text use InputWithSuggestions.

tsx
const countries = [
  { value: "US", label: "United States" },
  { value: "DE", label: "Germany" },
  { value: "GB", label: "United Kingdom" },
];
const [selected, setSelected] = React.useState<string[]>(["US"]);

<MultiSelect
  options={countries}
  value={selected}
  onValueChange={setSelected}
  placeholder="Filter by country"
  searchPlaceholder="Search countries"
  emptyText="No country matches."
/>

.Design Sync Docs NavigationMenu (`.design-sync/docs/NavigationMenu.md`)


category: Navigation

Horizontal top-level nav bar (Radix) whose triggers open a content panel that drops from below the bar. It is for marketing/site-wide headers with a few sections, not for the dashboard sidebar (which is a plain list) and not for per-item actions (DropdownMenu). Plain links live in the same bar via NavigationMenuLink styled with navigationMenuTriggerStyle().

Parts: NavigationMenu (root; renders the NavigationMenuViewport for you. Uncontrolled by default; pass value / onValueChange to force an item open) → NavigationMenuList (flex row) → NavigationMenuItem (give it value when controlling) containing either NavigationMenuTrigger (36px button, chevron built in and flips when open) + NavigationMenuContent (the panel body; size it yourself, e.g. w-[400px] p-2), or a bare NavigationMenuLink (href, asChild). NavigationMenuIndicator is an optional caret under the active trigger. The panel is a bg-neutral-950 surface with a hairline border-neutral-800.

Conventions: 2-4 triggers plus 1-2 plain links; inside a panel list links as block rounded-md p-3 hover:bg-neutral-800 blocks with a 14px medium title and a 12px text-neutral-400 description. Give the container enough height below the bar for the panel. Hover-to-open and the slide animation are live behaviors; the preview forces the panel open with value.

tsx
<NavigationMenu>
  <NavigationMenuList>
    <NavigationMenuItem>
      <NavigationMenuTrigger>Reports</NavigationMenuTrigger>
      <NavigationMenuContent>
        <div className="grid w-[400px] gap-1 p-2">
          <NavigationMenuLink asChild>
            <a href="/overview" className="block rounded-md p-3 hover:bg-neutral-800">
              <div className="text-sm font-medium">Overview</div>
              <p className="mt-1 text-xs text-neutral-400">Visitors, pageviews and top pages.</p>
            </a>
          </NavigationMenuLink>
        </div>
      </NavigationMenuContent>
    </NavigationMenuItem>
    <NavigationMenuItem>
      <NavigationMenuLink href="/docs" className={navigationMenuTriggerStyle()}>Docs</NavigationMenuLink>
    </NavigationMenuItem>
  </NavigationMenuList>
</NavigationMenu>

.Design Sync Docs Popover (`.design-sync/docs/Popover.md`)


category: Overlays

Non-modal floating panel anchored to a trigger (Radix Popover). Use it for small, focused controls that belong next to the thing they change: date-range presets, a filter's value picker, the site selector, a one-paragraph explanation. Use DropdownMenu for a list of commands, Tooltip for a label that needs no interaction, and Dialog for anything that needs a title and footer.

Parts: Popover (root, open/onOpenChange) → PopoverTrigger (asChild around a Button) → PopoverContent (portalled panel: w-72 p-4 rounded-md, raised neutral surface; props align="start" | "center" | "end" (default center), side, sideOffset (default 4), onOpenAutoFocus). PopoverAnchor positions the panel against an element other than the trigger.

Conventions: prefer align="start" so the panel lines up with the trigger's left edge. Override width per use (w-64, w-80, or w-[var(--radix-popover-trigger-width)] to match the trigger); pass p-0 when the content is a Command list or a Calendar. Inside, use text-sm, muted labels in text-xs text-neutral-400, and a right-aligned size="sm" action row. Inputs inside inherit a slightly lighter border automatically.

tsx
<Popover>
  <PopoverTrigger asChild>
    <Button variant="outline" size="sm"><CalendarIcon /> Last 7 days</Button>
  </PopoverTrigger>
  <PopoverContent align="start" className="w-64">
    <div className="grid gap-1">
      {["Today", "Last 7 days", "Last 30 days"].map(p => (
        <button key={p} className="rounded-md px-2 py-1.5 text-left text-sm hover:bg-neutral-700/60">{p}</button>
      ))}
    </div>
    <Separator className="my-3 dark:bg-neutral-700" />
    <div className="grid grid-cols-2 gap-2">
      <Input defaultValue="2026-09-06" className="h-8 text-xs" />
      <Input defaultValue="2026-09-13" className="h-8 text-xs" />
    </div>
  </PopoverContent>
</Popover>

.Design Sync Docs Progress (`.design-sync/docs/Progress.md`)


category: Data display

Determinate progress / usage meter (Radix Progress). One export, Progress: an 8px-tall, fully rounded neutral-800 track with an emerald (accent-500) indicator that slides in by value percent. Props: value (0-100, undefined renders empty), max (Radix, default 100), className on the track. There is no indeterminate mode — for "loading" use CardLoader or Skeleton.

Conventions: the bar carries no text, so always pair it with a label and a figure. The canonical usage meter is a flex justify-between text-sm row (name left, 812K / 1M in tabular-nums text-neutral-400 right), the bar, then a 12px muted note. For inline rows (countries, browsers) shrink the track with h-1.5 and give the bar flex-1 between a fixed-width name and a right-aligned percentage. Emerald here means "amount used / achieved", not success; do not recolor the indicator per row (chart series use periwinkle in Nivo, not this component). Use Slider when the user should change the value.

tsx
<div className="flex flex-col gap-2">
  <div className="flex items-center justify-between text-sm">
    <span className="font-medium">Events this month</span>
    <span className="tabular-nums text-neutral-400">812K / 1M</span>
  </div>
  <Progress value={81} />
  <div className="text-xs text-muted-foreground">Resets in 9 days · Pro plan</div>
</div>

.Design Sync Docs RadioGroup (`.design-sync/docs/RadioGroup.md`)


category: Forms

Radix radio group for one-of-N choices that need every option visible (retention period, chart interval, visitor scope). Items are 16px circles: 1px neutral-700 border when idle, emerald (accent-500) fill with a white dot when selected, 3px emerald focus ring. Use Select instead when there are more than ~5 options or space is tight; use Checkbox when choices are independent.

Parts:

  • RadioGroup — the root (grid gap-2 by default). Props: value / defaultValue, onValueChange, disabled, name, className.
  • RadioGroupItem — one option. Props: value (required), id, disabled, className. Indicator is internal.

Conventions:

  • Each item sits in a flex items-center gap-2 row with a Label htmlFor matching its id; option labels use className="font-normal", the group heading is text-sm font-medium above.
  • Horizontal: <RadioGroup className="flex gap-4"> for short enums like hour / day / week.
  • With descriptions: flex items-start gap-3, className="mt-0.5" on the item, description in text-xs text-neutral-400.
  • disabled on an item fades it to 50%; add opacity-70 to that label.
tsx
<RadioGroup defaultValue="365">
  <div className="flex items-center gap-2">
    <RadioGroupItem value="90" id="r-90" />
    <Label htmlFor="r-90" className="font-normal">90 days</Label>
  </div>
  <div className="flex items-center gap-2">
    <RadioGroupItem value="365" id="r-365" />
    <Label htmlFor="r-365" className="font-normal">1 year</Label>
  </div>
</RadioGroup>

.Design Sync Docs ResponsiveDialog (`.design-sync/docs/ResponsiveDialog.md`)


category: Overlays

One overlay, two presentations: at viewports ≥640px it renders a centered Radix Dialog; below that it renders a vaul Drawer (bottom sheet with drag handle). Use it for any dialog that also has to work on phones — invite a member, edit a goal, expand a dashboard card. If you only ever need one presentation, use Dialog, Sheet or Drawer directly.

Parts: ResponsiveDialog (root; open/onOpenChange, plus direction for the drawer side, onlyDialog / onlyDrawer to pin a presentation, dismissible={false} to block overlay/Escape, alert to make it an un-dismissable confirm with no X) → ResponsiveDialogTrigger → ResponsiveDialogContent (showCloseButton, closeButtonClassName, dragHandleClassName) → ResponsiveDialogHeader / ResponsiveDialogTitle / ResponsiveDialogDescription → body → ResponsiveDialogFooter (row on desktop, reversed column on mobile). ResponsiveDialogClose wraps a dismissing element. useResponsiveDialog() (call inside the root) returns { modal, dismissible, direction, onlyDrawer, onlyDialog, alert } for custom parts.

Conventions: ResponsiveDialogContent sets no width or padding, so always pass them — className="max-w-md p-6" for a form, max-w-[1000px] w-[calc(100vw-2rem)] p-4 for a wide card expansion. In drawer mode add px-4 pb-4 instead. Same footer rule as Dialog: one accent/destructive confirm, one ghost/outline cancel.

tsx
<ResponsiveDialog>
  <ResponsiveDialogTrigger asChild><Button variant="accent">Invite</Button></ResponsiveDialogTrigger>
  <ResponsiveDialogContent className="max-w-md p-6">
    <ResponsiveDialogHeader>
      <ResponsiveDialogTitle>Invite a teammate</ResponsiveDialogTitle>
      <ResponsiveDialogDescription>They get access to every site in Tomato Labs.</ResponsiveDialogDescription>
    </ResponsiveDialogHeader>
    <Input placeholder="[email protected]" />
    <ResponsiveDialogFooter>
      <ResponsiveDialogClose asChild><Button variant="ghost">Cancel</Button></ResponsiveDialogClose>
      <Button variant="accent">Send invite</Button>
    </ResponsiveDialogFooter>
  </ResponsiveDialogContent>
</ResponsiveDialog>

.Design Sync Docs ScrollArea (`.design-sync/docs/ScrollArea.md`)


category: Data display

Custom-scrollbar container for any fixed-height list or wide row (Radix ScrollArea, with a touch-device fallback to native scrolling). Parts: ScrollArea (root + viewport; already includes a vertical ScrollBar, a corner, and an edge-fade mask) and ScrollBar (add one with orientation="horizontal" as a child when the content is wider than the panel).

ScrollArea props beyond Radix's: viewportClassName (classes on the scrolling viewport, e.g. pr-3), maskHeight (px, default 30; 0 disables the fade), maskClassName. Radix props pass through — type="always" keeps the bar visible, the default (hover, scrollHideDelay={0}) reveals it only while the pointer is over the area. The mask is a panel-colored (neutral-900) gradient that appears only on edges with more content, so it tells the user "there is more" without a visible scrollbar.

Conventions: the root needs a height (h-64, h-[314px], or h-full min-h-0 inside a flex column) or nothing scrolls; put padding on the inner content, not the root, so the mask and bar sit at the true edge. Use it for dashboard lists (countries, pages, bot rows), dialog bodies, and chip rows; use plain Table for short lists that fit. Rows inside are ordinary flex justify-between lines with tabular-nums figures. The mask assumes the panel is neutral-900; on the raw canvas set maskClassName or maskHeight={0}.

tsx
<ScrollArea className="h-64 rounded-lg border border-neutral-800 bg-neutral-900">
  <div className="flex flex-col p-2 text-sm">
    {countries.map(([name, n]) => (
      <div key={name} className="flex items-center justify-between px-2 py-1.5">
        <span>{name}</span>
        <span className="tabular-nums text-neutral-400">{n.toLocaleString()}</span>
      </div>
    ))}
  </div>
</ScrollArea>

<ScrollArea className="rounded-lg border border-neutral-800">
  <div className="flex w-max gap-2 p-3">{chips}</div>
  <ScrollBar orientation="horizontal" />
</ScrollArea>

.Design Sync Docs Select (`.design-sync/docs/Select.md`)


category: Forms

Single-value dropdown (Radix Select) for picking one option from a short, known list: time range, site, timezone, metric. Compose the flat parts: Select (root; value/defaultValue, onValueChange, open, disabled) → SelectTrigger (the 36px bordered field; size="sm" for the 28px toolbar variant) containing SelectValue (shows the chosen item's text or its placeholder) → SelectContent (portalled popper list, dark neutral-800 panel; pass the same size) → SelectItem (value required; the chosen one shows an emerald check on the right; disabled dims it). Group with SelectGroup + SelectLabel heading and separate groups with SelectSeparator. SelectScrollUpButton / SelectScrollDownButton are already inside SelectContent.

Conventions: the trigger is w-full by default, so size it with the parent. Keep size consistent across Trigger/Content/Item/Label. Use Select when there are 3-12 mutually exclusive options; for 2 options use a RadioGroup or Switch, for many searchable options use MultiSelect/Command, for free text with hints use InputWithSuggestions. Inside a Form, wrap the SelectTrigger in FormControl and bind value={field.value} onValueChange={field.onChange}.

tsx
<Select defaultValue="7d" onValueChange={setRange}>
  <SelectTrigger>
    <SelectValue placeholder="Pick a range" />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>Recent</SelectLabel>
      <SelectItem value="24h">Last 24 hours</SelectItem>
      <SelectItem value="7d">Last 7 days</SelectItem>
      <SelectItem value="30d">Last 30 days</SelectItem>
    </SelectGroup>
    <SelectSeparator />
    <SelectItem value="all">All time</SelectItem>
  </SelectContent>
</Select>

.Design Sync Docs Separator (`.design-sync/docs/Separator.md`)


category: Data display

A 1px hairline (Radix Separator) in neutral-800, the same tone as table row borders. One export, Separator. Props: orientation ("horizontal" default → h-[1px] w-full; "vertical" → h-full w-[1px]), decorative (default true, so it is hidden from assistive tech; set false only when the division is semantic), and className for margins or an explicit height.

Conventions: horizontal separators divide text blocks inside a Card or settings page — give them vertical breathing room with my-4 (or my-3 in dense lists). Between list rows prefer border-b border-neutral-800 on the row itself rather than a Separator per row. A vertical separator is h-full, so its parent must have a height: either make the row a fixed-height flex container (flex h-4 items-center) or set the height directly (className="h-5") when dividing toolbar button groups. Do not use separators as decoration between every element; the grayscale ramp and spacing should do most of the dividing.

tsx
<div className="text-sm font-medium">Tracking script</div>
<Separator className="my-4" />
<div className="text-sm font-medium">Data retention</div>

<div className="flex h-9 items-center gap-2">
  <Button variant="ghost" size="sm">Filter</Button>
  <Separator orientation="vertical" className="h-5" />
  <Button variant="ghost" size="sm">Export</Button>
</div>

.Design Sync Docs Sheet (`.design-sync/docs/Sheet.md`)


category: Overlays

Edge-anchored panel that slides in over a dimmed page (Radix Dialog under the hood). Use it for secondary work that keeps the page in context: site settings, a site switcher, a filter editor, a bulk-action strip. For a centered task use Dialog; for a touch-first bottom panel with drag-to-dismiss use Drawer.

Parts: Sheet (root, open/onOpenChange) → SheetTrigger (asChild around a Button) → SheetContent (portalled panel; side="right" | "left" | "top" | "bottom", default right; showClose={false} hides the built-in X) → SheetHeader / SheetTitle / SheetDescription → your content → SheetFooter (right-aligned actions). SheetClose wraps any element that should dismiss.

Conventions: right is the default and the one to reach for; left reads as navigation; bottom/top are full-width strips, so keep them short (a title line plus actions). Side panels are w-3/4 capped at max-w-sm (384px), so lay the body out as a single column with grid gap-4 py-6. Footer holds one accent confirm plus a ghost/outline cancel. Group settings rows as bordered cards (rounded-lg border border-neutral-800 p-3) with a Switch on the right.

tsx
<Sheet>
  <SheetTrigger asChild><Button variant="outline">Settings</Button></SheetTrigger>
  <SheetContent>
    <SheetHeader>
      <SheetTitle>Site settings</SheetTitle>
      <SheetDescription>tomato.gg</SheetDescription>
    </SheetHeader>
    <div className="grid gap-4 py-6">
      <div className="grid gap-1.5">
        <Label htmlFor="domain">Domain</Label>
        <Input id="domain" defaultValue="tomato.gg" />
      </div>
    </div>
    <SheetFooter>
      <SheetClose asChild><Button variant="ghost">Cancel</Button></SheetClose>
      <Button variant="accent">Save changes</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>

.Design Sync Docs Skeleton (`.design-sync/docs/Skeleton.md`)


category: Data display

Loading placeholder. Skeleton is a single div with animate-pulse, 2.8px radius, and a neutral-850 fill (one step above the card surface); it has no intrinsic size, so every instance needs a height and width class. Compose skeletons to mirror the shape of the content they replace — the layout should not jump when real data lands.

Conventions:

  • Stat tile: keep the real label, replace the figure with h-9 w-[60px] and the delta with h-5 w-[50px] (this is exactly what the overview does).
  • Table / list: one h-4 bar per row, flex-1 for the name column and a fixed w-12 for the figure column; 5-6 rows.
  • Text: h-5 for a title, h-4 w-full body lines, make the last line shorter (w-64).
  • Avatar: h-8 w-8 rounded-full.
  • Put skeletons inside the same Card / CardContent the data will use; for an in-place refetch over existing data use CardLoader instead.
tsx
<Card>
  <div className="flex flex-col px-3 py-2">
    <div className="text-xs font-medium text-muted-foreground">Unique visitors</div>
    <div className="flex items-center justify-between">
      <Skeleton className="h-9 w-[60px] rounded-md" />
      <Skeleton className="h-5 w-[50px] rounded-md" />
    </div>
  </div>
</Card>

.Design Sync Docs Slider (`.design-sync/docs/Slider.md`)


category: Forms

Numeric range control (Radix Slider) for settings that are tuned rather than typed: replay sample rate, session-duration filter, retention window. One component, no sub-parts: Slider. Props are Radix's: defaultValue / value as a number array (one entry per thumb, so [40] is a single thumb and [30, 300] is a two-thumb range), onValueChange(values), min / max (default 0-100), step, disabled, orientation. It stretches to w-full.

Appearance: a 6px neutral-800 track, the filled range in emerald (accent-500), 16px near-black thumbs with a neutral-800 border. There is no built-in value label; pair it with a Label and put the current value in a muted text-sm tabular-nums text-neutral-400 span on the same row. disabled blocks interaction but draws the same (Radix puts the disabled state on a span, so the thumb's opacity rule never matches); dim the whole row yourself if the state must read visually. Use Slider for approximate values where seeing the position matters; use an Input type="number" when the exact figure matters.

tsx
const [rate, setRate] = React.useState([40]);

<div className="flex items-center justify-between mb-3">
  <Label>Session replay sample rate</Label>
  <span className="text-sm tabular-nums text-neutral-400">{rate[0]}%</span>
</div>
<Slider value={rate} onValueChange={setRate} max={100} step={5} />

// two thumbs = range
<Slider defaultValue={[30, 300]} min={0} max={600} step={10} />

.Design Sync Docs Switch (`.design-sync/docs/Switch.md`)


category: Forms

Radix toggle for a single setting that takes effect immediately (session replay on/off, public dashboard, live mode). Track is 36×20px with a 16px white thumb: emerald (accent-500) when on, neutral-800 when off, 50% opacity when disabled. Use Checkbox when the choice is submitted with a form or is one of many, and RadioGroup for mutually exclusive options.

Parts: Switch only (thumb is internal). Props: checked / defaultChecked, onCheckedChange, disabled, id, name, className. It carries the peer class, so a following Label fades automatically when the switch is disabled.

Conventions:

  • Inline: flex items-center gap-2 with the Switch first, then Label htmlFor.
  • Settings list (the common case): rows of flex items-center justify-between gap-4 px-4 py-3 inside a divide-y divide-neutral-800 rounded-lg border border-neutral-800 bg-neutral-900 panel; label + text-xs text-neutral-400 description on the left, switch on the right.
  • One switch per row; never stack switches horizontally. Keep the label a noun phrase ("Web vitals"), not a verb.
  • Hover/pressed transitions are not shown in previews.
tsx
<div className="flex items-center justify-between gap-4 px-4 py-3">
  <div className="grid gap-0.5">
    <Label htmlFor="replay">Session replay</Label>
    <p className="text-xs text-neutral-400">Record sessions for the last 7 days.</p>
  </div>
  <Switch id="replay" defaultChecked />
</div>

.Design Sync Docs Table (`.design-sync/docs/Table.md`)


category: Data display

Data table for dense analytics lists. Parts: Table (scroll wrapper + ``) → TableHeader (neutral-850 band) → TableRow → TableHead; TableBody → TableRow → TableCell; optional TableFooter (totals) and TableCaption (below the table, muted). TableSortIndicator renders the sort chevrons for a sortable column: sortDirection="asc" | "desc" shows a blue arrow, undefined shows the neutral double chevron, canSort={false} hides it.

Conventions: 14px text, 8px cell padding, hairline row borders, no zebra striping. Right-align numeric columns and add tabular-nums. Keep the first column a name/path in font-medium. Use Badge for status cells and Skeleton rows while loading.

tsx
<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Page</TableHead>
      <TableHead className="text-right">Visitors <TableSortIndicator sortDirection="desc" /></TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell className="font-medium">/pricing</TableCell>
      <TableCell className="text-right tabular-nums">6,120</TableCell>
    </TableRow>
  </TableBody>
</Table>

.Design Sync Docs Tabs (`.design-sync/docs/Tabs.md`)


category: Navigation

Segmented tabs (Radix). A filled TabsList pill strip where the active TabsTrigger lifts to a darker surface; each TabsContent panel shows only while its value is active. Use it to switch between views of one thing (Overview / Sessions / Goals, Desktop / Mobile). For the lighter underline style used inside report cards use BasicTabs.

Parts: Tabs (root; defaultValue for uncontrolled, value / onValueChange for controlled) → TabsList (36px strip, bg-neutral-800, inline-flex by default; add grid w-full grid-cols-N to stretch triggers evenly) → TabsTrigger (value, disabled; active state is bg-neutral-950 with bright text) and TabsContent (value; adds mt-2).

Conventions: 2-5 triggers, sentence-case labels, no icons. Keep the list left-aligned above its content; stretch it full-width only for two-way toggles. Disable a trigger for a feature the plan lacks rather than hiding it. Focus rings are keyboard-only and are not shown in the static preview.

tsx
<Tabs defaultValue="overview">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="sessions">Sessions</TabsTrigger>
    <TabsTrigger value="goals">Goals</TabsTrigger>
    <TabsTrigger value="replay" disabled>Replay</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">
    <div className="grid grid-cols-2 gap-3">{/* stat cards */}</div>
  </TabsContent>
  <TabsContent value="sessions">…</TabsContent>
  <TabsContent value="goals">…</TabsContent>
</Tabs>

.Design Sync Docs Textarea (`.design-sync/docs/Textarea.md`)


category: Forms

Multi-line text field for notes, descriptions and pasted snippets. Same chassis as Input (transparent fill, 1px neutral-800 border, neutral-400 placeholder, ring on focus) with a 60px minimum height and vertical resize. Use it for annotation notes, site descriptions, alert messages, and raw filter/config text; use Input for anything that fits on one line.

Parts: Textarea only. Forwards every native <textarea> prop (rows, placeholder, defaultValue, disabled, aria-invalid, ref).

Conventions:

  • Default height is min-h-[60px]; set rows={5} (or className="min-h-32") for longer content.
  • Text is 14px at md+ (text-base on phones so iOS doesn't zoom). Add font-mono text-xs for code-like content such as rule DSLs.
  • There is no invalid style baked in (unlike Input): pass aria-invalid="true" for accessibility plus className="dark:border-red-400" for the red border (the dark: prefix is required: the component's own dark:border-neutral-800 overrides a bare border-red-400), and put the message in a text-xs text-red-400 line below. A right-aligned text-xs text-neutral-400 tabular-nums counter goes on the same row.
  • disabled fades to 50% opacity. Always pair with a Label via htmlFor.
tsx
<div className="grid gap-1.5">
  <Label htmlFor="note">Annotation note</Label>
  <Textarea id="note" placeholder="What changed on this date?" />
</div>

<Textarea rows={5} className="font-mono text-xs" defaultValue={"path contains /docs\nAND country is Germany"} />

.Design Sync Docs TimelineSlider (`.design-sync/docs/TimelineSlider.md`)


category: Replay

The minimal playback rail of the session-replay transport bar: a 2px neutral-800 track, an emerald (accent-500) played range and a 10px emerald-300 thumb that scales up on hover and shows a grab cursor. Use it for any "position in time" control (replay progress, a funnel-step scrubber); use Slider for a value input with a visible track height, and ActivitySlider when the rail should also plot events and activity.

Single part: TimelineSlider, a thin wrapper over Radix Slider.Root, so the whole Radix API applies: value / defaultValue as a one-element array in the unit you choose (milliseconds for replay), max (the duration), min, step, onValueChange, onValueCommit (seek on release), disabled, orientation. Pass className="flex-1" so it fills the space between the elapsed and total times.

Conventions: sit it in a flex items-center gap-3 bar on a neutral-900 panel with ghost smIcon transport buttons on the left, m:ss elapsed and total in 12px muted tabular figures on either side, and a playback-rate label at the end. Hover/active scaling can't be rendered statically. The thumb hue is the only chromatic element on the bar; everything else stays neutral.

tsx
import { Button, TimelineSlider } from "@rybbit/ui";
import { Pause } from "lucide-react";

<div className="flex items-center gap-3 rounded-lg border border-neutral-800 bg-neutral-900 px-4 py-3">
  <Button size="smIcon" variant="ghost" aria-label="Pause"><Pause /></Button>
  <span className="text-xs tabular-nums text-neutral-400">0:42</span>
  <TimelineSlider
    className="flex-1"
    max={duration}
    step={100}
    value={[currentTime]}
    onValueChange={([t]) => setCurrentTime(t)}
    onValueCommit={([t]) => player.seek(t)}
  />
  <span className="text-xs tabular-nums text-neutral-400">3:00</span>
</div>

.Design Sync Docs Toaster (`.design-sync/docs/Toaster.md`)


category: Feedback

Transient confirmations after an action ("Settings saved", "Couldn't verify domain"). Mount ONE <Toaster /> at the app root (it is a configured react-hot-toast host: bottom-right, neutral-850 card with neutral-200 text in dark mode, 4.8px radius, 14px medium) and fire messages from anywhere with the toast function. Toasts stack newest at the bottom and auto-dismiss (success ~2s, default/error ~4s), so keep the message to one line that reads on its own. For state that must persist on the page use Alert; for something the user must acknowledge use AlertDialog.

Parts: Toaster (the host, no props) and toast (the API). The API takes a plain string: toast(message) (no icon), toast.success(message) (emerald check), toast.error(message) (red cross), toast.info(message) (an "i" glyph), toast.dismiss(id?) (dismiss one or all), toast.custom(node, opts) (escape hatch for a fully custom body). Every call returns the toast id.

Conventions: one toast per user action; name the object ("Goal “Signup completed” created", "Settings saved for tomato.gg"); an error says what to do next. Never use a toast for validation (that belongs in FormMessage) or for anything the user has to read later.

tsx
import { Button, Toaster, toast } from "@rybbit/ui";

// once, at the root
<Toaster />

// anywhere
<Button
  variant="accent"
  onClick={async () => {
    try {
      await saveSettings();
      toast.success("Settings saved for tomato.gg");
    } catch {
      toast.error("Couldn't save settings. Try again.");
    }
  }}
>
  Save changes
</Button>

.Design Sync Docs Tooltip (`.design-sync/docs/Tooltip.md`)


category: Overlays

Hover/focus label (Radix Tooltip). Use it to name icon-only buttons and to explain a metric or setting in one or two short lines. It is not interactive: if the content needs a link, a button or more than a couple of sentences, use Popover instead. RybbitTheme already mounts the required TooltipProvider, so never add another.

Parts: Tooltip (root; open for a controlled state, delayDuration default 300ms) → TooltipTrigger (asChild around the Button or icon) → TooltipContent (portalled: text-xs, raised neutral surface with a hairline border; props side="top" | "right" | "bottom" | "left" (default top), align, sideOffset default 4). TooltipProvider is exported but only for apps that opt out of RybbitTheme.

Conventions: keep the default top unless the trigger sits at a viewport edge. Content is sentence case, no trailing period for a single label. For a two-line explainer, put a font-medium heading first and the body in text-neutral-400, and cap width with max-w-56. Always give icon-only triggers an aria-label that matches the tooltip text. The hover-in state cannot be rendered statically; the previews use open.

tsx
<Tooltip>
  <TooltipTrigger asChild>
    <Button variant="outline" size="icon" aria-label="Site settings">
      <Settings />
    </Button>
  </TooltipTrigger>
  <TooltipContent>Site settings</TooltipContent>
</Tooltip>

<Tooltip>
  <TooltipTrigger asChild>
    <button type="button" aria-label="What is bounce rate?"><Info className="h-3.5 w-3.5" /></button>
  </TooltipTrigger>
  <TooltipContent side="right" align="start" className="max-w-56">
    <p className="font-medium">Bounce rate</p>
    <p className="mt-1 text-neutral-400">Sessions with one pageview and no events.</p>
  </TooltipContent>
</Tooltip>

Public Security (`docs/public/security.txt`)

security.txt for rybbit.com

We accept vulnerability reports. We do not offer monetary or bug bounty rewards.

Canonical: https://rybbit.com/.well-known/security.txt
Contact: mailto:[email protected]
Preferred-Languages: en
Expires: 2027-01-01T00:00:00Z


Server Docs Better Auth 1.7.3 (`server/docs/better-auth-1.7.3.md`)

Better Auth 1.7.3 migration

This upgrade moves the client, server, API key and SSO packages from 1.6.25 to
1.7.3. MCP OAuth now uses @better-auth/mcp, @better-auth/oauth-provider and
the JWT plugin, with an explicit consent page at /auth/consent.
@better-auth/infra keeps its separate 0.1.x version. The MCP SDK and stateless
transport remain on their existing versions; dynamic client registration stays
enabled.

Database and cutover

Migration 0018_better_auth_173.sql is additive. It creates the OAuth/JWT tables,
copies registered clients, hashes their previously plaintext secrets, and adds
team membership keys and member counts. Counts are backfilled; existing nullable
membership keys use Better Auth's compatibility lookup. New memberships use the
same key calculation as Better Auth.

Auth uses the native PostgreSQL/Kysely adapter, so its array fields are stored
as JSONB. Drizzle's native PostgreSQL arrays are not compatible with that
adapter's serialization. The Drizzle schema and snapshot reflect this.

  1. Back up PostgreSQL and record the current application version. Run only the
    opening DO block from migration 0018 as a read-only preflight: it checks
    duplicate account identities and parses every nonempty legacy client metadata
    value with PostgreSQL's JSON parser. Fix any reported record before cutover.
  2. Stop the old backend workers before migrating. Client registrations and team
    membership changes must not race the data copy and count backfill.
  3. Apply migration 0018 with the existing file-based migration runner. The Docker
    backend entrypoint already runs npm run db:migrate before starting. For a
    manual deployment, run that command from server/ against the deployment's
    database before starting the new backend.
  4. Deploy the matching client and server. Keep BASE_URL set to the public site
    origin and keep the existing BETTER_AUTH_SECRET. OAuth requires HTTPS, with
    an exception for HTTP loopback during development. Plain HTTP LAN deployments
    keep session and API key authentication but do not enable OAuth.
  5. Check password login, Google/GitHub sign-in where configured, existing
    sessions/API keys, team updates, and an MCP connection through discovery,
    login, consent and a tool call. Sign out of the linked session and confirm
    its MCP bearer is rejected on the next request. The explicit BASE_URL must
    produce the registered /api/auth/callback/google and
    /api/auth/callback/github URLs; check this in both development and production.

No live database migration is part of preparing this change.

The migration stops before schema writes if accounts contain duplicate
(providerId, accountId) pairs or legacy OAuth client metadata is invalid JSON.
The metadata error identifies the affected oauthApplication.id without logging
its contents. Resolve these records explicitly before retrying. Do not apply an
account issuer backfill from the 1.7.0–1.7.2 instructions: upstream reverted
that migration in 1.7.3.

MCP client behavior

  • Existing OAuth clients must reconnect and approve fresh consent. Old access
    tokens, refresh tokens and consent are retained in the legacy tables but are
    not accepted by the new provider. Password sessions and API keys are not
    rewritten by this migration.
  • Registered client IDs and redirect URIs are preserved. Imported confidential
    clients use client_secret_basic; the old schema did not record their token
    authentication method. Clients that previously posted a secret must switch to
    HTTP Basic or register again with their intended method.
  • New native clients with loopback redirects must register
    application_type: "native" and token_endpoint_auth_method: "none".
    Other public clients must also explicitly send
    token_endpoint_auth_method: "none". Omitting it now creates a confidential client using
    client_secret_basic, even for unauthenticated registration. Such a client
    receives a secret and must authenticate with it or subsequent token requests
    fail with invalid_client.
  • Discovery advertises the new /api/auth/oauth2/* endpoints. Clients caching
    the old endpoints should rediscover the server.
  • Resource tokens are verified for signature, issuer, expiry, audience,
    client state and validity of the linked session. These Bearer routes reject
    proof-bound tokens.
    The single resource remains /api/mcp; per-client resource join
    enforcement is disabled so imported clients can request it.
  • Access tokens are JWTs with the upstream default lifetime of one hour. There
    is no per-access-token database revocation check: /oauth2/revoke rejects JWT
    access tokens with unsupported_token_type. Revoking a refresh token prevents
    future refreshes but leaves already-issued access JWTs valid until expiry.
    Deleting or disabling the OAuth client, or signing out of the linked session,
    makes those JWTs fail verification. Do not describe a refresh-token revocation
    or consent deletion as immediately disconnecting existing access tokens.
  • Existing scope semantics remain: a grant with only standard OIDC scopes has
    full API access within the user's permissions. The consent page states this
    explicitly. Custom scopes narrow that access.

Rollback

The old OAuth tables remain available, and the new columns are additive. A
rollback needs a coordinated stop of the new workers before restoring the old
application version. New client registrations and consent are not copied back;
those clients will need to register or reconnect again. Old tokens retained in
the legacy tables can become usable again under the old binary, including tokens
whose corresponding access was revoked after cutover. Account for those grants
before restoring OAuth traffic. Recompute team member counts before returning
to 1.7 after any period of running 1.6.

Verification

The team's internal memberCount reservation counter can drift after user
deletions that cascade to memberships. Rybbit's team lists read the membership
rows, and maximumMembersPerTeam is not configured. Reconcile this counter on
all deletion paths before enabling that limit; it is not an authoritative count
for reporting. This remains a follow-up rather than a cutover requirement.

The disposable PostgreSQL integration test applies every migration through 0018
using the production Kysely adapter. It checks client migration and secret
hashing, duplicate-account and malformed-metadata rejection, discovery, signed
consent, PKCE token exchange, refresh and its revocation, JWT revocation limits,
bearer verification, sign-out revocation, pre-migration
sessions/API keys, team keys and email OTP password recovery. Client tests cover
consent, rejection, error retry and preservation of the signed login query.

Validation used Node 24: the server suite passed with four workers, the client
suite passed, both packages type-checked, and the client production build passed
with npm run build -- --webpack. A headless browser check also exercised the
real client plugin's signed-query submission and the mobile consent layout.
Local Turbopack builds cannot resolve the existing @rybbit/shared symlink
outside the client root; the production Dockerfile already materializes that
package before building. npm run lint is blocked by the existing empty ESLint
configuration scanning generated .next files and encountering undefined rules.
The 17 new consent messages are translated in all 11 non-English locales, with
their interpolation placeholders and existing translations preserved.

Upstream references: 1.7 upgrade guide,
MCP plugin, and
changelog.


Content Blog 5k Stars (`docs/content/blog/5k-stars.mdx`)


title: "How Rybbit Got 5,000 GitHub Stars in 9 Days" date: 2025-05-26 updated: 2026-09-28 keyword: "github stars" tags: ['growth'] author: Rybbit Team

The backstory

I run an open source web analytics platform with a freemium hosted version. I have a gaming stats site that gets around 1.5 million visits a month, so I needed serious web analytics. The existing platforms were either too simple (just a basic dashboard) or too complicated and enterprisey, so I decided to build my own web analytics platform since I love stats anyway.

I started working on it in January 2025, and it took around 110 days for me to launch. Rybbit being open source made the distribution problem so much easier, as I could freely advertise it in many places since it was self-hostable and free to use.

The launch strategy

I made posts on 2 major subreddits - r/SideProject and r/selfhosted, plus a few smaller subs. Pretty much every post reached #1 in hot and I probably got 150k free impressions from this.

TL;DR - all I did was make like 4 Reddit posts with the same headline, got 150k free impressions, and then proceeded to go viral on X, Hacker News, Bluesky, Mastodon, and some random Asian language sites from other people sharing my project.

What I think I did right

1. Full iframe embed as hero section

I put a full iframe embed of my demo site as my hero section. A lot of analytics platforms have a demo site, but I went a step further by embedding the entire demo site into the landing page so any visitor is immediately greeted by a full demo of the product. I haven't ever seen anyone do this, but I think this is an insane hack if this suits your product.

2. Visual-first GitHub README

I included a ton of product screenshots on my GitHub README and kept the text content short since I had a landing and docs page. People have short attention spans and aren't gonna read a wall of text.

3. Catchy positioning

I used the post title "I built an open source Google Analytics replacement" - simple but effective positioning that immediately communicates the value.

4. Strong visual assets

I created a good OpenGraph image that showed the best features of the product, which helped with social sharing.

5. Polish matters

I had a genuinely good & polished product with a fully functional landing page, docs, self-hosting guide, and SaaS product that I personally use every single day. I doubt I would've gotten as much traction if I released a barely-working MVP after a few weeks.

What didn't work

I made a couple launch posts from my X account, but I only had a couple hundred followers so nobody cared (though I didn't expect to go viral on X in the first place).

Some big Discord servers like the Next.js server have #showcase channels. Literally nobody reads these.

The results and reality check

I optimized my launch for reach instead of revenue, so I wasn't surprised that I didn't get a huge surge of paid plan signups. The vast majority of people are self-hosting it since my most viral post was in r/selfhosted. I'm just playing the numbers game and hoping that a small single-digit percentage of people who see it will pay for the cloud version.

So far I have around 700 total signups and $900 in total revenue in 3 weeks - not life-changing money, but solid validation.

What's next

The virality cooled down after the first week, but the organic growth is still going strong. I'm at 5.8k stars 3 weeks post-launch, so the daily gain is still around ~50 stars. I expect to hit 10k stars in around 3 months.


Content Blog Amplitude Alternatives (`docs/content/blog/amplitude-alternatives.mdx`)


title: "9 Best Amplitude Alternatives for Product Teams in 2026" keyword: "amplitude alternatives" date: 2026-09-27 tags: ["product analytics", "comparisons", "tools"] author: Rybbit Team

The best Amplitude alternatives for most product teams are Mixpanel and PostHog: both cover event funnels, retention, behavioral cohorts, experiments, feature flags and session replay, and both publish their prices. Heap and Pendo suit teams that want autocapture or in-app guides. OpenPanel, Rybbit, Countly and Matomo are open source and can run on your own servers, and Google Analytics 4 is the $0 fallback. This guide compares nine tools on price at your event volume, the Amplitude features each one replaces, and what each is missing.

Disclosure up front: we build Rybbit, one of the nine, and we rank it sixth for a typical Amplitude user because it replaces less of Amplitude than the five tools above it. Every claim about the other tools comes from that vendor's own pricing or documentation pages, linked inline and checked in September 2026. Where a vendor doesn't publish a number, we say so.

Quick comparison

Tool Best for Free option Paid pricing (Sept 2026) Priced by Open source / self-host
Amplitude (for reference) Analytics, experiments, guides and replay in one platform 2M events/month Plus from $0; $94/month at 3M events Events or MTUs No / No
Mixpanel The closest like-for-like switch 1M events/month Growth from $0; $140/month at 1.5M events Events No / No
PostHog Analytics plus flags, experiments and surveys 1M events and 5K recordings/month $0.00005/event after 1M, tiered down Events, per product MIT core / Yes, unsupported
Heap Autocapture, priced by sessions 10K sessions/month Growth by estimate; Pro and Premier by quote Sessions No / No
Pendo B2B SaaS analytics plus in-app guides 500 MAUs By quote Monthly active users No / No
OpenPanel Low-cost open-source event analytics Self-hosting (cloud: 30-day trial) $2.50/month for 5K events; $90 at 1M Events AGPL-3.0 / Yes
Rybbit Web products that also need traffic analytics Self-hosting (cloud: 7-day trial) $19/month for 100K events Events AGPL-3.0 / Yes
Countly Mobile, desktop and IoT apps on your own servers Countly Lite, self-hosted Flex from $175/month MAUs and data points Lite edition / Yes
Google Analytics 4 $0 budgets and Google Ads reporting All of it Analytics 360 by quote No usage fee No / No
Matomo Self-hosted web analytics with funnels On-Premise, self-hosted Cloud from $26/month for 50K hits Hits GPL-3.0 / Yes

Why teams look for Amplitude alternatives

Amplitude is generous where most tools are stingy. Its pricing page gives the Free plan 2 million events a month, 10,000 session replays and unlimited seats with no credit card, and every plan includes unlimited feature flags plus a small allowance of experiments, guides and surveys. The reasons teams leave are in the plan comparison table further down the same page:

  • Analysis depth is tiered. Free has no behavioral cohorts and 10 saved charts per organization; Plus allows 20 behavioral cohorts. Conversion drivers and saved metrics start on Growth, and predictions, computations and cross-product analysis are limited there and complete only on Enterprise.
  • History is tiered. Data history is 1 year on Free, 2 years on Plus, and unlimited only on Growth and Enterprise.
  • Growth and Enterprise are sales-priced. Both are listed as "Custom", and SSO and project-level permissions start there. Role-based access control and SCIM are Enterprise-only.
  • The extras are add-ons. Account-level (group) analytics, more active experiments than the one (Free, Plus) or two (Growth, Enterprise) included, extra guides and surveys, and replay retention past 1 month cost more. Amplitude says its expanded packages are "priced as a percentage of your platform plan".
  • It sets cookies by default. The Browser SDK keeps user identity in a cookie with a 365-day expiry unless you change identityStorage, which usually means asking EU visitors for consent.

What the same volume costs elsewhere

Price is a weaker reason than it sounds. Amplitude Plus starts at $0 for the first 2 million events, and on each vendor's own calculator it costs less than Mixpanel Growth at every volume we checked. Monthly billing, on the cheapest plan that covers the volume:

Plan 3M events/month 10M events/month 20M events/month
Amplitude Plus $94 $720 $1,575
Mixpanel Growth $378 $1,176 $2,289
PostHog (analytics only) $84 $324 $643
OpenPanel Cloud $250 $350 $530
Rybbit Standard (no replay) $149 $249 $399
Rybbit Pro (with replay) $299 $499 $799

Sources: the calculators on Amplitude's and Mixpanel's pricing pages, PostHog's per-event rates, OpenPanel's price table and our pricing page. Annual billing cuts most rows; Amplitude's 10M tier drops to $576 a month and Mixpanel's to $840. Replay allowances differ too: 10,000 a month on Amplitude Plus, 20,000 on Mixpanel Growth, a tenth of the event tier on Rybbit Pro (1 million at 10M events), and billed separately on PostHog. Vendors also count events differently, so treat the table as a rough guide, not a quote.

Startups with under $10 million in funding and fewer than 20 employees can also get a free year of Amplitude's Growth plan. Switching makes more sense when you need what the plan gates hold back without a sales contract, want to self-host, or want tracking without cookies.

How we ranked these Amplitude alternatives

We ranked by how much of a typical Amplitude setup each tool replaces: funnels, retention and cohorts first, then experiments, flags and replay, then mobile SDKs and published pricing. Mixpanel and PostHog replace nearly all of it. Heap and Pendo cover the core analytics but don't publish paid prices. OpenPanel and Rybbit cost less at volume but replace less (Rybbit has no experiments or behavioral cohorts), and Countly, GA4 and Matomo fit narrower cases. Open source gets its own section.

1. Mixpanel

Best for: the closest like-for-like switch.

Mixpanel works the way Amplitude does: insights, funnels, retention and flows reports on an event model, for web and mobile. Its pricing page now puts experiments and feature flags on Free and Growth (Free gets 1,000 monthly experiment users and 10 active flags), alongside session replay, heatmaps, and US or EU data residency. SAML SSO and SCIM come with self-serve Growth, while Amplitude starts SSO at its sales-priced Growth plan.

Free is tighter than its event count suggests. The pricing docs cap it at 5 saved reports per user, let you create cohorts but not save them, and block your reports (rather than billing you) once a month passes 1 million events. On Growth the first million events are free, and the calculator shows $140 a month at 1.5 million events and $1,176 at 10 million, or $840 a month billed annually.

  • Free option: 1M events and 10K session replays a month, unlimited seats
  • Paid: Growth from $0; $1,176/month at 10M events
  • Drawbacks: costs more than Amplitude Plus at every volume we checked; group analytics is a paid add-on; anomaly detection and root cause analysis are Enterprise-only

Our Mixpanel pricing breakdown has every Growth tier, and Mixpanel alternatives covers the rest of the field.

2. PostHog

Best for: engineering-led teams that want analytics, flags, experiments, surveys and error tracking on one usage-based bill.

PostHog covers trends, funnels, retention, paths, stickiness, lifecycle and SQL, with autocapture of pageviews, clicks and form submissions (docs). Cohorts can be behavioral (did an event several times, completed a sequence, stopped, started again), and there are SDKs for iOS, Android, React Native and Flutter. Replay, flags, experiments, surveys, error tracking and a data warehouse are separate products, each with its own monthly allowance on the pricing page. Past the first million events, analytics costs $0.00005 per event, falling in tiers to $0.000009 above 250 million. Events are kept for 1 year on the free plan and 7 years on pay-as-you-go.

The core is MIT-licensed, but the self-hosting docs call self-hosted deployments "officially unsupported" and keep all paid-plan features cloud-only. More in Rybbit vs PostHog.

  • Free option: 1M events, 5K recordings, 1M flag requests, 100K exceptions and 1,500 survey responses a month, no card
  • Paid: per product; about $324/month at 10M events for analytics alone
  • Drawbacks: many meters to watch; replay, flags and surveys are billed on top; self-hosting isn't a supported path

For the per-product rates and worked examples, see PostHog pricing, explained.

3. Heap (Contentsquare)

Best for: teams that don't want to plan tracking up front and would rather pay per session than per event.

Heap has been part of Contentsquare since December 2023, when the acquisition closed, and it still sells its own plans. Its autocapture "grabs every click, swipe, tap, pageview, and fill", so you can analyze history without deciding in advance what to track, and there are autocapture SDKs for web, iOS, Android, React Native and Flutter (docs). The gap has narrowed, though: Amplitude now lists autocapture on every plan.

On the pricing page, Free includes core charts, SSO and 6 months of history. Growth adds unlimited users and reports and 12 months of history, but shows its price only after you install the snippet. Pro adds account analytics and report alerts, and Premier adds data warehouse integration and unlimited projects; both are "Custom Session Pricing", with session replay as an add-on.

  • Free option: up to 10K sessions a month
  • Paid: Growth by estimate after install; Pro and Premier by quote
  • Drawbacks: no public paid prices; replay costs extra; 6 to 12 months of history below Pro

4. Pendo

Best for: B2B SaaS teams that want product analytics and in-app guides in one tool.

Pendo pairs product analytics with in-app guides, a natural next step for teams that have outgrown Amplitude's Guides and Surveys (one guide or survey on Free and Plus). Its pricing page charges by monthly active users (MAUs), says the cost per MAU falls as you add more, and advertises your complete data history from the day you install. Free includes product analytics, guides, and Pendo-branded roadmaps and NPS surveys across unlimited web and mobile apps. Base, Core and Ultimate are "Request pricing"; session replay is an add-on on Base and included on Core and Ultimate.

  • Free option: up to 500 MAUs
  • Paid: by quote; a 30-day trial of the full platform goes through sales
  • Drawbacks: no published prices; 500 MAUs is a low ceiling; no experiments or feature flags in the plan table

5. OpenPanel

Best for: small teams that want Mixpanel-style event analytics, open source, at low cloud prices.

OpenPanel calls itself an open-source alternative to Mixpanel. Its README lists funnels, cohorts, user profiles, session replay, custom dashboards, alerts, revenue tracking and A/B variant breakdowns, with cookieless tracking and SDKs for web, iOS, Android, React Native and servers. Retention is event-based: you pick an initial event and a return event. Cloud pricing is by events, with websites, users and dashboards unlimited and two months free on yearly billing. Self-hosting is free "with unlimited events" under AGPL-3.0.

  • Free option: self-hosting; the cloud has a 30-day trial with no card
  • Paid: $2.50/month for 5K events, $90 at 1M, $350 at 10M
  • Drawbacks: nothing free on the cloud after the trial; feature flags aren't in its feature list

6. Rybbit

Best for: web products (SaaS apps, stores, content sites) that want product and traffic analytics in one cookieless tool. If that describes you, start here.

Rybbit is open-source web analytics with the product-analytics reports in the same dashboard: funnels built from page paths, custom events or autocaptured clicks, outbound links, form submits and copies; retention; user journeys; user profiles with identify() and custom traits; sessions; error tracking; and Web Vitals, next to referrers, UTM campaigns, pages and devices. Tracking is cookieless by default, so Rybbit's own tracking needs no consent banner, and the cloud is hosted in the EU.

Pricing is by events. Standard is $19 a month for 100,000 events (up to 5 websites and 3 team members) and includes everything above plus goals, email reports, API access and 3 years of data retention, against 2 on Amplitude Plus. Pro is $39 a month for 100,000 events and adds session replay (10,000 replays a month at that tier, rising with the plan), unlimited websites and team members, and 5-year retention. Cloud plans start with a 7-day trial (the card is collected at signup), and self-hosting is free under AGPL-3.0.

Where it falls short of Amplitude:

  • No experiments or feature flags.
  • No behavioral cohorts at Amplitude's depth. Segments are saved filters on properties such as country, UTM source, page or event name, and retention groups users by the day or week of their first visit and counts any return, with no choice of start and return events.
  • Journeys are page-path Sankey diagrams, not event sequences.
  • No account-level (group) analytics and no heatmaps.
  • Mobile goes through the React Native SDK, which tracks screens, events, errors and users in an App Analytics dashboard mode. There's no native iOS or Android SDK and no mobile session replay.
  • No Amplitude importer. Data import covers Plausible, Umami and Simple Analytics, so switching means running both tools side by side.

Pick something else if you run experiments in Amplitude today (PostHog or Mixpanel), are mobile-first (Mixpanel, PostHog or Heap), or target users with behavioral cohorts (Mixpanel or PostHog).

  • Free option: self-hosting, replay included; the cloud has a 7-day trial
  • Paid: Standard from $19/month, Pro from $39/month; $249 and $499 at 10M events
  • Drawbacks: the list above

7. Countly

Best for: mobile, desktop and connected-device apps that have to run on your own infrastructure.

Countly collects data from mobile, web and desktop apps, with crash reporting, push notifications and remote config next to analytics (README). Flex, a managed private cloud priced on MAUs and data points (Flex page), includes events, sessions, user profiles and dashboards. Per the pricing page, funnels, retention and flows are in the Intelligence add-on and A/B testing and remote config in the Adaptivity add-on, neither with a published price. Enterprise includes everything and runs on your own servers.

The free Countly Lite is AGPL-3.0 with a modified Section 7 that requires keeping Countly's branding (license). Its README calls Lite "core analytics and essential features" under "an open-source, non-commercial license", and lists no funnels or retention.

  • Free option: Countly Lite, self-hosted
  • Paid: Flex from $175/month with a 14-day trial; Enterprise by quote
  • Drawbacks: funnels and retention cost extra on Flex; add-on prices unpublished; Lite lacks product-analytics reports

8. Google Analytics 4

Best for: $0 budgets and teams whose questions are mostly about acquisition and Google Ads.

Google says Analytics gives you its tools "free of charge", and one property can hold up to 50 web and app data streams (help center). For product questions, explorations offer funnel, path, cohort, segment overlap, user and user-lifetime reports.

The limits show up fast. Explorations are sampled once a query exceeds 10 million events, and their event-level data is kept 2 or 14 months on standard properties; longer retention needs Analytics 360, which has no public price, only a "Talk to Sales" button. Optimize, Google's testing tool, has been unavailable since September 30, 2023, and GA4's _ga cookies last 2 years, which generally means asking EU visitors for consent. More in Rybbit vs Google Analytics.

  • Free option: all of it; there's no usage fee
  • Paid: Analytics 360, by quote
  • Drawbacks: sampling on large queries; 14-month cap on exploration data; cookie consent in the EU; no built-in experiments

9. Matomo

Best for: organizations that need self-hosted, GA-style web analytics with funnels and cohorts.

Matomo is open-source web analytics under GPL v3, with funnels, users flow, cohorts, A/B testing, heatmaps, session recordings and visitor profiles on top of its traffic reports. Matomo Cloud's Business plan includes all of those, with 24 months of raw data, no sampling, 30 websites, 30 team members and data stored in Frankfurt. Its pricing page shows US visitors $26 a month for 50,000 hits, and the trial needs no card.

Self-hosted On-Premise is free with unlimited users and hits, but the product-analytics parts are paid plugins: funnels, users flow and heatmaps in the Team bundle (€275 a month, or €230 billed annually), cohorts in Business (€1,450 a month) and A/B testing in Enterprise (€3,400 a month). Plugins can also be bought one at a time.

Rybbit vs Matomo compares the two in detail.

  • Free option: Matomo On-Premise, self-hosted
  • Paid: Cloud from $26/month for 50K hits; on-premise bundles from €275/month
  • Drawbacks: on-premise product features are paid plugins, with cohorts and A/B tests in the pricier bundles

Open-source Amplitude alternatives

If analytics data can't leave your servers, or you'd rather pay for servers than per event, these are the open-source options, and their licenses differ in ways that matter:

Tool License Self-hosted edition Watch out for
PostHog MIT core Free Docker Compose deployment "Officially unsupported"; paid-plan features are cloud-only
OpenPanel AGPL-3.0 Free, unlimited events Feature flags aren't in its feature list
Rybbit AGPL-3.0 Free, all features including replay Web-first; no experiments, flags or behavioral cohorts
Countly Lite AGPL-3.0 with branding terms Free README calls it a "non-commercial license"; no funnels or retention
Matomo GPL-3.0 Free core, unlimited users and hits Funnels, cohorts and A/B tests are paid plugins
GrowthBook MIT outside its enterprise folders Free Experiments and flags first; analytics reads your own warehouse

PostHog is the closest to Amplitude's feature set, but its maintainers don't support self-hosted installs. Of the rest, OpenPanel is the nearest match to Amplitude's event model, and Rybbit is the pick for websites and web apps. If what you'll miss most is Amplitude's experimentation, GrowthBook covers feature flags and A/B tests: its cloud Starter plan costs nothing for up to 3 users, Pro is $40 per seat per month, and the self-hosted edition is free and reads from your own data warehouse (see its license).

Names to drop from older lists

Some lists of alternatives to Amplitude still recommend tools that are now part of Amplitude or on their way out:

Which Amplitude alternative should you pick?

  • Same workflow, different vendor: Mixpanel.
  • Flags, experiments, surveys and error tracking on one usage-based bill: PostHog.
  • Autocapture, priced by sessions: Heap.
  • B2B SaaS where in-app guides matter as much as charts: Pendo.
  • Open-source event analytics on a small budget: OpenPanel.
  • A website or web app that needs traffic analytics, product analytics, errors and Web Vitals in one cookieless tool: Rybbit.
  • Mobile, desktop or IoT apps that must stay on your servers: Countly Enterprise.
  • A $0 budget: GA4, or Mixpanel and PostHog within their no-cost limits.
  • GA-style web analytics on your own servers: Matomo.

If price is your only complaint, check Amplitude's Plus calculator first: at 3, 10 and 20 million events it came in under Mixpanel Growth. For the web-analytics side of the decision, see our guide to the best web analytics tools.

FAQ

What is the best alternative to Amplitude?
For most product teams, Mixpanel: the same event model (funnels, retention, flows, behavioral cohorts), with experiments, feature flags and session replay, and a public price calculator. PostHog is the better pick if you also want error tracking and surveys, or an open-source core.

Is there a free Amplitude alternative?
Yes. Mixpanel costs nothing up to 1 million events a month, PostHog up to 1 million events and 5,000 recordings, Heap up to 10,000 sessions and Pendo up to 500 monthly active users, and GA4 has no usage fee. Self-hosted OpenPanel, Countly Lite, Matomo and Rybbit cost only the server they run on.

Who are Amplitude's main competitors?
Amplitude's own site footer lists ten under "Compare us": Adobe, Google Analytics, Contentsquare, Fullstory, Heap, LaunchDarkly, Mixpanel, Optimizely, Pendo and PostHog. In product analytics specifically, the direct competitors are Mixpanel, PostHog, Heap and Pendo.

Is there an open-source alternative to Amplitude?
Yes: PostHog (MIT core), OpenPanel and Rybbit (both AGPL-3.0), Countly Lite (AGPL-3.0 with branding terms) and Matomo (GPL-3.0), plus GrowthBook for experiments and feature flags. PostHog is the closest to Amplitude but doesn't support self-hosted installs; OpenPanel is the nearest of the rest.

Is Amplitude cheaper than Mixpanel?
On their own calculators, yes: $720 against $1,176 a month at 10 million events, and $94 against $378 at 3 million, both on monthly billing. Mixpanel's edge is SAML SSO on self-serve Growth, which Amplitude keeps for its sales-priced plans.


Content Blog Best Analytics Mcp Servers (`docs/content/blog/best-analytics-mcp-servers.mdx`)


title: "Best Analytics MCP Servers in 2026: GA4, PostHog, Mixpanel" keyword: "analytics mcp server" date: 2026-09-27 tags: ["mcp", "ai", "comparisons", "tools"] author: Rybbit Team

An analytics MCP server connects an AI assistant (Claude, ChatGPT, Cursor, Codex and others) to your analytics through the Model Context Protocol. You ask "which pages lost the most traffic last week?" and the assistant calls the vendor's tools instead of you exporting a CSV. Most major analytics vendors now ship one, and they differ more than the name suggests. PostHog, Amplitude, Mixpanel, Contentsquare and Rybbit run hosted servers you connect with a URL and a login. Google Analytics and Microsoft Clarity ship local servers you install yourself, and both are read-only. Matomo's runs inside your own Matomo. Google Search Console has no official server, and Plausible's docs point to one built by Sentry.

Disclosure: we build Rybbit, which has its own MCP server and is listed first. Every claim about the other servers comes from that vendor's own documentation or pricing pages (for the two community projects, their GitHub READMEs), linked inline and checked in September 2026. Tool counts are what each vendor's docs listed on the day we checked, and they change often.

Analytics MCP servers compared

Server Hosting Auth Read / write Standout Plan needed
Rybbit Hosted, or your own server if self-hosted OAuth or scoped API key Read + write (44 tools) Read-only SQL and Search Console in one server Any Cloud plan, from $19/mo; self-hosting free
Google Analytics Local (Python) Google Cloud credentials Read only (7 tools) Funnel and realtime reports GA4 is free; needs a Google Cloud project
PostHog Hosted (US/EU) OAuth or personal API key Read + write, optional read-only mode 1,000+ tools: flags, experiments, SQL, replay Free to connect; normal usage billing
Amplitude Hosted (US/EU) OAuth 2.0 Read + write (45 tools) Edits charts, experiments and the tracking plan All plans, including Free
Mixpanel Hosted (US/EU/India) OAuth or service account (beta) Read + write (63 tools) Lexicon and data-quality cleanup All plans; older accounts need an admin to enable it
Google Search Console No official server; community servers run locally Your own Google Cloud OAuth client or service account mcp-gsc: read + write URL inspection and sitemaps (community) Free, self-managed
Microsoft Clarity Local (Node.js) Data Export API token Read only (3 tools) Lists session recordings Free; 10 requests per project per day
Contentsquare (Hotjar) Hosted OAuth Read only for now Funnel, journey and impact analysis All plans; Free has 300 tool calls/mo
Matomo Inside your Matomo token_auth or OAuth 2.0 Read; write if an admin allows it Runs on your own infrastructure Included in Cloud; GPL plugin on-premise
Plausible Sentry-built: hosted or local Stats API key Read only (4 tools) Period-over-period comparisons Business plan (Stats API)

Rybbit MCP server

Rybbit is open-source, cookieless web and product analytics. Its MCP server is hosted at https://app.rybbit.io/api/mcp, and self-hosted installations serve the same endpoint from their own domain. It has 44 tools: 28 read-only and 16 that write. The read tools cover traffic overviews and breakdowns, live visitors, custom events, JavaScript errors, Web Vitals, retention, user journeys, sessions and raw events. The write tools create and edit goals, funnels, sites, members and teams, and user profiles, including GDPR erasure of one person's data.

Two tools set it apart. run_query runs read-only ClickHouse SQL against a site-scoped events table, capped at 1,000 rows and 60 seconds of execution, for questions the fixed tools can't answer. And on Rybbit Cloud, once a site admin connects Google Search Console under Site settings > Integrations, get_search_console_data returns clicks, impressions, CTR and average position by query, page, country, device or date, with filters and up to 25,000 rows, through the same connection as your on-site data.

Claude Code, Claude Desktop, Codex and opencode connect with OAuth. Other clients (Cursor, VS Code, anything that speaks Streamable HTTP) use an API key, which you can limit to resource:action scopes such as analytics:read; the tool list then shrinks to match. Every call re-runs the dashboard API's role checks and rate limits, and the delete tools carry the MCP destructive hint so well-behaved clients confirm first. The server contains no AI model and sends nothing to an AI provider on its own.

The limits: the MCP server has no tools for feature flags, experiments, dashboards or session replay, so an agent can't review recordings through it. The Search Console tools read Search Analytics only (no URL Inspection, sitemaps or indexing requests) and need Rybbit Cloud. The server comes with every Cloud plan: Standard from $19/month for 100k events, or Pro from $39/month with higher API rate limits, per the pricing page. Plans start with a 7-day trial, and the card is collected at signup. Self-hosting is free under AGPL-3.0.

  • Setup: claude mcp add --transport http rybbit https://app.rybbit.io/api/mcp in Claude Code; the MCP docs cover other clients
  • Skip it if: you want an agent to manage feature flags and experiments or dig through session replays; PostHog, Amplitude and Mixpanel expose tools for both.

Google Analytics (GA4) MCP server

Google's Google Analytics MCP server is official (it's published under the googleanalytics GitHub organization and linked from Google's developer guide) but still labeled experimental. It's a local server: a Python package you start with pipx run analytics-mcp. There is no hosted URL, and Analytics isn't on Google's list of its remote MCP servers.

It has seven tools: account summaries, property details, Google Ads links, custom dimensions and metrics, and three report runners over the Data API (run_report, run_funnel_report and run_realtime_report). Google's guide is explicit that the server "is available for read requests only. It can't edit your Google Analytics configuration or settings."

Setup is where it costs you; our Google Analytics MCP setup guide walks through each step. You need a Google Cloud project with the Analytics Admin API and Analytics Data API enabled, Application Default Credentials that carry the analytics.readonly scope (from your own OAuth client, or by impersonating a service account through gcloud), and Python with pipx. The README documents Gemini CLI, Gemini Code Assist and Claude Code. GA4 itself is "free of charge", and the server's calls draw on Data API quotas: 200,000 core tokens per property per day on a standard property. For how the products compare, see Rybbit vs Google Analytics.

Setup: the Google Cloud, credential and client steps are in the README.

PostHog MCP server

PostHog's MCP server is the broadest one here. It's hosted at https://mcp.posthog.com/mcp and routes you to the US or EU region based on the account you log in with. Its tools reference lists more than 1,000 tools in about 70 categories, spanning product and web analytics, SQL (execute-sql), feature flags, experiments, error tracking, session recordings, surveys, logs and data pipelines. To keep that list from flooding the model's context, most clients get a "CLI mode" with a single exec tool that finds and calls the others on demand; Cursor registers every tool.

It reads and writes by default. The FAQ documents a read-only switch (the x-posthog-read-only header or ?readonly=true), filters by feature category or tool name, and headers that pin a session to one organization or project. You log in with OAuth, or use a personal API key created with the "MCP Server" preset; enterprise plans can manage access through an identity provider.

On cost, PostHog's FAQ says: "Connecting to the MCP server and calling its MCP tools is free. Standard PostHog usage and billing still apply to the underlying data your queries consume." A few AI-powered tools are billed as PostHog AI spend and only appear when AI data processing is enabled. PostHog's pricing includes the first 1 million product analytics events each month free. If you land on the PostHog/mcp GitHub repo, it's archived; the server now lives in PostHog's main repository. For the products themselves, see Rybbit vs PostHog.

Setup: npx @posthog/wizard mcp add, or add the URL by hand using the client guides.

Amplitude MCP server

Amplitude's MCP server is hosted at https://mcp.amplitude.com/mcp, with https://mcp.eu.amplitude.com/mcp for EU data residency, and authenticates with OAuth 2.0. Its docs list 45 tools. On the read side it searches charts, dashboards, notebooks, experiments and cohorts, runs ad-hoc segmentation, funnel and retention queries, and finds session replays from the last 30 days. On the write side it creates and edits charts, dashboards, notebooks, cohorts, experiments, feature flags and metrics, and manages the tracking plan, with a two-step confirmation before deleting events or properties.

Access is on by default for every user in the organization. Admins can block it under Settings > Content Access > MCP, and two role actions, Use MCP (read) and Use MCP (write), limit what an agent can do per project. ChatGPT gets a curated subset of about 25 tools that leaves out session replay. The docs say "any existing Amplitude customer" can use it, and the pricing page lists "AI Agents and MCP" on the Free plan, which covers 2M events a month.

Setup: add the URL as a remote server in your client; the Amplitude MCP docs have guides for Claude, ChatGPT, Cursor, Codex, Gemini CLI and others.

Mixpanel MCP server

Mixpanel's MCP server is hosted in three regions: https://mcp.mixpanel.com/mcp plus EU and India endpoints. Its docs list 63 tools: insights, funnel, flow and retention queries; dashboards (create, update, duplicate, delete); cohorts, custom properties, lookup tables and saved metrics; a tool that analyzes one user's replays alongside their events; and a large set of Lexicon tools that edit event descriptions, merge duplicate event names and mark PII. Experiment and feature flag tools are in open beta.

You connect with OAuth, or with a service account (in beta) sent as a static header for CI jobs and shared agents. Mixpanel's docs are direct about the trade-offs: "the AI assistant can both read and write to Mixpanel on your behalf," your data goes to whichever AI provider you use, and "The MCP server does not currently support HIPAA requirements." Each user gets up to 600 MCP requests per hour.

Whether it's switched on depends on account age. Free and Growth accounts created after August 1, 2026 have MCP enabled by default; on every other account, an organization admin has to enable it under Settings > Org > Overview. The pricing page lists a Free plan with up to 1M events a month.

Setup: add your region's URL to your client; the Mixpanel MCP docs cover OAuth and service-account configs per client.

Google Search Console MCP

As of September 2026, Google doesn't publish a Search Console MCP server. It isn't on Google's list of Google and Google Cloud MCP servers, which covers products like BigQuery, Maps and Workspace, and the "Google Search Console MCP" servers you'll find are third-party projects built on the public Search Console API. Our Google Search Console MCP guide compares them, with setup steps and example prompts.

One widely used option is mcp-gsc (MIT). It runs locally via uvx mcp-search-console and authenticates with an OAuth desktop client you create in your own Google Cloud project, or with a service account you add to the property. Its 20 tools go past reporting: URL inspection (up to 10 URLs per batch), indexing-issue checks, sitemap submission and deletion, and adding or removing properties. That's useful, and it also means an agent can change your Search Console setup, so review the tools before you let one run.

There are two other routes to search data. GA4's Data API, which the GA server's run_report calls, exposes organic Google Search clicks, impressions, CTR and average position for properties with an active Search Console link. And Rybbit's hosted Search Console tools, covered above, need no Google Cloud project, but they cover Search Analytics only (no URL inspection or sitemaps) and only on Rybbit Cloud.

Setup: credential and client steps are in the mcp-gsc README.

Microsoft Clarity MCP server

Microsoft's Clarity MCP server is official and local: you run it with npx @microsoft/clarity-mcp-server --clarity_api_token=... or install it as a Claude Desktop extension. It authenticates with a Data Export API token that a project admin generates under Settings > Data Export. The GitHub repo lists three read-only tools: query-analytics-dashboard (traffic, scroll depth, engagement time and other dashboard metrics), list-session-recordings (recordings filtered by URL, device, browser, OS, country, city and more) and query-documentation-resources (answers from Clarity's docs).

The limits come from the API underneath. Microsoft's MCP guide states: "Each project permits up to 10 API requests per day, with a maximum of 3 days' data and up to 3 dimensions per request." The Data Export API docs add that a response is capped at 1,000 rows, with no pagination. Ten requests covers a morning check-in; an agent that iterates will use them up quickly. Microsoft lists higher limits as a planned improvement. Clarity itself is, per its FAQ, "a free service forever."

Setup: token and client config are in the Microsoft Learn guide.

Contentsquare (Hotjar) MCP server

Hotjar doesn't have its own MCP server. Contentsquare's Hotjar page says the two products "have merged into a single, more powerful experience intelligence platform" and that new users sign up on Contentsquare, so for "Hotjar MCP" the official route is Contentsquare's MCP server. Contentsquare's help center doesn't say whether legacy Hotjar accounts can connect.

The server is hosted and uses OAuth, with access tokens that expire hourly and refresh automatically. Only Customer Admins can set it up (Analysis setup > Model Context Protocol) and grant access to other users. It supports funnel, journey and impact analysis, page comparison, site and page metrics, and error analysis, which requires Experience Monitoring; the help center's capability list doesn't mention session replay or heatmaps. It's read-only for now: the Write permission is "reserved for future MCP tools" and selecting it "now has no effect."

It's the only server here with a published tool-call allowance per plan. Only calls that return analytics count, and the pool is shared across the whole account:

Contentsquare plan MCP tool calls
Free Up to 300 per month
Growth Up to 36k per year
Pro Up to 72k per year
Enterprise Up to 108k per year

When the allowance runs out, the server stops processing new requests until it resets, unless you're on Pro or Enterprise and buy more capacity.

Setup: an admin follows the in-app steps for each AI client, as described in the help center.

Matomo MCP server

Matomo's MCP Server plugin is official, licensed GPL-3.0, and runs inside your Matomo, so the endpoint is your own instance. According to Matomo's setup guide, "The MCP is included with Matomo Cloud. For Matomo On-Premise, you must first install and activate the MCP Server plugin." Either way, a superuser has to switch it on under Administration > System > General settings.

Out of the box it's read-oriented, with tools for sites, reports and report metadata, processed report data, goals, segments and dimensions. Raw Matomo API access is off by default. An admin can open it partially, with separate checkboxes for read, create, update and delete methods, or fully, at which point an agent can call state-changing methods such as creating segments. Admins can also cap the highest privilege level usable over MCP.

You authenticate with a Matomo token_auth sent as a Bearer token, or with OAuth 2.0 through Matomo's OAuth2 plugin. The OAuth path has caveats: tokens last 3,600 seconds by default, refresh "must currently be handled manually," and clients that expect fully spec-compliant discovery endpoints may not connect. See Rybbit vs Matomo for the analytics side.

Setup: enable it, copy the endpoint from the admin screen, then follow Matomo's setup guide and its per-client guides.

Plausible MCP server

Plausible doesn't maintain an MCP server itself. Its data access docs point to one "built by the Sentry team": getsentry/plausible-mcp (MIT). It has four read-only tools (get_timeseries, get_breakdown, get_conversions and compare_periods) and runs either locally over stdio or on Sentry's hosted instance at https://plausible-mcp.sentry.dev/mcp, where you pass your own Plausible API key as a Bearer token.

That key is the catch. The server needs a Stats API key, and per the Stats API docs, "Stats API is a Business plan feature," with a default limit of 600 requests per hour. On Plausible's lower plans, there's no key to connect with. For the analytics products themselves, see Rybbit vs Plausible.

Setup: hosted and local configs are in the getsentry/plausible-mcp README.

How to choose an analytics MCP server

Most teams use the server that comes with the analytics tool they already have; few switch vendors for an MCP server. If you're choosing both, or connecting more than one tool, these are the differences that matter:

  • Product-analytics depth: PostHog, Amplitude and Mixpanel have the deepest servers, with funnels, retention, cohorts, experiments, feature flags and replay tools, and write access to match. PostHog covers the most products; Amplitude and Mixpanel add tracking-plan and Lexicon cleanup that data teams will use.
  • Web analytics and search: For traffic, sources, pages and conversions, look at Rybbit, GA4, Matomo or Plausible. Rybbit has dedicated Search Console tools, and GA4's server reaches Search Console metrics if the property is linked. GA4's is reporting only; Rybbit's also manages goals, funnels and sites.
  • Least setup: Hosted servers with OAuth (Rybbit, PostHog, Amplitude, Mixpanel, Contentsquare) connect with a URL and a browser login. GA4 needs a Google Cloud project, two APIs, gcloud credentials and Python; Clarity needs Node.js and a token.
  • Privacy and data control: Self-hosting keeps the analytics side on your infrastructure, and Rybbit and Matomo both serve their MCP endpoint from your own server when self-hosted. Whatever you pick, query results go to your AI provider.
  • Guardrails: GA4, Clarity, Contentsquare and the Plausible server can't change anything. For the rest, restrict the agent with PostHog's read-only mode, Rybbit's scoped keys, Amplitude's role actions or Matomo's API access setting.
  • Quotas: Clarity's 10 requests a day is the tightest limit here, followed by the 300 tool calls a month on Contentsquare's Free plan. Mixpanel allows 600 MCP requests per user per hour, and Plausible's Stats API 600 per hour per key.

FAQ

What is an analytics MCP server?
It's a service that exposes an analytics product's data and actions as tools an AI assistant can call through the Model Context Protocol, an open standard. A hosted server is a URL you add to your client; a local server is a program your client starts on your machine. Either way, the assistant picks a tool, the server runs it with your credentials, and the result comes back into the conversation.

Is it safe to connect an AI assistant to my analytics?
It can be, with the care you'd give any API key. Give each integration its own revocable credential with the narrowest access that works, such as PostHog's read-only mode, Rybbit's scoped keys, Amplitude's MCP role actions or a dedicated low-privilege Matomo user, and keep confirmations on for anything that deletes. Watch for prompt injection too: page titles, referrers and event names are written by your visitors, and PostHog's FAQ warns that "LLMs can be tricked into following untrusted commands." Rybbit strips control and bidirectional-override characters from tracked text, but an agent should still treat that text as data, never as instructions.

Does my analytics data leave the vendor?
Yes. Whatever a tool returns goes to the model provider behind your AI client. Mixpanel's docs say so directly, and Amplitude's docs note that "The AI service you're using (for example, Claude or Gemini) processes your Amplitude data." The servers differ in what they do with it themselves: PostHog says its server "does not store your analytics data," Contentsquare says it is "not training any third-party models with data accessed through the MCP server," and Rybbit's server contains no AI model and returns data only to the client you connect. Mixpanel's MCP server isn't covered by its HIPAA BAA, so check compliance scope before connecting regulated data.

How much do analytics MCP servers cost?
Usually nothing beyond the analytics plan. Google Analytics, Clarity and the community Search Console server are free but self-run; PostHog's server is free to connect (some AI-powered tools bill as PostHog AI spend); Amplitude and Mixpanel include theirs on their Free plans. The exceptions: Contentsquare meters tool calls by plan, and the Sentry-built Plausible server needs Plausible's Business plan. Your AI client's subscription is a separate cost.

Rybbit's server comes with Cloud plans from $19/month, or at no cost when self-hosted.

Is "MCP analytics" the same thing?
No. "MCP analytics" usually means measuring how AI agents use your own MCP server: which tools they call and where calls fail. PostHog's MCP Analytics (in beta) is one example. This guide covers the reverse: servers that let an agent read your analytics.


Content Blog Best Google Analytics Alternatives (`docs/content/blog/best-google-analytics-alternatives.mdx`)


title: "9 Best Google Analytics Alternatives in 2026 (Free & Paid)" keyword: "google analytics alternatives" date: 2026-07-19 updated: 2026-09-28 tags: ["analytics", "privacy", "comparisons"] author: Rybbit Team

Google Analytics is still the default, and still the tool people most want to leave. Since the forced GA4 migration, the complaints have been remarkably consistent: the interface buries simple questions under 150+ reports, busy properties get sampled data, cookie-based tracking forces consent banners that silently drop a third or more of your real traffic, and several EU data-protection authorities have ruled that Google Analytics data transfers violated GDPR.

The good news is that the alternatives in 2026 aren't compromises anymore. Several of them ship features GA4 has never had (session replay, error tracking, uncomplicated funnels) while staying cookie-free and consent-banner-free.

Full disclosure up front: we build Rybbit, the first tool on this list. We've kept every claim about the other tools factual (pricing and limits below come from each vendor's own published pages as of July 2026), and we'll tell you honestly when another tool is the better fit.

Quick comparison

Tool Starting price Free tier Open source / self-host Best for
Rybbit $19/mo (100k events) ❌ (7-day trial) ✅ Free Teams that want GA4-level depth without the complexity
Plausible $9/mo (10k pageviews) 30-day trial ✅ Community Edition Simple, lightweight traffic stats
Matomo €29/mo cloud (50k hits) Free on-premise ✅ Free Feature parity with old Universal Analytics
Fathom Slider pricing ($45/mo at 500k) 7-day trial ❌ Set-and-forget simple stats
Umami $20/mo (1M events) ✅ Free Hobby plan ✅ Free (MIT) Developers and hobby projects
PostHog Usage-based after free tier ✅ 1M events/mo free Partially Product teams that want the full toolkit
Simple Analytics $20/mo ✅ For hobby sites ❌ EU-hosted simplicity
Pirsch $6/mo (10k pageviews) 30-day trial Enterprise only Budget-friendly privacy analytics
Cloudflare Free ✅ Entirely free ❌ A free rough signal, not a GA replacement

Why people are leaving GA4

Before the list, it's worth being precise about what you're escaping, because it determines which alternative fits:

  • Consent loss. GA4 needs cookies, so GDPR/ePrivacy requires a consent banner in most jurisdictions. A large share of visitors decline. Teams that switch to cookieless analytics routinely find their real traffic is 30–60% higher than GA4 reported.
  • Sampling and retention. Complex reports on busy properties get sampled, and standard GA4 properties keep event-level data for at most 14 months. Unsampled exports live in GA360, which starts around $50,000/year.
  • Complexity. GA4 is an event warehouse with a reporting UI, not an analytics product. Most teams need a dashboard, not a data modeling project.
  • Legal risk. Data-protection authorities in Austria, France, and Italy all found GA's EU–US transfers unlawful in the Schrems II era. The regulatory direction hasn't reversed.

If you searched for a GA4 alternative specifically because of one of these, jump to the tool that fixes it: consent loss and legal risk → any cookieless tool below; sampling → Rybbit or Matomo; complexity → Plausible, Fathom, or Pirsch.

1. Rybbit

Best for: teams that want GA4-level depth (funnels, session replay, error tracking) without GA4's complexity or cookie banners.

Rybbit is an open-source, cookie-free analytics platform that deliberately sits between the "simple stats" tools and the heavyweight product-analytics suites. The dashboard reads like Plausible (one page, instantly legible), but underneath it ships the features that usually force teams back to Google: funnels, user journeys, session replay, error tracking, Web Vitals, user profiles, and custom events with attributes.

Because it's cookie-free with daily-rotating visitor hashes, you don't need a consent banner, and you see every visitor, including the ones GA4's banner was hiding from you. There's no sampling at any traffic level. Independent reviewers have come to the same conclusion: Travis of travis.media called Rybbit the best Google Analytics alternative after testing it against the field.

Pricing starts at $19/month for 100k events with a 7-day free trial; session replay and unlimited sites come with Pro, from $39/month. It's also fully open source and free to self-host with Docker. The main limitation is that there's no historical import from GA4 (Plausible, for one, offers a GA importer); Rybbit's importers cover Plausible, Umami, and Simple Analytics. Rybbit is also younger than Matomo or Plausible, though it ships things Plausible lacks, such as session replay and error tracking.

2. Plausible

Best for: simple, lightweight traffic stats with the longest track record among privacy tools.

Plausible is the tool that made privacy-first analytics mainstream: a single-page dashboard, a script it advertises as ~54× smaller than GA's, and cookieless tracking that needs no consent banner. It's open source, EU-hosted, and independent.

The trade-off is depth. Plausible intentionally stops at traffic analytics: goals and simple funnels exist, but there's no session replay, no error tracking, no user-level view. If your questions are "where do visitors come from and what do they visit," it answers beautifully; if you ask "why did signups drop," you'll need another tool alongside it.

Starter is $9/month for 10k pageviews on one site; Growth at $14/month and Business at $19/month add sites and team members. The trial runs 30 days with no card required, annual billing gets you two months free, and self-hosters have a free Community Edition. Two things to watch: pageview-based pricing climbs with traffic, and the feature set is deliberately minimal.

See our detailed Rybbit vs Plausible comparison.

3. Matomo

Best for: teams that want the closest thing to old Universal Analytics, or a mature free self-hosted option.

Matomo (formerly Piwik) is the veteran of this list, nearly two decades old, with the largest feature surface of any GA alternative: heatmaps, A/B testing, tag manager, and hundreds of plugins. Its free on-premise Community Edition has no hit limits, which has made it the default for public-sector and compliance-heavy deployments.

The honest downside is age: the interface feels like the era it came from, and the full feature set on-premise means assembling paid plugin bundles (Team bundles from €275). Cloud pricing is hit-based and EU-hosted (Frankfurt) with no data sampling.

Cloud starts at €29/month for 50k hits with a no-card free trial, while the on-premise Community Edition is free forever, and it's the most battle-tested self-hosted analytics there is. Weigh that against the dated UX, plugin-based pricing complexity, and a heavier footprint to run than newer tools.

See Rybbit vs Matomo.

For a head-to-head with GA4, see Matomo vs Google Analytics.

4. Fathom Analytics

Best for: set-and-forget simple stats from a long-running independent company.

Fathom was one of the first paid privacy-analytics products, and it stays deliberately simple: one dashboard, cookieless tracking, forever data retention, and every plan includes at least 50 sites. It's a polished, stable product from a small company that's been profitable for years.

Pricing is a slider by pageviews: $45/month at 500k pageviews (the entry 100k tier is priced lower on their slider), with a 7-day trial and two months free on annual billing. There's no free tier (their FAQ is upfront about that) and no self-hosting. Simple by design also means no funnels on the level of product tools and no replay.

See Rybbit vs Fathom.

5. Umami

Best for: developers and hobby projects that want free, minimal, MIT-licensed analytics.

Umami is a lightweight open-source analytics tool that developers love: trivial to self-host, MIT-licensed, and its cloud has a genuinely free Hobby plan. The product covers pageviews, referrers, custom events, and basic reports, and stops there.

The Hobby cloud plan is free forever; Pro is $20/month for 1M events, with a 14-day trial on paid plans. Self-hosting is free and among the easiest to run (a small Node app plus a database). Keep expectations in check, though: reporting depth is minimal, the team behind it is small, and most of its adoption is the free tier.

See Rybbit vs Umami.

6. PostHog

Best for: product engineering teams that want analytics, feature flags, A/B testing, and a data warehouse in one platform.

PostHog comes at this list from the other direction: it's a product-analytics platform that happens to include a web analytics dashboard. If you want feature flags, experiments, SQL access to raw events, and session replay in a single vendor, it's the strongest option, and its free tier is huge: 1M events every month, no card required.

The flip side: PostHog is a lot. The pricing is per-product and usage-based (from $0.00005/event after the free tier), which is cheap at small scale and requires spreadsheet attention at large scale, and the interface has GA4-ish complexity because it's solving a bigger problem than web analytics.

You get 1M events/month free, then usage-based billing, with a card only needed past the free tier. A hobby self-deploy exists, but the real product is cloud. Both the complexity and the cookie/consent posture depend on how you configure it, and web analytics is a side dish, not the meal.

See Rybbit vs PostHog.

7. Simple Analytics

Best for: EU-based companies that want the most privacy-conservative option.

Amsterdam-based Simple Analytics is exactly what the name says, with a notable 2026 twist: it now positions itself as a complement that fixes the traffic GA4 misses, arguing GA can under-report by up to 60%. It's EU-hosted end to end, has a free tier for hobby sites, and an AI chat interface over your stats.

The hobby tier is free (1 user, 5 websites, 1 month of history); paid plans start at $20/month with usage-based sliders, and the 14-day trial needs no card. Self-hosting is not offered. Watch the per-user pricing if you have a team, and note the short history on the free tier. Like Plausible and Fathom, it's simple by design.

See Rybbit vs Simple Analytics.

8. Pirsch

Best for: the lowest-priced paid entry point in privacy analytics.

Pirsch is a German-built, cookieless analytics tool with developer-friendly touches (server-side tracking via SDKs is a first-class feature) and aggressive pricing: $6/month covers 10k pageviews across up to 50 websites with unlimited team members and unlimited retention. The $12/month Plus tier adds funnels and A/B testing. It also ships importers for GA, Plausible, and Fathom.

Plans start at $6/month with a 30-day no-card trial. Self-hosting is gated to the paid Enterprise tier; the code isn't free OSS. And the feature set is smaller than Rybbit's or PostHog's, with no session replay or error tracking.

9. Cloudflare Web Analytics

Best for: a free rough signal when you can't justify any budget, not a GA replacement.

Cloudflare's free web analytics is cookieless and takes one minute to enable if you're already behind Cloudflare. But know what you're getting: per Cloudflare's own developer docs, dashboard stats are based on a ~10% sample of page-load events and retention is limited to 30 days. There are no custom events, goals, or funnels.

The price is free. The limitations are the point: sampled reporting, ~30-day retention, and minimal features make it fine as a sanity check and unusable as your only analytics.

Which Google Analytics alternative should you pick?

  • Depth without the pain (funnels, replay, errors, and a dashboard anyone can read): Rybbit. That's the exact gap we built it for.
  • The simplest possible stats page: Plausible, Fathom, or Pirsch (cheapest), in roughly that order of polish.
  • Maximum features, and you don't mind the learning curve: PostHog (modern, product-focused) or Matomo (mature, UA-like).
  • Free forever: Umami's Hobby cloud tier, or self-host Rybbit, Umami, Plausible CE, or Matomo, all genuinely free to run yourself.
  • Zero budget and zero setup: Cloudflare, eyes open about sampling.

FAQ

Do I still need a cookie banner with these tools?
Not for the cookieless ones (Rybbit, Plausible, Fathom, Umami, Simple Analytics, Pirsch, Cloudflare) when used as designed: they don't set cookies or track individuals across sites. GA4 and full-configuration PostHog generally do require consent in the EU.

Can I import my Google Analytics history?
Effectively no. Google only exports GA4 data to BigQuery, and no alternative can rebuild its reports from that. The standard move is to run your new tool in parallel with GA4 for a few weeks, then archive the GA4 property. (Rybbit does import data from Plausible, Umami, and Simple Analytics if you're switching between alternatives.)

Is Google Analytics illegal in Europe?
It's not banned, but DPAs in Austria, France, and Italy ruled its EU–US data transfers unlawful under GDPR, and using it compliantly requires consent banners plus careful configuration. Cookieless alternatives remove most of that burden.

What's the best free Google Analytics alternative?
Self-hosting an open-source tool (Rybbit, Umami, Plausible CE, Matomo) gives you the most for free if you can run Docker. If you want free cloud hosting, Umami's Hobby plan and PostHog's 1M-events tier are the real ones; Cloudflare is free but sampled.


Start a free Rybbit trial or explore the live demo to see what GA4 has been hiding. Setup is one script tag and takes about five minutes.


Content Blog Best Session Replay Tools (`docs/content/blog/best-session-replay-tools.mdx`)


title: "10 Best Session Replay Tools in 2026 (Free & Open Source)" keyword: "session replay tools" date: 2026-09-13 updated: 2026-09-27 tags: ["session replay", "comparisons", "tools"] author: Rybbit Team

Session replay tools record what a visitor does on your site (clicks, scrolls, page changes, masked typing) and let you watch it back like a video. This guide compares ten of them on price, limits, privacy defaults, and whether you can run them on your own servers.

Disclosure up front: we build Rybbit, one of the tools on this list. Every claim about the other nine comes from that vendor's own pricing or documentation pages, linked inline and checked in September 2026. Where a vendor doesn't publish a number, we say so rather than guess.

Quick comparison

Tool Best for Free tier Starting price (Sept 2026) Self-host Open source
Rybbit Replay inside privacy-first analytics No (7-day trial) Pro from $39/mo (not on Standard) Yes, free Yes, AGPL-3.0
Microsoft Clarity Free, unlimited recordings Yes, unlimited $0 No No
Hotjar / Contentsquare Heatmaps, replay and surveys Yes, 10k replays/mo Growth from $49/mo No No
FullStory Enterprise experience analytics Yes, 30k sessions/mo Quote only No No
LogRocket Front-end debugging Yes, 1k sessions/mo Core from $83/mo (10k sessions) Enterprise only No
PostHog Product analytics suite Yes, 5k recordings/mo $0.005 per recording after Yes, with caveats Yes, MIT core
OpenReplay Self-hosted replay for developers Self-hosted only Cloud from $199/mo Yes, free Yes, AGPL-3.0 core
Lucky Orange Small-business CRO bundle No (7-day trial) $39/mo (3,500 sessions) No No
Mouseflow Friction and form analytics Yes, 500 sessions/mo $25/mo (5,000 sessions) No No
Sentry Session Replay Replay attached to errors Yes, 50 replays/mo Team $26/mo plus usage Yes Fair Source (FSL)

How to choose a session replay tool

Privacy controls. What is masked before data leaves the browser, and is that the default? Sentry masks all text, inputs and media unless you loosen it. Clarity masks numbers, emails and every input box. PostHog and Rybbit mask input values by default but show page text unless you tag it. LogRocket only guarantees that passwords are never recorded.

Sampling and limits. Vendors meter replay per session (Mouseflow, Lucky Orange, LogRocket), per recording (PostHog, Sentry), or with a replay cap separate from the session or event quota (Contentsquare, and Rybbit, whose Pro plan includes replays equal to 10% of its event allowance). Retention runs from 30 days (Clarity, Rybbit cloud, Sentry's free plan) to 12 months (FullStory's free plan, Mouseflow Premium).

Bundled or standalone. Replay is most useful next to the data that sent you there: a funnel drop-off, a JavaScript error, a rage-click. Rybbit, PostHog, Sentry and LogRocket bundle replay with analytics or error tracking; Clarity, Hotjar, Mouseflow and Lucky Orange pair it with heatmaps.

Self-hosting. If recordings can't leave your infrastructure, the shortlist is Rybbit, OpenReplay, PostHog (with caveats) and Sentry. Everything else here is cloud only.

Cost at 100k sessions a month. Starting prices hide the curve. This is what each tool's published pricing works out to at roughly 100,000 sessions or recordings a month, computed from the pages linked in each section:

Tool Approximate monthly cost at 100k sessions
Rybbit $39 to $99 on Pro (billed by events: 100k to 500k)
Microsoft Clarity $0
Hotjar / Contentsquare $409 on Growth, but replays are capped at 20k
FullStory Quote only (the free plan stops at 30k sessions)
LogRocket Not published (25k sessions starts at $176)
PostHog About $272 (5k free, then tiered per recording)
OpenReplay $199 Dedicated, no recording limits
Lucky Orange $839 billed annually (Scale, the 300k tier)
Mouseflow $319 (Premium)
Sentry About $383 (Team plan plus pay-as-you-go replays)

Rybbit

Rybbit is open-source, privacy-first web analytics with session replay built into the same dashboard as funnels, user journeys, error tracking, Web Vitals and user profiles. Replay records DOM mutations rather than video. You filter recordings by minimum duration, browser, device, country, referrer, channel or user ID, then press play; each replay carries a breadcrumb timeline of pages, clicks and custom events.

Replay is opt-in per site (feature guide), all input values are masked by default, and the script attributes let you block elements entirely, ignore interactions on specific fields, or mask text by selector. The analytics side is cookieless, so there is no consent banner to lose visitors behind.

The honest limits: on Rybbit Cloud, replay is a Pro-plan feature. Standard (from $19/month) doesn't include it; Pro starts at $39/month for 100k events, or $26/month billed annually, per the pricing page. Plans start with a 7-day trial (a card is collected at signup and not charged until the trial ends). Cloud replays are kept for 30 days. There are no heatmaps, and replay is web only; the React Native SDK tracks events but doesn't record screens. If you self-host, everything including replay is free under AGPL-3.0 and you set your own retention.

  • Pros: replay, analytics, errors and funnels in one tool; inputs masked by default; replay quota grows with the plan (10,000 a month at 100k events); free self-hosting with replay included
  • Cons: replay needs the Pro plan on cloud; 30-day cloud retention; no heatmaps; no native mobile replay
  • Pricing (as of September 2026): Standard from $19/month, Pro (with replay) from $39/month at 100k events; self-hosted free. Source: rybbit.com/pricing

Microsoft Clarity

Clarity is what people mean by "free session replay". Microsoft's FAQ says: "Clarity is a free service forever. You never encounter traffic limits or are compelled to upgrade to a paid version", with no sampling and no credit card. Recordings, heatmaps and ML insights are included, and there are mobile SDKs for Android, iOS, React Native, Flutter and Cordova/Ionic.

Recordings are kept for 30 days (favorites and a random sample stay up to 9 months). Data lives with Microsoft, with no self-hosted option. And the same FAQ notes that for visitors in the EEA, UK and Switzerland, "valid consent is required" to use recordings, and without it Clarity limits data collection in those regions.

Masking defaults are sensible: Balanced mode masks numbers and email addresses, input boxes and drop-downs are masked in every mode, and Strict mode masks everything.

  • Pros: free with no traffic limits; no sampling; heatmaps and native mobile SDKs included
  • Cons: 30-day recording retention; consent required for EEA/UK/Swiss visitors; cloud only, hosted by Microsoft
  • Pricing (as of September 2026): $0, no paid tiers. Source: Clarity FAQ

Hotjar (Contentsquare)

Hotjar no longer exists as a separate product. Contentsquare's Hotjar page says the two platforms "have merged into a single, more powerful experience intelligence platform", and hotjar.com/pricing now redirects to contentsquare.com/pricing. New signups get Contentsquare's Free, Growth, Pro and Enterprise plans.

Free gives 200,000 monthly sessions, unlimited heatmaps, basic funnels and error monitoring, on 1 project. The catch is that replay is a separate, fixed allowance of 10,000 captures a month, which the pricing page says "can't be increased". Growth starts at $49/month for 7,000 sessions and 7,000 replays and reaches $409/month at 100,000 sessions, but the replay cap tops out at 20,000 on every Growth tier. Growth keeps session data for 13 months and replays for 2 months; Contentsquare's free-plan post lists 1 month of retention on Free. Annual billing takes 20% off.

Privacy defaults carry over from Hotjar: keystrokes are suppressed by default and replaced with asterisks, and you add data-hj-allow to fields you want captured, per the Hotjar help center. Contentsquare describes Experience Analytics as covering "sites and mobile apps". There is no self-hosted edition.

  • Pros: generous free session quota; heatmaps, surveys and replay together; 15-day Growth trial without a card
  • Cons: replay allowance is fixed and small relative to sessions; 2-month replay retention on Growth; cloud only
  • Pricing (as of September 2026): Free (200k sessions, 10k replays); Growth from $49/month. Source: contentsquare.com/pricing

For heatmaps and surveys as well as replay, see our Hotjar alternatives guide.

FullStory

FullStory is the enterprise option: replay plus product analytics, funnels and retroactive data history. The paid tiers (Business, Advanced, Enterprise) have no published prices; each reads "Request pricing & demo" on the pricing page. Mobile analytics is an add-on on paid plans.

What is published is the free plan: FullstoryFree gives "30,000 sessions per month" with "12 months of data retention" for up to 10 users, no credit card required.

Privacy is chosen at setup from three modes: Full Capture, Form Privacy, and Private by Default, under which "no text is captured or sent outside the user's browser unless it is explicitly allowlisted as safe to capture".

  • Pros: 30k free sessions with 12-month retention; strong privacy mode; mature enterprise analytics
  • Cons: no public pricing above the free plan; cloud only; mobile is an add-on
  • Pricing (as of September 2026): Free (30k sessions/month); paid plans by quote. Source: fullstory.com/pricing

For more options by use case, see our FullStory alternatives guide.

LogRocket

LogRocket is replay for engineers. Recordings include console logs, network requests and exceptions, and the mobile SDKs cover Android, iOS, React Native, Flutter, Ionic and Expo. Analytics, error and log events are unlimited on every plan; you pay for sessions captured.

The pricing page calculator starts with a Free plan for 1,000 sessions a month, then Core at $83/month for 10,000 sessions and $176/month for 25,000; paid features also come with a 14-day trial. A Conditional Recording option captures roughly a quarter of sessions, and Pro features are "included free above 100K sessions/mo". Self-hosting is an Enterprise option for regulated industries.

On privacy, LogRocket's documentation states that password fields are never recorded; masking anything else uses a data-private attribute or the SDK's sanitizers, so the safe default is your responsibility.

  • Pros: deep debugging context per session; broad mobile coverage; unlimited non-session events
  • Cons: the Free plan stops at 1k sessions, and 25k sessions costs $176/month; masking beyond passwords is opt-in; self-host is Enterprise only
  • Pricing (as of September 2026): Free for 1k sessions/month; Core from $83/month for 10k sessions; 14-day trial. Source: logrocket.com/pricing

PostHog

PostHog bundles replay with product analytics, feature flags, experiments and surveys on usage-based pricing. The replay pricing page gives 5,000 web recordings and 2,500 mobile recordings free every month, no card required, then charges $0.0050 per web recording from 5k to 15k, falling to $0.0015 above 500k. Mobile recordings cost $0.0100 each on the first paid tier.

Retention follows your plan: up to 30 days free and up to 90 days on pay-as-you-go. Inputs are masked by default; page text isn't, and you mask it with maskTextSelector or maskTextFn.

The core is MIT-licensed with a one-command "hobby" Docker Compose deployment, but PostHog's self-host page calls self-hosted deployments "officially unsupported" and steers anyone expecting more than roughly 300k events or 1k recordings a month to PostHog Cloud. We compare the two products further in Rybbit vs PostHog.

  • Pros: real free tier renewed monthly; replay next to funnels, flags and experiments; transparent volume pricing
  • Cons: breadth brings complexity; self-hosting is unsupported and not meant for scale; page text unmasked by default
  • Pricing (as of September 2026): 5,000 web recordings free per month, then from $0.005 per recording. Source: posthog.com/docs/session-replay/pricing

OpenReplay

OpenReplay is the self-hosting purist's replay tool. The core is AGPL-3.0 (an ee/ directory carries a separate license, per the repository), and the pricing page says "Open-Source is free — you host it on your own infrastructure". It is aimed squarely at developers, with iOS and Android SDKs.

The cloud option is a single "Dedicated" plan: "Starting from $199/mo", billed hourly at $0.276, a dedicated VM in its own VPC "with no limits on users, recordings, or retention", with a 7-day trial and no card. Enterprise is custom.

Self-hosting asks for at least 2 vCPUs, 8 GB of RAM and 50 GB of storage per the Docker guide. Privacy defaults are good: inputs are obscured by default, emails are obscured in inputs and text, and the docs note that obscured data "will never leave the user's browser". Numbers in text are not obscured unless you turn that on.

  • Pros: free open-source self-hosting with no recording limits; developer-grade context; strong default sanitization
  • Cons: cloud starts at $199/month; needs an 8 GB server at minimum; no shared cloud tier for small sites
  • Pricing (as of September 2026): self-hosted free; Dedicated cloud from $199/month. Source: openreplay.com/pricing.html

Lucky Orange

Lucky Orange is a conversion-optimization bundle for small businesses: recordings, heatmaps, funnels, surveys and an AI layer it calls Discovery, with unlimited team members on every plan. Pricing is by monthly sessions: Build is $39/month, or $32/month paid annually, for 3,500 sessions; Grow is $72/month paid annually for 10,000; Expand is $199/month paid annually ($249 monthly) for 50,000; Scale is $839/month paid annually ($1,049 monthly) for 300,000; Enterprise starts at 700,000. Extra sites cost $5 each.

There is no free plan, only a 7-day trial with every feature. Base storage "starts at 60 days" with 90, 180 and 365-day upgrades. It is cloud only, and the pricing page doesn't mention native app recording.

  • Pros: affordable entry price with everything included; unlimited seats; surveys in the same tool
  • Cons: no free plan; 60-day base retention; large jump from the 50k to the 300k tier; cloud only
  • Pricing (as of September 2026): from $39/month ($32 billed annually) for 3,500 sessions. Source: luckyorange.com/pricing

Mouseflow

Mouseflow offers replay and heatmaps, plus friction scoring, form analytics and journey analysis on higher tiers. The pricing page is public and simple. Free is $0 for 500 sessions a month, 1 month of retention, 1 website. Essential is $25/month for 5,000 sessions and 3 months of retention. Advanced is $109/month for 25,000 sessions and 6 months. Premium is $319/month for 100,000 sessions and 12 months. Enterprise is custom. The page advertises up to 35% off for annual billing and a 14-day money-back guarantee, and all plans include unlimited users.

Privacy defaults are conservative; Mouseflow's help center says it does not record keystrokes by default, and you whitelist individual fields from the UI. It is cloud only, and the pricing page doesn't mention native app SDKs.

  • Pros: clear public pricing; 12-month retention on Premium; keystrokes excluded by default
  • Cons: the 500-session free plan is a demo, not a tier; cloud only; web only
  • Pricing (as of September 2026): Free (500 sessions); Essential $25/month for 5,000 sessions. Source: mouseflow.com/pricing

Sentry Session Replay

Sentry is error monitoring first, and its replay exists to show the seconds before an exception. The default setup in the JavaScript docs records 10% of sessions and 100% of sessions with an error, and replay is available for web plus Android, iOS, React Native and Flutter.

The free Developer plan (one user) and the paid Team ($26/month billed annually) and Business ($80/month billed annually) plans each include 50 replays a month on the pricing page. Beyond that you pay as you go; the pricing docs list Team replays at $0.00375 each up to 5k and $0.003563 each from 5k to 100k. Replays are retained for 30 days on Developer and 90 days on paid plans.

Defaults are the strictest here: maskAllText, maskAllInputs and blockAllMedia are all true out of the box, per the privacy docs. Self-hosting exists under the Functional Source License, needs at least 4 CPU cores and 16 GB of RAM per the self-hosted docs, and has included replay since release 23.3.1 according to the project's issue tracker.

  • Pros: replay linked to the exact error; mask-everything defaults; sampling built in; self-hostable
  • Cons: 50 included replays; per-replay costs add up; built for debugging errors rather than UX research
  • Pricing (as of September 2026): Developer free (50 replays); Team $26/month billed annually plus $0.00375 per extra replay. Source: sentry.io/pricing

Which one should you pick

  • You want replay and web analytics in one privacy-first tool, or need to self-host without a giant server: Rybbit.
  • You have no budget and can live with 30-day retention and a consent banner in Europe: Microsoft Clarity.
  • You're a marketer who wants heatmaps, surveys and replay with a big free session quota: Hotjar / Contentsquare.
  • Your engineers want console logs and network calls next to every recording: LogRocket, or Sentry Session Replay if you already use Sentry and only need replays for errored sessions.
  • You want the widest product-analytics suite with a real free tier, and cloud hosting is fine: PostHog.
  • Recordings must never leave your infrastructure and you have a spare 8 GB box: OpenReplay.
  • You need 12 months of replay history at a published price: Mouseflow Premium, or FullStory's free plan if 30k sessions is enough.

One tool to skip: Smartlook. Its homepage now carries a Cisco end-of-life notice, with no new subscriptions since 31 May 2026, no renewals after 31 August 2026, and the platform decommissioned on 30 September 2027. If you already use Amplitude, its pricing page lists 10,000 replay sessions a month on the free plan with 1 month of retention.

For the analytics side of the decision, see our guides to the best web analytics tools and the best Google Analytics alternatives.

FAQ

Is there a free session replay tool?

Yes, several. Microsoft Clarity is free with no traffic limits. Contentsquare's Free plan includes 10,000 replay captures a month, PostHog gives 5,000 web recordings a month, LogRocket's Free plan covers 1,000 sessions a month, and FullStory's free plan covers 30,000 sessions with 12-month retention. Rybbit and OpenReplay are free if you self-host; Rybbit Cloud has a 7-day trial rather than a free tier.

Is session replay software the same as session recording software?

Yes. Vendors use "session recording", "session replay" and "visitor recordings" for the same thing: capturing DOM changes and interactions in the browser and reconstructing them for playback.

Is session replay GDPR compliant?

It can be, but your configuration makes it so, not the tool. Mask inputs and personal text before data leaves the browser, record only with a lawful basis, honor consent where you rely on it, keep retention short, and sign a processing agreement with the vendor. Self-hosting (Rybbit, OpenReplay, PostHog, Sentry) removes the vendor-transfer question entirely.

Which session replay tools are open source?

Rybbit (AGPL-3.0), OpenReplay (AGPL-3.0 core with a separately licensed ee/ directory) and PostHog (MIT core) are open source and self-hostable. Sentry is source-available under the Functional Source License, which is not an OSI-approved open-source license.

Does session replay slow down a website?

Modern tools record DOM mutations, not screenshots or video, and load their capture script asynchronously, so the effect on page load is small. Bandwidth on long, interaction-heavy sessions is the real cost, which is why tools offer sampling: Sentry defaults to 10% of sessions, and Rybbit's script attributes let you throttle mouse-move and scroll events and slim the recorded DOM.


Content Blog Best Web Analytics Tools (`docs/content/blog/best-web-analytics-tools.mdx`)


title: "10 Best Web Analytics Tools in 2026, Compared by Use Case" keyword: "best web analytics tools" date: 2026-07-19 updated: 2026-09-28 tags: ["analytics", "tools", "comparisons"] author: Rybbit Team

Every list of web analytics tools pretends there's one "best" answer. There isn't. A solo blogger checking referrers, a SaaS team debugging a signup funnel, and an EU retailer with a nervous legal department need genuinely different products.

So this guide is organized around the question that actually decides the purchase: what do you need your analytics to do? We build one of these tools (Rybbit), so we'll flag that bias where it matters. Every price and limit below comes from the vendor's own published pages, checked July 2026.

The short version

If you need... Use Price anchor
Depth + simplicity in one tool Rybbit $19/mo, 100k events
The simplest possible dashboard Plausible, Fathom $9/mo · slider ($45 at 500k)
The cheapest paid plan Pirsch $6/mo, 10k pageviews
Full product-analytics suite PostHog 1M events/mo free, then usage-based
Free ads-ecosystem default Google Analytics 4 Free (GA360 ≈ $50k/yr)
Free forever, cloud-hosted Umami $0 Hobby plan
Maximum features, self-hosted, free Matomo Free on-premise; cloud €29/mo
Strictest EU privacy posture Simple Analytics Free hobby tier; $20/mo paid
Zero budget, zero setup Cloudflare Free (sampled)

How to choose a web analytics tool

Four questions sort the whole market:

  1. Traffic stats or behavior? Counting visits, referrers, and top pages is the easy half. Understanding behavior (funnels, session replay, user journeys, errors) is where tools diverge sharply. Simple trackers stop at the first half.
  2. Cookies or cookieless? Cookie-based tools (GA4 chief among them) require consent banners in most of the world, and every declined banner is a visitor deleted from your data. Cookieless tools see everyone and need no banner.
  3. Cloud or self-hosted? If analytics data legally can't leave your infrastructure, your shortlist is the open-source tools: Rybbit, Matomo, Plausible CE, Umami.
  4. Who reads the dashboard? If the answer includes non-analysts, weight simplicity heavily. The most common analytics failure is a tool nobody opens, not missing data.

Rybbit

The all-rounder: product-analytics depth on a dashboard anyone can read.

Most tools on this list force a choice between simple-but-shallow and deep-but-painful. Rybbit exists to refuse that choice: a single-page, real-time dashboard in the Plausible tradition, with funnels, session replay, user journeys, error tracking, Web Vitals, and user profiles built in. That feature set otherwise requires a PostHog-class suite. Tracking is cookieless (no consent banner, no lost visitors, no sampling), the whole platform is open source, and there's a live demo with real data you can click around right now.

Pricing: from $19/month for 100k events on Standard; session replay and unlimited sites come with Pro, from $39/month. 7-day trial. Self-hosting is free with Docker.
Skip it if: you need years of GA-style ad-attribution workflows, or feature flags and experiments in the same tool (that's PostHog territory).

Google Analytics 4

The default, and the tool everyone on this page is measured against.

GA4 is free, integrates natively with Google Ads, and exports raw events to BigQuery. For paid-acquisition teams inside Google's ecosystem, it remains genuinely hard to replace. It's on this list on merit.

But treat its price with suspicion: "free" is funded by your visitors' data feeding Google's ad network. The operational costs are consent banners (and the 30–60% of traffic they cost you), report sampling on busy properties, 14-month standard retention, and an interface that has made "how do I just see my top pages?" a support-forum genre. Unsampled data means GA360, at roughly $50,000/year. If those are your pain points, our detailed GA4 comparison breaks down the switch, or see the full guide to Google Analytics alternatives.

Plausible

The standard-bearer for simple, privacy-first analytics.

One page of stats, a tiny script (their claim: ~54× smaller than GA's), cookieless by design, EU-owned and EU-hosted. Plausible proved a paid GA alternative could be a real business, and a decade of polish shows. Its scope is deliberately narrow (traffic analytics, goals, basic funnels), which is either its best feature or its dealbreaker depending on your needs. From $9/month (10k pageviews), 30-day no-card trial, free self-hosted Community Edition. Rybbit vs Plausible →

Matomo

The open-source veteran with the largest feature list.

Heatmaps, A/B tests, a tag manager, hundreds of plugins, and a free on-premise edition with unlimited hits: Matomo (ex-Piwik) is what "we replaced Google Analytics" meant for a decade, and it's still the compliance-department favorite (cloud data lives in Frankfurt, no sampling). The cost is a dated interface and pricing complexity on-premise, where advanced features come in paid plugin bundles from €275. Cloud from €29/month for 50k hits; no-card trial. Rybbit vs Matomo →

For a head-to-head with GA4, see Matomo vs Google Analytics.

PostHog

The product-analytics suite that swallowed web analytics.

PostHog bundles product analytics, session replay, feature flags, experiments, surveys, and a SQL-queryable event warehouse, with a web analytics dashboard on the side. For engineering-led product teams it's the most capable platform here, and the free tier is the market's most generous: 1M events every month, no card. Past that it's usage-based (from $0.00005/event), which needs monitoring at scale. The trade: GA4-class complexity, because it's solving a much bigger problem than "how's my traffic." Rybbit vs PostHog →

For the per-product rates and worked examples, see PostHog pricing, explained.

Fathom Analytics

Simple stats from one of the original privacy-analytics companies.

Cookieless, one clean dashboard, forever data retention, and at least 50 sites on every plan. Fathom's pitch is stability: a profitable independent company that's been doing exactly this since before it was a category. Pricing is a pageview slider ($45/month at 500k pageviews; smaller tiers cost less), 7-day trial, no free tier, no self-hosting. Rybbit vs Fathom →

Umami

Free, minimal, MIT-licensed: the developer's hobby-project default.

Umami is the easiest analytics tool to self-host (a small Node app), and its cloud Hobby plan is free forever, with Pro at $20/month for 1M events. Reporting is deliberately minimal (pageviews, referrers, events), and that's the point. If your need is "decent stats on my side projects for $0," this is the answer. Rybbit vs Umami →

Simple Analytics

The most privacy-conservative option, made and hosted in the EU.

Simple Analytics collects the least data of any paid tool here and wears it proudly. In 2026 it's also carved out a clever niche: positioning itself as the fix for GA4's consent-loss problem (their number: GA can miss up to 60% of traffic). Free for hobby sites (5 sites, 1 month history); paid from $20/month per user with usage sliders; 14-day no-card trial. No self-hosting. Rybbit vs Simple Analytics →

Pirsch

The lowest-cost paid entry in privacy analytics.

German-built, cookieless, with first-class server-side tracking SDKs and importers for GA, Plausible, and Fathom. $6/month buys 10k pageviews across up to 50 sites with unlimited team members and retention; $12/month adds funnels and A/B testing; 30-day no-card trial. Self-hosting exists but only behind the Enterprise tier. For tight budgets it's the best dollar-for-dollar deal on this list.

Cloudflare Web Analytics

Free and instant, as long as you understand what it isn't.

If your site is behind Cloudflare, this is one toggle. It's cookieless and costs nothing, but Cloudflare's own docs note the dashboard is built on a ~10% sample of page-load events with roughly 30-day retention, and there are no goals, events, or funnels. Use it as a free second opinion, not as your analytics.

What we'd actually do

  • Under 10k visits a month and a $0 budget: Umami's free cloud tier, or self-host if you enjoy it.
  • A business site where decisions ride on the data: Rybbit. You'll use the funnels and replay within the first week, and cookieless tracking means the numbers are actually complete.
  • A content site that just needs clean traffic stats: Plausible or Fathom. Pirsch if price wins.
  • An engineering-led product team: PostHog, or Rybbit if you want your PMs and marketers to actually open the dashboard.
  • A regulated or self-host-only environment: Rybbit or Matomo, both free to run on your own infrastructure.

FAQ

What's the difference between web analytics and product analytics?
Web analytics answers traffic questions (visitors, sources, pages); product analytics answers behavior questions (activation, retention, funnels) usually tied to identified users. GA4 and PostHog are product-analytics tools wearing web-analytics clothes; Plausible and Fathom are pure web analytics; Rybbit deliberately covers both sides for websites and apps.

Are free web analytics tools good enough?
Often, yes. Umami and PostHog's free tiers are real products, and self-hosting an open-source tool costs only your server. The free options to be wary of are the ones with invisible costs: GA4 (consent loss, complexity, your users' data) and Cloudflare (sampling, 30-day window).

Can I run two analytics tools at once?
Yes, and during any migration you should. Scripts from different vendors don't conflict, and a few weeks of parallel data is the only honest way to compare them. It's also the standard way to leave Google Analytics without losing your bearings.

Do cookieless analytics tools comply with GDPR?
The cookieless tools here (Rybbit, Plausible, Fathom, Umami, Simple Analytics, Pirsch) are built to run without consent banners: no cookies, no cross-site identifiers, no personal profiles. Self-hosting adds the strongest guarantee, since data never touches a third party at all.


Want the deep version of any matchup? Every comparison above links to a feature-by-feature breakdown, or start with Rybbit vs Google Analytics, the switch most teams are weighing.


Content Blog Bounce Rate (`docs/content/blog/bounce-rate.mdx`)


title: "What Is Bounce Rate? How to Calculate and Reduce It" keyword: "bounce rate" date: 2025-11-21 updated: 2026-09-28 tags: ['analytics', 'metrics'] author: Rybbit Team

Bounce rate measures the percentage of visitors who leave your site after viewing only one page. For anyone running a website, this number matters, it shows whether people find what they're looking for or bail immediately. Rybbit makes tracking bounce rate simple, and I'll walk you through everything you need to know.

Understanding Bounce Rate

A bounce happens when someone lands on your site and leaves without clicking anywhere else. That's it. The bounce rate is just the percentage of these single-page sessions compared to total sessions (the bounce rate calculator does the division for you). To understand bounce rate in the broader context of web analytics, it's one of the most fundamental metrics you'll track.

High bounce rates aren't always terrible. Someone might read your entire 2,000-word guide, get their answer, and close the tab feeling satisfied. But if users hit your homepage and immediately leave? That signals a real problem.

Accessing Your Bounce Rate Data

Once you've added the Rybbit tracking script (takes about five minutes), bounce rate data appears immediately in your dashboard.

Dashboard Access

Log into app.rybbit.io and select your website from the list.

Main Metrics View

Your bounce rate sits right on the main dashboard next to:

  • Unique Users
  • Sessions (see our sessions guide for deeper analysis)
  • Pageviews
  • Pages Per Session
  • Session Duration

The interface stays clean and simple. No digging through buried menus for your key numbers are front and center.

The Math Behind Bounce Rate

Rybbit calculates bounce rate with this formula:

Bounce Rate = (Single-Page Sessions ÷ Total Sessions) × 100

Real example: You had 1,000 sessions yesterday. 350 were single-page visits. Your bounce rate: 35%.

For more on session behavior and user engagement patterns, see our Sessions Tab Guide, Churn Rate vs Retention Rate article, and Website Traffic Drop Analysis.

Segmenting Your Data

Rybbit's filtering capabilities let you dig into the "why" behind your bounce rate numbers.

Traffic Source Comparison

Click "Referrer" or "UTM Source" to compare channels:

  • Organic Search: Your SEO pages converting?
  • Social Media: How do Twitter, LinkedIn, and Facebook visitors behave?
  • Direct Traffic: Performance from bookmarks and typed URLs
  • Paid Campaigns: Getting ROI from paid traffic?

Device Breakdown

Mobile users behave differently than desktop users. Filter by device type:

  • Desktop
  • Mobile

If mobile bounce rates run significantly higher, your mobile experience needs work.

Geographic Patterns

The Location Filter shows bounce rates by country, region, and city. Useful for spotting regional issues or informing localization choices.

Advanced Filtering Options

Rybbit offers 15+ dimensions for detailed segmentation:

  • Browser and Browser Version
  • Operating System
  • Screen Resolution
  • Language
  • UTM parameters (Campaign, Medium, Term, Content)

You can easily select the right filter at the top of the page of your main dashboard

Real-Time Monitoring

Rybbit shows bounce rate changes as they happen. This matters during:

  • Campaign launches
  • New content releases
  • A/B tests
  • Site updates

Historical Trend Analysis

Looking at bounce rate over time reveals patterns and shows whether your optimizations work.

Time Range Selection

Pick your date range from the top of your dashboard:

  • Today
  • Yesterday
  • Last 7 days
  • Last 30 days
  • Last 90 days
  • Custom date range

Pattern Recognition

The bounce rate graph shows you what's happening over time. Watch for:

  • Spikes: Something caused a sudden jump—what was it?
  • Declines: Did your recent changes help?
  • Weekly rhythms: Certain days consistently different?
  • Seasonal shifts: How does time of year affect behavior?

Setting Up a Funnel

  1. Navigate to Funnels
  2. Click "Create Funnel"
  3. Map your steps (Landing Page → Product Page → Cart → Checkout)
  4. Analyze drop-off rates

Your first step drop-off equals your bounce rate for that entry point. For a comprehensive guide on using funnels to measure conversions, see our sales funnel measurement guide.

Core Web Vitals Monitoring

Page performance impacts bounce rate. Rybbit automatically measures Core Web Vitals:

  • LCP (Largest Contentful Paint): How fast the main content loads
  • FID (First Input Delay): How quickly the page responds to interactions
  • CLS (Cumulative Layout Shift): Visual stability during loading

Poor scores here often match high bounce rates.

Actionable Ways to Reduce Bounce Rate

Now that you know how to track bounce rate, here's how to improve it:

1. Improve Load Speed

Check Web Vitals data, find slow pages, fix them.

2. Align Content with Expectations

Review referrer data, do landing pages deliver what traffic sources promise?

3. Optimize Mobile Experience

High mobile bounce rates mean your mobile site needs attention now. If you need to understand how different devices are performing, check out our e-commerce analytics guide which covers device-specific optimization.

4. Clarify Next Steps

Watch session replays to see if users understand where to go next. Session replays are invaluable for understanding user behavior patterns that metrics alone can't reveal.

5. Match Content to Headlines

Blog posts with high bounces often fail to deliver what titles promise.

6. Fix Technical Issues

Session replays expose broken elements, confusing navigation, error messages. If you're seeing sudden bounce rate spikes, consult our website traffic drop analysis guide to diagnose what went wrong.

7. Improve Audience Targeting

Use UTM tracking to measure campaign-level bounce rates and adjust targeting. Understanding how different traffic sources perform helps you optimize your acquisition strategy.

Comparing Rybbit to Alternatives

Switching from Google Analytics, Plausible, or Umami? Key differences:

  • Content-friendly: Long-form content doesn't get unfairly penalized
  • Complete data: No sampling—you see every session
  • Instant updates: Real-time data without processing delays
  • Privacy-first: No cookies required, no consent banners needed

Support Resources

Need help with bounce rate or other Rybbit features?

Final Thoughts

Bounce rate reveals a lot about user engagement. Rybbit makes the data accessible and actionable. Real-time tracking, session replays, detailed filters, and privacy-focused analytics give you everything needed to convert insights into improvements.

What works:

  1. Regular monitoring: Check bounce rate trends each week
  2. Deep segmentation: Examine sources, devices, and pages—not just overall numbers
  3. Session replay review: Watch real user behavior to understand the story
  4. Change testing: Implement improvements and measure results
  5. Privacy respect: Get accurate data without invasive tracking

Ready to start? Start a free trial or explore the live demo featuring a real site handling millions of events annually.


Additional Resources:


Content Blog Core Web Vitals (`docs/content/blog/core-web-vitals.mdx`)


title: "Core Web Vitals Explained: LCP, INP, CLS and How to Fix Them" keyword: "core web vitals" date: 2026-09-13 tags: ["web vitals", "performance", "seo"] author: Rybbit Team

Core Web Vitals are three metrics Google uses to judge how a page feels to real visitors: how quickly the main content shows up, how fast the page reacts when someone clicks or taps, and how much the layout jumps around while it loads. Google scores them from real Chrome visits at the 75th percentile, and a page passes when all three are in the "good" range.

The three metrics and the thresholds Google publishes on web.dev:

Metric What it measures Good Needs improvement Poor
Largest Contentful Paint (LCP) Loading: when the largest image, text block or video in the viewport has rendered 2.5 s or less 2.5 s to 4 s Over 4 s
Interaction to Next Paint (INP) Responsiveness: how long clicks, taps and key presses take to produce a visual update 200 ms or less 200 ms to 500 ms Over 500 ms
Cumulative Layout Shift (CLS) Visual stability: how much visible content moves unexpectedly 0.1 or less 0.1 to 0.25 Over 0.25

All three are evaluated at the 75th percentile of page loads, segmented by mobile and desktop: at least three out of four visits have to be good before the page counts as good.

INP is the newest of the three. It replaced First Input Delay (FID) on March 12, 2024, and FID was removed from Search Console the same day. Any guide that still talks about FID is out of date.

The three Core Web Vitals metrics, and how to fix each one

Largest Contentful Paint (LCP)

LCP is the time from the start of navigation until the largest visible element has rendered: an img element, an image inside an inline SVG, a video poster frame, an element with a CSS background image, or a block of text (web.dev). It is a proxy for "when did the page look loaded".

Common causes of a poor LCP:

  • Slow server response. Nothing else can start until the HTML arrives.
  • The LCP image is discovered late. It is a CSS background-image, injected by JavaScript, or lazy-loaded.
  • Render-blocking CSS and synchronous scripts in the <head>.
  • Oversized images in old formats, served at desktop size to phones.

Citing the 2024 Web Almanac, web.dev notes that 73% of mobile pages have an image as their LCP element, and 35% of those images had source URLs that were not discoverable in the initial HTML response.

Fixes that usually work, from web.dev's Optimize LCP guide:

  • Put the LCP image in the HTML as a plain img element with a src, and add fetchpriority="high". The guide is blunt: "Never lazy-load your LCP image."
  • If the hero must be a CSS background image, preload it with <link rel="preload">.
  • Cut time to first byte: a CDN in front of the HTML, a proper cache-control policy, no redirect chains.
  • Compress images, use modern formats, serve responsive sizes.
  • Keep synchronous scripts out of the <head>; web.dev calls it "almost never necessary". Use defer or async, and defer non-critical CSS.

Interaction to Next Paint (INP)

INP measures responsiveness. It observes every click, tap and key press during a visit and reports the slowest one: the time from the input until the next frame is painted. Hovering and scrolling do not count, and on pages with many interactions one outlier is ignored per 50 (web.dev).

Common causes:

  • Long tasks. Anything that holds the main thread for more than 50 milliseconds is a long task, and while it runs the browser blocks interactions (web.dev).
  • Too much JavaScript. Bundles, tag managers, chat widgets and A/B testing scripts all compete for one thread.
  • Large DOMs. Rendering work scales with DOM size.

Fixes, from web.dev's Optimize INP guide:

  • Break up long tasks. Yield to the main thread with scheduler.yield() or setTimeout() so input can be handled between chunks.
  • Ship less JavaScript: code-split, drop unused code, audit every third-party tag.
  • In handlers, paint first and defer the rest: requestAnimationFrame() followed by setTimeout().
  • Keep the DOM small and avoid reading layout right after writing styles.

Cumulative Layout Shift (CLS)

CLS measures how much visible content moves unexpectedly. Each shift is scored by how much of the viewport moved and how far, shifts that happen close together are grouped into a session window, and CLS is the largest window's total (web.dev). Shifts within 500 ms of a user input are excluded, so an accordion expanding on click does not count.

Common causes:

  • Images and videos without dimensions, so the browser cannot reserve space.
  • Ads, embeds and iframes that arrive late and push everything down.
  • Content injected above existing content: cookie banners, notification bars.
  • Web fonts that render larger or smaller than the fallback font.
  • Animations that use layout properties like top and left.

web.dev reports that 66% of pages have at least one unsized image.

Fixes, from web.dev's Optimize CLS guide:

  • Always set width and height attributes on img and video elements, or use CSS aspect-ratio.
  • Reserve space for ads and embeds with min-height, and do not collapse the slot if no ad is returned.
  • Do not insert content above the fold unless the user asked for it. Use an overlay for banners.
  • Preload critical fonts, use font-display: optional where the design allows, and use size-adjust so the fallback font takes the same space.
  • Animate with transform instead of layout properties.

Lab data vs field data

Lab data is collected in a controlled environment with predefined device and network settings: one device, one network, one location. Field data, also called real user monitoring (RUM), comes from the people actually visiting your site, and it is a distribution rather than a single number (web.dev).

Lighthouse and a DevTools trace are lab tools. The Chrome User Experience Report (CrUX) is Google's field dataset and feeds both PageSpeed Insights and Search Console; your own real-user monitoring is field data too.

Google's assessment is based on field data; lab data is for diagnosis. A page can pass in the lab and fail in the field because:

  • Viewports differ. A desktop test measures the hero image; on a phone the largest element may be a text block waiting on a web font.
  • Devices differ. A lab run is a cold load on fast hardware; real visits include mid-range Android phones on cellular connections far from your server.
  • The lab has no interactions. INP is missing entirely, and field CLS includes shifts from scrolling into lazy-loaded content.

There is also a coverage gap. CrUX only includes Chrome users who opted in to usage statistics reporting and history sync, on desktop and Android; Chrome on iOS, WebViews and other Chromium browsers do not contribute, and a page has to be publicly discoverable and "sufficiently popular" to appear at all (CrUX methodology).

Do Core Web Vitals affect rankings?

Yes, but by less than most performance vendors imply. Google's page experience documentation states: "Core Web Vitals are used by our ranking systems." It also says, "There is no single signal. Our core ranking systems look at a variety of signals that align with overall page experience," and: "Google Search always seeks to show the most relevant content, even if the page experience is sub-par."

Read that as: relevance comes first, and Core Web Vitals help at the margin between comparable pages. They matter more directly for people: a page that takes four seconds to show anything loses visitors first, and that shows up in your bounce rate. When you investigate a drop in organic traffic, rule out a Core Web Vitals regression, but it is rarely the whole story.

How to measure Core Web Vitals

PageSpeed Insights

Enter a URL and you get CrUX field data for the previous 28 days plus a fresh Lighthouse lab run (PSI docs). When a URL lacks field data of its own, PSI falls back to origin-level data and says so.

Rybbit's Core Web Vitals checker runs the same PageSpeed Insights test for any public URL, mobile or desktop, and puts the 75th-percentile field metrics next to the Lighthouse lab metrics and largest opportunities.

Search Console's Core Web Vitals report

The Core Web Vitals report groups your indexed URLs by status (Poor, Needs improvement, Good), metric and URL group, with separate mobile and desktop views. Two things trip people up: URL groups without enough LCP and CLS data are omitted, and only indexed URLs appear.

Chrome DevTools

The Performance panel shows local LCP and CLS as soon as you open it, INP once you interact, and the CrUX field data for the URL alongside them (DevTools docs). Record a trace with CPU and network throttling to find the long tasks behind a bad INP.

Real-user monitoring

Everything above is a snapshot or a 28-day aggregate of popular pages. Real-user monitoring gives you field numbers for every page, continuously; the web-vitals JavaScript library web.dev recommends is what Rybbit's tracking script uses.

Rybbit's Web Vitals report is real-user monitoring built into the analytics you already run. Enable Web Vitals in your site settings and the Performance tab starts collecting LCP, INP and CLS from your visitors, plus First Contentful Paint and Time to First Byte.

Each metric is graded against the same thresholds Google uses. A percentile selector switches between p50, p75, p90 and p99, each card shows the change from the previous period, and the breakdowns split every metric by page, device, browser, operating system and country. That is usually how the cause shows up: LCP is fine on desktop but poor on mobile, or fine everywhere except one region.

To make the business case, the page speed calculator turns current and target load times, monthly visitors, conversion rate and order value into an estimated revenue figure.

"Core Web Vitals assessment: Failed" and what to do first

PageSpeed Insights passes a page only when the 75th percentiles of LCP, INP and CLS are all good over the last 28 days of CrUX data; with too little INP data it passes on LCP and CLS alone (PSI docs). "Failed" means at least one metric's 75th percentile is in the needs improvement or poor range. What to do, in order:

  1. Check what the verdict covers. PSI says whether it is showing this URL or the whole origin; an origin-level failure is about the site, not the page.
  2. Find the failing metric and device. It is usually one metric, and usually mobile.
  3. Check the blast radius in Search Console. One failing URL group is one template; most groups failing is something shared, like hosting or a tag manager.
  4. Reproduce it in the lab. Lighthouse for LCP and CLS; a throttled DevTools trace for INP.
  5. Fix, deploy, then watch the field. CrUX is a trailing 28-day window, so PSI and Search Console reflect the fix slowly. Real-user monitoring shows it the same day.
  6. Validate in Search Console. Start Tracking opens a 28-day monitoring session; if the issue does not reappear, it is marked fixed.

Core Web Vitals on WordPress

WordPress sites fail for predictable reasons: a heavy theme, too many plugins, unoptimized images, and scripts that plugins inject into the <head>. Sliders, page builders and font loaders hurt LCP; ad and embed plugins that do not reserve space cause CLS; a stack of plugins each adding JavaScript is where poor INP comes from.

The fixes are the ones WordPress's own performance documentation recommends: a lightweight theme ("a fast lightweight theme will perform much more efficiently than a heavy graphic-laden inefficient one"), deactivating and deleting plugins you do not need, optimizing images and considering WebP, a caching plugin, a CDN, a current PHP version, and minified CSS and JavaScript.

WordPress core also helps with LCP. Since 6.3 it adds fetchpriority="high" to the image it determines is most likely the LCP image and omits loading="lazy" from the first few images on the page. That only covers images rendered through WordPress's own functions: a hero set as a CSS background image, or a slider that injects images with JavaScript, gets none of it, and a plugin that lazy-loads everything can undo it. Check the rendered HTML of your slowest template.

Search Console's URL groups usually map to templates, so fixing one template moves a whole group.

Monitor Core Web Vitals over time

A one-off test is a single load on a single device on a single day. Sites change constantly: a new plugin, a new ad partner, a redesigned hero, a tag pasted into the tag manager. Any of them can push p75 LCP from 2.3 to 3 seconds, and a Lighthouse score from March will not tell you.

CrUX is a trailing 28-day window, so a regression deployed on Monday takes weeks to fully show in PageSpeed Insights and Search Console, and never shows for pages below CrUX's popularity threshold.

Real-user monitoring closes that gap. Track p75 per page, compare the week after a deploy with the week before, and treat a metric crossing from good to needs improvement as a bug. In Rybbit, the trend chart and the previous-period comparison make a regression visible quickly, and the breakdowns show whether it hit everyone or only one device, country or page.

FAQ

What is a good Core Web Vitals score?

A page is good when its 75th-percentile LCP is 2.5 seconds or less, INP 200 milliseconds or less, and CLS 0.1 or less, measured separately for mobile and desktop. Needs improvement runs up to 4 seconds, 500 milliseconds and 0.25; above that is poor. There is no combined score; the Lighthouse performance score is a lab number.

Do Core Web Vitals affect SEO?

Yes. Google says Core Web Vitals are used by its ranking systems, and in the same breath that there is no single page experience signal and that it always seeks to show the most relevant content. Treat them as a tie-breaker and a user experience requirement, not a way to outrank better content.

How often are Core Web Vitals updated?

PageSpeed Insights and Search Console report field data over the previous 28 days, so a change takes up to four weeks to be fully reflected. The metrics themselves change rarely: web.dev states that "developers should expect the definitions and thresholds of the Core Web Vitals to be stable, and updates to have prior notice and a predictable, annual cadence."

What replaced FID?

Interaction to Next Paint (INP) replaced First Input Delay (FID) on March 12, 2024. FID only measured the delay before the first interaction's handler started; INP measures the full time to the next paint for every interaction, which is why pages that passed FID can fail INP.

Why does my page pass in Lighthouse but fail the Core Web Vitals assessment?

Lighthouse is one simulated load on one device and cannot measure INP at all. The assessment is 28 days of real Chrome visits at the 75th percentile across every device and network your visitors use. Use field data to decide whether there is a problem, the lab to find the cause, and your own real-user monitoring to confirm the fix landed.


Content Blog Ecommerce Analytics (`docs/content/blog/ecommerce-analytics.mdx`)


title: "Ecommerce Analytics: The Metrics and Reports That Matter" date: 2025-11-22 updated: 2026-09-28 keyword: "ecommerce analytics" tags: ["analytics", "ecommerce", "conversion"] author: Rybbit Team

If you run an e-commerce website, understanding your customer behavior and sales funnel is crucial for growth. Whether you're running a small online store or a large marketplace, having the right analytics in place can mean the difference between scaling successfully and struggling to understand why your sales are declining.

E-commerce analytics are a specialized application of web analytics, focused specifically on understanding purchase behavior, conversion rates, and customer journeys through your store.

This is where Rybbit Analytics comes in. Unlike traditional analytics platforms that overwhelm you with data you don't need, Rybbit gives you actionable insights into your e-commerce metrics while respecting your customers' privacy and keeping your data under your control.

Why E-commerce Sites Need Better Analytics

Most e-commerce platforms come with basic built-in analytics, but they're often limited in scope. You might see how many people visited your product pages, but can you easily answer these critical questions:

  • Which marketing channels are actually driving purchases, not just traffic?
  • What's the conversion rate for mobile vs desktop users?
  • Are customers from certain countries more likely to complete a purchase?
  • Which product pages have high traffic but low conversion rates?
  • How is your site's performance affecting your sales?
  • What's the customer journey from landing to checkout?

Traditional analytics tools like Google Analytics can answer some of these questions, but they come with significant downsides: complex setup, privacy concerns, cookie consent banners, data ownership issues, and an overwhelming interface that requires training to navigate effectively.

What Makes Rybbit Different for E-commerce

Rybbit is built from the ground up to be simple, privacy-focused, and actionable. Here's what that means for your e-commerce business:

No Cookie Consent Banners Required - Rybbit doesn't use cookies, which means you don't need those annoying cookie banners that hurt your conversion rates. Your checkout flow stays clean and simple.

Lightning Fast - The Rybbit script is designed to be lightweight and minimize performance impact on your product pages and checkout process. Load time matters for e-commerce conversions.

You Own Your Data - With the self-hosted option, all your customer data stays on your servers. No third-party access, no data sharing, complete control.

Simple, Actionable Metrics - Rybbit focuses on the metrics that actually matter for e-commerce, without drowning you in vanity metrics.

Setting Up Rybbit for Your E-commerce Site

Getting started with Rybbit is straightforward. You can either use the hosted version or self-host it (which many e-commerce businesses prefer for data sovereignty).

Installation

Add the Rybbit tracking script to your site's header. For most e-commerce platforms, this goes in your theme's template file. Refer to the official Rybbit documentation for the correct installation steps.

That's it. No complex configuration, no tag managers required, no cookies to configure.

Tracking E-commerce Events

While Rybbit automatically tracks page views, you'll want to track key e-commerce events like:

  • Product views
  • Add to cart
  • Checkout initiated
  • Purchase completed

See the Rybbit event tracking documentation for specific implementation details on how to track these events properly for your platform.

Key Metrics for E-commerce Success

1. Traffic Sources and Quality

Understanding where your traffic comes from is table stakes, but Rybbit makes it easy to see which sources actually convert.

Using Rybbit's channel filter, you can quickly see traffic from:

  • Organic search
  • Paid advertising
  • Social media
  • Email campaigns
  • Direct traffic
  • Referrals

But more importantly, you can combine this with your purchase events to see which channels drive actual revenue, not just visits. This helps you allocate your marketing budget more effectively.

2. Geographic Performance

If you're selling internationally, geographic data is crucial. Rybbit makes it easy to analyze performance by country.

You can use geographic filtering to:

  • Identify high-converting markets worth investing in
  • Spot countries with high traffic but low conversion (maybe you need better shipping options or payment methods)
  • Optimize your ad spend by focusing on profitable regions
  • Plan inventory and shipping logistics based on where your customers are

3. Device and Platform Analysis

E-commerce conversion rates often vary significantly between desktop and mobile. Rybbit lets you filter by device type to understand these differences.

Common insights you might discover:

  • Mobile traffic is high but conversion is low → your mobile checkout might need work
  • Tablet users have higher average order values → consider tablet-specific promotions
  • Desktop users browse more pages before purchasing → optimize your navigation for desktop

4. Page Performance Analysis

Page speed directly impacts e-commerce conversions.

Rybbit's performance tab shows you the Largest Contentful Paint (LCP) for each page. If your product pages or checkout pages have poor performance metrics, you should optimize them. The performance dashboard makes it easy to identify which pages need optimization.

What to look for:

  • Product pages with high traffic but slow load times → optimize images, reduce scripts
  • Checkout pages with performance issues → this is costing you direct sales
  • Category pages that load slowly → customers will bounce before seeing your products

5. Conversion Funnel Insights

One of the most powerful uses of Rybbit for e-commerce is tracking your conversion funnel. Similar to how content marketing funnels work for awareness and consideration stages, your e-commerce funnel tracks the path from product discovery to purchase. Using the pages tab, you can see the customer journey:

The embedded graphs make it easy to spot where customers are dropping off:

  • High traffic to product pages but low "add to cart" pages → product pages need better CTAs
  • Many add to cart events but few checkout initiations → shipping costs revealed too late?
  • Checkout started but not completed → payment issues or complicated forms?

6. Time-Based Patterns

Understanding when your customers shop helps you optimize promotions and advertising.

Using Rybbit's date range filters, you can:

  • Compare this month vs last month to track growth
  • Identify seasonal patterns in your business
  • See if your recent marketing campaigns are working
  • Spot unusual drops or spikes that need investigation

7. Referrer Analysis

Beyond broad traffic sources, Rybbit lets you drill down into specific referrers to see exactly which sites, influencers, or affiliate partners are driving traffic.

This is especially valuable for:

  • Affiliate programs - see which affiliates drive the most valuable traffic
  • Influencer marketing - track which influencers actually drive sales
  • Content marketing - identify which guest posts or mentions are worth pursuing
  • Partner relationships - measure the value of co-marketing efforts

Real-World E-commerce Scenarios

Scenario 1: Optimizing Marketing Spend

When analyzing which traffic sources drive actual revenue (not just clicks), you can make more informed decisions about how to allocate your marketing budget. Using Rybbit's channel filtering combined with conversion tracking helps you see which acquisition channels produce the highest-quality users.

Scenario 2: Fixing Mobile Conversion Issues

If you notice that mobile users make up a large portion of your traffic but a smaller portion of your conversions, Rybbit's device filtering helps you identify where the problem is. Common issues include:

  • Checkout experience problems on mobile
  • Performance issues on mobile devices
  • Form complexity or navigation problems on small screens

By identifying where mobile users drop off, you can prioritize fixes to improve mobile conversion rates.

Scenario 3: International Expansion

Using Rybbit's geographic filtering, you can see which countries or regions show strong engagement and conversion patterns. This helps you:

  • Identify markets worth investing in
  • Spot countries with traffic but low conversion (signaling a potential need for localization)
  • Prioritize international optimizations based on data, not assumptions

Scenario 4: Product Page Optimization

By analyzing individual product pages with Rybbit, you can identify patterns:

  • Which pages get high traffic but have poor conversion rates
  • What content attributes correlate with higher conversions (detailed descriptions, multiple images, videos, etc.)
  • Which optimization efforts have the most impact

For a comprehensive guide on understanding your entire purchase journey, see our sales funnel guide, which covers how to track and optimize all stages from awareness to purchase.

Privacy and E-commerce: Why It Matters

E-commerce businesses collect sensitive customer data - names, addresses, payment information. Using privacy-respecting analytics like Rybbit sends a positive signal to customers:

No Data Sharing - Customer behavior data stays with you, never sold to advertisers

GDPR and Privacy Compliant - Rybbit is built to respect privacy laws by default

No Cookie Banners - Better user experience means better conversion rates

Build Trust - Privacy-conscious customers appreciate businesses that respect their data

In an era where data breaches make headlines regularly, being able to tell your customers that you use privacy-first analytics is a competitive advantage.

Comparing Rybbit to Other E-commerce Analytics

vs. Google Analytics

Google Analytics:

  • Requires complex e-commerce tracking setup
  • Needs cookie consent banners
  • Data belongs to Google
  • Overwhelming interface with hundreds of metrics
  • Slower script impacts page speed

Rybbit:

  • Simple event tracking
  • No cookie banners needed
  • You own all data
  • Focused on actionable metrics
  • Minimal performance impact

vs. Platform Built-in Analytics (Shopify, WooCommerce, etc.)

Built-in Analytics:

  • Limited to basic metrics
  • Can't easily track custom events
  • Limited filtering and segmentation
  • No performance metrics
  • No data portability

Rybbit:

  • Comprehensive yet simple analytics
  • Custom event tracking
  • Powerful filtering and segmentation
  • Built-in performance monitoring
  • Self-hostable with full data control

Advanced E-commerce Tracking with Rybbit

For more detailed tracking beyond page views, refer to the Rybbit event tracking documentation for implementation guidance on:

Customer Lifetime Value (CLV) Tracking

Track repeat purchases to understand customer lifetime value. The documentation covers best practices for tracking first-time purchases and repeat customer behavior.

Cart Abandonment Analysis

Track the checkout flow to identify where customers are abandoning carts. By measuring events at key steps (add to cart, checkout started, purchase completed), you can calculate your cart abandonment rate and work to reduce it.

Product Category Performance

Track which product categories perform best by recording category and product information when users interact with products. This helps you understand which categories drive the most engagement and sales.

Getting Started with Rybbit for Your E-commerce Store

  1. Sign up or self-host - Choose between the hosted version for convenience or self-hosting for complete data control

  2. Add the script - Install the Rybbit tracking script on your e-commerce site (works with any platform)

  3. Set up event tracking - Add events for your key conversion points (add to cart, checkout, purchase)

  4. Define your metrics - Decide what success looks like for your business and set up your Rybbit dashboard accordingly

  5. Start analyzing - Use the filtering and segmentation features to understand your customer behavior

  6. Optimize and iterate - Use insights from Rybbit to make data-driven decisions about your store

Best Practices for E-commerce Analytics with Rybbit

Track What Matters - Don't track everything, focus on metrics that drive business decisions

Segment Your Analysis - Use filters to understand different customer segments, devices, and channels

Compare Time Periods - Always look at trends over time, not just individual snapshots

Combine with Other Data - Rybbit analytics work best when combined with your e-commerce platform's sales data

Act on Insights - Analytics are only valuable if you use them to make changes

Monitor Performance - Keep an eye on page performance metrics - they directly impact conversion

Review Regularly - Set aside time weekly or monthly to review your analytics and spot trends

Conclusion

Running a successful e-commerce business requires understanding your customers, your traffic, and your conversion funnel. Rybbit Analytics provides all the tools you need to make data-driven decisions without the complexity, privacy concerns, or performance impact of traditional analytics platforms.

Whether you're just starting out with a small online store or scaling a multi-million dollar e-commerce business, Rybbit gives you the insights you need to grow - in a simple, privacy-respecting package that your customers will appreciate.

The combination of Rybbit's clean interface, powerful filtering capabilities, performance monitoring, and privacy-first approach makes it the ideal analytics solution for modern e-commerce businesses. And with both hosted and self-hosted options, you can choose the setup that best fits your needs.

Ready to get better insights into your e-commerce business? Give Rybbit a try and see how much clearer your data becomes when you focus on what actually matters.


Related Reading:


Content Blog Fullstory Alternatives (`docs/content/blog/fullstory-alternatives.mdx`)


title: "Best FullStory Alternatives in 2026: 11 Tools by Use Case" keyword: "fullstory alternatives" date: 2026-09-27 tags: ["session replay", "comparisons", "product analytics"] author: Rybbit Team

FullStory is an enterprise digital experience platform: session replay, heatmaps, frustration signals and product analytics, bought through a sales conversation because none of its paid plans has a public price. Which of the FullStory alternatives fits depends on why you're leaving. For the same enterprise depth from another vendor, look at Contentsquare and Quantum Metric. If your engineers mainly use replay to debug, look at LogRocket, Sentry and OpenReplay. Product teams that want analytics with replay attached should compare PostHog, Amplitude and Heap. Microsoft Clarity and Mouseflow cover small budgets, and OpenReplay and Rybbit let you keep recordings on your own servers.

Disclosure up front: we build Rybbit, one of the eleven tools below. Every claim about FullStory and the other ten tools comes from that vendor's own pricing or documentation pages, linked inline and checked in September 2026. Where a vendor doesn't publish a number, we say so rather than guess.

FullStory alternatives at a glance

Tool Session replay Heatmaps Product analytics Dev tools Entry pricing (Sept 2026) Self-host
FullStory (for reference) Web; mobile is an add-on Yes Yes Console, network Free: 30k sessions/mo; paid plans by quote No
Contentsquare Web and apps; fixed replay allowance Yes Separate app for now JS errors, network waterfall Free: 200k sessions; Growth from $49/mo No
Quantum Metric Web and native apps Yes Yes Errors, performance Quote only No
LogRocket Web and mobile Yes Funnels, paths Console, network, errors, performance Free: 1k sessions; $176/mo for 25k Enterprise plan
Sentry Session Replay Web and mobile — — Errors, traces, console, network Free: 50 replays, 1 user; Team $26/mo billed annually Yes (FSL)
OpenReplay Web and mobile Yes Funnels, journeys Console, network, performance, state Self-host free; cloud from $199/mo Yes (AGPL-3.0 core)
PostHog Web and mobile; 5k/mo free Yes Yes Console, network Free tier, then per recording Hobby deploy, unsupported
Amplitude Web and mobile; 10k/mo free Plus plan and up Yes — Free: 2M events; Plus usage-based No
Heap Add-on (Pro, Premier) Add-on (Pro, Premier) Yes Add-on (Pro, Premier) Free: 10k sessions; paid by estimate or quote No
Microsoft Clarity Web and mobile; no traffic limits Yes Funnels — Free No
Mouseflow Yes Yes Funnels, journeys, forms JS error detection Free: 500 sessions; $25/mo for 5k No
Rybbit Web, on the Pro plan No Funnels, journeys, retention Error tracking, outside replay Pro from $39/mo; 7-day trial Yes, free (AGPL-3.0)

"No" in the self-host column means the vendor's pricing and documentation pages we checked list no self-hosted or on-premises option. A dash in the other columns means those pages don't list the feature. If you only need replay, not FullStory's analytics, our session replay tools comparison goes deeper on replay pricing, masking defaults and retention, and what is session replay covers the basics.

What FullStory includes and what it costs

FullStory's pricing page lists three analytics plans, Business, Advanced and Enterprise, and each one says "Request pricing & demo". None has a public price.

The published part is the free plan. FullstoryFree is "a permanent version of our platform designed for individuals and small teams": 30,000 sessions a month, 12 months of data retention, "Session Replay, basic analytics, and debugging tools for up to 10 users", and no credit card. Dashboards, StoryAI (FullStory's AI agents) and anything above 30,000 sessions need a paid plan. The paid plans' comparison table lists heatmaps, click and scroll maps, funnels, journeys, retention analysis, sentiment signals, co-browsing and retroactive data history, with mobile analytics and Guides and Surveys as add-ons on all three.

Three things make FullStory hard to replace one for one:

  • Sentiment signals. FullStory flags rage clicks, error clicks, dead clicks and thrashed cursors (developer docs) and says these signals "are automatically prioritized by business impact" (sentiment signals).
  • Dev Tools in the player. A Console and a Network view open beside any replay, on every plan including Free.
  • Mobile coverage. The mobile SDKs cover iOS, Android, Flutter, React Native and Cordova/Capacitor.

If 30,000 sessions a month covers you, FullstoryFree may be all you need.

Why teams look for alternatives to FullStory

  • Price opacity and sales-led buying. Past 30,000 sessions, every route runs through a demo and a quote. Teams that budget from a price list, or pay by card, need a vendor that publishes one.
  • Where the data lives. Private by Default is strict ("no text is captured or sent outside the user's browser unless it is explicitly allowlisted as safe to capture"), but you turn it on by asking FullStory's support team. Fullstory Relay routes capture through your own domain, yet the traffic is still "relayed immediately to the Fullstory servers for processing". Recordings that must stay on your infrastructure need a self-hosted tool.
  • Debugging depth. Teams that mostly use replay to fix bugs want it inside the tool that already holds their errors, traces and logs.
  • Paying for more than you use. Watching replays and running a few funnels doesn't need an enterprise contract.

Enterprise digital experience analytics

These two cover most of what FullStory does, and both sell their top tiers through sales, as FullStory does.

Contentsquare

Contentsquare is the closest like-for-like replacement: replay, heatmaps, funnels, journey analysis, JavaScript error and performance monitoring, surveys and an AI assistant called Sense, for "sites and mobile apps". Unlike FullStory, its lower tiers are self-serve. From the pricing page:

  • Free: 200,000 monthly sessions, replays for 5% of them (up to 10,000), 1 project.
  • Growth: from $49/month for 7,000 sessions to $409/month at 100,000, with 13 months of session data, 2 months of replays, 3 projects and zone-based heatmaps.
  • Pro (from 1 million monthly sessions) and Enterprise: no published price.

Replay volume is the limit to understand. The replay allowance is "a fixed amount per month — it can't be increased", and on Growth every session is replayable up to 20,000 a month; above that, 15% of sessions are, with a floor of 20,000. Growth has a 15-day trial without payment details and 20% off when paid annually by card. Product analytics is still "a separate application for the next few months", and Contentsquare also owns Heap, which it acquired in December 2023.

Pick it if you want FullStory-level experience analytics with a free tier and public prices up to 200,000 sessions. Skip it if you need any session on demand: past 20,000 a month, most replays are sampled.

Contentsquare also runs Hotjar now; if Hotjar is what you're replacing, see our Hotjar alternatives guide.

Quantum Metric

Quantum Metric is the other enterprise platform in this category, and FullStory's own alternatives article groups it with Contentsquare. There's no self-serve tier: the pricing page offers "Enterprise plans built around your business", priced on "your annual session volume, the digital properties where we are deployed, product add-ons and other things like the amount of professional services you're looking for".

Every plan includes the Felix AI analyst, analytics with anomaly detection and Core Web Vitals monitoring, journeys, and replay with AI summaries, plus implementation and PII remediation; contact center, data streaming and voice of customer are among the add-ons. Replay covers "web or native mobile applications", interaction heatmaps show "page-level clicks, scrolls, and attention", and Quantum Metric says "Everything is encrypted at the client device and exposed personal information is never sent across the network."

Pick it if you're a large company replacing FullStory with a peer and want onboarding and PII cleanup in the contract. Skip it if sales-led pricing is why you're leaving: the buying process is the same.

Developer debugging: replay tied to errors, console and network

These three suit teams whose main use of FullStory is Dev Tools: reproducing bugs from real sessions.

LogRocket

LogRocket pairs replay with debugging data. Each session carries "logs, network requests, JavaScript exceptions and performance data, as well as user and device metadata" (session replay for developers), and the mobile SDKs run on Android, iOS, React Native, Flutter, Ionic and Expo. Core also includes clickmaps, heatmaps, rage-click maps, path analysis, funnels and surveys.

The pricing page charges for sessions; analytics, error and log events are unlimited. Its calculator runs from a free tier at 1,000 sessions a month to $83/month for 10,000, $176/month for 25,000 and $717/month for 100,000 on Core, and the page notes that the final price also depends on seats, retention and add-ons. Pro adds Galileo AI (session summaries, issue detection) and is "included free above 100K sessions / mo"; Enterprise adds unlimited seats and a self-hosted option. The trial is 14 days with no card.

Pick it if engineers own replay and you need mobile apps covered. Skip it if you want a flat price: costs climb quickly past 25,000 sessions.

Sentry Session Replay

If you already use Sentry for errors, its replay covers the debugging half of FullStory. "Errors, traces, network requests, and console logs are all connected to the replay timeline", and rage clicks and dead clicks are linked to replays too, per the Session Replay docs. It records the web plus Android, iOS, React Native and Flutter.

The pricing page lists Developer free for one user, Team at $26/month and Business at $80/month billed annually, each with 50 replays a month; new users get 5,000 free replays a month for their first 3 months. Extra replays start at $0.00375 each pay-as-you-go and get cheaper with volume (pricing docs), so 100,000 replays a month on Team comes to about $383 including the plan. Self-hosted Sentry is "Fair Source under the FSL (Functional Source License)" and needs 4 CPU cores and 16 GB of RAM plus 16 GB of swap (self-hosted docs).

Pick it if you want replays of the sessions that broke, next to the stack trace. Skip it if UX research is the main job: Sentry is an error-monitoring tool first.

OpenReplay

OpenReplay is open-source, developer-first replay. Its DevTools show "JavaScript errors with full stack traces and sourcemaps", let you "Inspect API calls, track failing requests, and analyze response payloads with sensitive data sanitized", and track CPU, memory and application state through a session. The pricing page also lists co-browsing, heatmaps, funnels, journeys, product analytics and mobile.

The code defaults to AGPL-3.0, with the ee/ directory under its own license (LICENSE). "Open-Source is free — you host it on your own infrastructure", on at least 2 vCPUs, 8 GB of RAM and 50 GB of storage (Docker guide). The managed Dedicated plan starts at $199/month, billed hourly: a dedicated VM "with no limits on users, recordings, or retention", with a 7-day trial and no card. The page also lists a usage-based Serverless option and a self-hosted Enterprise tier.

Pick it if recordings must stay on your servers and developers are the main users. Skip it if you'd rather not run the infrastructure and $199/month is too much for a managed instance.

FullStory alternatives for product teams

These three put product analytics first and attach replay to it, which suits teams that use FullStory mostly for funnels, retention and cohort questions.

PostHog

PostHog is the broadest product-team option: product analytics, replay, heatmaps, feature flags, experiments, surveys and error tracking on one usage-based bill. Rybbit vs PostHog compares it with our product in detail.

Each month the pricing page gives 1 million analytics events, 5,000 recordings, 1 million feature-flag requests, 100,000 exceptions and 1,500 survey responses free, with no card. Replay pricing then runs from $0.0050 per web recording down to $0.0015 above 500,000, so 100,000 web recordings a month cost $272.50; mobile gets 2,500 free, then $0.0100 each. Replays can include console logs and the network waterfall, and recording works in React Native, iOS, Android and Flutter apps as well as on the web. Heatmap data, per PostHog, "is captured along with other events, so it doesn't contribute to your bill".

PostHog offers "a free Docker Compose deployment under an MIT license", but its self-host page calls self-hosted deployments "officially unsupported" and says it has no guarantees for self-hosted instances.

Pick it if your product team wants flags, experiments and replay in one tool. Skip it if you need supported self-hosting or a narrower tool.

Amplitude

Amplitude is product analytics first, with replay, heatmaps, experimentation, and guides and surveys in one platform; its pricing page says "Every plan includes the full platform and unlimited seats". Free gives 2 million events and 10,000 session replays a month with no card. Plus "Starts at $0", keeps the first 2 million events free, scales to 70 million, and adds heatmaps and 2-year data retention; it needs a card. Growth and Enterprise are custom and include 20,000 and 50,000 monthly replays. Replays cover web and mobile but are kept for 1 month on every plan, with more retention sold as an add-on on Growth and Enterprise.

Pick it if analytics is the main job and replay is for spot checks. Skip it if you watch replays at volume or need more than a month of them.

Heap

Heap is autocapture product analytics, owned by Contentsquare since December 2023. On its pricing page, Free covers up to 10,000 monthly sessions with 6 months of data history and SSO. Growth adds Contentsquare's Sense AI assistant, unlimited users and reports and 12 months of history, priced by estimate once you install the snippet; Pro and Premier have "Custom Session Pricing". Session replay, heatmaps and error analysis aren't available on Free or Growth; they're add-ons on Pro and Premier.

Pick it if you want autocapture analytics and already buy from Contentsquare. Skip it if replay is why you're shopping: here it's an add-on to an enterprise plan.

Free and low-cost options

Microsoft Clarity

Clarity is the zero-cost answer. Microsoft's FAQ says "Clarity is a free service forever. You never encounter traffic limits or are compelled to upgrade to a paid version", with no sampling. You get recordings, heatmaps and insights, and mobile SDKs cover Android, iOS, React Native, Flutter and Cordova/Ionic.

The trade-offs are retention and control. Data "is retained for the webmaster's consumption up to 30 days from the time of recording", the FAQ states that Microsoft has access to it, and for visitors in the EEA, UK and Switzerland you must pass a consent signal; without valid consent, Clarity "limits data collection in those regions".

Pick it if the budget is zero and 30 days of history is enough. Skip it if you need longer retention, or a large European audience would leave you with thin data.

Mouseflow

Mouseflow publishes its prices and bundles session replay; click, scroll and geo heatmaps; friction and JavaScript error detection; funnels; journey and form analytics; and feedback surveys. Per the pricing page, Free covers 500 sessions a month with 1 month of retention. Essential is $25/month for 5,000 sessions and 3 months, Advanced $109/month for 25,000 and 6 months, and Premium $319/month for 100,000 and 12 months, adding API access, an MCP connection and SSO. Every plan has unlimited users, annual billing saves up to 35%, and paid plans start with a 14-day trial.

Pick it if you want heatmaps and 12 months of history at a published price. Skip it if you need product analytics or debugging depth: Mouseflow is built for UX and conversion work.

Privacy-first and self-hosted options

OpenReplay, above, is the self-hosted pick for developer replay, and PostHog and Sentry self-host with the caveats in their sections. Rybbit fits if you want replay inside privacy-first web analytics.

Rybbit

Rybbit is open-source web and product analytics with session replay on the Pro plan. The player's timeline marks page navigations, clicks, typing and rage clicks, so you can jump to where a visitor got stuck. Around it sit funnels, user journeys, goals, retention, user profiles, Web Vitals and error tracking, which groups uncaught JavaScript errors and unhandled promise rejections with stack traces and affected-session counts.

Input values are masked by default, and script attributes mask or block elements by class or selector. Analytics tracking is cookieless and needs no consent banner. The cloud is hosted in the EU, and you can self-host everything, replay included, free under AGPL-3.0.

Standard starts at $19/month for 100,000 events with funnels, journeys, error tracking and Web Vitals, but no replay. Pro starts at $39/month for 100,000 events and adds replay, unlimited sites and team members, and 5-year data retention; billed annually, it's $26/month (pricing). Both start with a 7-day trial, and a card is collected at signup. Replays are stored for 30 days.

Where it falls short of FullStory:

  • No heatmaps, click maps or scroll maps.
  • Rage clicks are marked in each replay, but there are no aggregated frustration reports and no error-click, dead-click or thrashed-cursor signals.
  • Replays don't capture console output or network requests; errors live in the separate error tracking view.
  • Web only: the React Native SDK tracks screens, events and errors, but session replay doesn't apply to mobile apps.
  • No surveys, no AI session summaries and no FullStory importer; data import covers Plausible, Umami and Simple Analytics only.

Pick it if you want replay next to privacy-first analytics, at a published price, with the option to self-host. Skip it if heatmaps, mobile replay, frustration dashboards or console and network capture are why you use FullStory; LogRocket, Contentsquare or PostHog fit those better.

Switching from FullStory: what to plan for

  • History doesn't move. Rybbit has no FullStory importer, and none of the pages we checked for this article advertise one. Run both tools until the new one has enough history for your reports.
  • Replay retention varies. FullstoryFree keeps 12 months of data. Clarity keeps 30 days, Amplitude 1 month, Contentsquare Growth 2 months of replays, Mouseflow 1 to 12 months by plan and Rybbit 30 days; OpenReplay's Dedicated plan sets no limit.
  • Know what's capped. Contentsquare and Amplitude cap replays separately from sessions or events. Sentry, PostHog and LogRocket bill per replay or session past the free allowance.
  • Rebuild privacy rules. FullStory's .fs-exclude, .fs-mask and .fs-unmask classes are FullStory's own. Map each to the new tool's equivalent before you record; in Rybbit, that's the block and mask options in the script docs.
  • Mobile narrows the list. Contentsquare, Quantum Metric, LogRocket, Sentry, OpenReplay, PostHog, Amplitude and Clarity document mobile app replay; Rybbit records the web only.

Which FullStory alternative should you choose?

  • Same enterprise depth, different vendor: Contentsquare for a free tier and a price list to start with; Quantum Metric for implementation and PII remediation in the contract.
  • Engineers debugging production issues: LogRocket for web and mobile; Sentry Session Replay if you already run Sentry; OpenReplay if recordings must stay on your servers.
  • Product teams: PostHog for analytics, flags, experiments and replay together; Amplitude if analytics comes first; Heap if you already buy from Contentsquare.
  • No budget: Microsoft Clarity, if 30 days of data and an EU consent signal are acceptable, or FullstoryFree, if 30,000 sessions a month is enough.
  • Heatmaps and a year of history at a published price: Mouseflow Premium, $319/month for 100,000 sessions.
  • Privacy-first analytics with replay and a self-hosted option: Rybbit, unless heatmaps or mobile replay are requirements.

FAQ

What is the best free FullStory alternative?
Microsoft Clarity is free with no traffic limits, but keeps data for 30 days. PostHog (5,000 recordings and 1 million events a month), Amplitude (10,000 replays and 2 million events) and Contentsquare (200,000 sessions, up to 10,000 replays) also have free plans, and OpenReplay and Rybbit are free to self-host.

How much does FullStory cost?
FullstoryFree costs nothing for 30,000 sessions a month, 12 months of data and up to 10 users. Business, Advanced and Enterprise have no public price; the pricing page says "Request pricing & demo" for each.

Who are FullStory's main competitors?
Contentsquare and Quantum Metric in enterprise experience analytics; LogRocket, Sentry and OpenReplay in developer-focused replay; Amplitude, Heap and PostHog in product analytics. FullStory's own alternatives article also names Adobe Analytics, Mixpanel, Pendo, Datadog and Dynatrace.

Is there an open-source alternative to FullStory?
Yes. OpenReplay (AGPL-3.0 by default, with a separately licensed ee/ directory) and Rybbit (AGPL-3.0) self-host with replay included. PostHog's Docker Compose deployment is MIT-licensed but officially unsupported, and Sentry self-hosts under a license it calls Fair Source rather than open source.

Which FullStory alternatives have heatmaps?
Contentsquare, Quantum Metric, LogRocket, OpenReplay, PostHog, Microsoft Clarity and Mouseflow; Amplitude from its Plus plan; Heap as an add-on on Pro and Premier. Rybbit has none, and Sentry's pages we checked don't list them.


Content Blog Funnel Analysis (`docs/content/blog/funnel-analysis.mdx`)


title: "Funnel Analysis: How to Find Where Users Drop Off" keyword: "funnel analysis" date: 2025-11-26 updated: 2026-09-28 tags: ["analytics", "funnels", "conversion", "optimization"] author: Rybbit Team

Your site gets thousands of visitors a month. How many of them do the thing you built the site for?

Funnel analysis is how you answer that. You define the sequence of steps a user takes to reach a goal, count how many sessions reach each step, and find the step where most of them leave. Traffic numbers tell you how many people arrived. The funnel tells you where you lost them.

This guide covers what a conversion funnel is, the metrics that matter, how to set up and read a funnel, how to segment it by source and device, the patterns that point at specific problems, and how to fix them. Examples use Rybbit's funnels, but the method applies to any tool.

What is a conversion funnel?

A conversion funnel is the path a visitor follows from arriving on your site to completing a goal: a purchase, a signup, a demo request, a download. It is called a funnel because it narrows. Many people enter at the top and a few come out the bottom.

Marketers often describe the same thing as a sales funnel with stages such as awareness, consideration, decision, and retention. In web analytics the stages are concrete: pages viewed and events fired, in order.

Some typical funnels:

  • Ecommerce: product page → add to cart → checkout → payment complete
  • SaaS: landing page → signup form → email verified → first key action
  • Lead generation: landing page → form submitted → thank-you page
  • Content: blog post → CTA click → form → download confirmed

Not everyone who enters completes the funnel, and that is normal. If 1,000 people view a product page, 300 add it to the cart, and 150 pay, the overall conversion rate is 15%. The opportunity is in the steps: lifting checkout completion from 50% to 60% yields 30 more orders from the same 300 carts, without buying a single extra visitor.

The funnel metrics that matter

For every step you want four numbers:

  • Sessions at the step. How many sessions reached it. The absolute count tells you how much is at stake at each stage.
  • Step conversion rate. The share of sessions from the previous step that reached this one. This is the efficiency of the step.
  • Drop-off rate. The inverse: the share that completed the previous step but not this one. The step with the highest drop-off and the most traffic is your first priority.
  • Overall conversion rate. The share of sessions that entered the funnel and completed it. This is the number that connects the funnel to revenue.

Two more are worth knowing. Time to convert shows whether a step is slow as well as leaky. Conversion by segment (source, device, country, new vs returning) shows whether the funnel has one problem or several.

How to run a funnel analysis

1. Start from the goal. Pick the single most important action on the site. If you cannot name one, you are not ready to build a funnel.

2. Map three to five steps backward from it. Every extra step adds noise. Start with the steps a user must pass through, and add optional pages later if they turn out to matter.

3. Decide how strict the order is. A strict funnel counts only sessions that hit the steps in sequence. Looser definitions count any session that eventually reached each step. Strict funnels are easier to act on; loose ones are more forgiving of real user behaviour. Know which one your tool uses.

4. Pick the time window. A checkout funnel should complete within a session. A B2B signup-to-activation funnel might take two weeks. If the window is too short you undercount conversions; too long and you blur the signal.

5. Segment before you conclude. A 40% drop-off at checkout might be 20% on desktop and 70% on mobile. Split by device, traffic channel, country, and landing page before deciding what is broken.

6. Compare to a previous period. The absolute drop-off matters less than whether it got better or worse after the change you shipped.

Funnel analysis in Rybbit

Rybbit's funnel builder lets you define steps from page paths, custom events, or autocaptured interactions (outbound link clicks, button clicks, form submissions, and copied text), then shows sessions, conversion rate, dropped sessions, and drop-off rate for every step.

Define the steps. Path steps match pageviews by URL, with * wildcards for things like /products/*. Event steps match custom events such as add_to_cart. Each step can carry property filters, so "purchase where plan = pro" is one step, not a separate funnel.

Read the result. The visualization updates as you edit. For each step you see how many sessions arrived, the percentage of first-step sessions that made it this far, and how many were lost since the previous step. The biggest bar-to-bar drop is where to look first.

Drill into the sessions. Every step links to the sessions behind it, so when a number looks wrong you can open session replay and watch what people did at that step instead of guessing.

Track single-step conversions as goals. Not every conversion is multi-step. Goals track one action, such as a thank-you page view, a form submission, or a button click, and report conversions, conversion rate, and performance by channel and device.

Find the pages that feed the funnel. The Pages report shows traffic, bounce rate, and time on page for every URL. Click into a page to see only the sessions that visited it and check whether they convert more or less than average. This is how you learn which product pages produce cart additions and which blog posts produce signups.

Segment by channel and device. Any filter applies to the funnel. Filter by channel to compare organic search, paid, email, social, and referral; filter by device to compare mobile and desktop.

A common finding: two channels send the same traffic, but one converts twice as well. Another: mobile brings half the sessions and a tenth of the conversions, which makes mobile checkout the highest-value fix on the site.

Check the paths you did not define. A funnel assumes a sequence. Real users skip steps, loop back, and leave and return. The Journeys report shows the actual paths through the site, so you can work backward from a conversion to see what successful users did, or forward from a landing page to see where people actually go.

Reading the patterns

A steep drop at one step. Something specific is wrong there: a confusing form, a bug, a slow page, an unexpected cost, a trust gap at payment, or a broken mobile layout. Watch replays of sessions that dropped at that step; the cause is usually visible within ten recordings.

Gradual erosion at every step. No single step looks terrible, but 30–40% leaks at each one add up. This usually means the value proposition is unclear or the traffic is poorly matched to the offer. Fix the message and the targeting before the UI.

Segments that disagree. Organic converts and paid does not; new visitors convert and returning ones do not; one country lags the rest. Each of these points at a specific audience or campaign rather than the funnel itself.

A drop that appeared on a date. Line the funnel up against your deploys and campaigns. A step that lost 20% overnight is a regression, not a design problem.

Fixing the leaks

  • Reduce friction. Every field, step, and second of load time costs conversions. Remove fields you do not need, merge steps, and measure page speed at the leaky step.
  • Build trust where money changes hands. Security badges, transparent pricing, a clear privacy policy, and recognizable payment options matter most at checkout.
  • Say what happens next. Progress indicators and a preview of the following step reduce abandonment from uncertainty.
  • Fix mobile first if mobile is worse. It is usually where the largest gap is.
  • Address the objection at the step where it appears. High price: show value. Long commitment: offer a trial. Confusing process: add one sentence of explanation.
  • Change one thing and re-measure. Compare the funnel before and after, on the same segment, over the same window. If you change three things at once you will not know which one worked.

Typical funnel conversion rates

Ranges vary enormously by industry and traffic quality, so use them to sanity-check, not to grade yourself:

Funnel type Typical path Typical overall conversion Usual weak point
Ecommerce product → cart → checkout → payment 1–5% payment step, mobile checkout
SaaS signup landing → form → verify email → first action 5–30% form abandonment, email verification
Lead generation landing → form → thank-you 10–40% forms that ask too much
Content download post → CTA → form → confirmation 5–20% unclear value of the download

For ecommerce specifically, the ecommerce analytics guide goes deeper on the metrics behind each step.

Funnel analysis tools

Most analytics products can build a funnel; they differ in how much setup they need and what you can do with a leaky step once you find it.

  • Rybbit defines steps from paths, events, or autocaptured clicks and forms, filters by any dimension, and links each step to session replays. Open source and self-hostable.
  • Google Analytics 4 builds funnels in Explorations. Powerful, but every step must be an event you configured in advance, and the interface is the hardest of the group to learn.
  • PostHog, Mixpanel, and Amplitude are product-analytics suites with deep funnel tooling, including strict ordering and conversion-time analysis. They assume an instrumented product and event schema.
  • Matomo offers funnels as a paid plugin on self-hosted installs and as part of its cloud plans.

If you already track pageviews and a handful of events, you can build your first funnel in any of these in an afternoon. Pick the one whose data you already have.

Getting started

  1. Define the one conversion that matters most.
  2. Map three to five steps backward from it.
  3. Build the funnel and note the overall conversion rate and the biggest drop.
  4. Segment by device and channel to see whether the drop is universal.
  5. Watch a few replays at the leaky step.
  6. Change one thing, wait a week or two, and compare.
  7. Repeat.

Most sites do not need more traffic to grow. They need to convert more of the traffic they already have, and funnel analysis is how you find out where that traffic is going.


Related reading:


Content Blog Geographic Segmentation (`docs/content/blog/geographic-segmentation.mdx`)


title: "Geographic Segmentation: How to Use Location Data in Analytics" keyword: "geographic segmentation" date: 2025-12-03 updated: 2026-09-28 tags: ['marketing', 'analytics', 'segmentation', 'targeting', 'geographic'] author: Rybbit Team

Geographic segmentation divides your audience by location—country, region, city, or neighborhood. For marketers, it's one of the most practical segmentation strategies because location data reveals real behavioral differences across markets. Whether you're optimizing landing pages, adjusting ad spend, or localizing content, geographic insights drive better decisions.

What is Geographic Segmentation?

Geographic segmentation groups your visitors, customers, or prospects based on where they're located. It's straightforward in concept but powerful in application.

Simple examples:

  • US visitors might see pricing in USD with US shipping costs
  • European visitors might see GDPR-compliant messaging
  • Mobile-heavy regions might receive mobile-optimized experiences
  • High-traffic regions might warrant dedicated marketing budgets

The core idea: Different locations have different needs, behaviors, and preferences. When you recognize and respond to those differences, your marketing becomes more effective.

Why Geographic Segmentation Matters

Geographic segmentation isn't just about translation or currency conversion. It addresses fundamental marketing questions:

Market Penetration

Some geographic markets are more mature than others. Your product might dominate in Germany but be relatively unknown in Brazil. Segmentation helps you identify which regions are underperforming and deserve more attention or different positioning.

Regional Preferences

Consumer behavior varies by region. Payment methods differ, holidays differ, language preferences differ, seasonal patterns differ. What sells in summer in the Northern Hemisphere sells in winter in the Southern Hemisphere.

Resource Allocation

Marketing budgets are finite. Geographic data shows you where your customer acquisition is most efficient. Instead of spreading spend evenly, you concentrate resources on high-performing regions and test carefully in emerging markets.

Regulatory Compliance

Different regions have different rules. GDPR applies to Europe. Privacy laws differ in California, Canada, Australia. Geographic segmentation helps you understand where compliance requirements apply.

Competitive Positioning

Competition isn't uniform globally. You might face different competitors in different regions. Knowing where competitors are strong helps you position strategically.

Common Applications of Geographic Segmentation

Localized Marketing Campaigns

Tailoring campaigns by region increases relevance. A campaign that resonates in New York might miss entirely in rural Montana. Geographic segmentation lets you test different messages in different places.

Pricing Strategy

International pricing often varies based on local purchasing power, competition, and regulations. Geographic segmentation reveals whether your pricing strategy is competitive in each region.

Content Localization

Beyond translation, content localization means adjusting tone, examples, references, and focus for different audiences. A tech product aimed at decision-makers in Japan might emphasize different benefits than the same product for German audiences.

Regional Product Focus

Some product features resonate more in certain regions. Geographic segmentation shows you which features drive engagement where, informing your regional roadmap.

Event and Campaign Timing

Geographic data reveals whether events and campaigns land better at certain times for certain regions. Holiday campaigns, seasonal promotions, and product launches can be timed regionally.

With geographic performance data, you adjust ad spend per region, pause campaigns in underperforming areas, and double down on high-ROI locations.

How to Implement Geographic Segmentation in Your Analytics

Analytics platforms that support geographic filtering let you slice your data by location. This reveals actual visitor behavior by region rather than guessing.

Accessing Geographic Data in Your Analytics

Modern analytics tools display visitor locations automatically. This typically comes from IP geolocation data. Once you can see how your visitors distribute geographically, you can start segmenting your analysis.

The geographic filter lets you focus on specific regions. Rather than analyzing all visitors together, you can examine Germany separately from the US, which often reveals completely different behavior patterns.

Comparing Geographic Segments

Once you can filter by location, comparison becomes possible. You can ask questions like:

  • Which country has the highest bounce rate? If visitors from Brazil bounce 2x more than visitors from Canada, that signals either a targeting problem or a product-market fit issue in Brazil.

  • Where do users spend the most time? Some regions might have significantly longer session durations, suggesting higher engagement.

  • Which regions convert best? Conversion rates often vary dramatically by geography.

  • Where do users engage most with specific features? Geographic data combined with event tracking shows which regions engage with which parts of your product.

Combining Geographic Filters with Other Dimensions

Geographic data becomes even more powerful when combined with other segmentation dimensions:

  • Geography + Traffic Source: Do visitors from organic search in Germany behave differently than paid visitors from Germany?
  • Geography + Device Type: Is your mobile experience performing worse in specific regions?
  • Geography + Traffic Channel: Which channels work best in which regions?

By layering filters, you discover specific insights that single-dimension analysis misses.

Challenges in Geographic Segmentation

Geographic segmentation, while powerful, comes with practical challenges:

Data Quality

IP geolocation isn't perfect. It's usually accurate to the country level, sometimes to the city level, but not to the street address. VPNs and proxies distort location data. Mobile networks sometimes mis-report location.

Sample Size

If you're optimizing for 50 different countries, some might have tiny visitor volumes. Statistical significance becomes harder to achieve. You need enough traffic per region to draw reliable conclusions.

Cultural Complexity

Just because two people live in the same country doesn't mean they're the same audience. Large countries like the US, India, and Brazil have significant cultural variation within their borders.

Implementation Complexity

Localizing campaigns for 20 different regions requires 20x more marketing effort. It's powerful but expensive. Most companies focus on their top 5-10 regions rather than trying to personalize for everyone.

Privacy Considerations

IP-based geolocation relies on collecting location data. Depending on your audience and jurisdiction, you may face privacy restrictions. Privacy-first analytics approaches need to balance geolocation with visitor privacy.

Real-World Example: Using Geographic Data to Optimize

Here's how geographic segmentation might work in practice:

Your situation: You run a B2B SaaS tool. You notice overall bounce rates are 40%. That seems high.

Geographic breakdown reveals:

  • US visitors: 35% bounce rate
  • German visitors: 45% bounce rate
  • Brazilian visitors: 65% bounce rate

This tells you:

  • US is working fine
  • Germany needs attention
  • Brazil has a serious problem

Next steps:

  • For Germany: Maybe pricing is unclear, or the mobile experience is poor. You dig deeper.
  • For Brazil: Currency conversion, payment methods, or messaging might be off. Or maybe you're attracting wrong audience through ads.

By segmenting geographically first, you avoid the trap of "our bounce rate is too high" and instead ask "our bounce rate is too high in specific regions, and here's what I'll do about it."

Segmentation and Analytics Tools

Not all analytics platforms handle geographic segmentation equally. Some key questions to ask:

  • Can you filter your data by country and region in real time?
  • Can you combine geographic filters with other dimensions (traffic source, device, custom properties)?
  • How accurate is the location data?
  • Can you set up comparisons between geographic segments?
  • Does the tool support historical trend analysis by region?

Having geographic segmentation built into your analytics workflow means you can answer location-based questions quickly without exporting data or running custom reports.

Moving Beyond Basic Segmentation

Once you master basic geographic segmentation (country-level filtering), more advanced approaches become possible:

Regional Cohort Analysis

Track specific cohorts by region. Do monthly active users in Japan have different retention curves than monthly active users in the UK?

Regional Funnels

Build funnels for specific regions. Does your checkout funnel drop off at different steps in different countries?

Regional Benchmarking

As you expand into new markets, compare early performance against established markets. This reveals whether you're on track.

Watch how performance changes over time in specific regions. Is Germany growing or declining? When did that shift happen?

Common Mistakes in Geographic Segmentation

Mistake 1: Over-Segmentation

Trying to customize for 100 different locations dilutes your effort. Start with your top 5-10 regions.

Mistake 2: Ignoring Within-Country Variation

Treating "Germany" as homogeneous misses important local differences. Large countries need sub-regional analysis.

Mistake 3: No Action on Geographic Insights

Discovering that bounce rates differ by region only matters if you then act on it. Insights without action are just interesting facts.

Mistake 4: Assuming Causation

If US visitors engage more than Brazil visitors, that could be many things—product-market fit, targeting, product maturity, time of day when they visit. Don't assume one cause.

Mistake 5: Static Segmentation

Geographic markets change. Competition shifts, regulations evolve, audience preferences shift. Regular review of geographic performance prevents stale strategies.

Geographic Segmentation and Broader Analytics Strategy

Geographic segmentation doesn't live in isolation. It connects to your broader web analytics approach.

Understanding traffic sources by geography reveals which channels work best where. Tracking bounce rates by region shows you where engagement is weakest. Analyzing session behavior by location reveals usage patterns.

For e-commerce businesses, geographic data combines with product-level analytics to show which products sell where. For content creators, geography reveals which content resonates in different markets.

Getting Started with Geographic Segmentation

If you're new to geographic segmentation, start simple:

Week 1: Look at your visitor distribution. Which countries/regions send you the most traffic? Which are you surprised by?

Week 2: Compare performance metrics by region. Bounce rate, session duration, conversion rate—which regions stand out?

Week 3: Layer in traffic source. Which channels work best in which regions?

Week 4: Make one small change based on geographic insights and measure the result.

This four-week approach turns geographic data from "interesting dashboard numbers" into actionable insights.

Privacy-Conscious Geographic Segmentation

If you're using Rybbit or another privacy-first analytics platform, geographic segmentation works through IP-based location data without tracking individual users across sites. You see regional patterns without collecting personally identifiable information.

This matters because geographic segmentation becomes both more useful (you get real data) and more ethical (you're not invading privacy to get it).

Final Thoughts

Geographic segmentation is one of the most practical marketing optimizations available. It doesn't require expensive tools or complex infrastructure. It simply requires answering: How do different locations behave differently?

The answer often surprises you. What you assume is true for "your audience" might only be true for your largest geographic segment. What you think is a product problem might actually be a regional messaging problem.

By implementing geographic segmentation in your analytics, you move from "one-size-fits-all" marketing to location-aware marketing. You spend more where it works, test carefully where it doesn't, and optimize regionally rather than globally.

Start by exploring your geographic data. Segment by country. Compare performance. Act on what you learn.


Additional Resources:


Content Blog Google Analytics Mcp (`docs/content/blog/google-analytics-mcp.mdx`)


title: "Google Analytics MCP Server: Setup Guide and Alternatives" keyword: "google analytics mcp" date: 2026-09-27 tags: ["google analytics", "mcp", "ai"] author: Rybbit Team

The Google Analytics MCP server is Google's official, open-source bridge between GA4 and AI assistants. It runs on your own computer, reads your properties through the Google Analytics Admin and Data APIs, and lets Claude, Gemini and other MCP clients answer questions like "how many users did I have yesterday?" It is free, read-only and still labeled experimental. Setup takes Python and pipx, the gcloud CLI, a Google Cloud project with two APIs enabled, an OAuth client and one entry in your AI client's config.

This guide covers that setup in Claude Code, Claude Desktop and Gemini CLI using Google's documented configuration, then the nine tools (the README names seven), fixes for common errors, the server's limits, and hosted alternatives.

Disclosure: we build Rybbit, a privacy-first analytics tool with its own hosted MCP server, which appears in the alternatives. Every claim about Google's server, Stape and Zapier comes from that vendor's own documentation, repository or pricing page, linked inline and checked in September 2026.

Google Analytics MCP at a glance

Detail Google Analytics MCP server
Publisher Google, in the googleanalytics GitHub organization; Apache-2.0 license
Package analytics-mcp on PyPI, version 0.7.0 (29 July 2026)
Status "Experimental" in the README title; support through GitHub issues and a Discord channel
Where it runs On your machine, as a local process your AI client starts. Google doesn't host it
Access Read-only: it "can't edit your Google Analytics configuration or settings"
Tools 9: account and property info, annotations, custom definitions, and core, realtime, funnel and conversions reports
Documented clients Gemini CLI, Gemini Code Assist, Claude Code
Price Free; requests count against GA's Data API quotas

What the Google Analytics MCP server is

MCP (Model Context Protocol) lets an AI application call tools on a server. Google's server wraps the Google Analytics Admin API and Data API as tools, so an assistant can find your property, pull a report and explain it in one conversation.

It is official: the Google Analytics team maintains it, the Analytics developer docs link to it, and Google's list of official MCP servers includes it among the open-source servers you run yourself. Two design choices shape everything below:

  • It is read-only. Google's developer page says: "The MCP server is available for read requests only. It can't edit your Google Analytics configuration or settings."
  • It runs locally. Your AI client launches it as a subprocess. Google hosts remote MCP servers for BigQuery, Cloud SQL and dozens of other products, but that list (updated 25 September 2026) has no Analytics entry, and Anthropic's connector docs say local servers aren't available on claude.ai or in Cowork, only in Claude Desktop.

One naming trap: Google's package is analytics-mcp. The similarly named google-analytics-mcp on PyPI is a separate community project by a different author.

What you need before you start

  • A Google account with access to the GA4 properties you want to query. The server sees exactly what that account can see.
  • Python 3.10 or newer, the package's minimum, and pipx, which runs the server in an isolated environment.
  • The Google Cloud CLI (gcloud), used to create credentials.
  • A Google Cloud project where you can enable APIs and create an OAuth client. It hosts nothing; it owns the OAuth client and the API quota.

How to set up the Google Analytics MCP server

Steps 1 to 5 are the same for every AI client. They follow Google's README, plus the Google Cloud pages it links to.

Step 1: Install pipx

On macOS with Homebrew:

bash
brew install pipx
pipx ensurepath

On Ubuntu or Debian use sudo apt install pipx, and on Windows scoop install pipx, then run pipx ensurepath and open a new terminal (pipx docs). If you use uv, a Google maintainer confirmed that uvx analytics-mcp works too.

Step 2: Enable the Admin and Data APIs

In your Google Cloud project, enable the Google Analytics Admin API and the Google Analytics Data API, or do both from the terminal with gcloud services enable:

bash
gcloud services enable analyticsadmin.googleapis.com analyticsdata.googleapis.com --project=YOUR_PROJECT_ID

Step 3: Create an OAuth client

The login in step 4 needs an OAuth client of your own: Google's ADC troubleshooting page explains that gcloud's default login can block scopes for services outside Google Cloud, and that you add them with your own client ID.

  1. Open the Google Auth Platform Clients page in the same project (Google's guide). If the project isn't registered with Google Auth yet, you'll be asked to do that first.
  2. Click Create client, pick the Desktop app type, and create it.
  3. Download the client's JSON file.

If you chose an External audience, the app starts in Testing, which only accepts listed test users, so add your own Google account under Audience. Google also expires test users' authorizations seven days after consent, refresh tokens included, so a setup left in Testing stops working after a week until you log in again. Publishing the app removes that expiry.

Step 4: Log in with Application Default Credentials

Run Google's documented command, pointing --client-id-file at the JSON from step 3:

bash
gcloud auth application-default login \
  --scopes https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform \
  --client-id-file=YOUR_CLIENT_JSON_FILE

Sign in with the Google account that can see your GA4 properties. gcloud then prints Credentials saved to file: [PATH_TO_CREDENTIALS_JSON]. Copy that path: it is the file the server reads, not the client JSON you downloaded. By default it is $HOME/.config/gcloud/application_default_credentials.json on macOS and Linux and %APPDATA%\gcloud\application_default_credentials.json on Windows (Google Cloud docs).

Then give the credentials a quota project, the Google Cloud project that API usage from user credentials counts against (troubleshooting page). The APIs from step 2 must be enabled there, and your account needs the serviceusage.services.use permission, which the Service Usage Consumer role includes:

bash
gcloud auth application-default set-quota-project YOUR_PROJECT_ID

The README's other route is impersonating a service account, but Google notes that locally impersonated credentials aren't supported by every authentication library, so the OAuth route is simpler for a personal setup.

Step 5: Test the credentials with one API call

Before involving an AI client, check that the credentials, APIs and property access work with a call a Google maintainer suggests in the issue tracker (macOS and Linux shells). Your numeric property ID is under Admin > Property Settings in GA (Google's docs):

bash
curl \
  -H "x-goog-user-project: YOUR_CLOUD_PROJECT_ID" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  https://analyticsdata.googleapis.com/v1beta/properties/YOUR_PROPERTY_ID/metadata

A JSON list of dimensions and metrics means you're ready. Any error here would also break the server inside Claude or Gemini, where it's harder to read; the errors section maps the usual ones to fixes.

Add the GA4 MCP server to Claude Code, Claude Desktop or Gemini CLI

Every client needs the same three things: the command pipx run analytics-mcp, the full path to your credentials file, and your project ID. On Windows, JSON strings need doubled backslashes (C:\\Users\\...).

Claude Code

Google's README gives a one-command setup:

bash
claude mcp add analytics-mcp \
  --scope user \
  -e "GOOGLE_APPLICATION_CREDENTIALS=PATH_TO_CREDENTIALS_JSON" \
  -e "GOOGLE_PROJECT_ID=YOUR_PROJECT_ID" \
  -- pipx run analytics-mcp

--scope user makes the server available in all your projects, not just the current one (Claude Code docs). Run claude mcp list, or /mcp inside a session, to confirm it connected.

Claude Desktop

The README doesn't cover Claude Desktop, but Google's maintainers say they tested it with Claude, including on Windows. Open Settings > Developer > Edit Config from the Claude menu, which opens ~/Library/Application Support/Claude/claude_desktop_config.json on macOS or %APPDATA%\Claude\claude_desktop_config.json on Windows (MCP docs), and add Google's server block:

json
{
  "mcpServers": {
    "analytics-mcp": {
      "command": "pipx",
      "args": ["run", "analytics-mcp"],
      "env": {
        "GOOGLE_APPLICATION_CREDENTIALS": "PATH_TO_CREDENTIALS_JSON",
        "GOOGLE_PROJECT_ID": "YOUR_PROJECT_ID"
      }
    }
  }
}

Quit Claude Desktop completely and reopen it (on Windows, quit from the system tray, the maintainers note). analytics-mcp then appears in the chat box's connectors menu. If it doesn't, its log says why: ~/Library/Logs/Claude/mcp-server-analytics-mcp.log on macOS, or the same file name under %APPDATA%\Claude\logs on Windows.

Gemini CLI and Gemini Code Assist

This is the path Google documents first. Add the same analytics-mcp block to ~/.gemini/settings.json, merged into any existing mcpServers object, then start Gemini and type /mcp; analytics-mcp should be listed with its tools.

Cursor, VS Code, Codex and other clients

Any client that launches local servers takes the same command, arguments and environment variables. Asked about Google's Antigravity IDE, a maintainer answered: "The config is the same as in the setup in the readme." Clients that only connect to remote URLs need a hosted alternative.

The 9 Google Analytics MCP tools

The README lists seven tools, but release 0.7.0 registers nine in its tool list, adding list_property_annotations and run_conversions_report.

Tool What it returns
get_account_summaries Every Google Analytics account and property your credentials can access
get_property_details Details of one property
list_google_ads_links Google Ads accounts linked to a property
list_property_annotations The property's annotations: notes on dates or periods such as releases or campaign launches
get_custom_dimensions_and_metrics The property's custom dimensions and metrics
run_report A Data API report: dimensions, metrics, date ranges, filters and sort order, 10,000 rows by default and up to 250,000
run_realtime_report Activity from the last 30 minutes; realtime reports can't use custom metrics
run_funnel_report A funnel from event or filter steps, with optional breakdowns, next actions and segments
run_conversions_report Attributed conversions, ad clicks, ad cost and return on ad spend, with data-driven or last-click attribution

The realtime tool doesn't expose minute ranges, so it always gets the API's default window of the last 30 minutes (realtime docs). Funnel and conversions reports use the Data API's alpha version: Google warns that funnel reporting may see breaking changes and that conversion reporting "may not be available to your Google Analytics property" (funnel docs, conversions docs).

Questions to ask it

Google's developer page and README suggest prompts like "How many users did I have yesterday?", "What were my top selling products yesterday?" and "Were most of my users in the last 6 months logged in?" The newer tools open up a few more:

  • "Build a funnel from view_item to add_to_cart to purchase for last month, broken down by device category." (run_funnel_report)
  • "Traffic dropped in the second week of August. Are there annotations for that week?" (list_property_annotations, then run_report)
  • "Compare return on ad spend by campaign under last-click and data-driven attribution for Q3." (run_conversions_report)

Start by asking for your account summary, so the assistant picks up the numeric property IDs every other tool needs.

Common errors and fixes

Each fix comes from Google's documentation or a Google maintainer's answer in the repository's issue tracker.

"This app is blocked" or "Access blocked: Authorization Error" at login. Usually the Analytics scope went through gcloud's default client. Create your own OAuth client (step 3) and pass it with --client-id-file (ADC troubleshooting).

"Access blocked: [app] has not completed the Google verification process." The OAuth app is in Testing and your account isn't a test user. Add it under Audience (audience docs).

"Unrecognized credential type." GOOGLE_APPLICATION_CREDENTIALS points at the OAuth client JSON you downloaded; Google says "Client ID files are not supported to provide credentials for ADC." Point it at the file gcloud wrote in step 4.

Errors about a missing quota project, an API that isn't enabled, or project 764086051850. Your credentials have no quota project (764086051850 is gcloud's own project). Run gcloud auth application-default set-quota-project YOUR_PROJECT_ID, or set GOOGLE_CLOUD_QUOTA_PROJECT in the server's env, which Google's auth library reads as the quota project.

Permission errors on one property while others work. The account behind the credentials has no access to it. The README requires "a user with access to your Google Analytics accounts or properties", so grant access in GA's Admin settings or log in as a different account.

It worked for a week, then stopped authenticating. The OAuth app is still in Testing, where authorizations expire after seven days. Rerun the step 4 login, or publish the app.

The server is missing in Claude Desktop, or tool calls hang. A maintainer's checklist: use the full path in GOOGLE_APPLICATION_CREDENTIALS, set the project variable, quit Claude completely and reopen it, and check that pipx works with pipx run cowsay -t mooooo. If the log says pipx can't be found, use its full path (from which pipx) as the command. Release 0.4.0 fixed tool-call deadlocks and 0.6.0 a Windows-specific one (both May 2026), so make sure you're on a current version.

Gemini CLI seems to hang after your question. It is probably waiting on its "Allow execution of MCP tool" prompt; a maintainer notes that Gemini won't proceed until you pick an option. Run gemini --debug for detail.

Answers cut off, or Claude Code warns about output size. Claude Code warns above 10,000 tokens of tool output and caps it at 25,000 by default (Claude Code docs). A run_report with no limit returns up to 10,000 rows, so ask for the top 20 or 50, or raise MAX_MCP_OUTPUT_TOKENS.

Limitations of Google's GA4 MCP server

  • Read-only by design. It can't create key events, custom dimensions, audiences or annotations, or change any setting.
  • Local only. Everyone who wants it installs Python, the gcloud CLI and their own credentials. There's no shared endpoint for a team, and claude.ai, Claude's mobile apps and other remote-only clients can't use it.
  • Experimental, and moving. Eight releases shipped between August 2025 and July 2026 (PyPI history), and the annotations, funnel and conversions tools call alpha APIs.
  • Broader credentials than it needs. gcloud's login requires the cloud-platform scope alongside the Analytics one. The server asks for read-only Analytics access, but a Google maintainer explains that this narrowing only applies to service account credentials; user credentials keep every scope they were created with. Treat the credentials file like a password.
  • GA's quotas apply. A standard property gets 200,000 core tokens a day, 40,000 an hour (at most 14,000 from any one Cloud project) and 10 concurrent requests, with separate, equal buckets for realtime and funnel requests. Most requests cost 10 tokens or fewer; long date ranges and high-cardinality dimensions such as pagePath cost more, and every reporting tool accepts return_property_quota to show the cost. Analytics 360 gets ten times the token quota (our Google Analytics pricing guide covers what 360 costs).
  • GA's data caveats come along. Results can be sampled, thresholded or folded into an "(other)" row, and each response flags these in its metadata (samplingMetadatas, subjectToThresholding, dataLossFromOtherRow). Ask the assistant to check before quoting a number.
  • Consent gaps stay in the data. GA4 identifies visitors with a _ga cookie that lasts two years (cookie docs). Where you ask for consent, visitors who decline are missing from every report, unless your property qualifies for Consent Mode's behavioral modeling, which requires, among other things, at least 1,000 denied events and 1,000 consenting users a day.
  • Analytics only. None of the nine tools reads Google Search Console. For search data, see our Google Search Console MCP guide.

Alternatives to Google's server

Google's server Stape Zapier MCP Rybbit MCP
Where it runs Your machine Stape's servers Zapier's servers Rybbit Cloud, or your own server if you self-host
Login gcloud credentials plus your own OAuth client Google OAuth in the browser Zapier account with Google Analytics connected OAuth, or an API key in any client
Data it reads Your GA4 properties Your GA4 properties Your GA4 properties Rybbit's own tracking, plus Search Console
Can it write? No No Some GA4 actions Goals, funnels, sites, members, teams, user traits
Tools 9 The same 9 6 GA4 actions 44
Price Free Free Included in Zapier plans; 2 tasks per tool call API access on Standard (from $19/month) and Pro; free to self-host

Stape: Google's server, hosted for you

Stape, a server-side tagging company, hosts a version at https://mcp-google-analytics.stape.io/mcp. Its repository describes a fork of Google's server under the same Apache-2.0 license, with the same nine tools, read-only through the analytics.readonly scope. You log in with Google OAuth in the browser, so there's no Google Cloud project, gcloud or pipx, and Stape says it is "available for free". It works wherever remote servers do, including as a custom connector on claude.ai, and in Claude Code:

bash
claude mcp add --transport http ga4-mcp-server https://mcp-google-analytics.stape.io/mcp

The trade-off is spelled out in Stape's own README: with the hosted server, your data passes through mcp-google-analytics.stape.io; with a local install, it goes straight from your machine to Google.

Zapier MCP: a few GA4 write actions

Zapier's Google Analytics 4 MCP exposes six actions: Create Conversion Event for a Property, Create Measurement (a measurement secret), Run Report for a Property, Send Measurement Events for an Application, Find Conversion, and a beta raw API Request. It's the only option here that can change something in GA4, though reporting is a single action rather than nine specialized tools.

Zapier MCP is included in every Zapier plan, and "each tool call uses two tasks" from your plan's quota. Zapier's pricing page lists a Free plan with 100 tasks a month, about 50 tool calls, and paid plans from $19.99 a month billed yearly.

Ask Advisor, inside Google Analytics

For quick questions about one property, Google's Ask Advisor is a Gemini-powered assistant built into GA, with nothing to install. It's in beta, currently limited to properties whose language is English, and it doesn't feed GA data into Claude or your own agents.

Rybbit: a hosted MCP server for cookieless analytics

Rybbit's MCP server isn't a Google Analytics connector: it reads what Rybbit, the analytics tool we build, collects with its own script. It suits people who'd rather not run a local server with Google Cloud credentials, and people wondering whether GA's consent-banner gaps are worth keeping.

  • Hosted, with OAuth. Claude Code, Claude Desktop (as a custom connector, which also works on claude.ai and mobile), Codex and opencode log in with OAuth; other clients use an API key, optionally scoped to resource:action permissions such as analytics read-only (MCP docs). In Claude Code: claude mcp add --transport http rybbit https://app.rybbit.io/api/mcp.
  • 44 tools that read and write. Overviews, breakdowns, funnels, retention, journeys, Web Vitals, errors, sessions and user profiles; write tools for goals, funnels, sites, members, teams and user traits; and run_query, read-only ClickHouse SQL. Session replay isn't available over MCP.
  • Search Console in the same chat. On Rybbit Cloud, once a site admin connects Search Console under Site settings > Integrations, get_search_console_status and get_search_console_data return clicks, impressions, CTR and position by query, page, country, device or date, with filters, up to 25,000 rows per call. No URL Inspection, sitemaps or indexing requests.
  • Cookieless tracking. Rybbit is cookieless by default, so its own tracking needs no consent banner, and the cloud is EU-hosted.

The limits, plainly. Rybbit can't import GA history: its importers cover Plausible, Umami and Simple Analytics only, so switching means running both side by side for a while. If your reporting leans on GA's Google Ads data, keep GA. Standard starts at $19 a month for 100k events and Pro at $39, after a 7-day trial with the card collected at signup. Rybbit is open source under AGPL-3.0, and self-hosted installs include the MCP endpoint (the Search Console tools are Cloud-only). Our Google Analytics comparison goes feature by feature, and the best Google Analytics alternatives roundup covers the rest of the field.

Which option should you use?

For servers beyond GA4 (PostHog, Mixpanel, Amplitude and others), see our comparison of the best analytics MCP servers.

  • Google's server if you're comfortable with gcloud, want requests to go straight from your machine to Google, and only need to read.
  • Stape for the same nine tools on claude.ai or without a local install, if a third party in the data path is acceptable.
  • Zapier if you already pay for it, or need a GA4 write action such as creating a conversion event.
  • Ask Advisor for quick questions inside GA.
  • Rybbit if you're open to changing the analytics tool itself and want a hosted MCP server with write tools and Search Console. Not if you need your GA history or Google Ads data in one place.

FAQ

Is there an official Google Analytics MCP server?
Yes. Google's Analytics team publishes it on GitHub as googleanalytics/google-analytics-mcp and on PyPI as analytics-mcp, and Google lists it among its official MCP servers. The README labels it experimental.

Is the Google Analytics MCP server free?
Yes. It's open source under Apache-2.0. Requests count against the Data API's token quotas: 200,000 core tokens a day on a standard property.

Can the GA4 MCP server change my Google Analytics settings?
No. Google says it is "available for read requests only" and "can't edit your Google Analytics configuration or settings." Of the options above, only Zapier's GA4 MCP has write actions.

How do I use the GA4 MCP server with Claude?
In Claude Code, run Google's claude mcp add analytics-mcp command with your credentials path and project ID. In Claude Desktop, add the analytics-mcp block to claude_desktop_config.json and restart. Claude on the web and mobile can't run local servers, and Google doesn't host one, so use a hosted server such as Stape's there.

Does it work with Universal Analytics?
No. It uses the Data API, which Google says doesn't support Universal Analytics properties.


Want an MCP server with no Google Cloud project to set up? Start a Rybbit trial, connect it to Claude with one command, or read the MCP docs first.


Content Blog Google Analytics Pricing (`docs/content/blog/google-analytics-pricing.mdx`)


title: "How Much Does Google Analytics Cost? GA4 vs GA 360 Pricing" keyword: "google analytics pricing" date: 2026-09-13 tags: ["google analytics", "pricing", "comparisons"] author: Rybbit Team

Google Analytics 4, the standard version, is free. Google's product page says it gives you the tools "free of charge", and there is no usage fee at any traffic level. Google Analytics 360 is the paid enterprise tier: higher limits, SLAs and multi-property features, sold by quote through Google sales and authorized resellers at a reported starting price of $50,000 per year. The real cost of the free version sits elsewhere: the visitors you lose to consent banners, the retention and sampling limits, the BigQuery bill once you export, and the hours it takes to set up and learn.

One disclosure up front: we build Rybbit, which appears in the alternatives table at the end. All prices are as of September 2026.

Is Google Analytics free?

Yes. Standard GA4 has no subscription, no traffic-based fee and no paid upgrade inside the product; the only paid version is Analytics 360, a separate enterprise contract. It is the same product for a hobby blog and a company, and Google doesn't restrict commercial use.

What "free" doesn't mean is unlimited. Standard properties get lower limits on retention, sampling, exports, custom dimensions and API quotas, and those limits are why large companies upgrade.

What the free version includes and where it stops

Every row below comes from Google's own help pages, checked in September 2026.

Limit GA4 standard (free) Google Analytics 360
Event data retention (explorations and funnel reports) 2 or 14 months 2, 14, 26, 38 or 50 months
Sampling threshold per query 10 million events 100 million by default, up to 1 billion with "More detailed results"; unsampled explorations are 360-only
BigQuery daily export 1 million events per day (streaming export has no limit) "Billions of events" per day
Event parameters per event 25 100
Event-scoped custom dimensions / metrics 50 / 50 125 / 125
Key events (conversions) 30 50
Audiences 100 400
Data API quota 200,000 tokens per property per day, 40,000 per hour, 10 concurrent requests 2,000,000 per day, 400,000 per hour, 50 concurrent requests
Intraday data freshness 2–6 hours About 1 hour
Expanded data sets for the "(other)" row Not available Expand up to 2 million rows, 100 per property
Subproperties and roll-up properties Not available 360 only (roll-up events billed separately)
Service level agreements None SLAs "from data collection to attribution", including the BigQuery daily export

Three of these deserve a closer look.

Retention. The setting only controls event-level data used in explorations and funnel reports; Google states it does not affect standard aggregated reports. Standard traffic reports keep working past 14 months, but you can't build an exploration over two years of data on a free property. New properties default to 2 months, so change the setting the day you create one.

Sampling. Google says sampling "may occur when the number of events used to create a report, exploration, or request exceeds the quota limit", which is 10 million events per query on a standard property. In practice it bites in explorations and API pulls over long date ranges on busy sites.

What isn't limited. Web data streams have no limit on distinctly named events, and there is no monthly event cap. High-traffic sites can stay on the free tier; they live with sampling, the export cap and the "(other)" row instead.

Google Analytics 360 pricing

Google does not publish a price list for Analytics 360. The Analytics 360 page has no prices and one call to action: "Talk to sales". It is sold by Google's sales team and by Google Marketing Platform sales partners such as InfoTrust and Cardinal Path, which describe themselves as official resellers.

Because there is no price list, the numbers that circulate are reported ones:

  • Cardinal Path, a GA 360 reseller, wrote in August 2022 that Google "suggests for the cost of Google Analytics 4 360 to start at a retail price of USD $50,000/year, which entitles customers to 25 million events per month", with usage-based pricing above that volume.
  • Vision Labs, a Google Cloud partner, publishes a GA 360 cost calculator (undated, checked September 2026) with a base of $4,200 per month for 25 million events plus $17.50 per additional million, and says organizations typically invest "anywhere from $50,000 to over $150,000 annually".
  • Under the older Universal Analytics model, InfoTrust quoted $150,000 per year for up to 500 million hits per month (April 2020), plus a suggested $30,000–$50,000 for implementation.

Treat these as reported figures. Your quote depends on event volume, property count and support level, and contracts are typically annual. Two line items come on top: roll-up property events are billed at half the rate of the source property, and implementation is usually a separate agency engagement.

What the money buys, per Google's 360 documentation: retention up to 50 months, unsampled explorations, BigQuery exports of billions of events per day, 100 event parameters and 125 custom dimensions, subproperties for access control, roll-ups for multi-brand reporting, intraday data in about an hour, and SLAs from collection to the BigQuery export. For a large company already on the Google Marketing Platform, that list is the product.

The hidden costs of free GA4

None of this appears on an invoice from Google, which is why it's easy to miss.

GA4 sets first-party cookies that last two years to distinguish users and sessions. France's regulator CNIL is blunt about what that means: operators must obtain consent before setting trackers, and "most large audience measurement offerings do not fall within the scope of the exemption". Every visitor who ignores or declines the banner is invisible to your reports.

How many? It varies widely by country, industry and banner design. Didomi's consent benchmark, built on hundreds of millions of banner interactions collected throughout 2025, puts opt-in rates (opt-ins divided by banners displayed) between 55.7% in Western Europe and 67.6% in Eastern Europe. For a Western European audience, roughly four in ten banner impressions never become a tracked visitor. That gap costs you twice: in the campaign you can't attribute, and in the tool you buy to fill it.

Setup and agency time

GA4 is an event model with a reporting layer on top. Someone has to design the event taxonomy, register custom dimensions within the 50-dimension cap, mark key events, fix the retention setting, link Google Ads and Search Console, and build explorations for whatever the standard reports don't answer. Small teams do this in evenings; larger ones pay an agency. The InfoTrust figure above ($30,000–$50,000 for an enterprise implementation) marks the top end.

BigQuery storage and query costs

Exporting raw events to BigQuery is the standard fix for sampling and retention limits, and the export is free on the Analytics side. BigQuery is not. The free tier covers 1 TiB of queries and 10 GiB of storage per month; the sandbox caps storage at 10 GiB for life, expires every table after 60 days and doesn't support streaming, so the unlimited streaming export needs billing enabled. Google's export documentation says you need a valid form of payment on file and that BigQuery charges for storage and query processing. A busy site keeping years of raw events with dashboards on top will see a monthly Cloud bill, and someone has to write the SQL.

Training and complexity

The standard reports answer a fixed set of questions. Anything else means learning explorations, segments, the "(other)" row, and which reports are sampled. That curve is paid in hours, and for a marketing team it's often the difference between using the tool and glancing at it.

This is a factual summary, not legal advice. In 2022, three EU data protection authorities found that sending Google Analytics data to the US violated the GDPR's transfer rules: Austria's DSB on 13 January, France's CNIL on 10 February, which gave the website operator one month to comply, and Italy's Garante in June, which gave 90 days. The ground shifted on 10 July 2023, when the European Commission adopted the EU–US Data Privacy Framework adequacy decision, and again on 3 September 2025, when the EU General Court dismissed a challenge to it. Transfers to certified US companies currently rest on that framework; cookie consent is a separate rule and hasn't changed.

The cost is compliance work: a consent management platform, a data processing agreement, a privacy policy that describes the transfer, and legal time whenever the framework is challenged again.

When free GA4 is enough

Plenty of teams should stay on the free tier:

  • You run on Google Ads. The Ads integration, audiences and key-event import are the strongest reasons to use GA4, and no alternative replicates them fully.
  • Your traffic is under the sampling line. If your explorations rarely touch 10 million events, sampling and the 1-million-event export cap don't affect you.
  • You have an analyst comfortable in BigQuery. The export plus SQL turns GA4 into a capable warehouse, and the bill for a mid-size site is often small.
  • You track a website and a mobile app together. GA4's shared event model across web and Firebase is hard to match.
  • You already have consent infrastructure and have accepted the data loss that comes with it.

If none of those apply, compare the free tier's real price with what paid tools charge.

What paid alternatives cost

Prices below come from each vendor's pricing page as of September 2026. Rybbit is our product; the other numbers are the vendors' own.

Tool Starting price What it covers Trial or free tier
Rybbit $19/month (Standard) 100k events/month, 5 sites, 3 team members, 3-year retention; Pro from $39/month adds session replay, unlimited sites and members, 5-year retention; 4 months free on annual billing 7-day free trial; a card is collected at signup and nothing is charged until the trial ends
Plausible $9/month (Starter) 10k pageviews/month; Growth $14, Business $19 30-day trial, no card
Fathom $15/month 100k pageviews/month, rising to $470/month at 25M 7-day trial; no free plan
Matomo Cloud €29/month 50k hits/month Free trial, no card
Simple Analytics $20/month Slider from 100k pageviews; 1 user, $20 per extra user 14-day trial, no card; free plan with 1 user, 5 sites and 1 month of history
PostHog Usage-based 1M events/month free, then $0.00005/event for 1–2M, falling to $0.000009/event above 250M Free tier, no card

Self-hosting is free for Rybbit, Plausible's Community Edition, Matomo's On-Premise edition and PostHog's open-source build, which PostHog labels officially unsupported.

The entry price of a paid tool is $9–$29 per month, less than an hour of agency time, and what you're paying for is mostly the removal of the hidden costs: a cookieless tool like Rybbit sets no tracking cookies, so the traffic a GA4 banner hides shows up, there is no sampling line, and Standard plans keep three years of data. Our Google Analytics comparison goes feature by feature, and if you're weighing several tools, see the best Google Analytics alternatives and best web analytics tools roundups.

FAQ

Is Google Analytics free?
Yes. The standard version of Google Analytics 4 is free with no usage fees. The paid version is Google Analytics 360, an enterprise contract sold by quote.

How much is Google Analytics 360?
Google doesn't publish a price. A GA 360 reseller reported a starting retail price of $50,000 per year for 25 million events per month in 2022, and a partner calculator puts typical contracts between $50,000 and more than $150,000 per year.

Does GA4 have a paid version?
Yes. Google Analytics 360 runs on GA4 properties with higher limits (50-month retention, a 1-billion-event sampling threshold, billions of BigQuery events per day, 125 custom dimensions), subproperties, roll-ups, expanded data sets and SLAs.

Is Google Analytics free for business?
Yes. There is no separate business license; companies of any size use the free tier. Businesses pay in other ways: consent-banner data loss, BigQuery costs if they export, setup and training time, and compliance work.

What are the limits of free Google Analytics?
The ones that matter most: 14 months of event data for explorations (2 months by default), sampling above 10 million events per query, a BigQuery export of 1 million events per day, 25 event parameters per event, 50 custom dimensions, 30 key events, 100 audiences and 200,000 Data API tokens per day. Standard reports are not affected by the retention setting.


Want to see what the banner has been hiding? Start a free Rybbit trial or open the live demo. Setup is one script tag.


Content Blog Google Search Console Mcp (`docs/content/blog/google-search-console-mcp.mdx`)


title: "Google Search Console MCP: Setup Guide and 5 Options Compared" keyword: "google search console mcp" date: 2026-09-27 tags: ["mcp", "seo", "ai", "comparisons"] author: Rybbit Team

A Google Search Console MCP server lets an AI assistant such as Claude, Cursor or ChatGPT read your Search Console performance data (clicks, impressions, click-through rate and average position by query, page, country, device and date) by calling Google's Search Console API when you ask it a question. As of September 2026 Google doesn't publish one, so every option is third-party, and they come in three kinds: open-source servers you run on your own machine with your own Google Cloud credentials, hosted connectors you sign in to, and analytics tools that include Search Console in their own MCP server. This guide compares five of them on setup effort, capabilities and cost, then walks through a setup and the prompts that work.

Disclosure: we build Rybbit, one of the five. Every claim about the other four comes from that project's own README or the vendor's own documentation and pricing pages, linked inline and checked in September 2026.

Quick comparison

Option Type What you set up Beyond performance data Other data in the same MCP Price (Sept 2026)
mcp-gsc Self-run, open source (MIT) Google Cloud project, OAuth client or service account, local config URL Inspection, sitemaps None Free
suganthan-gsc-mcp Self-run, open source (Apache 2.0) Google Cloud OAuth client, then a setup wizard URL Inspection, sitemaps, Indexing API submissions None Free
Windsor.ai Hosted connector Windsor account, Search Console connection, connector with OAuth Sitemap status; computed fields such as branded vs non-branded 350+ sources, including GA4 and Google Ads Free plan (1 source, 30 days of history); Basic $23/mo
Coupler.io Hosted connector Coupler account, a Search Console data flow, connector with a token URL Inspection and index coverage data 400+ sources Free plan (100 rows per run); Starter $32/mo
Rybbit Web analytics with a hosted MCP server Connect Search Console in site settings, add one URL to your client None Rybbit's own analytics: sessions, goals, funnels, errors From $19/mo, 7-day trial

What a Google Search Console MCP server can answer

The Model Context Protocol (MCP) is an open standard for connecting AI assistants to outside tools. An MCP server publishes tools with descriptions; the assistant picks one, fills in the arguments and reads the result. A Search Console MCP server wraps Google's Search Console API, which has three parts that matter here:

  • Search Analytics, the data behind the Performance report: clicks, impressions, CTR and average position, grouped by query, page, country, device, date, search appearance or hour, filtered by exact, substring or regex matches, across web, image, video, news, Discover and Google News. One request returns up to 25,000 rows (API reference).
  • URL Inspection: Google's indexing status for one URL, capped at 2,000 inspections per property per day (usage limits).
  • Sitemaps: list, submit and delete.

Google says all use of the API is free of charge within those limits. Requesting indexing is a separate API, the Indexing API.

That covers questions like "which queries does this page rank for", "which pages lost clicks since last month" or "is this URL indexed". It won't give you search volume for keywords you don't appear for, backlinks, or competitors' rankings; Search Console only knows about your own property.

Is there an official Google Search Console MCP server?

No, not as of September 2026. Google's list of official MCP servers covers Google Analytics, BigQuery, Maps, Firebase and a long list of Google Cloud products, but not Search Console, and the Search Console API documentation doesn't mention one.

What Google does publish is a Google Analytics MCP server, a read-only server built for GA4 data; we cover it in our Google Analytics MCP guide and compare it with other vendors' servers in best analytics MCP servers. (If GA4 itself is what you're weighing, see Rybbit vs Google Analytics.) Every Search Console MCP server below is third-party code calling Google's public API, so the real choice is who holds your Google credentials and how much setup you do.

Google Search Console MCP servers compared

mcp-gsc

mcp-gsc is an MIT-licensed Python server that runs on your computer with your own Google credentials. Its README counts 20 tools: search analytics tools such as compare_search_periods and get_advanced_search_analytics (filters, sorting, and paging up to 25,000 rows per call); URL inspection with inspect_url_enhanced and batch_url_inspection (up to 10 URLs at once); check_indexing_issues; and tools to list, submit and delete sitemaps. Tools that add or delete properties or delete sitemaps stay off unless you set GSC_ALLOW_DESTRUCTIVE.

Setup starts in Google Cloud: create a project, enable the Search Console API, then create either an OAuth client ID of type Desktop app (you sign in through a browser on first use) or a service account that you add as a user on your Search Console property. Then install uv, add an entry that runs uvx mcp-search-console to your client's config with the credentials path as an environment variable, and restart the client. Because the server runs locally, the README notes it works in the Claude Desktop app but not on claude.ai in a browser; an SSE mode and a Dockerfile are there if you want to host it. The server code asks Google for the full Search Console scope (webmasters), not the read-only one.

  • Pros: free; URL inspection and sitemap tools; a ready-made period comparison
  • Cons: needs a Google Cloud project and credentials; local by default; Search Console data only; full-access Google scope
  • Price (as of September 2026): free and open source (MIT). Source: mcp-gsc README

suganthan-gsc-mcp

Suganthan's GSC MCP (suganthan-gsc-mcp on npm, Apache 2.0) is a Node server whose 29 tools lean toward finished analysis rather than raw rows: quick_wins (queries at positions 4 to 15 with high impressions), cannibalization_check, content_decay, traffic_drops, an eight-tool image-search suite, and genai_conversation_queries, which hunts for AI Mode follow-ups that Google logs as ordinary queries. It has inspect_url and sitemap tools, and it is the only one of the five that submits URLs to Google's Indexing API (submit_url, and submit_batch for up to 200 a day). Google restricts that API to pages with JobPosting or BroadcastEvent structured data (Indexing API docs), so for most sites it isn't a general "request indexing" button.

npx -y suganthan-gsc-mcp setup runs a wizard that signs you in, lets you pick a property and writes the config for Claude Desktop and Claude Code. You still need your own OAuth client JSON from a Google Cloud project, or a service account. The wizard defaults to read-only access and asks for full access only if you want the submission tools, and your data goes from your machine straight to Google.

  • Pros: free; ready-made SEO analyses; indexing and sitemap tools; read-only by default through the wizard
  • Cons: Google Cloud credentials still required; local only; Search Console data only
  • Price (as of September 2026): free and open source (Apache 2.0). Source: suganthan-gsc-mcp README

Windsor.ai

Windsor.ai is a data-connector service whose hosted MCP server (https://mcp.windsor.ai/) reaches 350+ sources, Search Console among them, alongside GA4, Google Ads and Meta Ads. You connect Search Console to a Windsor account, then add the server to your client. The server's README lists OAuth 2.0 sign-in, one-click listings in Claude's connector directory and as a ChatGPT app, and API keys for clients without OAuth. The tools are generic (get_connectors, get_fields, get_data). For Search Console, Windsor adds computed fields the raw API lacks: branded vs non-branded queries, words per query, the results page a query landed on, and URL path levels for rolling up by directory. Its Claude setup guide says it pulls Search Analytics and sitemap status (submitted vs indexed counts, errors and warnings) but not the URL Inspection API.

Pricing is per data source. The free plan starts as a 30-day trial, then keeps 1 user, 1 data source and 1 account, and on it "you can access and retrieve data from the past 30 days only", a real limit when Search Console holds 16 months. Basic is $23/month, or $19/month billed annually, for 3 data sources; Standard is $118/month. Every plan includes the MCP server.

  • Pros: no Google Cloud setup; OAuth in Claude and ChatGPT; Search Console next to GA4 and ad platforms
  • Cons: free plan reaches back 30 days; no URL Inspection; generic tools; priced per data source
  • Price (as of September 2026): free plan; Basic $23/month ($19/month billed annually). Source: windsor.ai/pricing

Coupler.io

Coupler.io syncs 400+ sources into spreadsheets, BI tools and AI clients, and its MCP sits on top of those syncs. You build a data flow from Search Console with an AI tool as the destination, then connect Claude, ChatGPT, Cursor, Gemini, Copilot or Perplexity, authenticated with a personal access token. Coupler's post on its MCP endpoint says the endpoint exposes your data flow and that you "automate data refresh on a schedule", so the assistant queries the data your flow last pulled rather than asking Google on demand. The Search Console MCP page lists search performance, performance by search appearance, Discover and Google News data, URL Inspection data and page-indexing coverage.

Coupler's pages disagree on the plan you need. The Search Console MCP page says "at least the Professional plan"; the Claude integration page says any active plan, "starting from free-forever"; and the pricing page has no Professional tier. It lists Free (1 account, manual refresh, 100 rows per run), Starter at $32/month or $24/month billed annually (daily refresh, 5,000 rows per run), Active at $132/month and Pro at $259/month (hourly refresh). New accounts get 7 days of full access.

  • Pros: no Google Cloud setup; URL Inspection and index coverage data; many other sources
  • Cons: scheduled snapshots, not live queries; row caps on Free and Starter; conflicting plan requirements
  • Price (as of September 2026): free plan; Starter $32/month ($24/month billed annually). Source: coupler.io/pricing

Rybbit

Rybbit is privacy-first web analytics with a hosted MCP server of 44 tools, two of which read Search Console. A site admin connects Search Console once under Site settings > Integrations on Rybbit Cloud, signing in with Google and picking the property. Rybbit asks Google for read-only Search Console access and keeps that grant for the site, so any teammate with access to the site can query the property from the dashboard or their own assistant, with no Google Cloud project, service account or Search Console access of their own.

get_search_console_status reports whether a property is connected and which one. get_search_console_data returns clicks, impressions, CTR and average position grouped by up to three of query, page, country, device and date, with up to 10 filters on query, page, country or device (equals, notEquals, contains, notContains, includingRegex, excludingRegex) across the web, image, video, news, discover and googleNews search types. It returns 100 rows by default and up to 25,000 per call, paged with start_row, and data_state: "all" adds fresh, still-changing numbers.

The difference from the other four is what sits next to those two tools: sessions, goals, funnels, user journeys, errors and Web Vitals for the visitors Google sent, plus read-only SQL over your raw events. "Which Google landing pages turn clicks into signups" becomes one conversation instead of two tools and a spreadsheet. OAuth sign-in works in Claude Code, Claude Desktop and claude.ai, Codex and opencode; other clients, including Cursor and VS Code, use an API key, which you can limit to scopes such as Search Console read and Analytics read. Rybbit is open source (AGPL-3.0) and self-hostable, but the Search Console connection is a Rybbit Cloud feature.

  • Pros: no Google Cloud project; read-only Google access; Search Console and on-site conversions in one assistant; one connection serves the whole team
  • Cons: a paid analytics product rather than a standalone connector; no URL Inspection, sitemaps or indexing; one property per site; no built-in period comparison (the assistant makes two calls)
  • Price (as of September 2026): Standard from $19/month for 100k events, Pro from $39/month; 7-day free trial with a card collected at signup. Source: rybbit.com/pricing

How to set up a Search Console MCP with Rybbit

You need a site on Rybbit Cloud with the tracking script installed, and a Google account that can see the site's Search Console property.

1. Connect Search Console to your site

  1. Open your site's Site settings, go to Integrations and click Connect Google Search Console. This needs admin access to the site.
  2. Sign in with Google and approve read-only access to Search Console.
  3. Pick the property, either a domain property (sc-domain:example.com) or a URL-prefix property, and click Connect Property.

Rybbit sends you back to the dashboard, where a Search Console card now shows top queries, pages, countries and devices.

2. Add the MCP endpoint to your client

The endpoint for Rybbit Cloud is https://app.rybbit.io/api/mcp. In Claude Code, add it:

bash
claude mcp add --transport http rybbit https://app.rybbit.io/api/mcp

Then run /mcp, select rybbit and choose Authenticate. A browser window opens for you to log in to Rybbit, and the connection acts with your user's role.

In Claude Desktop or claude.ai, open Settings > Connectors, click Add custom connector, name it Rybbit, paste the URL and click Connect. Anthropic offers custom connectors on every Claude plan, with Free users limited to one.

In Codex:

bash
codex mcp add rybbit --url https://app.rybbit.io/api/mcp
codex mcp login rybbit

In Cursor, create an API key in Rybbit under Settings > Account > Personal API Keys, then add it to ~/.cursor/mcp.json:

json
{
  "mcpServers": {
    "rybbit": {
      "url": "https://app.rybbit.io/api/mcp",
      "headers": {
        "Authorization": "Bearer <RYBBIT_API_KEY>"
      }
    }
  }
}

Any other client that speaks Streamable HTTP takes the same URL and header. The MCP docs have walkthroughs for Claude Code, Claude Desktop, Cursor, Codex, VS Code and opencode.

3. Check the connection

Ask: "Is Search Console connected for my Rybbit site, and which property?" The assistant calls list_sites to find the site ID, then get_search_console_status. If a later call returns a 404 saying GSC isn't connected, step 1 didn't finish.

Example prompts that work

Each prompt below maps to calls the tools can make. Give the assistant a date range, and remember Search Console only covers the last 16 months.

  1. "Which queries does /pricing rank for over the last 28 days, with clicks, impressions and average position?" One call grouped by query, filtered to pages containing /pricing.
  2. "Find pages with more than 1,000 impressions and a CTR under 1% in the last three months, and suggest new titles for the worst five." One call grouped by page. Google sorts rows by clicks, so ask for a few thousand rows; low-CTR pages sit further down the list.
  3. "Compare clicks by query for the last 28 days against the 28 days before. Which queries lost the most?" Two calls, one per date range; the assistant does the diff.
  4. "Show daily clicks and impressions for the last 90 days, and flag any drop that lines up with our annotations." One call grouped by date, plus get_annotations for the launches and deploys your team pinned to the traffic chart.
  5. "For our top 20 queries in the US, compare CTR and position on mobile and desktop." One call grouped by query and device, filtered to country US.
  6. "How is the post we published on Monday doing in Google? Include fresh data." A page filter with data_state: "all", which adds the recent days Google hasn't finalized yet.
  7. "Which of our top Google landing pages bring organic-search visitors who complete the Signup goal?" Search Console grouped by page, then get_goals filtered to the Organic Search channel and to each landing page.
  8. "Build a funnel for organic-search visitors: any blog post, then /pricing, then /signup, over the last 30 days. Where do they drop off?" analyze_funnel with a channel filter; page steps accept wildcards, so /blog/** matches every post.

For the first prompt, the call the assistant makes looks like this:

json
{
  "site_id": 1,
  "start_date": "2026-08-27",
  "end_date": "2026-09-23",
  "dimensions": ["query"],
  "filters": [{ "dimension": "page", "operator": "contains", "expression": "/pricing" }],
  "row_limit": 100
}

Limitations

Limits that come from Google

Every server on this list reads the same API, so these apply to all of them.

  • 16 months of history. Google suggests pulling anything you want to keep longer through the API or the bulk data export and storing it yourself.
  • Two to three days of lag. Google says data is typically available after 2–3 days. Fresher, still-changing numbers need the all data state, which mcp-gsc and suganthan-gsc-mcp use by default and Rybbit exposes as data_state: "all".
  • Missing rows. Rare queries are anonymized and left out of query tables, and the API returns top rows rather than every row, at most 50,000 per day per search type. Query rows won't add up to the property total.
  • Pacific Time. API dates are in Pacific Time, so daily figures won't line up exactly with analytics that report in your time zone.
  • AI Overviews and AI Mode are inside web search. Google reports them within the Performance report's Web search type, and the API reference has no search type for the separate Generative AI report added in June 2026, so no MCP server can read that report's numbers. Visits from ChatGPT, Perplexity or Claude never appear in Search Console at all; see how to track AI referral traffic.

Limits of Rybbit's Search Console tools

  • Search Analytics only. No URL Inspection, sitemaps or indexing requests. For URL Inspection and sitemap management use mcp-gsc; to submit URLs to the Indexing API, suganthan-gsc-mcp.
  • One property per site. Each Rybbit site connects to one Search Console property, and the assistant can't browse other properties in your Google account.
  • A subset of the API. Five dimensions (no search appearance or hour), final or all data (no hourly data), and no comparison tool, so the assistant calls once per date range. Discover and Google News don't support the query dimension.
  • Clicks aren't sessions. Search Console counts clicks on Google results; Rybbit's Organic Search channel counts sessions from every search engine, Bing and DuckDuckGo included. Expect the numbers to differ, and filter Rybbit by a Google referrer for a closer match.
  • Queries join through landing pages. Search Console reports queries in aggregate, not per visit, so the assistant connects queries to conversions through the pages they land on. Treat query-level conversion figures as estimates.
  • Not an SEO suite. No rank tracking beyond Search Console's average position, no keyword volumes, no backlinks.

Which GSC MCP server to pick

  • You want it free and can set up a Google Cloud project: mcp-gsc for raw access plus URL Inspection and sitemaps, or suganthan-gsc-mcp for ready-made SEO analyses and Indexing API submissions.
  • You want Search Console in claude.ai or ChatGPT without running a server, next to GA4 and ad platforms: Windsor.ai, keeping in mind its free plan reaches back only 30 days.
  • You already move data into sheets and dashboards with Coupler: Coupler.io, knowing the assistant reads scheduled snapshots.
  • You want to connect search data to what visitors did after the click, and give teammates access without Google Cloud credentials: Rybbit.
  • You need URL Inspection, sitemap management or indexing requests: not Rybbit. Use mcp-gsc or suganthan-gsc-mcp.

FAQ

What is a Google Search Console MCP server?
A program that exposes Search Console data to AI assistants through the Model Context Protocol. The assistant calls the server's tools, and the server queries Google's Search Console API with credentials you authorized.

Is there an official Google Search Console MCP server?
Not as of September 2026. Google's list of official MCP servers includes one for Google Analytics but none for Search Console.

Is there a free Google Search Console MCP?
Yes. mcp-gsc and suganthan-gsc-mcp are free and open source, and Google doesn't charge for the Search Console API; you supply the Google Cloud project and credentials. Windsor.ai's free plan also includes its MCP server, with 30 days of history.

Which GSC MCP server is easiest to set up?
The hosted ones. Windsor.ai, Coupler.io and Rybbit need no Google Cloud project: you sign in with Google, connect the property and add a URL or token to your client (Coupler also has you build a data flow first). Among self-run servers, suganthan-gsc-mcp's wizard is the shortest path, but it still needs an OAuth client JSON from Google Cloud.

Does a Google Search Console MCP work with Claude?
Yes. Remote servers such as Rybbit's are added as custom connectors, which Anthropic offers on every Claude plan (Free users get one), and Windsor.ai is listed in Claude's connector directory. Local servers such as mcp-gsc run through the Claude Desktop app's config file, not claude.ai in a browser.

Can ChatGPT use a Search Console MCP server?
Yes, in developer mode, which is available to Plus, Pro, Business, Enterprise and Education accounts on the web and connects to remote servers over SSE or streaming HTTP. Local servers have to be hosted somewhere reachable first. Windsor.ai publishes a ChatGPT app. Rybbit's endpoint uses OAuth, which developer mode supports, but Rybbit's docs don't have a ChatGPT walkthrough yet, so treat that path as untested.

Can a Search Console MCP server request indexing?
Only servers that call Google's Indexing API, such as suganthan-gsc-mcp, and Google limits that API to pages with JobPosting or BroadcastEvent structured data. Rybbit's Search Console tools are read-only.


Content Blog Hotjar Alternatives (`docs/content/blog/hotjar-alternatives.mdx`)


title: "10 Hotjar Alternatives in 2026: Heatmaps, Replay, Surveys" keyword: "hotjar alternatives" date: 2026-09-27 tags: ["hotjar", "comparisons", "session replay", "heatmaps"] author: Rybbit Team

Hotjar alternatives are worth a fresh look in 2026 because Hotjar has merged into Contentsquare, and new users can no longer create a Hotjar account. Hotjar's core toolkit covers four jobs: heatmaps, session recordings, on-site surveys and feedback widgets. This guide sorts the best Hotjar alternatives by which of those jobs they cover, with prices and limits taken from each vendor's own pages.

The short version: Microsoft Clarity replaces heatmaps and recordings for free but has no surveys. Mouseflow and Lucky Orange cover all four jobs in one paid tool. PostHog adds product analytics and surveys with a monthly free tier. OpenReplay and Matomo are the self-hosted options with heatmaps. Rybbit, which we build, suits teams that mostly watched recordings and want them next to funnels and conversion data; it has no heatmaps or surveys.

Disclosure: we build Rybbit. Every claim about the other tools comes from that vendor's own pricing or documentation pages, linked inline and checked in September 2026. Prices are the US-dollar figures those pages show; some vendors show euros to European visitors.

Hotjar is now Contentsquare

Contentsquare's Hotjar page puts it plainly: "Hotjar is now part of Contentsquare. The two platforms have merged into a single, more powerful experience intelligence platform," and "it's no longer possible to create a new account on Hotjar." Existing users can still log in, and hotjar.com/pricing redirects to Contentsquare's pricing page, where Hotjar's toolkit is now split across products:

  • Experience Analytics (heatmaps, replays, funnels, error monitoring). Free covers 200,000 sessions a month for the whole account, 10,000 replays and 1 project, with one month of data according to Contentsquare's free plan announcement. Growth starts at $49/month ($39 billed yearly) for 7,000 sessions and 7,000 replays, and costs $409/month at 100,000 sessions, where replays stay at 20,000. Replays are "a fixed amount per month — it can't be increased." Growth keeps session data for 13 months and replays for 2 months.
  • Voice of Customer (surveys and feedback widgets). Free includes 100 responses a month, with 3 surveys or widgets live at a time. Growth starts at $99/month ($79 billed yearly) for 500 responses.
  • Interviews and user tests (Hotjar Engage). These are "no longer available for purchase by new customers," per Hotjar's help center.

Contentsquare also sells Product Analytics as a third product, free up to 10,000 monthly sessions. Staying is reasonable if the free quotas fit and you want heatmaps, recordings and surveys behind one login. The reasons to look elsewhere are specific: replays stop scaling with sessions on paid plans (20,000 replays at 100,000 sessions), surveys are billed as a separate product, and the pricing page lists no self-hosted option.

Hotjar alternatives and competitors at a glance

Tool Heatmaps Recordings Surveys and feedback Free plan Paid from (Sept 2026) Self-host
Hotjar (Contentsquare) Yes Yes, 10k/mo on Free Yes, separate product Yes, 200k sessions $49/mo No
Microsoft Clarity Yes Yes No Yes, no traffic limits No paid plan No
Mouseflow Yes Yes Advanced plan and up Yes, 500 sessions $39/mo No
Lucky Orange Yes Yes Yes, unlimited No, 7-day trial $39/mo No
Crazy Egg Yes Paid plans Yes Yes, heatmaps and surveys $29/mo, billed yearly No
FullStory Yes Yes Paid add-on Yes, 30k sessions Quote only No
PostHog Yes Yes Yes Yes, 5k recordings Usage-based Free features only
LogRocket Yes Yes Yes No, 14-day trial $176/mo at 25k sessions Enterprise only
Rybbit No Pro plan, 10k/mo at 100k events No No, 7-day trial $39/mo (Pro) Yes, free
OpenReplay Yes Yes No Self-hosted only $199/mo (cloud) Yes, free
Matomo Plugin or Cloud Plugin or Cloud No Self-hosted core USD 26/mo (Cloud) Yes

"No" under self-host means the vendor's pricing page lists only hosted plans. PostHog's self-hosted build includes only its free-plan features and is officially unsupported. Matomo's heatmaps and recordings are a paid plugin when you self-host and are included in Matomo Cloud.

Alternatives to Hotjar by the feature you use

  • Heatmaps. Clarity gives you click and scroll maps for free with no traffic limits. Mouseflow, Lucky Orange and Crazy Egg are heatmap-first tools, and PostHog, FullStory, LogRocket, OpenReplay and Matomo include heatmaps next to their other features. Rybbit has none.
  • Recordings. Every tool here records sessions, which most vendors call session replay; Crazy Egg's free plan doesn't list them. Free allowances differ the most: Clarity is unlimited, Contentsquare Free has 10,000 replays a month, FullStory's free plan covers 30,000 sessions, PostHog 5,000 recordings and Mouseflow 500 sessions. If recordings are all you need from Hotjar, our session replay tools comparison goes deeper on masking, sampling and retention.
  • Surveys and feedback widgets. Lucky Orange includes unlimited surveys on every plan, and Crazy Egg includes them even on its free plan. PostHog gives 1,500 responses a month free, LogRocket lists surveys on its Core plan, Mouseflow starts at its Advanced plan, and FullStory sells them as an add-on. Clarity, OpenReplay, Matomo and Rybbit have no surveys.
  • Funnels and product analytics. Clarity, Mouseflow, Lucky Orange and Crazy Egg include conversion funnels. PostHog, FullStory, LogRocket, OpenReplay and Rybbit add event-based product analytics on top.
  • Interviews and user tests. Engage is no longer sold to new customers. PostHog's surveys can book user interviews from in-app templates; for recruiting participants and running tests, you'll need a dedicated research tool.

The best free Hotjar alternative

Microsoft Clarity

Microsoft Clarity is the usual answer to "free Hotjar alternative". Its FAQ says: "Clarity is a free service forever. You never encounter traffic limits or are compelled to upgrade to a paid version." There's no sampling and no limit on the number of sites. The feature list covers session recordings, heatmaps (click and scroll maps), event and funnel tracking, and Copilot summaries, and there are SDKs for Android, iOS, Flutter and React Native apps.

The limits are retention and consent. Recordings are kept for 30 days (favorites and a random sample for up to 9 months), heatmaps stay available for 9 months, and each heatmap draws on up to 100,000 page views. For visitors in the EEA, UK and Switzerland, Clarity expects a consent signal; without one, the FAQ says, sessions are fragmented and "full session replays and user journey continuity are unavailable."

  • Covers: heatmaps, recordings, funnels, AI summaries
  • Missing or limited: no surveys or feedback widgets; 30-day recording retention; consent signal needed for European visitors
  • Pricing (September 2026): free, with no paid plan. Source: Clarity FAQ

Closest like-for-like Hotjar replacements

Hotjar's help center groups its product into Observe tools (heatmaps and recordings) and Ask tools (surveys and feedback). These three cover both in a single product, at published prices.

Mouseflow

Mouseflow lines up most closely with Hotjar's feature set. Every plan, Free included, gets session replays, conversion funnels, friction detection, unlimited users, and click, scroll and geo heatmaps. Feedback surveys, with NPS scoring and branch logic, start at the Advanced plan, along with attention and movement heatmaps. Form analytics start at Premium and journey analytics at Enterprise, per the pricing page.

Plans are sized by monthly sessions. Free covers 500 sessions and 1 month of data. Essential is $39/month ($25 billed yearly) for 5,000 sessions and 3 months; Advanced is $149/month ($109 yearly) for 25,000 sessions and 6 months; Premium is $399/month ($319 yearly) for 100,000 sessions and 12 months. The 14-day trial needs no credit card.

  • Covers: heatmaps, recordings, funnels, friction detection; surveys and feedback from Advanced
  • Missing or limited: no surveys on Free or Essential; form analytics only from Premium
  • Pricing (September 2026): free for 500 sessions a month; paid from $39/month, or $25/month billed yearly. Source: mouseflow.com/pricing

Lucky Orange

Lucky Orange keeps packaging simple: every plan has every feature. The pricing page lists dynamic heatmaps, session recordings, unlimited surveys and announcements, live chat, a live visitor view, conversion funnels, and page and form analytics on all plans, with unlimited team members. Its Discovery AI is an add-on.

You pay by sessions. Build is $39/month ($32 billed yearly) for 3,500 sessions and Grow is $72/month billed yearly for 10,000. Expand is $199/month billed yearly ($249 monthly) from 50,000, and Scale is $839/month billed yearly ($1,049 monthly) from 300,000. Data is kept for 60 days by default, with 90, 180 or 365-day upgrades, and each extra site costs $5. There's no free plan, only a 7-day trial.

  • Covers: heatmaps, recordings, surveys, announcements, funnels, form analytics, plus live chat
  • Missing or limited: no free plan; 60-day default retention
  • Pricing (September 2026): from $39/month, or $32/month billed yearly, for 3,500 sessions. Source: luckyorange.com/pricing

Crazy Egg

Crazy Egg is heatmap-first and prices by tracked pageviews, with a separate, smaller recording allowance. According to its pricing page, the free plan needs no credit card and includes web analytics, instant heatmaps, conversion analytics and on-site surveys. Paid plans are billed annually. Starter is $29/month for 5,000 tracked pageviews, 5 heatmap reports and 50 recordings a month. Plus is $99/month for 150,000 pageviews, 75 heatmap reports and 1,000 recordings, and adds A/B testing and error tracking. Pro is $249/month for 500,000 pageviews and 5,000 recordings.

Every paid plan includes unlimited surveys and responses, unlimited domains and team members, and a 30-day free trial. Heatmaps track 100% of pageviews on the pages you target unless you set a lower sample rate.

  • Covers: heatmaps, surveys, funnels, A/B testing (Plus and up); recordings in small volumes
  • Missing or limited: 50 recordings a month on Starter and 1,000 on Plus; no monthly billing
  • Pricing (September 2026): free plan with heatmaps and surveys; paid from $29/month billed annually. Source: crazyegg.com/pricing

Hotjar alternatives with product analytics

These tools are built around event data (funnels, retention, user paths) and attach recordings to it, which fits questions about conversion and retention across sessions better than questions about one page's layout.

PostHog

PostHog is the broadest single replacement on this list: heatmaps, session replay, surveys and product analytics in one product, each with a free monthly allowance. The pricing page lists 1 million analytics events, 5,000 recordings and 1,500 survey responses free every month, with the same allowance whether or not a card is on file. Beyond that, web recordings cost $0.005 each and fall to $0.0015 above 500,000 (replay pricing), and survey responses start at $0.10 each (survey pricing). Surveys can run as popovers, behind a feedback button, or as hosted links.

Heatmaps show clicks, dead clicks, rage clicks, mouse movement and scroll depth, and PostHog says heatmap data "doesn't contribute to your bill". Recordings are kept for 1 month on the free plan and 3 months on pay-as-you-go.

Self-hosting is limited. The MIT-licensed Docker deployment is "officially unsupported", wants roughly a 4 vCPU, 16 GB RAM machine, and "All paid-plan features are Cloud-only", per the self-hosting docs. We compare the two products in Rybbit vs PostHog.

  • Covers: heatmaps, recordings, surveys and a feedback button, funnels and product analytics
  • Missing or limited: per-recording and per-response billing past the free tier; self-hosting unsupported
  • Pricing (September 2026): free tier renewed monthly, then usage-based. Source: posthog.com/pricing

FullStory

FullStory is the enterprise option. FullstoryFree, a permanent free plan with no credit card, includes "30,000 sessions per month with 12 months of data retention" for up to 10 users, covering session replay, basic analytics and debugging tools, per the plans page. The paid Business, Advanced and Enterprise plans are sold through "Request pricing & demo", and the plan table lists click and scroll maps, funnels, dashboards, journeys and retention analysis. Surveys come with the Guides and Surveys add-on, and mobile analytics is an add-on too.

  • Covers: recordings, heatmaps, funnels and product analytics; surveys as an add-on
  • Missing or limited: no published prices above the free plan
  • Pricing (September 2026): free for 30,000 sessions a month; paid plans by quote. Source: fullstory.com/plans

If FullStory itself is what you're replacing, see our FullStory alternatives guide.

LogRocket

LogRocket records sessions along with "console and network logs, errors, warnings, crashes and performance data", per its docs, and adds product analytics on top. Its pricing page charges only for sessions captured; analytics events, errors, logs and network data are unlimited. The Core plan lists click maps, heatmaps and rage-click maps, path analysis, conversion funnels, alerting, SSO and surveys. Pro adds the Galileo AI layer and is "included free above 100K sessions / mo".

The pricing calculator shows $176/month at 25,000 sessions. There's no free plan, only a 14-day trial that needs no credit card, and self-hosting is an Enterprise option.

  • Covers: recordings with console and network data, heatmaps, surveys, funnels, error tracking
  • Missing or limited: no free plan; self-hosting only on Enterprise
  • Pricing (September 2026): $176/month at 25,000 sessions. Source: logrocket.com/pricing

Rybbit

Rybbit is the only tool on this list without heatmaps: no click or scroll maps, and no surveys or feedback widgets either. What it does have is session replay inside a full web and product analytics tool: funnels, user journeys, goals, error tracking, Web Vitals, retention and user profiles. The replay timeline marks clicks, page navigations, typing and rage clicks, so you can jump straight to the moment a visitor got stuck. Input values are masked by default, the analytics are cookieless, and Rybbit Cloud is hosted in the EU.

It fits teams that mostly watched recordings in Hotjar and read them against conversion numbers. If heatmaps are why you open Hotjar, Clarity or Mouseflow will serve you better. If it's surveys, stay on Contentsquare's Voice of Customer or use Mouseflow's feedback surveys.

On Rybbit Cloud, replay is a Pro feature: from $39/month for 100k events, or $26/month billed yearly. Pro's replay quota is 10% of its event allowance, so 10,000 replays a month on the 100k tier. Standard, from $19/month, includes the analytics but not replay. Both start with a 7-day trial, and the card is collected at signup. Cloud replays are kept for 30 days. Rybbit is open source under AGPL-3.0, and self-hosting is free with replay included. There's no Hotjar importer; data import covers Plausible, Umami and Simple Analytics.

  • Covers: recordings with rage-click markers, funnels, journeys, goals, error tracking, Web Vitals
  • Missing or limited: no click or scroll heatmaps, surveys or feedback widgets; replay needs the Pro plan on cloud, with a monthly replay quota
  • Pricing (September 2026): Pro, with replay, from $39/month; self-hosting free. Source: rybbit.com/pricing

Open-source and self-hosted Hotjar alternatives

If recordings can't leave your servers, these two self-host with heatmaps included. PostHog's self-hosted build (above) covers its free-plan features, and Rybbit self-hosts replay and analytics but has no heatmaps.

OpenReplay

OpenReplay is an open-source session replay and product analytics suite; most of the code is AGPL-3.0, with a separately licensed ee/ directory, per the license file. Its pricing page marks session replay, developer tools, product analytics, co-browsing and bug reports as included in the free self-hosted edition, and the heatmaps show clicks and rage clicks per page. The managed option is a Dedicated instance from $199/month, billed hourly at $0.276, "with no limits on users, recordings, or retention", with a 7-day trial and no credit card.

  • Covers: recordings with developer tools, heatmaps, funnels and journeys, co-browsing
  • Missing or limited: no surveys or feedback widgets; you run the servers
  • Pricing (September 2026): self-hosted free; Dedicated cloud from $199/month. Source: openreplay.com/pricing

Matomo

Matomo is a Google Analytics alternative first, with Hotjar-style features on top. Its Hotjar comparison page says self-hosted users can buy heatmaps, session recordings, funnels and form analytics from the Matomo Marketplace, and that "all these features are included in the Matomo Cloud Business plan." It is just as clear that Matomo "doesn't currently offer feedback or surveys tools."

On Matomo's pricing page, Cloud is priced by traffic: the 50,000-hit tier is USD 26/month (excluding tax) and includes 1,500 heatmap pageviews and 150 session recordings a month, with data stored in Frankfurt. The self-hosted core is free, and the heatmap and recording plugin has a 30-day trial. See how it compares with Rybbit in Rybbit vs Matomo.

  • Covers: heatmaps, recordings, funnels, form analytics, full web analytics
  • Missing or limited: no surveys or feedback; small recording quotas on entry Cloud tiers; heatmaps are a paid plugin when self-hosted
  • Pricing (September 2026): self-hosted core free; Cloud from USD 26/month for 50,000 hits. Source: matomo.org/pricing

Before you switch: what you can export from Hotjar

Heatmaps and recordings are built from each tool's own tracking script, so your history stays behind and the new tool starts from zero. According to Hotjar's export guide, you can take out:

  • Survey responses as CSV or XLSX. Exports over 10,000 responses arrive as an emailed link.
  • A list of recordings with metadata (pages, duration, device, country, rage-click flag and more) as CSV or XLSX, up to 100,000 recordings per export. The recordings themselves stay in Hotjar, though Highlights download as video files.
  • Heatmaps as a JPG image, or as a CSV of clicks per element and scroll depth.

Then run both scripts side by side until the new tool has enough sessions to answer the questions you used Hotjar for, rebuild your surveys, and remove the Hotjar snippet last.

Which Hotjar alternative should you pick?

  • Heatmaps and recordings for free: Microsoft Clarity, if 30-day recordings and sending consent signals for European visitors are acceptable.
  • All of Hotjar in one paid tool with public prices: Mouseflow (surveys from Advanced) or Lucky Orange (every feature on every plan).
  • Heatmaps and surveys matter more than recordings: Crazy Egg.
  • Product analytics, heatmaps, replay and surveys with a free tier: PostHog.
  • Engineers who debug from recordings: LogRocket, or OpenReplay if it has to be self-hosted.
  • Recordings next to funnels and conversion data, cookieless and open source: Rybbit, with the caveat that it has no heatmaps or surveys.
  • Heatmaps, recordings and surveys behind one login, and the free quotas fit: stay on Contentsquare.

One to skip: Smartlook. Its homepage says end of sale took effect on 31 May 2026, renewals stopped on 31 August 2026, and the platform will be decommissioned on 30 September 2027.

FAQ

What is the best free alternative to Hotjar?
Microsoft Clarity. It's free with no traffic limits and includes heatmaps, recordings and funnels, but it has no surveys, and recordings are kept for 30 days. If you also need free surveys, PostHog's free tier includes 1,500 responses a month and Crazy Egg's free plan includes on-site surveys.

Who are Hotjar's main competitors?
Contentsquare now owns Hotjar. The direct competitors in heatmaps and recordings are Microsoft Clarity, Mouseflow, Lucky Orange, Crazy Egg and FullStory. PostHog, LogRocket, OpenReplay, Matomo and Rybbit compete from the product analytics, debugging or self-hosting side.

Can you still sign up for Hotjar?
No. Contentsquare says "it's no longer possible to create a new account on Hotjar." New users sign up for Contentsquare instead, whose Free plan includes 200,000 monthly sessions, 10,000 replays, heatmaps and basic surveys. Existing Hotjar users can still log in.

Is there a Hotjar alternative with product analytics?
Yes. PostHog, FullStory, LogRocket, OpenReplay and Rybbit pair recordings with event-based product analytics. PostHog and LogRocket also include heatmaps and surveys; Rybbit has neither. Contentsquare sells Product Analytics as a separate product with its own free tier.

Is there an open-source Hotjar alternative?
OpenReplay (AGPL-3.0 core) self-hosts recordings and heatmaps for free. PostHog's MIT-licensed build self-hosts its free-plan features without official support. Matomo is open source too, but heatmaps and recordings are a paid plugin when self-hosted. Rybbit (AGPL-3.0) self-hosts replay and analytics, without heatmaps.

Does Rybbit have heatmaps or surveys?
No. Rybbit has session replay on the Pro plan, with rage clicks marked on the timeline, plus web and product analytics. It has no click or scroll heatmaps, surveys or feedback widgets. For heatmaps, use Clarity or Mouseflow; for surveys, use Contentsquare, Lucky Orange or Mouseflow.


Content Blog Marketing Attribution Tools (`docs/content/blog/marketing-attribution-tools.mdx`)


title: "Best Marketing Attribution Tools in 2026 (Compared)" keyword: "marketing attribution tools" date: 2025-11-23 updated: 2026-09-28 tags: ['marketing', 'attribution', 'analytics', 'channels', 'ROI'] author: Rybbit Team

You've spent $50,000 on marketing this month. Your revenue jumped by $100,000. Great, right?

But here's the uncomfortable question: Which $50,000 actually drove that $100,000?

Was it the Google Ads campaign? The content marketing from two months ago? The viral tweet? The demo request from your sales rep who knows everyone?

If you can't answer that question with confidence, you're flying blind.

That's where marketing attribution comes in. It's the practice of assigning credit to the marketing touchpoints that led to a conversion. And if you're not using an attribution tool, you're leaving money on the table - lots of it.

In this guide, we'll cover what attribution is, why it matters, the different models available, and the best tools to implement it in your business.

What is Marketing Attribution?

Marketing attribution is the process of determining which marketing channels, campaigns, and touchpoints deserve credit for a customer conversion.

Think of it like this: A customer's journey to your product is usually a series of touches, similar to how customers progress through a sales funnel:

  1. They see your Google Ads (touch 1)
  2. They read a blog post you published (touch 2)
  3. They watch a YouTube video about your industry (touch 3)
  4. They click your email newsletter (touch 4)
  5. They sign up for a free trial (conversion)

So which one deserves credit? All of them? Just the last one? The first one?

That's what attribution tries to answer.

Why does it matter?

Because if you think only the email newsletter drove the conversion, you'll gut your content and YouTube budgets. You'll reallocate money away from the channels that actually built awareness and intent. Your revenue will eventually drop because you killed the early-stage awareness that makes your demand gen work.

Attribution prevents this disaster. It shows you the full picture of how customers find you.

The Attribution Problem: Why Most Companies Get It Wrong

Here's the dirty secret: Most companies don't do attribution at all. They use what's called "last-click attribution."

Last-click attribution means you give 100% credit to the last touchpoint before conversion. So in our example above, the email newsletter gets 100% credit. Everything else that happened? Ignored.

This is insanely common because it's easy to implement (Google Analytics does it by default). But it's also insanely misleading.

Here's why last-click attribution breaks down:

Problem 1: It Undervalues Awareness & Consideration

Your blog post and YouTube video didn't convert anyone immediately. They created awareness. They made people know you exist. But last-click attribution gives them zero credit.

So marketing leaders think: "Blog posts don't drive conversions. Cut the budget."

Then 6-12 months later, brand awareness drops, and conversion rates plummet. Oops.

This is why understanding content marketing funnels is crucial—early-stage content drives long-term value that last-click attribution completely misses.

Problem 2: It Ignores the Middle of the Funnel

Most sales cycles have multiple stages:

  • Awareness (someone learns you exist)
  • Consideration (they're comparing you to alternatives)
  • Decision (they choose you or a competitor)
  • Purchase (they convert)

Last-click attribution only credits the decision/purchase stage. The consideration stage - which often takes weeks or months - gets ignored.

Problem 3: It Creates Channel Bias

Channels that are "close" to conversion (retargeting ads, sales calls, email) will always win in last-click. But they're often the last link in a chain that started with organic search or content.

You end up over-investing in bottom-funnel channels and starving top-funnel channels. Your funnel becomes leaky because you never put enough demand in the top.

Problem 4: It Doesn't Reflect Reality

In reality, conversions are the result of multiple touches. No single touchpoint is responsible. But last-click makes you think one channel is a hero and others are villains. It's false.

Attribution Models: Which One Should You Use?

There are several different attribution models. Each one allocates credit differently.

1. Last-Click Attribution

How it works: The final touchpoint before conversion gets 100% credit.

Why to use it:

  • Easiest to implement
  • Works for bottom-funnel optimization
  • Default in Google Analytics

Why not to use it:

  • Ignores all the awareness and consideration work that feeds those conversions
  • Creates misleading ROI pictures
  • Leads to poor budget allocation decisions

When it's useful: If you're only optimizing the last step before purchase (like retargeting), this is fine. But as a company-wide model, it's dangerous.

2. First-Click Attribution

How it works: The first touchpoint (the one that started the journey) gets 100% credit.

Why to use it:

  • Highlights the importance of awareness
  • Prevents you from over-investing in retargeting
  • Good for understanding which channels bring in new customers

Why not to use it:

  • Ignores all the middle touches that actually built intent
  • Might give credit to accidental clicks or random impressions
  • Incomplete picture

When it's useful: For companies focused on brand building and awareness. But again, only part of the story.

3. Linear Attribution

How it works: All touchpoints get equal credit.

If a customer has 4 touchpoints before converting, each gets 25% credit.

Why to use it:

  • Simple to understand
  • Acknowledges all touchpoints matter
  • More balanced than first/last click

Why not to use it:

  • Assumes all touches are equally important (they usually aren't)
  • Doesn't reflect different funnel stages
  • Can still mislead about which channels drive value

Example: Touchpoint 1 (organic search) might be massively important. Touchpoint 4 (remarketing ad) might be just a reminder. But they'd get equal credit in a linear model.

4. Time-Decay Attribution

How it works: Touchpoints closer to conversion get more credit.

Typical setup: The touchpoint right before conversion gets 40% credit, the second-to-last gets 30%, the third-to-last gets 20%, the first gets 10%.

Why to use it:

  • Reflects that recent touches are usually more important
  • Gives some credit to earlier touches (unlike last-click)
  • Works well for shorter sales cycles

Why not to use it:

  • Arbitrary weighting - why 40/30/20/10? Why not 50/30/15/5?
  • Still doesn't account for different funnel stages
  • Can underweight important top-of-funnel touches

When it's useful: For most B2B SaaS companies, this is a good middle ground. It acknowledges that the last touches matter more, but doesn't completely ignore the journey.

5. Position-Based Attribution (U-Shaped)

How it works: The first and last touchpoints get the most credit (typically 40% each), and middle touches split the remaining 20%.

Why to use it:

  • Balances awareness and conversion
  • Reflects that first and last touches matter most
  • Better than simple linear

Why not to use it:

  • Still somewhat arbitrary
  • Might miss important mid-funnel touches
  • Doesn't account for your actual sales cycle length

When it's useful: When you want to balance top-of-funnel and bottom-of-funnel investments.

6. Multi-Touch (Custom) Attribution

How it works: You define the rules for how credit is allocated based on your actual sales cycle and business model.

Example: For a B2B SaaS with a 3-month sales cycle, you might say:

  • First touch: 30% (created awareness)
  • Middle touches: 40% (built consideration and intent)
  • Last touch: 30% (facilitated decision)

Or you might weight based on channel. If you know organic search drives qualified leads:

  • Organic search: 35%
  • Content: 20%
  • Retargeting: 25%
  • Direct: 10%
  • Referral: 10%

Why to use it:

  • Reflects your actual business model
  • Can be very accurate
  • Gives you strategic control over attribution

Why not to use it:

  • Requires more data and analysis to set up
  • Needs ongoing refinement
  • Easy to bias toward channels you prefer

When it's useful: When you have the sophistication to track your sales cycle and customer journey data. This is what advanced teams use.

The Real Truth About Attribution

Here's what most attribution experts won't tell you: The model matters less than consistency.

It's better to use a simple, consistent model that you understand than to use a sophisticated model that confuses everyone.

Pick a model (time-decay or position-based are good defaults), implement it, understand it, and stick with it. Use it to make decisions. Review and refine it quarterly.

What you're really doing with attribution is creating a consistent framework for how you evaluate marketing performance. The specific percentages matter less than the discipline of looking at the full customer journey instead of just the last click.

The Best Marketing Attribution Tools for 2025

Now that you understand attribution, which tools should you use to implement it?

Top Tier: Best Overall Tools

1. **Rybbit** - Best for Comprehensive Customer Analytics

Rybbit is our top recommendation because it's purpose-built for understanding customer journeys and behavior.

What makes Rybbit great for attribution:

  • Full user journey tracking: See every touchpoint a user had before converting. Not just last-click. The entire history.
  • Custom event tracking: Define exactly what "conversion" means for your business. Not just form submissions - actual valuable outcomes.
  • Session tracking: See when users engaged, what they did, how long they spent. This context is crucial for attribution.
  • Behavioral segmentation: Group users by how they converted. Did demo-request converters have different journeys than free-trial converters? Rybbit shows you.
  • Source tracking: Automatically capture UTM parameters, referral source, and organic keywords to understand where traffic came from.
  • Cohort analysis: Compare conversion patterns across different user cohorts (by traffic source, geographic region, device type, etc.).

Real example of Rybbit for attribution:

You can see:

  1. User lands on content marketing blog post (utm_source=organic)
  2. User browses 5 pages over 3 days
  3. User clicks email newsletter link (utm_source=email)
  4. User watches demo video
  5. User requests a demo
  6. User becomes customer

With Rybbit, you can see that entire journey. You can then report: "Of users who came from organic search who also received an email, 25% converted. Of users who came from email alone, only 8% converted."

This immediately shows that organic traffic + email combination is powerful. You shouldn't cut either one.

Best for: Any company that wants to understand the full customer journey, not just last-click. Especially good for companies with multiple touchpoints and longer sales cycles.

2. **Mixpanel** - Best for Product Analytics Attribution

Mixpanel is excellent if your attribution needs are product-focused (in-app features, free trials, etc.).

What makes Mixpanel great for attribution:

  • Comprehensive event tracking: Track everything that happens in your product
  • Funnels: See where users drop off in your conversion funnel
  • User segmentation: Break down funnels by user properties
  • Cohort analysis: Compare different user cohorts
  • Attribution reporting: See the top sources/campaigns driving conversions

Limitations: Mixpanel is primarily a product analytics tool. It's not as strong for multi-channel marketing attribution (email, ads, content) as tools specialized for that.

Best for: Companies with strong in-product conversion flows who want to understand how product usage leads to revenue.

3. **Segment (now part of Twilio Engage)** - Best for Data Collection & Unification

Segment is excellent for collecting data from all your marketing and product tools and sending it to a data warehouse.

What makes Segment great for attribution:

  • Data collection: Pull data from Google Analytics, email platforms, ads platforms, CRM, product analytics - everything
  • Data warehouse: Send all data to Snowflake, BigQuery, Redshift for custom analysis
  • Standardization: Put all data in the same format so you can analyze across sources
  • Data governance: Ensure data quality and compliance

Real example: You can use Segment to pull user behavior from your product (Mixpanel), ad interactions (Google Ads), email clicks (Mailchimp), and website visits (Google Analytics) into one unified data warehouse. Then you can build custom attribution models.

Limitations: Segment is a data pipe, not an attribution tool. It gets the data to the right place, but you still need to analyze it.

Best for: Large companies with technical data teams who want to build custom attribution models.

4. **HubSpot** - Best for B2B Sales-Driven Attribution

HubSpot offers attribution modeling specifically designed for B2B sales teams.

What makes HubSpot great for attribution:

  • Contact lifecycle tracking: Track every touchpoint with a lead (email, web visit, call, etc.)
  • Deal tracking: Tie touches to actual revenue deals
  • Attribution modeling: Built-in multi-touch attribution
  • Lead scoring: Automatically score leads based on engagement
  • Integration with sales: Your sales team sees attribution data, not just marketing

Limitations: HubSpot's attribution works best if you're using HubSpot for CRM and email. If you're using separate tools, it's less effective.

Best for: B2B companies using HubSpot as their CRM. Great for small-to-medium companies.

5. **Google Analytics 4 (GA4)** - Best for Free Option (with limitations)

GA4 now offers multi-touch attribution modeling.

What makes GA4 great for attribution:

  • Free (with limitations)
  • Multiple attribution models: Offers time-decay, first-click, last-click, etc.
  • Data-driven attribution: Machine learning model that learns from your data
  • Cross-platform tracking: See journeys across web and app
  • Integration with ads: Direct integration with Google Ads

Limitations:

  • Data-driven attribution requires significant traffic volume to work
  • Limited to Google-owned sources (Google Ads, Search, YouTube)
  • Doesn't track email or other channels as well
  • Complexity has increased significantly

Best for: Small companies just starting with attribution. It's free and decent for web analytics. But you'll outgrow it.

Mid-Tier: Specialized Tools

**Littledata** - Best for Ecommerce Attribution

Excellent if you're an ecommerce company. Connects your Shopify store to GA4 with proper multi-touch attribution.

**AppsFlyer** - Best for Mobile & App Attribution

If you're a mobile app company, AppsFlyer is the standard for attribution.

**Improvado** - Best for Marketing Agencies

Great for agencies managing multiple client accounts and needing consolidated reporting.

How to Implement Attribution (Step by Step)

Implementing attribution isn't as hard as it sounds. Here's the practical path:

Step 1: Choose Your Attribution Model

Start with time-decay or position-based. Don't overthink it.

Step 2: Pick Your Tool

Start with Rybbit or GA4. Don't go complex until you need to.

Step 3: Implement Tracking

Make sure you're capturing:

  • Where traffic comes from (UTM parameters, referral source)
  • What users do (events, page views, time on page)
  • When conversions happen (form submissions, purchases, sign-ups)
  • User properties (device, location, company size)

Step 4: Wait for Data

You need at least 100-300 conversions to see patterns. This usually takes 4-12 weeks depending on your volume.

Step 5: Analyze Results

Look at your top conversion sources. Are they what you expected? Are there surprises?

Step 6: Take Action

Increase investment in high-performing sources. Investigate underperforming sources. Test improvements.

You can use multivariate testing to optimize your marketing channels and see which combinations drive the best results.

Step 7: Review Quarterly

Attribution models should be revisited every quarter. If you're seeing patterns that don't make sense, refine your model.

Using Rybbit for Marketing Attribution: Implementation Guide

Since we recommend Rybbit, here's how to use it for attribution:

The Journey dashboard in Rybbit lets you visualize the complete customer journey, showing exactly how users moved through your funnel and which touchpoints led to conversion.

Setting Up Conversion Tracking

First, define what a "conversion" is for your business (signup, customer acquisition, purchase, etc.). Then track this conversion event in Rybbit. Refer to the event tracking documentation for implementation details.

Capturing Traffic Source

Rybbit automatically captures UTM parameters from your URLs. You can also manually enhance tracking by recording campaign details in your events. See the tracking documentation for specifics.

Tracking the Full Journey

As users interact with your site and marketing, track key behavioral events:

  • Content engagement (blog reads, video views, etc.)
  • Email interactions
  • Ad interactions
  • Product feature usage
  • Trial signups

The key is consistency: track the same events across all channels so you can compare how different sources interact and contribute to conversions.

  1. In the Users tab:

    • Filter by users who converted
    • Look at their "First Visit" to see acquisition source
    • See total sessions, pages viewed, time spent
    • Compare converters vs. non-converters
  2. In the Sessions tab:

    • See session frequency for users on different journeys
    • Compare session duration across different traffic sources
    • Identify patterns in converted vs. non-converted users
  3. In the Events tab:

    • See which events most frequently appear before conversion
    • Filter by conversion source to see typical journey
    • Compare event sequences across different acquisition channels
  4. Using Filtering and Segmentation:

    • Create segments like "Came from organic + opened email"
    • Create segments like "Came from ads + used feature X"
    • Compare conversion rates between segments
    • See which combinations drive the most conversions

Real Example: Multi-Channel Attribution Analysis

When analyzing multi-channel journeys, you can identify whether different traffic sources are working together or cannibalizing each other. Rybbit lets you compare conversion rates across different source combinations to understand channel interactions.

By comparing users who saw only one source versus multiple sources, you can determine whether to invest in multiple channels simultaneously or focus on one primary channel.

Creating Custom Attribution Reports

You can export Rybbit data to build custom attribution models based on your specific business needs. The key steps are to identify all user touchpoints, apply your custom weighting model based on your sales cycle, and calculate total conversions attributed to each channel.

Common Attribution Mistakes to Avoid

Mistake 1: Over-Optimizing Last-Click

Last-click is easy to understand but deeply flawed. Don't base all your decisions on it.

Fix: Use multi-touch attribution. See the full journey.

Mistake 2: Not Accounting for Seasonality

Your acquisition sources shift seasonally. Google searches spike in Q4. Email performance varies. Don't compare January to December.

Fix: Always compare same time periods year-over-year. Use rolling averages.

Mistake 3: Confusing Correlation with Causation

If users who convert have 10 touchpoints and users who don't convert have 2, don't assume the extra touches caused the conversion.

Maybe high-intent users naturally engage more. The touches are a signal, not the cause.

Fix: A/B test to validate. If you double email touches for one group and see different conversion rates, that's causation.

Mistake 4: Ignoring Offline Touchpoints

Your sales team talks to customers. Your brand building happens in podcasts. Your CEO speaks at conferences. None of these show up in your digital analytics.

Fix: For B2B, track offline touches manually. For B2C, understand that brand and word-of-mouth matter.

Mistake 5: Not Refreshing Your Model

Your business changes. Sales cycle changes. New channels emerge. Your attribution model should evolve too.

Fix: Review attribution quarterly. If something looks wrong, investigate.

The Future of Attribution: What's Changing

Privacy Changes

Apple's App Tracking Transparency is killing third-party cookies. This makes attribution harder because you have less data about users across the web.

This actually increases the importance of first-party data collection. Tools like Rybbit that use server-side tracking and your own data become more valuable.

First-Party Data Focused

Privacy regulations mean the future is first-party data. You'll track your own customer data more, and rely less on third-party pixels.

This is good for smaller companies - you can build attribution without relying on ad platform data.

AI and Machine Learning

GA4's data-driven attribution uses machine learning. Expect more AI-powered attribution that learns from your actual conversion patterns.

Instead of using a fixed model, the AI determines which touchpoints matter most for your specific business.

The Bottom Line

Marketing attribution answers one simple question: Which channels and campaigns are actually driving your revenue?

The answer matters enormously. It determines where you invest, where you cut, and what you optimize.

Most companies use last-click attribution because it's easy. But it's also dangerously misleading. It over-credits bottom-funnel channels and under-credits the awareness work that feeds those channels.

Here's what you should do:

  1. Implement multi-touch attribution. Use time-decay or position-based models.
  2. Pick a tool that tracks the full journey. Rybbit is excellent for this. GA4 works too.
  3. Capture everything. Traffic source, user behavior, events, conversions.
  4. Wait for data. Attribution needs volume to be reliable.
  5. Analyze patterns. Which sources appear most in successful customer journeys?
  6. Adjust spending. Increase channels that lead to conversions. Decrease or optimize those that don't.
  7. Test and refine. Attribution is not a set-it-and-forget-it system.

The companies that do attribution well gain a massive advantage. They know which $1 of marketing spend drives $3 of revenue. They can confidently increase spending on high-ROI channels. They avoid the trap of over-investing in bottom-funnel while starving top-funnel.

Don't be left guessing. Implement attribution today.


Related Reading:


Content Blog Mixpanel Alternatives (`docs/content/blog/mixpanel-alternatives.mdx`)


title: "9 Best Mixpanel Alternatives in 2026, by Reason for Switching" keyword: "mixpanel alternatives" date: 2026-09-27 tags: ["product analytics", "comparisons", "open source"] author: Rybbit Team

Most teams shopping for Mixpanel alternatives are leaving for one of five reasons: the bill grows faster than the product, the setup is more than a small team can maintain, they need open source or self-hosting, they want analytics that doesn't depend on a consent banner, or they pay for Mixpanel plus a separate web analytics tool. Each reason points to a different replacement, so this guide starts with the reasons, then compares nine tools on price, free allowance, license and the Mixpanel reports you keep.

Disclosure: we build Rybbit, one of the nine. Every claim about the other tools comes from that vendor's own pricing or documentation pages, linked inline and checked in September 2026. Where a vendor doesn't publish a number, we say so.

Quick comparison

Tool Best for Free allowance Paid plans from Open source / self-host
Rybbit Web and product analytics in one cookieless tool None (7-day trial) $19/mo for 100k events Yes, AGPL-3.0
PostHog Mixpanel's breadth plus flags, experiments, replay 1M events, 5K recordings/mo $0.00005/event after 1M MIT core; self-host unsupported
Amplitude Closest like-for-like replacement 2M events/mo Plus "starts at $0"; rate not published No
Heap Autocapture, events defined later 10k sessions/mo Estimate after install No
OpenPanel Cheapest published entry price 30-day trial $2.50/mo for 5K events Yes, AGPL-3.0
Countly Mobile apps on private or own servers Lite edition, self-hosted Flex from $175/mo Lite: AGPL-3.0 with branding terms
TelemetryDeck Privacy-first app analytics 50K events/mo €9/mo SDKs only
Matomo Self-hosted web analytics with funnels On-Premise edition Cloud €29/mo for 50k hits Yes, GPL v3
Google Analytics 4 Free funnels and cohorts for marketing sites Free Analytics 360 via sales No
Mixpanel, for reference 1M events/mo Growth: 1M free, then $0.00028/event on the base plan No

Why teams look for Mixpanel alternatives

Mixpanel is a capable product analytics tool with a real free plan: 1M events a month, unlimited seats and 10K session replays, per its pricing page; our Mixpanel pricing breakdown lists every tier. The reason you're leaving decides the replacement.

The bill grows faster than the product

On the Growth plan the first 1M events a month are free; after that, the base plan's overage rate is $0.00028 per event, or $280 per extra million, according to Mixpanel's pricing docs. Larger prepaid plans get a lower rate that only Mixpanel's calculator shows. Group Analytics adds 40% and Data Pipelines 20% on overage, and the MTU-based Enterprise plan starts at $20,000 a year. The Free plan never bills overages; past 1M monthly events it blocks your reports until you upgrade or the month rolls over.

Published prices at two volumes (tools count events differently, so treat this as a rough guide):

Monthly events 1M 10M
Mixpanel Growth $0 $2,520 at the base plan's overage rate; less on a larger prepaid plan (not published)
Amplitude Plus $0 (first 2M free) Not published
PostHog $0 About $324
OpenPanel $90 $350
Rybbit Standard / Pro $69 / $139 $249 / $499
Heap Priced by sessions, not published Not published

Best fits: PostHog keeps the breadth for less, OpenPanel and Rybbit have flat published tiers, and the open-source options cost only the server.

It's more tool than a small team needs

Mixpanel works best with a tracking plan, which its docs call "a centralized document that should serve as the source of truth on your Mixpanel implementation". That pays off for a product team with an analyst. For a three-person startup it's a lot of planning before the first useful chart, and the free plan caps saved reports at 5 per user.

Best fits: Heap captures interactions first and lets you define events later; in Rybbit, traffic, sessions, page funnels, journeys and retention work from the script alone; TelemetryDeck ships pre-made dashboards for apps.

You need open source or self-hosting

Mixpanel keeps your data on its own servers: in the US on Google Cloud by default, or in the EU if you choose EU residency when you create a project, and it can't move an existing project that landed in the wrong region. If data must stay on infrastructure you control, or you want to read and change the code, skip to the open-source Mixpanel alternatives.

Mixpanel's JavaScript SDK keeps each user's distinct ID and super properties in a first-party cookie that lasts 365 days by default, or in localStorage if you configure it (SDK docs). In the EU that generally needs consent: France's CNIL applies the consent rule to audience-measurement trackers outside a narrow exemption and says "most large audience measurement offerings do not fall within the scope of the exemption, regardless of their configuration." Visitors who decline the banner never reach your reports. This is a summary, not legal advice.

Best fits: Rybbit (cookieless by default, EU-hosted cloud), OpenPanel (cookieless, EU-hosted), TelemetryDeck (anonymizes on the device), or PostHog's opt-in cookieless mode.

You want web analytics and product analytics in one tool

Mixpanel has closed some gaps: its free plan includes 10K session replays a month, up to 10 active feature flags and a small experiments allowance, so check your plan if those were the reason. What many teams still pay for alongside it is a web analytics tool for traffic sources, campaigns, SEO and page speed.

Best fits: Rybbit, PostHog and Matomo.

The 9 best Mixpanel alternatives

1. Rybbit

Best for: web analytics and Mixpanel's core reports in one cookieless tool, or a Mixpanel open-source alternative that runs on a small server.

Rybbit covers the marketing site and the product in one dashboard. Pageviews, sources, campaigns and sessions come from the script, and custom events with properties are one function call. Standard includes funnels from pages or events, user journeys, cohort retention, goals, Web Vitals and error tracking. User profiles come from identify(), which links a visitor's earlier anonymous activity to their account and stores traits like plan or company (docs). Session replay is on Pro.

Tracking is cookieless by default, so Rybbit's own tracking needs no consent banner (identifying logged-in users is opt-in and needs its own lawful basis), and the cloud is hosted in the EU. An MCP server with 44 tools lets Claude, Codex and other MCP clients query your data, including read-only SQL, and create goals and funnels.

  • Pricing (as of September 2026): Standard from $19/month for 100k events, with 5 websites, 3 team members and 3-year retention; Pro from $39/month adds session replay (10,000 replays a month on the 100k-event tier), unlimited websites and members, and 5-year retention. 7-day trial, card collected at signup. Source: rybbit.com/pricing
  • Open source / self-host: AGPL-3.0, free to self-host, replay included
  • Gaps vs Mixpanel: no feature flags or experiments; no Mixpanel importer (imports cover Plausible, Umami and Simple Analytics), so you run both tools side by side for a while; retention groups users by first visit rather than an event you choose, with no behavioral cohorts or group analytics; for apps there is a React Native SDK that tracks screens, events, errors and users in an App Analytics dashboard mode, but no native iOS or Android SDK and no replay in apps

Pick PostHog or Amplitude instead if you need flags and experiments in your analytics tool, build a native iOS or Android app, or run on behavioral cohorts.

2. PostHog

Best for: product teams that want Mixpanel's breadth plus flags, experiments, replay and surveys, with a large free allowance.

PostHog is the widest platform here: product and web analytics, session replay, feature flags, experiments, surveys, error tracking and a data warehouse, each on its own meter. The free allowance resets monthly to 1M analytics events, 5K recordings, 1M flag requests, 100K exceptions and 1,500 survey responses, no card required (pricing). Events past 1M cost $0.00005 each up to 2M, falling in steps to $0.000009 above 250M (product analytics pricing), so 10M a month comes to about $324. Retention is 1 year on free and 7 years on paid. A managed migration exports your Mixpanel data, converts it to PostHog's schema and imports it, and the web analytics dashboard adds referrers, UTMs and Core Web Vitals.

  • Pricing: free within the allowances, then usage-based with volume tiers
  • Open source / self-host: MIT core; self-hosting is "officially unsupported" (details)
  • Drawbacks: several meters on one bill; cookieless tracking is an opt-in mode that needs a project setting, and in its strictest form you shouldn't call identify()

See Rybbit vs PostHog.

3. Amplitude

Best for: the closest like-for-like replacement, with a bigger free allowance.

Amplitude covers the same ground as Mixpanel (event analytics, session replay, feature flags, experiments), so switching is mostly remapping events and rebuilding reports. Its pricing page lists a bigger free plan than Mixpanel's or PostHog's: 2M events a month "forever", 10K session replays, unlimited flags and seats, and 1 year of retention, with no card. Plus "starts at $0" with 2M free events, scales to 70M, and adds 2-year retention and one active experiment, but the rate past the free events isn't published. Growth and Enterprise are custom-priced, with unlimited retention. The Migrations panel imports Mixpanel events through a guided UI; user profiles go through the Batch Event Upload API instead.

  • Pricing: Free (2M events/month); Plus from $0 at an unpublished rate; Growth and Enterprise by quote
  • Open source / self-host: no
  • Drawbacks: no public price past 2M events; active experiments capped at 1 on Plus and 2 on higher plans unless you buy an add-on

If you're comparing Amplitude against other tools too, see our Amplitude alternatives roundup.

4. Heap

Best for: teams whose Mixpanel data has gaps because events were never instrumented.

Heap, now part of Contentsquare, inverts the tracking plan: its autocapture collects "every click, swipe, tap, pageview, and fill" from install, and you name events later with the history already there. The free plan covers 10k monthly sessions, 6 months of history and one project, including funnels, journeys and retention (pricing). Growth adds 12 months of history, unlimited users and CSV exports, priced by an estimate once you install the snippet. Pro and Premier use custom session pricing, with replay and heatmaps as add-ons.

  • Pricing: Free (10k sessions/month); no published prices above it
  • Open source / self-host: no
  • Drawbacks: no public prices; billed by sessions; replay only as an add-on on the top two tiers

5. OpenPanel

Best for: small teams that want Mixpanel-style event analytics at the lowest published price, or an AGPL tool to self-host.

OpenPanel bills itself as an open-source alternative to Mixpanel, with the most granular pricing here: $2.50 a month for 5K events, $20 for 100K, $90 for 1M, $350 for 10M and $900 for 50M, all with unlimited sites, users, dashboards and profiles, and a 30-day trial without a card. Self-hosting is free with unlimited events. The README lists funnels, cohorts, user profiles, session replay and A/B test breakdowns, and the SDKs cover web frameworks, React Native, Swift, Kotlin and several server languages. Tracking is cookieless (IP and user agent hashed daily) and the cloud is EU-hosted, per the homepage.

  • Pricing: from $2.50/month for 5K events
  • Open source / self-host: AGPL-3.0, free to self-host
  • Drawbacks: Mixpanel data comes over by replaying your export through the server-side SDK (guide), not a one-click import; the self-hosting docs give no hardware requirements

6. Countly

Best for: mobile-heavy products that need analytics on a dedicated instance or their own servers.

Countly maintains SDKs for iOS, Android, React Native, Flutter and web (GitHub) and sells three editions (pricing). Flex is a dedicated cloud instance ("No shared compute") from $175 a month, priced by monthly active users and data points, with funnels, retention, flows, user profiles and crash reporting; push, A/B testing and journeys are an add-on, and the 14-day trial requires a card. Enterprise is self-hosted and custom-priced. The free Countly Lite isn't a Mixpanel replacement by itself: Countly's blog says user profiles, cohorts, funnels, formulas and drill rely on a database the Community edition lacks.

  • Pricing: Flex from $175/month; Enterprise by quote; Lite free
  • Open source / self-host: Lite is AGPL-3.0 with branding terms (below); Enterprise runs on your servers
  • Drawbacks: the highest published entry price among the hosted tools here; the free edition lacks funnels and profiles

7. TelemetryDeck

Best for: app developers who want product analytics without personal data.

TelemetryDeck is built for apps, with SDKs for Swift (iOS, macOS, visionOS), Kotlin, React Native, Flutter and JavaScript. Its homepage says it anonymizes on the device, uses no cookies and needs no consent banner; funnels, retention and pre-made dashboards are included. New accounts get 50,000 free events a month (accounts from before 1 July 2026 keep 100,000, per its pricing update), and paid plans start at €9 a month according to its Mixpanel comparison.

  • Pricing: 50K events/month free; paid from €9/month
  • Open source / self-host: SDKs under a modified MIT license (FAQ); the service is hosted
  • Drawbacks: it avoids identifying users, so it isn't built for looking up what one customer did; the full price list sits in a calculator inside its dashboard

8. Matomo

Best for: organizations that want self-hosted web analytics with funnels and can pay for plugins.

Matomo is web analytics first, with premium features that reach many of Mixpanel's reports. Matomo Cloud starts at €29 a month for 50,000 hits on the Business plan, which includes funnels, users flow, heatmaps, session recording and form analytics; data is stored in Frankfurt. The self-hosted Community edition is free with unlimited users and hits, but there those features are paid: the Team bundle (from €230 a month, 4 users, 5M hits) covers funnels, users flow and recordings, cohorts need the Business bundle (from €1,209 a month), and A/B testing the Enterprise bundle (from €2,834 a month). One line of code turns cookies off, which Matomo's FAQ says can reduce returning-visitor accuracy.

  • Pricing: Cloud from €29/month; On-Premise free, plugins extra
  • Open source / self-host: GPL v3 or later
  • Drawbacks: self-hosted product-analytics features cost extra, and cohorts sit in the €1,209/month bundle

See Rybbit vs Matomo.

9. Google Analytics 4

Best for: marketing-led teams that want free funnels and cohorts and already use Google's ad stack.

Google provides GA4 "free of charge", and Explorations cover a fair share of Mixpanel's reports: funnel, path and cohort exploration, segment overlap, user explorer and user lifetime (Google's list). The limits appear on product questions: standard properties keep exploration data for at most 14 months, queries over 10 million events get sampled, and the two-year _ga cookies mean a consent banner for most European traffic. Raw events can go to BigQuery, with a 1-million-event daily export cap on standard properties, uncapped streaming, and BigQuery charges for storage and queries.

  • Pricing: free; Analytics 360 is sold through "Talk to Sales"
  • Open source / self-host: no
  • Drawbacks: 14-month exploration window; sampling; cookie consent; built for marketing attribution more than product analysis

See Rybbit vs Google Analytics and our roundup of the best web analytics tools.

Open-source Mixpanel alternatives

If you want to run or read the server code yourself, these six publish it. What separates them is the license, the self-hosting effort, and which Mixpanel reports survive the move.

Tool License Self-hosting Mixpanel-style reports you keep
PostHog MIT, except the ee/ directory Docker Compose "hobby" install; 4 vCPU, 16 GB RAM, 30 GB+ disk Free-plan features: funnels, retention, profiles, replay, flags, experiments
OpenPanel AGPL-3.0 ./setup script on a VPS, tested on Ubuntu 24.04 Funnels, retention, cohorts, profiles, replay
Rybbit AGPL-3.0 ./setup.sh and Docker Compose on a VPS with 2 GB+ RAM Funnels, journeys, retention, profiles, replay
Matomo GPL v3 or later PHP 8.1+, MySQL 8.0+ or MariaDB 10.6+; 2 CPU and 2 GB RAM under 100k pageviews/month, 4 CPU and 8 GB up to 1M Funnels, flows, cohorts, recordings, A/B tests as paid plugins
Countly Lite AGPL-3.0 plus branding terms One-line script on Ubuntu, CentOS or RHEL, or Docker Events, sessions, crash reports, push, remote config; no funnels or profiles
Umami MIT Node.js 18.18+ and PostgreSQL 12.14+, or Docker Funnels, retention, journeys, goals, distinct IDs, replays

PostHog is the closest feature match, and code outside its ee/ directory is MIT-licensed. But its self-hosting page calls self-hosted deployments "officially unsupported", states that "All paid-plan features are Cloud-only", and steers you to Cloud if you expect more than 300k events, 1k recordings or 300k flag API calls.

OpenPanel and Rybbit are AGPL-3.0: free to run and modify for any purpose, including commercially, provided that if you offer a modified version to users over a network, you make its source available to them. OpenPanel's self-hosting docs ask for a VPS and a setup script. Rybbit's setup runs ClickHouse, Postgres, Redis and Caddy in Docker Compose on a 2 GB VPS, with session replay included.

Matomo is the most mature option, with published server requirements for every traffic level; the GPL covers the core, and the product-analytics features are the paid plugins in the table.

Countly Lite needs a careful read: the license adds a Section 7 term that forbids removing Countly's logos and branding, and the README calls Lite "free to use under an open-source, non-commercial license". Check Countly's licensing FAQ before building a commercial product on it.

Umami is MIT-licensed and needs only Node.js and PostgreSQL. Its reports include funnels, retention, journeys, goals and attribution, and session replays arrived in v3.1.0, kept for 30 days. See Rybbit vs Umami.

Switching from Mixpanel

Mixpanel lets you take your data: the Raw Event Export API returns events as JSONL, and the Engage API and the Users tab export profiles (export methods).

PostHog's managed migration does the export, conversion and import for you. Amplitude's Migrations panel brings in events, and profiles need a separate upload. OpenPanel has you replay the export through its server-side SDK. Rybbit has no Mixpanel importer, so run both tools in parallel until Rybbit has enough history, and keep the Mixpanel export as an archive.

Whichever you pick, run it beside Mixpanel for a few weeks, compare daily event and user counts, and rebuild only the reports your team actually opens.

Which Mixpanel alternative should you pick

  • The bill is the problem and you want the same breadth: PostHog (about $324 at 10M events) or OpenPanel ($350).
  • The closest like-for-like switch, with a bigger free plan: Amplitude.
  • Your team never kept a tracking plan: Heap.
  • Web and product analytics in one cookieless tool, or self-hosting on a 2 GB server: Rybbit.
  • A native app: TelemetryDeck for privacy-first analytics, Countly for private hosting, or PostHog and Amplitude.
  • Data must stay on your servers: the open-source options.
  • No budget, mostly marketing traffic: Google Analytics 4.

Staying on Mixpanel is reasonable if you rely on Group Analytics, experiments or a mature tracking plan and the bill works. Startups under five years old with up to $8M in funding can get their first year free on Mixpanel's Startup Plan (pricing).

FAQ

What is the best open-source Mixpanel alternative?
PostHog has the most features, but self-hosting it is officially unsupported. OpenPanel and Rybbit, both AGPL-3.0, install on one VPS with a setup script. Matomo is the most mature, but self-hosted it sells funnels and cohorts as plugins.

Who are Mixpanel's main competitors?
The most direct alternatives to Mixpanel are Amplitude and Heap in hosted product analytics and PostHog in open-source product analytics. Google Analytics 4 competes for teams that mostly need marketing analytics.

Is there a free Mixpanel alternative?
Yes. Amplitude includes 2M events a month at no cost, PostHog 1M events and 5K recordings, Heap 10k sessions and TelemetryDeck 50K events, and GA4 is free. The open-source tools cost only the server when you self-host.

Which Mixpanel alternatives have affordable pricing?
OpenPanel starts at $2.50 a month for 5K events and charges $90 at 1M. Rybbit Standard is $19 a month for 100k events and $69 at 1M. TelemetryDeck starts at €9 and Matomo Cloud at €29, and PostHog is free until 1M events.

Can I import my Mixpanel data into another tool?
Into PostHog, Amplitude or OpenPanel, yes. Rybbit has no Mixpanel importer; it imports from Plausible, Umami and Simple Analytics.


Content Blog Mixpanel Pricing (`docs/content/blog/mixpanel-pricing.mdx`)


title: "Mixpanel Pricing 2026: Plans, Calculator Prices and Add-ons" keyword: "mixpanel pricing" date: 2026-09-27 tags: ["mixpanel", "pricing", "product analytics", "comparisons"] author: Rybbit Team

Mixpanel pricing comes in three plans. Free costs $0 for up to 1 million events a month. Growth also starts at $0, because the first 1 million events each month are free; above that, Mixpanel's own calculator prices it from $140 a month for 1.5 million events to $2,289 a month for 20 million on monthly billing, or $120 to $1,635 a month billed annually. Enterprise is quoted by sales, and the user-based (MTU) version starts at $20,000 a year. Seats are unlimited on every plan, and add-ons such as Group Analytics and Data Pipelines have no public price.

Disclosure up front: we build Rybbit, which appears in the comparison at the end. Every Mixpanel number here comes from Mixpanel's own pricing page and its calculator, its pricing documentation and its changelog. Figures for other tools come from those vendors' own pricing pages, linked inline. Prices are as of September 2026, when we checked every source.

Mixpanel pricing at a glance

Free Growth Enterprise
Price $0 $0 for the first 1M events a month, then by tier: $140–$2,289/month billed monthly, $120–$1,635/month billed annually Custom quote; MTU-based plans from $20,000/year
Monthly events Up to 1M Up to 20M online, more through sales Up to 1 trillion
Seats Unlimited Unlimited Unlimited
Session replays included 10k a month 20k a month; buy up to 500k 20k a month; custom volumes
Saved reports 5 per user Unlimited Unlimited
Experiments 1k monthly experiment users (MEU) 5k MEU; buy up to 100k Custom
Active feature flags 10 50 Custom
Support Email, 24/5 Email, 24/5 24/7, dedicated success team
Payment No card Card required Custom terms through sales

Sources: mixpanel.com/pricing, the Session Replay and Experiments docs, and Mixpanel's February 2025 pricing announcement for the card requirement on Growth.

How Mixpanel pricing works

By default, Mixpanel bills on events, not on users or seats. An event is one tracked action (a page view, a sign-up, a purchase), and the pricing docs count them "across all projects in your organization". A handful of system events are excluded: $identify, $create_alias, $merge, $opt_in, session recording checkpoints, and user profile creation or updates.

Three rules decide how large that count gets:

  • Autocapture counts. Mixpanel's Autocapture records page views, scrolls, form interactions, element clicks, dead clicks and rage clicks by default, and the heatmap docs state that autocaptured clicks are billable. Page views and heatmap clicks recorded while a session replay is running are exempt.
  • Imports count when they arrive. Since September 1, 2025, Mixpanel bills by ingestion time (some older accounts still bill by event timestamp), so backfilling last year's data counts toward this month's total.
  • Warehouse syncs count updates. With Mirror-type warehouse syncs, every update or deletion of an existing event counts as one event.

Pricing by monthly tracked users (MTUs) still exists, but only as an Enterprise contract. Mixpanel's MTU guide says MTU plans "start at $20,000 per year", can't be bought online, and are worth considering above 100k users or 5M monthly events. Anonymous visitors count as MTUs.

The Mixpanel free plan: what's included and where it stops

The Free plan is "free forever" and needs no card. It includes unlimited seats, up to 1M events a month, 10k session replays, funnels and retention re