/ astro-integrations / How to Use Plausible with Astro: Complete Guide
astro-integrations 11 min read

How to Use Plausible with Astro: Complete Guide

Step-by-step guide to integrating Plausible Analytics with your Astro website. Installation, configuration, and best practices.

How to Use Plausible with Astro: Complete Guide

Plausible is a privacy-focused analytics tool that is lightweight, GDPR-compliant, and does not use cookies. It is the opposite of Google Analytics in all the right ways. The script is under 1KB, it does not slow down your site, and you do not need a cookie consent banner. For an Astro site where performance matters, Plausible is a perfect fit.

Prerequisites

  • Node.js 22.12.0 or higher (Astro 6 dropped Node 18 and Node 20 support entirely)
  • An Astro project (npm create astro@latest), Astro 6.4.2 at the time of writing
  • A Plausible account (Plausible Cloud, or self-hosted Community Edition for free)

Installation

Plausible does not need an npm package. You just add a tracking snippet, and that snippet is what most people use. There is no first-party package named @plausible/tracker. The only official npm package is plausible-tracker (currently 0.3.9), a small frontend library for sites that prefer to fire events from JavaScript rather than from a script tag:

npm install plausible-tracker

For a standard Astro blog the script-tag approach is simpler and is the one Plausible recommends, so the rest of this guide uses it. Reach for plausible-tracker only if you want to call trackEvent and trackPageview from your own bundle.

The Snippet Changed in October 2025

Plausible shipped a new tracking script in October 2025. Two things are different from the older guides you may have seen:

  1. Each site now has its own per-site snippet whose src looks like https://plausible.io/js/pa-XXXXXXXX.js (the exact filename is shown in your dashboard under Site Settings then Site Installation then Review Installation).
  2. Optional measurements (outbound links, file downloads, form submissions, 404 tracking, hash-based routing) are now toggled through plausible.init({ ... }) instead of by chaining filenames like script.tagged-events.outbound-links.js.

The classic https://plausible.io/js/script.js URL and the chained-extension URLs still return HTTP 200 and keep working, so existing installs do not break. New installs should prefer the per-site snippet plus plausible.init(). Both styles are shown below.

Configuration

The simplest approach is adding the Plausible script to your base layout. Add it to the <head> of your layout file.

One Astro-specific detail matters here. Astro processes <script> tags by default (bundling, TypeScript, import resolution). For a third-party analytics snippet you must add the is:inline directive so Astro renders the tag exactly as written, with its src, data-domain, and defer attributes untouched. Astro's own docs spell this out: is:inline scripts are "rendered into the HTML exactly as written" and are not transformed. Leaving is:inline off can cause Astro to try to process the external URL, which is not what you want.

---
// src/layouts/BaseLayout.astro
export interface Props {
  title: string;
  description?: string;
}

const { title, description } = Astro.props;
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{title}</title>
    {description && <meta name="description" content={description} />}

    <!-- Plausible Analytics (per-site snippet, October 2025 format) -->
    <script defer data-domain="yourdomain.com" src="https://plausible.io/js/pa-XXXXXXXX.js" is:inline></script>
    <script is:inline>
      window.plausible = window.plausible || function () { (window.plausible.q = window.plausible.q || []).push(arguments) };
    </script>
  </head>
  <body>
    <slot />
  </body>
</html>

Replace pa-XXXXXXXX.js with the exact filename from your dashboard (Site Settings then Site Installation then Review Installation) and yourdomain.com with your actual domain. The small inline stub defines the window.plausible queue so any custom events you fire before the main script finishes loading are captured rather than dropped.

If you are on an older install or self-hosting an older Plausible build, the classic snippet still works and needs the same is:inline:

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

That is it. One script tag and analytics are running.

Environment-Aware Setup

You probably do not want to track development visits. Use Astro's environment detection to conditionally load the script. import.meta.env.PROD is a built-in Astro environment value that is true only in a production build:

---
// src/layouts/BaseLayout.astro
const isProd = import.meta.env.PROD;
const domain = "yourdomain.com";
---

<html lang="en">
  <head>
    {isProd && (
      <script
        defer
        data-domain={domain}
        src="https://plausible.io/js/pa-XXXXXXXX.js"
        is:inline
      ></script>
    )}
  </head>
  <body>
    <slot />
  </body>
</html>

Tracking Custom Events

Plausible supports custom events for tracking things like button clicks, form submissions, and signups. With the new per-site snippet, manual custom events and CSS-class events are built in, so there is no separate script.tagged-events.js to add. You just call window.plausible(...) or use the class-based markup.

If you are still on the classic script, the tagged-events variant continues to work and is added like this (it is the only line that changes):

<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.tagged-events.js" is:inline></script>

Either way, trigger events in your Astro components:

<button id="signup-btn" class="btn-primary">
  Sign Up Free
</button>

<script is:inline>
  document.getElementById("signup-btn")?.addEventListener("click", () => {
    if (window.plausible) {
      window.plausible("Signup Click", {
        props: { location: "homepage" },
      });
    }
  });
</script>

Or use CSS class-based events without any JavaScript. Add the class plausible-event-name=YourEvent to any clickable element:

<a
  href="/pricing"
  class="plausible-event-name=Pricing+Click btn-primary"
>
  View Pricing
</a>

Tracking 404 Pages

