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.
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:
- Each site now has its own per-site snippet whose
srclooks likehttps://plausible.io/js/pa-XXXXXXXX.js(the exact filename is shown in your dashboard under Site Settings then Site Installation then Review Installation). - Optional measurements (outbound links, file downloads, form submissions, 404 tracking, hash-based routing) are now toggled through
plausible.init({ ... })instead of by chaining filenames likescript.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
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.
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.
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
- Add the tracking script: https://plausible.io/docs/plausible-script
- Where to find your JavaScript snippet: https://plausible.io/docs/javascript-snippet
- Enable optional measurements (outbound links, file downloads, form submissions, hash routing) via
plausible.init(): https://plausible.io/docs/script-extensions - Update your Plausible script (the October 2025 snippet change): https://plausible.io/docs/script-update-guide
- Custom event goals and the CSS class-based and
window.plausible()event syntax: https://plausible.io/docs/custom-event-goals - Stats API v2 reference (
POST /api/v2/query): https://plausible.io/docs/stats-api - Troubleshooting (ad blockers, proxying, data not showing): https://plausible.io/docs/troubleshoot-integration
- Astro client-side scripts and the
is:inlinedirective: https://docs.astro.build/en/guides/client-side-scripts/ - Astro on-demand rendering (
prerender = false, adapters): https://docs.astro.build/en/guides/on-demand-rendering/ - Example repo, the official frontend library
plausible-tracker: https://github.com/plausible/plausible-tracker
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.
- Astro current version 6.4.2, from the npm registry: https://registry.npmjs.org/astro/latest
plausible-trackercurrent version 0.3.9 and confirmation that@plausible/trackerdoes not exist, from the npm registry: https://registry.npmjs.org/plausible-tracker/latest- Official
plausible-trackerlibrary and example: https://github.com/plausible/plausible-tracker - Add the Plausible tracking script: https://plausible.io/docs/plausible-script
- Where to find your JavaScript snippet (per-site
pa-XXXX.jssnippet): https://plausible.io/docs/javascript-snippet - October 2025 script update and the deprecation of chained filenames in favor of
plausible.init(): https://plausible.io/docs/script-update-guide - Enable optional measurements with
plausible.init()(outbound links, file downloads, form submissions, hash routing): https://plausible.io/docs/script-extensions - File downloads tracking via
plausible.init({ fileDownloads: ... }): https://plausible.io/docs/file-downloads-tracking - Stats API v2 (
POST /api/v2/query, Bearer auth, 600 requests/hour, paid feature): https://plausible.io/docs/stats-api - Astro
is:inlinerenders scripts exactly as written and skips processing: https://docs.astro.build/en/guides/client-side-scripts/ - Astro static-by-default rendering,
export const prerender = false, and the adapter requirement for on-demand routes: https://docs.astro.build/en/guides/on-demand-rendering/ - Live HTTP 200 verification that
https://plausible.io/js/script.js,script.tagged-events.js, chained-extension, andscript.manual.jsURLs still resolve (viacurl -sI, 2026-05-29)
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.