Plausible Install / Embed Tag or SDK | OpsBlu Docs

Plausible Install / Embed Tag or SDK

Deployment approach for installing Plausible's tag or SDK across web, mobile, and server environments.

Overview

Plausible Analytics is a lightweight, privacy-first analytics platform that doesn't use cookies and respects user privacy by default. The Plausible script weighs less than 1KB (compared to Google Analytics at 45KB+), making it one of the fastest analytics solutions available. This guide covers all deployment methods across web, mobile, and server environments.

Key Benefits:

  • No cookies or persistent identifiers
  • GDPR, CCPA, and PECR compliant by default
  • Script size < 1KB for optimal performance
  • No impact on Core Web Vitals
  • Open source and self-hostable

Prerequisites

Before installing Plausible, ensure you have:

  1. Active Plausible Account

    • Sign up at plausible.io or deploy self-hosted instance
    • Add your domain to your Plausible account under Site Settings
    • Note your exact domain as configured (e.g., yourdomain.com vs www.yourdomain.com)
  2. Domain Configuration

    • Verify domain ownership if using Plausible Cloud
    • For self-hosted: Configure DNS and SSL certificates
    • Ensure your domain matches exactly what's configured in Plausible
  3. Environment Planning

    • Separate Plausible sites for staging, development, and production
    • Document which domains map to which Plausible sites
    • Plan first-party proxy strategy if avoiding ad blockers is critical
  4. Access Requirements

    • Ability to modify HTML <head> section or deploy via tag manager
    • For npm installations: Node.js 14+ and package manager access
    • For server-side: API access and authentication credentials
  5. Privacy & Compliance

    • Review whether consent management is required for your jurisdiction
    • Determine if IP anonymization or proxy requirements apply
    • Document data retention policies

Installation Methods

The simplest and most common installation method. Add the script to your site's <head> section.

Basic Installation

<!DOCTYPE html>
<html>
  <head>
    <script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script>
    <!-- Rest of your head content -->
  </head>
  <body>
    <!-- Your content -->
  </body>
</html>

Important Attributes:

  • defer: Loads script asynchronously without blocking page render
  • data-domain: Must match your domain exactly as configured in Plausible
  • Script automatically tracks pageviews on load and history changes

Self-Hosted Installation

If you're running Plausible on your own infrastructure:

<script defer data-domain="yourdomain.com" src="https://analytics.yourdomain.com/js/script.js"></script>

Replace analytics.yourdomain.com with your self-hosted Plausible instance URL.

Extended Script Variants

Plausible offers specialized script variants for different use cases:

Hash-based routing (for SPAs):

<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.hash.js"></script>

Outbound link tracking:

<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.outbound-links.js"></script>

File downloads tracking:

<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.file-downloads.js"></script>

404 error tracking:

<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.404.js"></script>

Multiple extensions (combine features):

<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.hash.outbound-links.file-downloads.js"></script>

Method 2: First-Party Proxy (Bypass Ad Blockers)

Ad blockers can prevent the Plausible script from loading. A first-party proxy routes requests through your domain.

Why Use a Proxy?

  • Bypasses ad blockers that block third-party analytics
  • Keeps all analytics traffic on your domain
  • Improves data accuracy by 10-30% in some cases
  • Maintains privacy-first approach

Proxy Setup Options

Option A: Using Cloudflare Workers

  1. Create a new Cloudflare Worker:
// cloudflare-worker.js
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const url = new URL(request.url)

  // Proxy script requests
  if (url.pathname === '/js/script.js') {
    return fetch('https://plausible.io/js/script.js')
  }

  // Proxy API events
  if (url.pathname === '/api/event') {
    const newRequest = new Request('https://plausible.io/api/event', request)
    return fetch(newRequest)
  }

  return fetch(request)
}
  1. Update your HTML to use the proxy:
<script defer data-domain="yourdomain.com" data-api="/api/event" src="/js/script.js"></script>

Option B: Using Nginx Reverse Proxy

# nginx.conf
location = /js/script.js {
    proxy_pass https://plausible.io/js/script.js;
    proxy_set_header Host plausible.io;
    proxy_ssl_server_name on;
    proxy_ssl_name plausible.io;
}

