npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pieowl/bots

v0.1.1

Published

Server-side helpers for Pieowl: report AI crawler and search bot visits from your server, and serve the tracking script through your own domain.

Readme

@pieowl/bots

Server-side helpers for Pieowl:

  • Bot tracking. AI crawlers and search bots (GPTBot, ClaudeBot, PerplexityBot, Googlebot…) don't run JavaScript, so the browser script never sees them. This package reports them from your server to Pieowl's AI & bots report.
  • Ad-blocker proxy. Serve the tracking script and send events through your own domain, so fewer events are lost to ad blockers.

For page views and events, add the HTML script to your site. This package is only for what the browser can't see.

Uses only fetch, so it runs on Node.js, edge runtimes, Cloudflare Workers, Deno and Bun. No dependencies on any framework.

npm install @pieowl/bots

Bot tracking needs your project's client ID and client secret. Keep the secret on the server (environment variables below).

How it works

  • Only requests whose user agent looks like a bot are reported. Everything else returns at once, with no network call.
  • It never blocks, slows or changes your response, and never throws. Reports are sent in the background, in batches (50 requests or every second).
  • Pieowl classifies the bot and checks the IP against the bot owner's published ranges, so fake "GPTBot" user agents are flagged. The IP is used only for this check and is not stored.

Next.js

import { trackBots } from '@pieowl/bots/next';

export const proxy = trackBots({
  clientId: process.env.PIEOWL_CLIENT_ID!,
  clientSecret: process.env.PIEOWL_CLIENT_SECRET!,
});

On Next.js 15 or older, name the file middleware.ts and export it as middleware. To skip static files, add export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };.

Already have a proxy? Call trackBotRequest in it and keep your own response:

import { trackBotRequest } from '@pieowl/bots';
import { type NextFetchEvent, type NextRequest, NextResponse } from 'next/server';

export function proxy(request: NextRequest, event: NextFetchEvent) {
  trackBotRequest(request, { clientId: '…', clientSecret: '…' }, event);
  return NextResponse.next();
}

Express

import { trackBots } from '@pieowl/bots/express';

app.use(trackBots({
  clientId: process.env.PIEOWL_CLIENT_ID,
  clientSecret: process.env.PIEOWL_CLIENT_SECRET,
}));

Reported after the response is sent, with its status code. The IP is req.ip, so behind a proxy or load balancer set app.set('trust proxy', true). Works with Connect and any (req, res, next) server too.

Hono

import { trackBots } from '@pieowl/bots/hono';

app.use(trackBots({
  clientId: process.env.PIEOWL_CLIENT_ID!,
  clientSecret: process.env.PIEOWL_CLIENT_SECRET!,
}));

On Cloudflare Workers, read the keys with import { env } from 'cloudflare:workers'.

Cloudflare Workers

Wrap your fetch handler. The report stays alive with ctx.waitUntil, and includes the response status.

import { env } from 'cloudflare:workers';
import { withBotTracking } from '@pieowl/bots';

export default {
  fetch: withBotTracking((request) => fetch(request), {
    clientId: env.PIEOWL_CLIENT_ID,
    clientSecret: env.PIEOWL_CLIENT_SECRET,
  }),
};

Cloudflare Pages (functions/_middleware.ts):

import { trackBotRequest } from '@pieowl/bots';

export const onRequest: PagesFunction<Env> = (context) => {
  trackBotRequest(context.request, {
    clientId: context.env.PIEOWL_CLIENT_ID,
    clientSecret: context.env.PIEOWL_CLIENT_SECRET,
  }, context);
  return context.next();
};

withBotTracking works for any (request, ...) => Response handler, e.g. Deno (Deno.serve(withBotTracking(handler, options))) and Bun.

Any other framework

trackBotRequest(request, options, context?) takes a Fetch Request, or { url, headers, method?, ip?, status? } when your server doesn't have one. Pass context (anything with waitUntil) on serverless and edge platforms.

import { trackBotRequest } from '@pieowl/bots';
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware((context, next) => {
  trackBotRequest(context.request, {
    clientId: import.meta.env.PIEOWL_CLIENT_ID,
    clientSecret: import.meta.env.PIEOWL_CLIENT_SECRET,
  });
  return next();
});
import { trackBotRequest } from '@pieowl/bots';

export default defineEventHandler((event) => {
  trackBotRequest({ url: getRequestURL(event).href, headers: getRequestHeaders(event) }, {
    clientId: process.env.PIEOWL_CLIENT_ID!,
    clientSecret: process.env.PIEOWL_CLIENT_SECRET!,
  });
});
import { trackBotRequest } from '@pieowl/bots';
import { env } from '$env/dynamic/private';

export const handle = async ({ event, resolve }) => {
  trackBotRequest(event.request, {
    clientId: env.PIEOWL_CLIENT_ID,
    clientSecret: env.PIEOWL_CLIENT_SECRET,
  });
  return resolve(event);
};

In Remix / React Router, call it at the top of your root loader with request. In Vercel Edge Middleware, call trackBotRequest(request, options, context) with the middleware's context argument.

No JavaScript server? Send the requests yourself: POST https://api.pieowl.com/bot with the headers pieowl-client-id and pieowl-client-secret and a body of { "requests": [{ "userAgent", "path", "host?", "method?", "status?", "ip?", "timestamp?" }] } (up to 100 per call).

Ad-blocker proxy

Serve the script and forward events through your own domain. The handler answers <route>/pieowl.js and forwards <route>/track… to Pieowl with the visitor's IP and user agent, so locations and devices stay correct.

import { createRouteHandler } from '@pieowl/bots/route-handler';

export const { GET, POST } = createRouteHandler();
import { createRouteHandler } from '@pieowl/bots/express';

app.use('/api/pieowl', createRouteHandler());

The Fetch handler from @pieowl/bots/route-handler works anywhere with a Request: Hono (app.all('/api/pieowl/*', (c) => handler(c.req.raw))), Astro endpoints, SvelteKit +server.ts, Nuxt (export default fromWebHandler(createRouteHandler())), Cloudflare Workers.

Then point the script at your route: load /api/pieowl/pieowl.js and set apiUrl: '/api/pieowl' in init.

API

| Import | Export | Use | |---|---|---| | @pieowl/bots | trackBotRequest(request, options, context?) | Report one request. Returns a promise that resolves when its batch was sent; never rejects. | | @pieowl/bots | withBotTracking(handler, options) | Wrap a Fetch handler; reports bots with the response status. | | @pieowl/bots/next | trackBots(options) | Next.js proxy / middleware. | | @pieowl/bots/express | trackBots(options), createRouteHandler(options?) | Express / Connect middleware, ad-blocker proxy. | | @pieowl/bots/hono | trackBots(options) | Hono middleware. | | @pieowl/bots/route-handler | createRouteHandler(options?) | Ad-blocker proxy as a Fetch handler. |

Options: clientId, clientSecret, apiUrl (default https://api.pieowl.com). The route handler takes only apiUrl.