# AI, LLM, agent, bot analytics with Express.js and PlainSignal Integrate PlainSignal with your Express.js applications to unlock powerful AI and bot analytics. Our middleware solution makes it simple to add AI SEO tracking capabilities with minimal configuration. By leveraging Express.js's built-in middleware, you can track bot interactions across your entire site without needing to modify individual pages. This solution supports both the App Router and Pages Router, providing complete coverage for your Express.js application. Breadcrumb navigation - [Privacy-first, simple website analytics](https://plainsignal.com/) - [Analytics integrations](https://plainsignal.com/integrations) - [Express.js](https://plainsignal.com/integrations/expressjs) ## Features * Seamless integration with Express.js middleware * Automatic tracking of all routes * Real-time AI visibility analytics * Easy installation with npm/yarn/pnpm * Compatible with app router and pages router * Zero impact on site performance (no scripts, backend solution) * Tracks all major AI systems including ChatGPT, Claude, Google, Amazon Nova, Bing, and Perplexity ## Express.js <> PlainSignal analytics integration Step by Step Guide To Track AI, LLM, Agent, Bot Traffics using Express.js ### Step#1: Get your DomainID and DomainApiKey from PlainSignal If you already added your website to PlainSignal for tracking, then follow the sub-steps below (Learn how to create your first website analytics in PlainSignal if you haven't done before): * Go to `https://app.plainsignal.com/s/<your domain>/settings` * In the `Configuration` panel you will see your `DomainID` * In the same `Configuration` panel, to get your `DomainApiKey` visible click on the `*****` link At the end of this step you should have 2 values: * DomainID * DomainApiKey ### Step#2: Add tracker to Express.js App In your `app.js`, add the tracker as middleware: ``` const express = require('express') const app = express() // Bot regex patterns const plainSignalBotPatterns = /(GPTBot|ChatGPT-User|OAI-SearchBot|Amazonbot|Applebot-Extended|Applebot|archive\.org_bot|adidxbot|MicrosoftPreview|bingbot|Bytespider|ClaudeBot|Claude-User|Claude-SearchBot|DuckAssistBot|DuckDuckBot|Googlebot-Image|Googlebot-Video|Googlebot-News|Googlebot|Storebot-Google|Google-InspectionTool|GoogleOther-Image|GoogleOther-Video|GoogleOther|GoogleCloud-VertexBot|Google-Extended|APIs-Google|AdsBot-Google-Mobile|AdsBot-Google|Mediapartners-Google|Google-Safety|FeedFetcher-Google|GoogleProducer|Google-Read-Aloud|Google-Site-Verification|facebookexternalhit|facebookcatalog|meta-externalagent|meta-externalfetcher|MistralAI-User|PerplexityBot|Perplexity-User|PetalBot|ProRateInc|Timpibot|YandexBot|CCBot|LinkedInBot|Yahoo!\sSlurp)/i; // PlainSignal domain configuration // Replace {YourDomainID} and {YourDomainApiKey} with your actual values from the first step const domainID = '{YourDomainID}'; const domainApiKey = '{YourDomainApiKey}'; const plainSignalAPIEndpoint = 'http://eu.plainsignal.com/s/' + domainID + '/19'; /** * Express.js middleware to track bot traffic using PlainSignal. * @param {object} req - The Express request object. * @param {object} res - The Express response object. * @param {function} next - The next middleware function in the stack. */ const trackBotTraffic = function (req, res, next) { const userAgent = req.headers['user-agent'] || ''; if (plainSignalBotPatterns.test(userAgent)) { const referrer = req.headers['referer']; // No typo here, 'referer' is correct for HTTP header let rd = ''; let rp = ''; try { if (referrer) { const refUrl = new URL(referrer); rd = refUrl.hostname; rp = refUrl.pathname; } } catch (error) { console.error('Error parsing referrer URL:', error); // Continue without referrer data if parsing fails } const payload = { b: { d: { a: userAgent }, l: {}, s: { us: req.query.utm_source || req.query.source, um: req.query.utm_medium, uc: req.query.utm_campaign, ut: req.query.utm_term, uo: req.query.utm_content, ur: req.query.ref, rd: rd, rp: rp }, p: { p: req.path, // Use req.path for the current URL pathname }, ci: (req.ip || req.headers['x-forwarded-for'] || '') + userAgent + (req.headers['sec-ch-ua'] || '') } }; fetch(plainSignalAPIEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': domainApiKey }, body: JSON.stringify(payload), }).catch(error => { console.error('Error calling PlainSignal tracker:', error); }); } // Pass control to the next middleware next(); } app.use(trackBotTraffic) ``` Ref: [https://expressjs.com/en/guide/using-middleware.html#using-middleware](https://expressjs.com/en/guide/using-middleware.html#using-middleware) ## Other integrations - [Next.js <> PlainSignal analytics integration guidelines](https://plainsignal.com/integrations/nextjs) - [Cloudflare <> PlainSignal analytics integration guidelines](https://plainsignal.com/integrations/cloudflare) ## Canonical Human friendly, reader version of this article is available at [Express.js](https://plainsignal.com/integrations/expressjs) ## Copyright (c) 2025 [PlainSignal](https://plainsignal.com/ "Privacy-focused, simple website analytics")