location = /api/event {
    proxy_pass https://plausible.io/api/event;
    proxy_set_header Host plausible.io;
    proxy_ssl_server_name on;
    proxy_ssl_name plausible.io;
    proxy_buffering on;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Option C: Using Apache Reverse Proxy

# .htaccess or apache config
ProxyPass /js/script.js https://plausible.io/js/script.js
ProxyPassReverse /js/script.js https://plausible.io/js/script.js

ProxyPass /api/event https://plausible.io/api/event
ProxyPassReverse /api/event https://plausible.io/api/event

Method 3: NPM Package Integration

For modern JavaScript applications with build processes.

Installation

npm install plausible-tracker
# or
yarn add plausible-tracker
# or
pnpm add plausible-tracker

Basic Usage

import Plausible from 'plausible-tracker'

const plausible = Plausible({
  domain: 'yourdomain.com',
  apiHost: 'https://plausible.io', // Optional: use your self-hosted instance
})

// Enable automatic pageview tracking
plausible.enableAutoPageviews()

// Track custom events
plausible.trackEvent('signup', {
  props: {
    plan: 'premium',
    method: 'google'
  }
})

Advanced Configuration

const plausible = Plausible({
  domain: 'yourdomain.com',
  apiHost: 'https://plausible.io',
  trackLocalhost: false, // Don't track localhost
  hashMode: true, // Enable hash-based routing
})

// Manual pageview tracking
plausible.trackPageview({
  url: 'https://yourdomain.com/custom-page',
  referrer: document.referrer,
  deviceWidth: window.innerWidth,
})

// Track with custom properties
plausible.trackEvent('purchase', {
  props: {
    amount: 99.99,
    currency: 'USD',
    product: 'subscription'
  },
  revenue: {
    currency: 'USD',
    amount: 99.99
  }
})

Method 4: Google Tag Manager Deployment

Deploy Plausible through GTM for centralized tag management.

Step 1: Create Custom HTML Tag

  1. In GTM, navigate to Tags → New → Tag Configuration
  2. Select Custom HTML tag type
  3. Add the Plausible script:
<script>
  (function() {
    var script = document.createElement('script');
    script.defer = true;
    script.setAttribute('data-domain', '{{Page Hostname}}');
    script.src = 'https://plausible.io/js/script.js';
    document.head.appendChild(script);
  })();
</script>

Step 2: Configure Trigger

  • Trigger Type: Page View - DOM Ready
  • This trigger fires on: All Pages
  • Priority: Set high priority (e.g., 100) to load early

Step 3: Environment-Specific Configuration

Create a GTM Variable for the domain:

  1. Variables → New → Data Layer Variable
  2. Name: Plausible Domain
  3. Logic:
function() {
  var hostname = {{Page Hostname}};
  // Map staging/dev domains to separate Plausible sites
  if (hostname.includes('staging')) {
    return 'staging.yourdomain.com';
  } else if (hostname.includes('localhost')) {
    return 'dev.yourdomain.com';
  }
  return 'yourdomain.com';
}

Step 4: Custom Event Tracking via GTM

Create additional tags for custom events:

<script>
  window.plausible = window.plausible || function() {
    (window.plausible.q = window.plausible.q || []).push(arguments)
  };

  plausible('{{Event Name}}', {
    props: {
      category: '{{Event Category}}',
      label: '{{Event Label}}',
      value: '{{Event Value}}'
    }
  });
</script>

Framework Integrations

React Integration

Using Script Tag in index.html

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script>
    <!-- Other head content -->
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

Using React Helmet

import { Helmet } from 'react-helmet';

function App() {
  return (
    <>
      <Helmet>
        <script
          defer
          data-domain="yourdomain.com"
          src="https://plausible.io/js/script.js"
        />
      </Helmet>
      {/* Your app components */}
    </>
  );
}

Using NPM Package with React Hooks

import { useEffect } from 'react';
import Plausible from 'plausible-tracker';

const plausible = Plausible({
  domain: 'yourdomain.com'
});

function App() {
  useEffect(() => {
    // Track pageviews on route changes
    plausible.trackPageview();
  }, []);

  return <YourAppComponents />;
}

// Custom hook for event tracking
function usePlausible() {
  const trackEvent = (eventName, props) => {
    plausible.trackEvent(eventName, { props });
  };

  return { trackEvent };
}

// Usage in components
function SignupButton() {
  const { trackEvent } = usePlausible();

  const handleClick = () => {
    trackEvent('signup-click', {
      location: 'header',
      plan: 'free'
    });
  };

  return <button Up</button>;
}

Next.js Integration

App Router (Next.js 13+)

// app/layout.js
import Script from 'next/script'

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

Pages Router (Next.js 12 and earlier)

// pages/_app.js
import Script from 'next/script'
import { useRouter } from 'next/router'
import { useEffect } from 'react'

function MyApp({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    // Track pageviews on route changes
    const handleRouteChange = () => {
      window.plausible?.('pageview')
    }

    router.events.on('routeChangeComplete', handleRouteChange)
    return () => router.events.off('routeChangeComplete', handleRouteChange)
  }, [router.events])

  return (
    <>
      <Script
        defer
        data-domain="yourdomain.com"
        src="https://plausible.io/js/script.js"
        strategy="afterInteractive"
      />
      <Component {...pageProps} />
    </>
  )
}

