DEV Community

John Schmidt
John Schmidt

Posted on • Updated on • Originally published at

Displaying a View Counter with Plausible Analytics API Data

Whilst rebuilding my personal homepage, I wanted to implement a simple view counter for my posts. I've seen this a few times on popular blogs (e.g. Lee Robinson) and thought it would be a nice thing to build.

Usually, these kinds of view counters involve some kind of database-API setup. Hence, I thought of multiple possibilities such as PlanetScale, Upstash or even a more custom approach with Prisma and some kind of self-hosted database. I do have an own VPS running in Germany, which currently only homes my self-hosted Plausible Analytics instance.

Using Plausible Analytics to retrieve the data

This was when I realized that my Analytics instance already has all the data I need. I just needed to retrieve the data to display. Plausible recently released an API - allowing us to perform the intended. So let's get right to it.

Creating an API token in Plausible

To work with our API, we first need to create an API token in our Plausible Analytics dashboard. You can find the corresponding option in your user settings.

Location of the API key settings in Plausible Analytics

Setting up an API route

First, I created an API route in my Next.js project. I wanted to retrieve the data according to the individual and unique post slug. Thus, the API retrieves its parameter via the URL: /api/views/[slug].ts. A first draft of the API route is shown below.

const viewsHandler = async (req: NextApiRequest, res: NextApiResponse) => {
  // Retrieve the slug from the query parameters
  const { slug } = req.query
  // If no slug is provided, return a 400
  if (!slug) {
    return res.status(400).message("Bad request")
  // Handle the API request and return the data
  // ...

export default viewsHandler

Enter fullscreen mode Exit fullscreen mode

Retrieving the data

Now we can fetch our data from the Plausible API. We need to send a GET request to our API endpoint and query for the needed information. We are going for the /api/v1/stats/aggregate endpoint because we want to cumulate a set of data (in our case, the views) into one value. The API needs a few parameters in the following syntax to provide us with the needed data:


(I've marked the placeholders with a set of brackets like this: <PLACEHOLDER>)

Let's break this down:

  • site_id is the site's domain name set in the Plausible dashboard. In my case, it's
  • period defines a time period to retrieve the data from. Here, I wanted to retrieve allviews from the beginning. Thus, the usual periods like 6 months, 12 months or last 7 days didn't work out. Fortunately, Plausible provides us with the possibility to define a custom date range.
  • filters offers a few methods to filter your data. In our case, I wanted to filter by the corresponding page slug. We filter by event and deliver the exact slug in the page sub-filter. Read more on filters in the API documentation.

Providing the date range range

The API filter accepts a custom date range with two commas separated dates in a YYYY-MM-DD format. Therefore, I set my start date to the day I started using Plausible on my homepage and retrieve the current date with a bit of JavaScript slickness.

const now = new Date()
const [nowDate] = now.toISOString().split("T")
// nowDate now yields a YYYY-MM-DD format of the current date
Enter fullscreen mode Exit fullscreen mode

Putting the pieces together

Now we got all the required parts and can put together our function to retrieve the all-time view count to a given page slug.

const fetcher = (input: RequestInfo, init?: RequestInit | undefined) =>
  fetch(input, init).then((res) => res.json())

async function getPlausibleViews(slug: string) {
  const url = `,${nowDate}&filters=event:page==/post/${slug}`
  return fetcher(url, {
    headers: {
      Authorization: `Bearer ${process.env.PLAUSIBLE_API_KEY}`,
      Accept: "application/json",
Enter fullscreen mode Exit fullscreen mode

You can see that I'm pointing the request to my personal instance of Plausible Analytics, hosted on my private VPS. If you're using Plausible's hosted solution, just replace the domain with I also set up a custom fetcher function to simplify the data transformation and yield the response data as serialized JSON.

⚠️ API limits
Plausible defaults to an API rate limit of 600 requests per hour. If you're self-hosting, there's a way to change this limit to avoid any blocked requests. If you're on the Plausible cloud service, you'd have to contact their team.

We need to authorize the request with our API token. I'd recommend putting the key in a private environment variable and retrieve it in the function.

Debugging our request will show that the API responds with the following data (the value is based on a random request I made for one of my pages).

  "results": {
    "visitors": {
      "value": 520
Enter fullscreen mode Exit fullscreen mode

Now we just need to process the data, maybe clean it up a bit and put it in the API response. Let's put it all together.

const viewsHandler = async (req: NextApiRequest, res: NextApiResponse) => {
  const { slug } = req.query
  if (!slug) {
    return res.status(400).send("Bad request")
  try {
    const data = await getPlausibleViews(String(slug))
    return res.status(200).json({
      requestedSlug: slug,
      date: now.toUTCString(),
      views: data?.results?.visitors?.value,
  } catch (err) {
    return res.status(500).json({ err })

export default viewsHandler
Enter fullscreen mode Exit fullscreen mode

Nice, well done. Test our your new API route with some sample slugs and see if it responds with the wanted data. Let's move on and see how we can display the data on our frontend.

Displaying the data on your frontend

You might have noticed that I am primarily using Next.js as my frontend solution. Thus, the following explanation targets a Next.js frontend.

A simple way to display your API data and even give it the ability to update in real-time is to use a client-side query library like Vercel's swr or react-query. In this example, I'll be using swr.

Create a DisplayViews component

All right, let's create a component to display our data and use the revalidation features of swr. You can read more about the usage of SWR ontheir documentation website. We're going to use the basic useSWR hook and target our API route. We provide the slug of interest via a property.

import { fetcher } from "lib/fetcher"
import useSWR from "swr"

type Props = {
  slug: string

const DisplayViews: React.FC<Props> = ({ slug }) => {
  // Fetch the data with the useSWR hook
  const { data, error } = useSWR(`/api/views/${slug}`, fetcher)
  // If there's no data and no error, display a loading state
  if (!data && !error)
    return (
      <div className="inline-block animate-pulse rounded bg-zinc-100 text-transparent dark:bg-zinc-800">
        Loading views
  // If there's data, display the data
  return (
    <div className="flex items-center">
      // Mabye place an icon here?
      <span className="tabular-nums">{data?.views} views</span>

export default DisplayViews

Enter fullscreen mode Exit fullscreen mode

In the component, we're fetching the data with the useSWR hook. We can determine if there's no data and no error either that the request ist still pending. In that case, we want to display a loading state. I put together a small component with a skeleton-like loading animation using Tailwind CSS.

If the data (or an error) arrived, we can display our final component featuring our data. Here, I am being optimistic and assume that there will be always some kind of data returned from the API. I did not set up a solution if the request only yields an error and no data.

Loading state of the DisplayViews component

Wrapping up

Done! Now you can use this component anywhere in your page to display view stats to a certain page slug. It even caches the API responses across your application. SWR offers you enough options to fine-tune your component. For example, you could turn off the focus revalidation and limit the revalidation to navigation events and entry visits. Happy coding!

Top comments (1)

letsbsocial1 profile image
Maria Campbell

Hi. Did not work for me.