Plausible Event Tracking | OpsBlu Docs

Plausible Event Tracking

How to implement custom event tracking in Plausible Analytics. Covers event naming conventions, required and optional parameters, ecommerce events.

Overview

Event tracking in Plausible Analytics allows you to measure specific user interactions beyond simple pageviews. Unlike traditional analytics platforms with complex event taxonomies, Plausible uses a streamlined approach where events are called "goals" and can be triggered either automatically (pageview goals) or manually via JavaScript (custom event goals).

Plausible's event model emphasizes simplicity and privacy. Events capture user actions without personal identifiers, maintaining GDPR and CCPA compliance. Each event can include custom properties to provide context, and revenue tracking can be attached to conversion events for e-commerce analytics.

Key Concepts

Goals: In Plausible terminology, trackable events are called "goals." Goals must be configured in your Plausible dashboard before data will appear in reports.

Custom Events: JavaScript-triggered events using the plausible() function, allowing you to track button clicks, form submissions, downloads, and other interactions.

Pageview Goals: Automatically triggered when users visit specific URL patterns, useful for tracking thank-you pages, specific sections, or conversion endpoints.

Event Properties: Additional key-value data attached to events to provide context (limited to 30 properties per site).

Event Model Architecture

Plausible events follow a flat, simple structure:

plausible(eventName, {
  props: { key: 'value' },  // Optional properties
  revenue: { currency: 'USD', amount: 10.00 }  // Optional revenue
});

This simplicity contrasts with complex event taxonomies in Google Analytics 4 or Adobe Analytics, making implementation and maintenance significantly easier.

Configuration

Setting Up Goals in Dashboard

Before sending custom events, configure goals in your Plausible dashboard:

  1. Log into your Plausible account
  2. Select your website
  3. Navigate to Settings → Goals
  4. Click "+ Add Goal"
  5. Choose goal type:
    • Custom Event: For JavaScript-triggered events
    • Pageview: For URL-based triggers

Custom Event Goal Configuration

For custom events, simply enter the event name exactly as it will appear in your JavaScript code:

Dashboard Configuration:

  • Goal Type: Custom Event
  • Event Name: Signup (case-sensitive)

JavaScript Implementation:

plausible('Signup');

The event name must match exactly, including capitalization.

Pageview Goal Configuration

Pageview goals trigger when users visit URLs matching a pattern:

Dashboard Configuration:

  • Goal Type: Pageview
  • Page Path: /thank-you (exact match)
  • Or: /products/* (wildcard match)
  • Or: /blog/** (recursive wildcard)

Pageview goals don't require JavaScript implementation - they fire automatically when the URL matches.

Enabling Custom Properties

To use custom properties in your events:

  1. Navigate to Settings → General
  2. Scroll to "Custom Properties"
  3. Enable the feature (available on paid plans)
  4. Properties are automatically available once enabled

Revenue Tracking Configuration

Revenue tracking is available on paid plans:

  1. Navigate to Settings → General
  2. Enable "Revenue Goals"
  3. Select which goals should track revenue
  4. Implement revenue tracking in your code

Core Events to Ship

1. Pageview (Automatic)

Automatically tracked on every page load:

// Automatic - no code required
// Plausible script handles this automatically

For Single-Page Applications (SPAs), manually trigger pageviews:

// After route change in SPA
plausible('pageview');

2. Signup Events

Track user registration and account creation:

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

// With properties
plausible('Signup', {
  props: {
    method: 'email',  // 'email', 'google', 'github'
    plan: 'free'      // 'free', 'starter', 'pro'
  }
});

3. Purchase Events

Track e-commerce transactions with revenue:

plausible('Purchase', {
  revenue: {
    currency: 'USD',
    amount: 99.99
  },
  props: {
    product_category: 'software',
    payment_method: 'credit_card',
    subscription_type: 'monthly'
  }
});

4. Newsletter Subscribe

Track newsletter signups and email subscriptions:

plausible('Newsletter Subscribe', {
  props: {
    location: 'footer',      // 'header', 'footer', 'popup'
    list_type: 'weekly'      // 'weekly', 'daily', 'announcements'
  }
});

5. Download Events

Track file downloads:

// Automatic file download tracking
// Add data-attribute to download links
<a href="/guide.pdf" data-plausible-event-name="Download">Download Guide</a>

// Or manual tracking
document.querySelector('.download-link').addEventListener('click', function(e) {
  plausible('Download', {
    props: {
      file_name: 'user-guide.pdf',
      file_type: 'pdf',
      file_size: '2.5MB'
    }
  });
});

Track clicks to external websites:

<!-- Automatic outbound link tracking -->
<!-- Use script extension -->
<script defer data-domain="example.com" src="https://plausible.io/js/script.outbound-links.js"></script>

<!-- Links to external sites are automatically tracked -->
<a href="https://external-site.com">External Link</a>

<!-- Or manual tracking -->
<a href="https://external-site.com" Link', {props: {url: 'external-site.com'}})">
  External Link
</a>

7. Video Engagement

Track video plays and engagement:

// Track video play
document.querySelector('#video').addEventListener('play', function() {
  plausible('Video Play', {
    props: {
      video_id: 'intro-2025',
      video_title: 'Product Introduction',
      video_duration: '180'  // seconds
    }
  });
});

// Track completion milestones
document.querySelector('#video').addEventListener('timeupdate', function(e) {
  const video = e.target;
  const percentComplete = (video.currentTime / video.duration) * 100;

  if (percentComplete >= 25 && !video.milestone25) {
    plausible('Video 25% Complete', {props: {video_id: 'intro-2025'}});
    video.milestone25 = true;
  }
  // Repeat for 50%, 75%, 100%
});

8. Form Submission

Track form submissions and conversions:

document.querySelector('#contact-form').addEventListener('submit', function(e) {
  plausible('Contact Form Submit', {
    props: {
      form_type: 'contact',
      page_location: window.location.pathname
    }
  });
});

9. Search Events

Track site search usage:

document.querySelector('#search-form').addEventListener('submit', function(e) {
  const query = document.querySelector('#search-input').value;

  plausible('Search', {
    props: {
      query_length: query.length.toString(),
      has_results: 'true'  // Update based on results
    }
  });
});

10. Add to Cart

Track e-commerce cart additions:

document.querySelector('.add-to-cart-btn').addEventListener('click', function() {
  plausible('Add to Cart', {
    props: {
      product_id: 'prod-123',
      product_category: 'electronics',
      quantity: '1'
    }
  });
});

Code Examples

Basic Event Tracking

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Event Tracking Example</title>

  <!-- Plausible script -->
  <script defer data-domain="example.com" src="https://plausible.io/js/script.js"></script>
</head>
<body>
  <button id="cta-button">Sign Up</button>

  <script>
    document.getElementById('cta-button').addEventListener('click', function() {
      plausible('CTA Click', {
        props: {
          button_text: 'Sign Up',
          button_location: 'hero'
        }
      });
    });
  </script>
</body>
</html>

E-commerce Full Funnel Tracking

// Product view
function trackProductView(product) {
  plausible('Product View', {
    props: {
      product_id: product.id,
      product_name: product.name,
      product_category: product.category,
      price: product.price.toString()
    }
  });
}

// Add to cart
function trackAddToCart(product, quantity) {
  plausible('Add to Cart', {
    props: {
      product_id: product.id,
      product_category: product.category,
      quantity: quantity.toString(),
      price: product.price.toString()
    }
  });
}

// Begin checkout
function trackBeginCheckout(cart) {
  plausible('Begin Checkout', {
    props: {
      cart_value: cart.total.toString(),
      item_count: cart.items.length.toString()
    }
  });
}

// Purchase completion
function trackPurchase(order) {
  plausible('Purchase', {
    revenue: {
      currency: order.currency,
      amount: order.total
    },
    props: {
      order_id: order.id,
      payment_method: order.paymentMethod,
      shipping_method: order.shippingMethod,
      item_count: order.items.length.toString(),
      discount_applied: order.discountCode ? 'true' : 'false'
    }
  });
}

SPA (Single-Page Application) Tracking

// React example with React Router
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function usePageViewTracking() {
  const location = useLocation();

  useEffect(() => {
    // Track pageview on route change
    plausible('pageview');
  }, [location]);
}

// Vue.js example with Vue Router
router.afterEach((to, from) => {
  // Track pageview on route change
  plausible('pageview');
});

// Vanilla JavaScript with History API
const originalPushState = history.pushState;
history.pushState = function() {
  originalPushState.apply(this, arguments);
  plausible('pageview');
};

const originalReplaceState = history.replaceState;
history.replaceState = function() {
  originalReplaceState.apply(this, arguments);
  plausible('pageview');
};

window.addEventListener('popstate', function() {
  plausible('pageview');
});

Advanced Event Tracking with Callbacks

// Track event with callback
function trackWithCallback(eventName, props, callback) {
  plausible(eventName, {
    props: props,
    callback: callback
  });
}

// Usage: Track signup and redirect after event is sent
document.querySelector('#signup-btn').addEventListener('click', function(e) {
  e.preventDefault();

  trackWithCallback('Signup',
    { method: 'email' },
    function() {
      // Redirect after event is tracked
      window.location.href = '/welcome';
    }
  );
});

Scroll Depth Tracking

let scrollDepthTracked = {
  '25': false,
  '50': false,
  '75': false,
  '100': false
};

function trackScrollDepth() {
  const windowHeight = window.innerHeight;
  const documentHeight = document.documentElement.scrollHeight;
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  const scrollPercentage = (scrollTop / (documentHeight - windowHeight)) * 100;

  Object.keys(scrollDepthTracked).forEach(depth => {
    if (scrollPercentage >= parseInt(depth) && !scrollDepthTracked[depth]) {
      plausible('Scroll Depth', {
        props: {
          depth: depth + '%',
          page: window.location.pathname
        }
      });
      scrollDepthTracked[depth] = true;
    }
  });
}

// Throttle scroll events
let scrollTimeout;
window.addEventListener('scroll', function() {
  if (scrollTimeout) {
    clearTimeout(scrollTimeout);
  }
  scrollTimeout = setTimeout(trackScrollDepth, 100);
});

Click Tracking with Data Attributes

<!-- Declarative event tracking using data attributes -->
<button data-plausible-event-name="CTA Click"
        data-plausible-event-location="hero">
  Get Started
</button>

<a href="/pricing"
   data-plausible-event-name="Pricing Link Click"
   data-plausible-event-source="navigation">
  View Pricing
</a>

<script>
  // Automatically track clicks with data attributes
  document.addEventListener('click', function(e) {
    const element = e.target.closest('[data-plausible-event-name]');

    if (element) {
      const eventName = element.getAttribute('data-plausible-event-name');
      const props = {};

      // Collect all data-plausible-event-* attributes
      Array.from(element.attributes).forEach(attr => {
        if (attr.name.startsWith('data-plausible-event-') && attr.name !== 'data-plausible-event-name') {
          const propName = attr.name.replace('data-plausible-event-', '');
          props[propName] = attr.value;
        }
      });

      plausible(eventName, { props: Object.keys(props).length > 0 ? props : undefined });
    }
  });
</script>

Error Tracking

// Track JavaScript errors
window.addEventListener('error', function(event) {
  plausible('JavaScript Error', {
    props: {
      message: event.message.substring(0, 100),
      filename: event.filename.split('/').pop(),
      line: event.lineno.toString(),
      column: event.colno.toString()
    }
  });
});

// Track unhandled promise rejections
window.addEventListener('unhandledrejection', function(event) {
  plausible('Unhandled Promise Rejection', {
    props: {
      reason: String(event.reason).substring(0, 100)
    }
  });
});

// Track API errors
async function apiCall(url) {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      plausible('API Error', {
        props: {
          endpoint: url.split('?')[0],
          status_code: response.status.toString(),
          status_text: response.statusText
        }
      });
    }

    return response;
  } catch (error) {
    plausible('Network Error', {
      props: {
        endpoint: url.split('?')[0],
        error_type: error.name
      }
    });
    throw error;
  }
}

Payload Rules

Event Name Requirements

  • Format: Use Title Case or lowercase-with-hyphens
  • Length: Keep under 50 characters
  • Characters: Letters, numbers, spaces, hyphens, underscores
  • Consistency: Match dashboard goal configuration exactly (case-sensitive)

Valid examples:

  • Signup
  • Purchase
  • newsletter-subscribe
  • Video Play
  • add_to_cart

Invalid examples:

  • signup! (special characters)
  • This Is An Extremely Long Event Name That Will Be Hard To Read In Reports (too long)
  • user-email@example.com (contains PII)

Property Requirements

Properties must be:

  • String values only: All property values must be strings
  • No PII: Never include email, name, phone, IP, user ID
  • Descriptive keys: Use clear, lowercase_with_underscores format
  • Reasonable length: Keep values under 500 characters
  • Limited count: Maximum 30 unique properties per site

Valid property examples:

{
  product_category: 'electronics',
  button_location: 'header',
  price_range: '50-100',
  user_type: 'premium'  // Status, not identifier
}

Invalid property examples:

{
  email: 'user@example.com',  // PII!
  userId: '12345',  // Personal identifier
  ipAddress: '192.168.1.1'  // PII!
}

Revenue Requirements

Revenue tracking requires specific format:

{
  revenue: {
    currency: 'USD',  // Required: ISO 4217 code
    amount: 49.99     // Required: Number (not string)
  }
}

Supported currencies: USD, EUR, GBP, CAD, AUD, JPY, and all ISO 4217 codes.

Naming & Conventions

Option 1: Title Case (Recommended)

plausible('Signup');
plausible('Add to Cart');
plausible('Newsletter Subscribe');
plausible('Video Play');

Option 2: lowercase-with-hyphens

plausible('signup');
plausible('add-to-cart');
plausible('newsletter-subscribe');
plausible('video-play');

Option 3: snake_case

plausible('signup');
plausible('add_to_cart');
plausible('newsletter_subscribe');
plausible('video_play');

Choose one convention and stick to it across your entire site.

Event Naming Categories

Organize events by category for easier reporting:

// User Actions
plausible('Signup');
plausible('Login');
plausible('Logout');

// E-commerce
plausible('Product View');
plausible('Add to Cart');
plausible('Purchase');

// Content Engagement
plausible('Article Read');
plausible('Video Play');
plausible('Download');

// Forms
plausible('Contact Form Submit');
plausible('Newsletter Subscribe');

// Navigation
plausible('Search');
plausible('Filter Applied');

Property Naming Standards

Use consistent snake_case for all properties:

plausible('Purchase', {
  props: {
    product_id: 'abc123',
    product_category: 'electronics',
    payment_method: 'credit_card',
    shipping_method: 'express',
    order_value: '99.99'
  }
});

Validation Steps

1. Pre-Deployment Checklist

Before deploying event tracking:

  • All goals configured in Plausible dashboard
  • Event names match dashboard configuration exactly
  • No PII included in event data
  • Property names follow naming convention
  • Revenue format validated
  • Code tested in staging environment

2. Real-Time Verification

Test events using Plausible's real-time dashboard:

// Test script to verify all events
const testEvents = [
  { name: 'Signup', props: { method: 'test' } },
  { name: 'Purchase', revenue: { currency: 'USD', amount: 0.01 }, props: { test: 'true' } },
  { name: 'Download', props: { file_type: 'test' } }
];

testEvents.forEach(event => {
  console.log('Testing event:', event.name);
  plausible(event.name, {
    props: event.props,
    revenue: event.revenue
  });
});

console.log('Check Plausible dashboard for events appearing in real-time');

3. Browser Console Verification

Check events in browser DevTools:

// Enable Plausible debugging
localStorage.plausible_debug = true;

// Trigger event
plausible('Test Event', { props: { test: 'true' } });

// Check console for debug output
// Check Network tab for /api/event requests

4. Network Request Inspection

Inspect the event payload being sent:

  1. Open DevTools (F12)
  2. Go to Network tab
  3. Filter for event
  4. Trigger an event
  5. Click the request to see payload

Expected payload:

{
  "n": "Signup",
  "u": "https://example.com/signup",
  "d": "example.com",
  "p": "{\"method\":\"email\",\"plan\":\"free\"}"
}

5. Goal Funnel Testing

Test complete user journeys:

// Simulate complete e-commerce funnel
async function testEcommerceFunnel() {
  console.log('Starting funnel test...');

  // Step 1: Product View
  plausible('Product View', { props: { product_id: 'test-001' } });
  await sleep(1000);

  // Step 2: Add to Cart
  plausible('Add to Cart', { props: { product_id: 'test-001' } });
  await sleep(1000);

  // Step 3: Begin Checkout
  plausible('Begin Checkout', { props: { cart_value: '99.99' } });
  await sleep(1000);

  // Step 4: Purchase
  plausible('Purchase', {
    revenue: { currency: 'USD', amount: 0.01 },
    props: { order_id: 'test-' + Date.now() }
  });

  console.log('Funnel test complete. Check dashboard.');
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

6. SPA Pageview Validation

For single-page applications, verify pageviews fire on route changes:

// Test SPA pageview tracking
let pageviewCount = 0;

const originalPlausible = window.plausible;
window.plausible = function(eventName, options) {
  if (eventName === 'pageview') {
    pageviewCount++;
    console.log(`Pageview ${pageviewCount} tracked:`, window.location.pathname);
  }
  return originalPlausible.apply(this, arguments);
};

// Navigate through SPA and verify pageviews are tracked

QA Notes

Pre-Launch Testing

  1. Goal Configuration: Verify all events are configured as goals in dashboard
  2. Staging Tests: Trigger each goal at least once in staging environment
  3. Real-Time Verification: Confirm events appear in real-time dashboard within 2 seconds
  4. Domain Verification: Ensure correct domain label appears with each event
  5. Property Verification: Check that all properties appear correctly in dashboard
  6. Revenue Testing: Verify revenue amounts display correctly in revenue reports

SPA-Specific Testing

For single-page applications:

  • Pageviews trigger on route changes
  • No duplicate pageviews on initial load
  • Browser back/forward buttons trigger pageviews
  • Direct URL access triggers pageview
  • Hash-based routing handled correctly (if applicable)

Common Testing Scenarios

// Test 1: Event fires on button click
document.querySelector('#test-button').addEventListener('click', function() {
  console.log('Button clicked - event should fire');
  plausible('Button Click');
});

// Test 2: Event fires only once (no duplicates)
let clickCount = 0;
document.querySelector('#test-button').addEventListener('click', function() {
  clickCount++;
  console.log(`Button clicked ${clickCount} times`);
  plausible('Button Click');
});

// Test 3: Properties are correctly formatted
plausible('Test Event', {
  props: {
    test_property: 'test_value',
    numeric_value: '123',  // Must be string
    boolean_value: 'true'  // Must be string
  }
});

// Test 4: Revenue tracking works
plausible('Test Purchase', {
  revenue: {
    currency: 'USD',
    amount: 0.01
  },
  props: {
    test: 'true'
  }
});

Performance Testing

Monitor the impact of event tracking on page performance:

// Measure event tracking performance
console.time('event-tracking');
plausible('Performance Test');
console.timeEnd('event-tracking');

// Typically should be < 10ms

// Test under load
async function loadTest() {
  for (let i = 0; i < 100; i++) {
    plausible('Load Test', { props: { iteration: i.toString() } });
    await sleep(10);
  }
}

Troubleshooting

Issue Possible Cause Solution
Events not appearing in dashboard Goal not configured in dashboard Add goal in Plausible settings before sending events
Event name mismatch Case sensitivity issue Ensure event name matches dashboard configuration exactly
Properties not showing Properties not enabled Enable custom properties in Plausible site settings (paid plans)
Revenue data missing Incorrect format Verify revenue object has currency (string) and amount (number)
Duplicate events Multiple event listeners Remove duplicate listeners; use event delegation
SPA pageviews not tracking Manual pageview not called Call plausible('pageview') after route changes
Events delayed Network latency Events may take 1-2 seconds to appear; this is normal
Ad blocker preventing events Plausible domain blocked Implement custom proxy with first-party domain
Property values truncated Value too long Limit property values to 500 characters
Events fire but props empty Props object incorrect format Ensure props are nested: {props: {key: 'value'}}
Revenue not in correct currency Currency code invalid Use ISO 4217 codes (USD, EUR, GBP, etc.)
Events tracked on wrong domain data-domain attribute wrong Verify data-domain in script tag matches your domain
Callback not firing Callback function error Check browser console for JavaScript errors
Pageview goal not triggering URL pattern mismatch Check URL pattern in goal configuration; test with wildcards
Outbound links not tracked Script extension not loaded Use script.outbound-links.js version
File downloads not tracked Missing data attribute Add data-plausible-event-name to download links
Historical data incomplete Events sent before goal configured Goals must be configured before sending events
Cross-domain events lost Session tracking broken Verify cross-domain tracking configuration
Mobile events not tracking Script blocked on mobile Check mobile browser console; verify script loads
Event fired too early Script not loaded yet Ensure plausible function exists before calling

Best Practices

  1. Configure Goals First: Always set up goals in dashboard before implementing tracking code
  2. Keep Names Simple: Use clear, concise event names that are easy to understand in reports
  3. Consistent Naming: Choose a naming convention (Title Case recommended) and stick to it
  4. Avoid PII: Never include personally identifiable information in events or properties
  5. Test Thoroughly: Test all events in staging before deploying to production
  6. Use Properties Wisely: Limit to essential context; remember the 30-property limit per site
  7. Document Events: Maintain a registry of all events and their purposes
  8. Monitor Performance: Track the impact of event code on page load times
  9. Version Control: Keep event tracking code in version control
  10. Regular Audits: Review events quarterly to remove unused goals and update documentation