export default MyApp

Using next-plausible Package

npm install next-plausible
// pages/_app.js
import PlausibleProvider from 'next-plausible'

export default function MyApp({ Component, pageProps }) {
  return (
    <PlausibleProvider domain="yourdomain.com">
      <Component {...pageProps} />
    </PlausibleProvider>
  )
}

// Track events in components
import { usePlausible } from 'next-plausible'

function MyComponent() {
  const plausible = usePlausible()

  const handleClick = () => {
    plausible('custom-event', {
      props: { buttonId: 'cta-main' }
    })
  }

  return <button Me</button>
}

Vue.js Integration

Vue 3 Composition API

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import Plausible from 'plausible-tracker'

const plausible = Plausible({
  domain: 'yourdomain.com'
})

const app = createApp(App)

// Make plausible available globally
app.config.globalProperties.$plausible = plausible

// Enable auto pageviews
plausible.enableAutoPageviews()

app.mount('#app')
<!-- Component.vue -->
<template>
  <button @click="trackClick">Track Event</button>
</template>

<script setup>
import { getCurrentInstance } from 'vue'

const instance = getCurrentInstance()
const plausible = instance.appContext.config.globalProperties.$plausible

const trackClick = () => {
  plausible.trackEvent('button-click', {
    props: { component: 'hero' }
  })
}
</script>

Vue 2 Options API

// main.js
import Vue from 'vue'
import App from './App.vue'
import Plausible from 'plausible-tracker'

const plausible = Plausible({
  domain: 'yourdomain.com'
})

Vue.prototype.$plausible = plausible
plausible.enableAutoPageviews()

new Vue({
  render: h => h(App),
}).$mount('#app')

Angular Integration

Add Script to index.html

<!-- src/index.html -->
<!doctype html>
<html lang="en">
<head>
  <script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script>
</head>
<body>
  <app-root></app-root>
</body>
</html>

Create Plausible Service

// services/plausible.service.ts
import { Injectable } from '@angular/core';

declare global {
  interface Window {
    plausible?: (event: string, options?: any) => void;
  }
}

@Injectable({
  providedIn: 'root'
})
export class PlausibleService {
  trackEvent(eventName: string, props?: Record<string, any>) {
    if (typeof window !== 'undefined' && window.plausible) {
      window.plausible(eventName, { props });
    }
  }

  trackPageview(url?: string) {
    if (typeof window !== 'undefined' && window.plausible) {
      window.plausible('pageview', url ? { u: url } : undefined);
    }
  }
}
// app.component.ts
import { Component } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
import { PlausibleService } from './services/plausible.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  constructor(
    private router: Router,
    private plausible: PlausibleService
  ) {
    // Track pageviews on route changes
    this.router.events
      .pipe(filter(event => event instanceof NavigationEnd))
      .subscribe(() => {
        this.plausible.trackPageview();
      });
  }
}

Svelte Integration

<!-- App.svelte -->
<svelte:head>
  <script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script>
</svelte:head>

<script>
  import { onMount } from 'svelte';

  function trackEvent(eventName, props) {
    if (typeof window !== 'undefined' && window.plausible) {
      window.plausible(eventName, { props });
    }
  }

  function handleClick() {
    trackEvent('button-click', {
      component: 'hero',
      action: 'signup'
    });
  }
</script>

<button on:click={handleClick}>
  Sign Up
