Skip to main content
Technology & EngineeringAnalytics Services345 lines

Plausible Analytics

"Plausible: privacy-friendly analytics, no cookies, lightweight script, custom events, goals, API, self-hosted option"

Quick Summary21 lines
Plausible is a lightweight, privacy-friendly web analytics tool that provides essential traffic
metrics without using cookies or collecting personal data. It is fully GDPR, CCPA, and PECR
compliant out of the box with no cookie banners required. The tracking script is under 1 KB,
making it dramatically lighter than traditional analytics. Plausible focuses on aggregate

## Key Points

- **Proxy the script through your domain** using Next.js rewrites to prevent ad blockers from
- **Use custom events sparingly.** Plausible is designed for simplicity. Track 5-15 meaningful
- **Leverage the `data-domain` attribute** correctly when running Plausible on staging versus
- **Use the Stats API to build internal dashboards** rather than giving every team member access
- **Set up revenue tracking** for e-commerce goals to see conversion value alongside visitor
- **Self-host with Docker** if you need full data ownership. Plausible provides a well-maintained
- **Use `script.tagged-events.js`** variant when you want CSS-class-based tracking for elements
- **Treating Plausible like a full product analytics suite.** It is intentionally minimal. If you
- **Adding the script without the proxy.** Many privacy-focused users run ad blockers that strip
- **Sending personally identifiable information in event props.** Plausible is designed to be
- **Polling the realtime endpoint too frequently.** The realtime API returns a simple visitor
- **Ignoring the `callback` option on conversion events.** If you track a goal and immediately
skilldb get analytics-services-skills/Plausible AnalyticsFull skill: 345 lines
Paste into your CLAUDE.md or agent config

Plausible Analytics

Core Philosophy

Plausible is a lightweight, privacy-friendly web analytics tool that provides essential traffic metrics without using cookies or collecting personal data. It is fully GDPR, CCPA, and PECR compliant out of the box with no cookie banners required. The tracking script is under 1 KB, making it dramatically lighter than traditional analytics. Plausible focuses on aggregate metrics rather than individual user tracking, providing page views, referral sources, device breakdowns, and custom event goals. It can be used as a managed cloud service or self-hosted using Docker. The philosophy is that you can get actionable product insights without sacrificing user privacy or bloating your site.

Setup

Script Installation

// next.config.ts — Proxy the Plausible script to avoid ad blockers
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  async rewrites() {
    return [
      {
        source: "/js/script.js",
        destination: "https://plausible.io/js/script.js",
      },
      {
        source: "/api/event",
        destination: "https://plausible.io/api/event",
      },
    ];
  },
};

export default nextConfig;
// app/layout.tsx — Add the tracking script
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <Script
          defer
          data-domain="yourdomain.com"
          data-api="/api/event"
          src="/js/script.js"
          strategy="afterInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

TypeScript Declarations

// types/plausible.d.ts
declare global {
  interface Window {
    plausible: (
      event: string,
      options?: {
        callback?: () => void;
        props?: Record<string, string | number | boolean>;
        revenue?: { currency: string; amount: number };
      }
    ) => void;
  }
}

export {};

Plausible Helper Utility

// lib/plausible.ts
type PlausibleEventProps = Record<string, string | number | boolean>;

interface PlausibleOptions {
  props?: PlausibleEventProps;
  revenue?: { currency: string; amount: number };
  callback?: () => void;
}

export function trackEvent(event: string, options?: PlausibleOptions): void {
  if (typeof window !== "undefined" && window.plausible) {
    window.plausible(event, options);
  }
}

export function trackGoal(goalName: string, props?: PlausibleEventProps): void {
  trackEvent(goalName, { props });
}

export function trackRevenue(
  event: string,
  amount: number,
  currency: string = "USD",
  props?: PlausibleEventProps
): void {
  trackEvent(event, {
    revenue: { currency, amount },
    props,
  });
}

Key Techniques

Custom Event Tracking

import { trackEvent, trackGoal, trackRevenue } from "@/lib/plausible";

// Track a signup conversion
function handleSignup(method: string): void {
  trackGoal("Signup", {
    method,
    page: window.location.pathname,
  });
}

// Track a file download
function handleDownload(fileName: string, fileType: string): void {
  trackEvent("Download", {
    props: {
      file_name: fileName,
      file_type: fileType,
    },
  });
}

// Track a purchase with revenue attribution
function handlePurchase(plan: string, amount: number): void {
  trackRevenue("Purchase", amount, "USD", { plan });
}

React Hook for Event Tracking

// hooks/usePlausible.ts
"use client";

import { useCallback } from "react";

type PlausibleProps = Record<string, string | number | boolean>;

export function usePlausible() {
  const track = useCallback((event: string, props?: PlausibleProps) => {
    if (typeof window !== "undefined" && window.plausible) {
      window.plausible(event, props ? { props } : undefined);
    }
  }, []);

  return { track };
}

// Usage in a component
function NewsletterForm(): JSX.Element {
  const { track } = usePlausible();

  const handleSubmit = (email: string) => {
    track("Newsletter Signup", { source: "footer" });
    // ... submit logic
  };

  return <form onSubmit={() => handleSubmit("user@example.com")}>{/* form fields */}</form>;
}

Plausible Stats API