With the current script, 404 tracking is an optional measurement you enable in the dashboard (Site Installation) or via plausible.init({ ... }), not by swapping in a separate script.404.js filename. The simplest portable approach for a blog is to fire a manual event from the 404 page itself, which works on every script version.

On your 404 page:

---
// src/pages/404.astro
---

<html>
  <body>
    <h1>Page Not Found</h1>
    <p>The page you are looking for does not exist.</p>
  </body>
</html>

<script is:inline>
  if (window.plausible) {
    window.plausible("404", { props: { path: document.location.pathname } });
  }
</script>

Using the Plausible API

Plausible has a Stats API you can use to display analytics data on your site, such as a view counter. The current version is the Stats API v2, a single POST https://plausible.io/api/v2/query endpoint that takes site_id, a metrics array, and a date_range in the JSON body. The older GET /api/v1/stats/... endpoints are now the legacy v1 API. Note that the Stats API is a paid-plan feature and keys are rate-limited (600 requests per hour by default), so cache the result rather than calling it on every request.

Two Astro details matter for this endpoint. First, an API route that fetches live data must opt out of static prerendering. In current Astro the whole site is static by default, so you add export const prerender = false to the route and install an adapter (@astrojs/node, @astrojs/vercel, @astrojs/cloudflare, and so on); on-demand rendering does not work without an adapter. Second, output: 'hybrid' was removed in Astro 5, so do not reach for it. Static is the default and per-route prerender flags handle the mix.

// src/pages/api/stats.ts
import type { APIRoute } from "astro";

// Required so this endpoint runs on demand instead of being prerendered.
// Also add an adapter in astro.config.mjs (for example @astrojs/node).
export const prerender = false;

export const GET: APIRoute = async () => {
  const response = await fetch("https://plausible.io/api/v2/query", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${import.meta.env.PLAUSIBLE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      site_id: "yourdomain.com",
      metrics: ["visitors"],
      date_range: "30d",
    }),
  });

  const data = await response.json();
  const visitors = data?.results?.[0]?.metrics?.[0] ?? 0;

  return new Response(JSON.stringify({ visitors }), {
    headers: {
      "Content-Type": "application/json",
      // Cache to stay well under the API rate limit.
      "Cache-Control": "max-age=3600",
    },
  });
};

Production Tips

  1. Use the self-hosted version to save money. If you have a server, self-hosting Plausible is free and gives you full control over your data. A small VPS is enough.

  2. Proxy the script through your domain. Some ad blockers block requests to plausible.io. Set up a proxy (using Cloudflare Workers or a simple redirect) to serve the script from your own domain.

  3. Enable extra measurements through init, not filenames. With the current snippet you turn on outbound links, file downloads, and form submissions from the Site Installation settings or with plausible.init({ outboundLinks: true, fileDownloads: true }). The old chained filename style (script.tagged-events.outbound-links.file-downloads.js) still resolves for backward compatibility, but the per-site snippet plus init is the supported path going forward.

  • Set up email reports. Plausible can send you weekly or monthly traffic reports via email. No need to check the dashboard manually.

  • Use the shared link feature. Generate a public dashboard link to share analytics with your team or clients without giving them account access.

  • Alternatives to Consider

    • Fathom if you want a similar privacy-first approach with slightly different pricing ($15/month for 100K pageviews). Both are excellent.
    • Umami if you want a free, self-hosted, open-source analytics tool with a clean interface. Very similar to Plausible.
    • Google Analytics if you need detailed conversion funnels, e-commerce tracking, or integration with Google Ads. More powerful but heavier and requires cookie consent.

    Common Errors and Fixes

    The script tag gets mangled or stripped in the build. Astro processes <script> tags by default (bundling, TypeScript, import resolution). If you drop the Plausible snippet in without is:inline, Astro can rewrite or hoist it and your data-domain and src attributes may not survive. Add is:inline so the tag renders exactly as authored. This is documented in Astro's client-side scripts guide.

    No data shows up because you are looking at the wrong site or your domain mismatches. Plausible matches incoming events by the data-domain value, which must match the site name in your Plausible dashboard exactly (no protocol, no trailing slash, no www. if your site is registered without it). A mismatch is the single most common reason the dashboard stays empty.

    An ad blocker swallows the request. Some blockers target plausible.io. Plausible's troubleshooting docs recommend proxying the script through your own domain so requests look first-party. This is the main reason a developer sees zero events on their own machine while real visitors are counted.

    The Stats API endpoint returns the page HTML instead of JSON, or 404s in production. This happens when the route was prerendered. Add export const prerender = false to the endpoint and install an adapter; on-demand routes do not run without one in current Astro.

    You followed an old guide and added output: 'hybrid'. That option was removed in Astro 5. Static is the default in Astro 5 and 6; use per-route export const prerender = false (or output: 'server' if most routes are dynamic) instead.

    You tried to npm install @plausible/tracker. That package does not exist on npm. The only official package is plausible-tracker, and most blogs do not need it at all because the script tag covers pageviews and custom events.

    Custom events fire before the script loads and get lost. Define the window.plausible queue stub shown in the Configuration section so early calls are buffered and replayed once the main script is ready.

    Official Docs and Examples

    Wrapping Up

    Plausible gives you the analytics you actually need without the bloat, privacy issues, or consent banners. For an Astro site, it is a one-line addition (plus is:inline) that respects your users and keeps your site fast. Hard to ask for more than that.

    Sources

    All versions and facts below were checked on 2026-05-29.