</button>

Configuration Options

Script Attributes

Configure Plausible behavior using data attributes:

<script
  defer
  data-domain="yourdomain.com"
  data-api="/api/event"
  src="https://plausible.io/js/script.js"
></script>

Available Attributes:

Attribute Purpose Example
data-domain Your site domain (required) yourdomain.com
data-api Custom API endpoint for proxying /api/event
data-exclude Comma-separated pages to exclude /admin,/dashboard
data-include Only track specified pages /blog,/docs

Excluding Pages from Tracking

<script
  defer
  data-domain="yourdomain.com"
  data-exclude="/admin/*,/dashboard/*,/internal/*"
  src="https://plausible.io/js/script.exclusions.js"
></script>

Manual Event Tracking

// Basic custom event
plausible('signup');

// Event with properties
plausible('purchase', {
  props: {
    product: 'subscription',
    plan: 'premium',
    amount: 99
  }
});

// Revenue tracking
plausible('purchase', {
  revenue: {
    currency: 'USD',
    amount: 99.00
  }
});

Mobile & Server-Side Implementation

Mobile Web

Mobile web uses the same JavaScript snippet as desktop. No special configuration needed.

Native Mobile Apps (iOS/Android)

Plausible doesn't offer native SDKs, but you can use the Events API:

// iOS - Swift
import Foundation

func trackPlausibleEvent(domain: String, name: String, url: String, props: [String: Any]? = nil) {
    let endpoint = "https://plausible.io/api/event"

    var body: [String: Any] = [
        "domain": domain,
        "name": name,
        "url": url
    ]

    if let props = props {
        body["props"] = props
    }

    guard let jsonData = try? JSONSerialization.data(withJSONObject: body) else { return }

    var request = URLRequest(url: URL(string: endpoint)!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = jsonData

    URLSession.shared.dataTask(with: request).resume()
}

// Usage
trackPlausibleEvent(
    domain: "app.yourdomain.com",
    name: "screen_view",
    url: "app://home",
    props: ["platform": "ios", "version": "2.1.0"]
)
// Android - Kotlin
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject

suspend fun trackPlausibleEvent(
    domain: String,
    name: String,
    url: String,
    props: Map<String, Any>? = null
) = withContext(Dispatchers.IO) {
    val client = OkHttpClient()
    val endpoint = "https://plausible.io/api/event"

    val json = JSONObject().apply {
        put("domain", domain)
        put("name", name)
        put("url", url)
        props?.let { put("props", JSONObject(it)) }
    }

    val body = json.toString().toRequestBody("application/json".toMediaType())
    val request = Request.Builder()
        .url(endpoint)
        .post(body)
        .build()

    client.newCall(request).execute()
}

// Usage
trackPlausibleEvent(
    domain = "app.yourdomain.com",
    name = "screen_view",
    url = "app://home",
    props = mapOf("platform" to "android", "version" to "2.1.0")
)

Server-Side Tracking

Node.js

const fetch = require('node-fetch');

async function trackServerEvent(domain, eventName, url, props = {}) {
  const response = await fetch('https://plausible.io/api/event', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'User-Agent': 'YourApp/1.0'
    },
    body: JSON.stringify({
      domain: domain,
      name: eventName,
      url: url,
      props: props
    })
  });

  return response.ok;
}

// Track backend events
await trackServerEvent(
  'yourdomain.com',
  'api_purchase',
  'https://yourdomain.com/api/checkout',
  {
    product: 'premium',
    amount: 99,
    userId: 'user-123'
  }
);

Python

import requests

def track_plausible_event(domain, event_name, url, props=None):
    endpoint = 'https://plausible.io/api/event'

    payload = {
        'domain': domain,
        'name': event_name,
        'url': url
    }

    if props:
        payload['props'] = props

    response = requests.post(
        endpoint,
        json=payload,
        headers={'User-Agent': 'YourApp/1.0'}
    )

    return response.ok

# Usage
track_plausible_event(
    domain='yourdomain.com',
    event_name='server_purchase',
    url='https://yourdomain.com/checkout',
    props={'amount': 99, 'plan': 'premium'}
)

Verification & Testing

1. Visual Verification

After installing, verify tracking in real-time:

  1. Visit your website
  2. Log in to Plausible dashboard
  3. Navigate to your site's dashboard
  4. Check the "Current Visitors" counter increments
  5. Verify your page appears in the realtime view