// lib/plausible-api.ts
const PLAUSIBLE_API_KEY = process.env.PLAUSIBLE_API_KEY!;
const SITE_ID = process.env.PLAUSIBLE_SITE_ID!;
const BASE_URL = process.env.PLAUSIBLE_HOST ?? "https://plausible.io";

interface AggregateResult {
  visitors: { value: number };
  pageviews: { value: number };
  bounce_rate: { value: number };
  visit_duration: { value: number };
}

export async function getAggregate(period: string = "30d"): Promise<AggregateResult> {
  const params = new URLSearchParams({
    site_id: SITE_ID,
    period,
    metrics: "visitors,pageviews,bounce_rate,visit_duration",
  });

  const response = await fetch(`${BASE_URL}/api/v1/stats/aggregate?${params}`, {
    headers: { Authorization: `Bearer ${PLAUSIBLE_API_KEY}` },
  });

  const data = await response.json();
  return data.results;
}

interface BreakdownEntry {
  page: string;
  visitors: number;
  pageviews: number;
}

export async function getTopPages(
  period: string = "30d",
  limit: number = 10
): Promise<BreakdownEntry[]> {
  const params = new URLSearchParams({
    site_id: SITE_ID,
    period,
    property: "event:page",
    metrics: "visitors,pageviews",
    limit: String(limit),
  });

  const response = await fetch(`${BASE_URL}/api/v1/stats/breakdown?${params}`, {
    headers: { Authorization: `Bearer ${PLAUSIBLE_API_KEY}` },
  });

  const data = await response.json();
  return data.results;
}

export async function getRealtime(): Promise<number> {
  const params = new URLSearchParams({ site_id: SITE_ID });

  const response = await fetch(`${BASE_URL}/api/v1/stats/realtime/visitors?${params}`, {
    headers: { Authorization: `Bearer ${PLAUSIBLE_API_KEY}` },
  });

  return response.json();
}

Server-Side Event Ingestion

// lib/plausible-server.ts
const PLAUSIBLE_HOST = process.env.PLAUSIBLE_HOST ?? "https://plausible.io";

interface ServerEvent {
  name: string;
  domain: string;
  url: string;
  props?: Record<string, string | number | boolean>;
  revenue?: { currency: string; amount: number };
}

export async function trackServerEvent(event: ServerEvent): Promise<void> {
  await fetch(`${PLAUSIBLE_HOST}/api/event`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "User-Agent": "Server-Side Tracker",
    },
    body: JSON.stringify({
      name: event.name,
      domain: event.domain,
      url: event.url,
      props: event.props ? JSON.stringify(event.props) : undefined,
      revenue: event.revenue,
    }),
  });
}

Dashboard API for Internal Tools

// app/api/analytics/route.ts
import { NextResponse } from "next/server";
import { getAggregate, getTopPages, getRealtime } from "@/lib/plausible-api";

export async function GET(): Promise<NextResponse> {
  const [aggregate, topPages, realtimeVisitors] = await Promise.all([
    getAggregate("30d"),
    getTopPages("30d", 5),
    getRealtime(),
  ]);

  return NextResponse.json({
    summary: {
      visitors: aggregate.visitors.value,
      pageviews: aggregate.pageviews.value,
      bounceRate: aggregate.bounce_rate.value,
      avgDuration: aggregate.visit_duration.value,
    },
    topPages,
    realtimeVisitors,
  });
}

Best Practices

  • Proxy the script through your domain using Next.js rewrites to prevent ad blockers from stripping the tracking script, ensuring accurate visitor counts.
  • Use custom events sparingly. Plausible is designed for simplicity. Track 5-15 meaningful goals rather than hundreds of granular events.
  • Leverage the data-domain attribute correctly when running Plausible on staging versus production to keep test traffic separate.
  • Use the Stats API to build internal dashboards rather than giving every team member access to the Plausible UI, keeping your analytics key secure.
  • Set up revenue tracking for e-commerce goals to see conversion value alongside visitor counts directly in the Plausible dashboard.
  • Self-host with Docker if you need full data ownership. Plausible provides a well-maintained docker-compose.yml with ClickHouse and PostgreSQL.
  • Use script.tagged-events.js variant when you want CSS-class-based tracking for elements like outbound links or file downloads without writing JavaScript.

Anti-Patterns

  • Treating Plausible like a full product analytics suite. It is intentionally minimal. If you need funnels, cohorts, or session-level analysis, pair it with a dedicated tool.
  • Adding the script without the proxy. Many privacy-focused users run ad blockers that strip third-party analytics scripts. The proxy rewrite pattern solves this reliably.
  • Sending personally identifiable information in event props. Plausible is designed to be privacy-first. Sending emails, user IDs, or IP addresses in props defeats the purpose and may violate your own privacy policy.
  • Polling the realtime endpoint too frequently. The realtime API returns a simple visitor count. Polling more than once every 30 seconds adds unnecessary load with no benefit.
  • Ignoring the callback option on conversion events. If you track a goal and immediately navigate away, the event may not fire. Use the callback to delay navigation until the event is sent.
  • Using Plausible and Google Analytics together without reason. Running two analytics scripts doubles the overhead. Choose one as primary and remove the other.

Install this skill directly: skilldb add analytics-services-skills

Get CLI access →