2. Network Inspection

Use browser DevTools to verify requests:

  1. Open DevTools (F12)
  2. Navigate to Network tab
  3. Filter by "event" or "script"
  4. Look for requests to:
    • Script: https://plausible.io/js/script.js (or your proxy path)
    • Events: https://plausible.io/api/event (or your proxy path)

Successful Event Request:

{
  "domain": "yourdomain.com",
  "name": "pageview",
  "url": "https://yourdomain.com/page",
  "referrer": "https://google.com",
  "screen_width": 1920
}

Response (Success):

Status: 202 Accepted

3. Console Testing

Test custom events via browser console:

// Check if Plausible is loaded
typeof plausible !== 'undefined'

// Fire test event
plausible('test-event', {
  props: {
    test: true,
    timestamp: Date.now()
  }
})

// Check for errors
// Should see no console errors if working correctly

4. Goal Verification

If tracking custom events:

  1. Go to Settings → Goals in Plausible
  2. Add a custom event goal matching your event name
  3. Trigger the event on your site
  4. Check Goal Conversions in dashboard
  5. Verify event appears with correct properties

Troubleshooting

Script Not Loading

Problem: Plausible script fails to load

Solutions:

  1. Check Content Security Policy (CSP) headers allow Plausible:

    Content-Security-Policy: script-src 'self' https://plausible.io
    
  2. Verify ad blocker isn't blocking the script (implement proxy if needed)

  3. Check for CORS issues if self-hosting:

    add_header Access-Control-Allow-Origin *;
    
  4. Ensure script URL is correct and accessible

Events Not Appearing

Problem: Pageviews or events don't show in dashboard

Solutions:

  1. Verify data-domain matches exactly what's configured in Plausible
  2. Check network requests show 202 Accepted response
  3. Disable ad blockers during testing
  4. Ensure you're not on an excluded page
  5. Check localStorage isn't blocking (Plausible uses it for deduplication)
  6. Verify you've created the goal in Plausible dashboard for custom events

Duplicate Pageviews

Problem: Same pageview tracked multiple times

Solutions:

  1. Ensure script only loaded once (check for duplicate tags)
  2. For SPAs, don't use both auto-pageview AND manual tracking
  3. Check GTM isn't firing multiple times
  4. Verify router integration isn't double-tracking

Cross-Domain Tracking Issues

Problem: Sessions break across subdomains

Solution: Use consistent domain configuration:

<!-- Use root domain for all subdomains -->
<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script>

All subdomains (app.yourdomain.com, www.yourdomain.com) will track to the same site.

Self-Hosted Connection Issues

Problem: Can't connect to self-hosted instance

Solutions:

  1. Verify instance is accessible: curl https://your-plausible-domain.com/api/health
  2. Check SSL certificates are valid
  3. Verify firewall rules allow traffic
  4. Check database connection in Plausible logs
  5. Ensure ClickHouse database is running

Security Best Practices

1. Use HTTPS Only

Always serve Plausible over HTTPS:

<!-- Good -->
<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script>

<!-- Bad - Never use HTTP -->
<script defer data-domain="yourdomain.com" src="http://plausible.io/js/script.js"></script>

2. Content Security Policy

Add Plausible to your CSP headers:

Content-Security-Policy:
  script-src 'self' https://plausible.io;
  connect-src 'self' https://plausible.io;

For proxied installations:

Content-Security-Policy:
  script-src 'self';
  connect-src 'self';

3. Subresource Integrity (SRI)

While Plausible's script changes occasionally, you can pin specific versions with SRI:

<!-- Note: This may break when Plausible updates -->
<script
  defer
  data-domain="yourdomain.com"
  src="https://plausible.io/js/script.js"
  integrity="sha384-[hash]"
  crossorigin="anonymous"
></script>

Recommendation: Use proxy method instead for better control.

4. Environment Separation

Never mix environments:

// Good - Environment-specific configuration
const plausibleDomain = process.env.NODE_ENV === 'production'
  ? 'yourdomain.com'
  : 'staging.yourdomain.com';

5. API Security for Server-Side

When using the Events API from servers:

// Use environment variables
const PLAUSIBLE_DOMAIN = process.env.PLAUSIBLE_DOMAIN;

// Validate inputs
function trackEvent(name, url, props) {
  if (!name || typeof name !== 'string') {
    throw new Error('Invalid event name');
  }

  // Sanitize URL
  const sanitizedUrl = new URL(url, PLAUSIBLE_DOMAIN).toString();

  // Continue with tracking...
}

6. Rate Limiting

Implement client-side rate limiting to prevent abuse:

const eventQueue = [];
const RATE_LIMIT = 10; // events per second
const WINDOW = 1000; // 1 second

function rateLimitedTrack(eventName, props) {
  const now = Date.now();

  // Remove old events outside window
  while (eventQueue.length && eventQueue[0] < now - WINDOW) {
    eventQueue.shift();
  }

  // Check limit
  if (eventQueue.length >= RATE_LIMIT) {
    console.warn('Rate limit exceeded');
    return;
  }

  eventQueue.push(now);
  plausible(eventName, { props });
}

7. Privacy Considerations

Even though Plausible is privacy-first:

  1. Don't track PII: Never send email addresses, names, or sensitive data in event properties
  2. Document data flows: Maintain inventory of what data is collected
  3. Respect Do Not Track: Optionally honor DNT headers:
if (navigator.doNotTrack === '1') {
  // Don't initialize Plausible
} else {
  // Load Plausible normally
}

Advanced Topics

Custom Domains for Self-Hosted

When self-hosting, use a custom subdomain:

  1. Create DNS A record: analytics.yourdomain.com → YOUR_SERVER_IP
  2. Configure SSL certificate
  3. Update Plausible configuration
  4. Use in script: src="https://analytics.yourdomain.com/js/script.js"

Revenue Tracking

Track monetary values with events:

plausible('purchase', {
  revenue: {
    currency: 'USD',
    amount: 99.99
  },
  props: {
    product: 'subscription',
    plan: 'annual'
  }
});

Hash-Based Routing for SPAs

For apps using hash-based routing (/#/page):

<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.hash.js"></script>

This treats /#/page1 and /#/page2 as different pages.

Bot Filtering

Plausible automatically filters known bots, but you can add additional checks:

// Block tracking from common automation tools
if (navigator.webdriver || window.callPhantom || window._phantom) {
  console.log('Bot detected, skipping analytics');
} else {
  // Load Plausible normally
}

Validation Checklist

Before going live, verify:

  • Script loads successfully in production environment
  • data-domain matches Plausible site configuration exactly
  • Pageviews appear in realtime dashboard within 30 seconds
  • Custom events appear under configured goals
  • Environment separation working (staging vs production)
  • Ad blocker bypass strategy tested if implemented
  • CSP headers allow Plausible connections
  • No console errors related to Plausible
  • Mobile and desktop tracking both functional
  • Cross-domain tracking works if needed
  • Server-side events appearing if implemented
  • Event properties showing correct values
  • Documentation updated with implementation details
  • Team trained on accessing dashboards

Configuration Recommendations

Proxy Setup: Implement a first-party proxy if your audience uses ad blockers heavily (tech, developer, or European audiences). Use Cloudflare Workers for the simplest setup — Plausible provides a ready-made worker script. For Nginx or Apache, proxy /js/script.js and /api/event to plausible.io. This typically recovers 15-25% of blocked pageviews.

Script Extensions: Combine extensions in a single script URL to minimize requests. Common combination: script.tagged-events.outbound-links.file-downloads.js. Add hash if you use hash-based routing (e.g., example.com/#/page). Add compat only if you need IE11 support. Full list at plausible.io/docs/script-extensions.

Privacy: Plausible is cookieless by default and does not require a consent banner in most jurisdictions. If your legal team requires explicit consent anyway, wrap the script load in your consent check. IP addresses are never stored — they're used only for unique visitor counting within a session and then discarded.

Goals: Create goals in Plausible dashboard (Site Settings → Goals) before deploying plausible('event_name') calls. Goals must exist in the dashboard to appear in reports. Revenue tracking requires the revenue property: plausible('Purchase', {revenue: {currency: 'USD', amount: 29.99}}).

Environment Separation: Create separate Plausible sites for production and staging (e.g., example.com and staging.example.com). Use data-domain attribute to target the correct site. Exclude localhost and dev environments by checking window.location.hostname before loading the script.