New to Claude Skills? Learn how to install them →

posthog on GitHub

Bot Traffic Filtering

Free

Accurately measure and exclude bot traffic in analytics.

by posthog37.6k stars on posthog/posthog
2 views
Updated Aug 11, 2026
Get this skill

Free · Opens the source repo

What Bot Traffic Filtering does

The Bot Traffic Filtering skill for PostHog allows users to effectively identify and manage bot, crawler, and AI-agent traffic within their web and product analytics. By utilizing the traffic classification capabilities of PostHog, this skill empowers users to distinguish between human and automated traffic, ensuring that analytics reflect genuine user engagement. The skill leverages HogQL functions and virtual properties to provide a comprehensive view of traffic sources, enabling users to filter out unwanted bot traffic and focus on human interactions.

This skill is particularly beneficial for analytics professionals and product managers who need to maintain the integrity of their data. By excluding bot traffic, users can obtain clearer insights into user behavior, conversion rates, and overall site performance. The skill also allows for the measurement of automated traffic, providing valuable information about which bots are accessing the site and how they impact analytics. Users can quantify the percentage of traffic that is automated and differentiate between various types of bots, such as search crawlers and AI agents.

The skill offers two primary methods for traffic classification: virtual properties for use in the insight builder and HogQL functions for raw SQL queries. This flexibility allows users to choose the best approach for their specific needs, whether they are building insights in the PostHog interface or writing custom SQL queries. By employing these methods, users can easily filter insights to present only human traffic or analyze trends based on traffic type, enhancing the overall quality of their analytics.

In summary, the Bot Traffic Filtering skill is an essential tool for anyone looking to refine their analytics by excluding bot traffic and measuring automated interactions. It is designed for users who need accurate, human-only data to make informed decisions about their web presence and product performance.

When to use it

Use this skill when you need to exclude bot traffic from analytics or measure how much of your traffic is automated.

When not to use it

Do not use this skill for real-time traffic analysis; it is not suited for the Live tab metrics.

What you can build with it

Excluding Bot Traffic from Reports

Use this skill to filter out bot traffic from your analytics reports, ensuring that only human interactions are counted.

Quantifying Automated Traffic

Measure the percentage of your website traffic that is automated, helping you understand the impact of bots on your analytics.

Analyzing Traffic Sources

Identify which bots are accessing your site and analyze their traffic patterns to improve your content strategy.

How to install Bot Traffic Filtering

View source

1. Install with the skills CLI

npx skills add posthog/posthog/filtering-bot-traffic --agent claude-code

2. Or install it manually

Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.

Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs

Inside SKILL.md

Written by posthog

Filtering and measuring bot traffic

PostHog classifies every request by user agent so you can tell humans apart from bots, crawlers, and AI agents anywhere HogQL runs — the SQL editor, insights, trends, and Web analytics breakdowns. This skill teaches you (the agent) how to use that classification to:

  • exclude bots so analytics reflect human traffic only
  • measure how much traffic is automated, and which bots / operators are responsible
  • separate AI-agent traffic (worth measuring) from noise (worth dropping)
  • pick the right surface — virtual properties for the insight builder, functions for raw SQL

For real-time ("right now", last 30 min) bot questions and the Live tab tiles, use the exploring-live-traffic skill instead. This skill is for historical windows, saved insights, dashboards, and filtering.

When to use this skill

Use it when the user wants to:

  • exclude or filter out bots ("remove bots from my pageviews", "humans only")
  • quantify automated traffic ("what % of traffic is bots?", "how much is AI crawlers?")
  • find which bots hit them ("which crawlers visit us?", "is ChatGPT reading our docs?")
  • break a trend down by traffic type or bot name
  • measure AI-agent / AI-search traffic specifically (AEO / answer-engine visibility)

Do not use it for the Live tab, real-time numbers, or the per-minute bot charts — that is exploring-live-traffic.

The classification surface

Two equivalent ways to reach the same classification. Prefer virtual properties in the insight builder and filters; use functions in hand-written SQL or when you need a value the virtual properties don't expose.

Virtual properties (insight builder, filters, breakdowns)

These read the user agent for you (falling back from $raw_user_agent to $user_agent), so you don't pass anything in. Available wherever you pick an event property.

PropertyValue
$virt_is_botboolean — true for bots / crawlers / automation
$virt_traffic_typeRegular, AI Agent, Bot, or Automation
$virt_traffic_categoryfiner category, e.g. ai_crawler, ai_search, ai_assistant, search_crawler, seo_crawler, social_crawler, monitoring, http_client, headless_browser, no_user_agent, regular
$virt_bot_namedisplay name, e.g. Googlebot, GPTBot, ClaudeBot
$virt_bot_operatorcompany behind the bot, e.g. Google, OpenAI, Anthropic

HogQL functions (raw SQL)

Pass the user agent explicitly. Use coalesce(nullIf(properties.$raw_user_agent, ''), properties.$user_agent) to cover both server-side ($raw_user_agent) and JS SDK ($user_agent) captures. The nullIf keeps an empty $raw_user_agent from shadowing a real $user_agent and being misread as a bot — this mirrors the expression the virtual properties use internally.

FunctionReturns
isLikelyBot(ua)true if the UA matches a bot/automation pattern (empty UA counts as a bot)
getTrafficType(ua)AI Agent / Bot / Automation / Regular
getTrafficCategory(ua)subcategory; regular for humans
getBotType(ua)same subcategory but empty string for humans — handy for filtering
getBotName(ua)bot name; empty for humans
getBotOperator(ua)operator/company; empty for humans

Traffic types — what to keep vs drop

getTrafficType / $virt_traffic_type sorts every request into four buckets. The default move differs per bucket — don't treat them all as noise:

TypeWhat it isDefault move
RegularHuman visitorsKeep
AI AgentAI crawlers, AI search, AI assistants (GPTBot, ClaudeBot, PerplexityBot, ChatGPT-User)Often measure, don't drop — these are how AI tools find and cite content
BotSearch crawlers, SEO tools, social previews, monitoring (Googlebot, AhrefsBot, Pingdom)Exclude from human metrics; track separately for SEO
AutomationHTTP clients and headless browsers (curl, python-requests, Puppeteer)Usually noise — exclude

Recipes

Exclude bots from an insight (humans only)

Add a property filter $virt_is_bot exact false:

{ "key": "$virt_is_bot", "value": ["false"], "operator": "exact", "type": "event" }

Drop it into any TrendsQuery / FunnelsQuery / etc. properties. Visitor, session, and pageview counts then reflect human traffic only, without changing stored data.

To exclude a narrower slice (e.g. keep AI agents but drop monitoring + automation), filter on $virt_traffic_type or $virt_traffic_category with operator: is_not instead.

What share of traffic is automated

Break a pageview trend down by $virt_traffic_type:

{
  "kind": "TrendsQuery",
  "dateRange": { "date_from": "-30d" },
  "series": [{ "kind": "EventsNode", "event": "$pageview", "math": "total" }],
  "breakdownFilter": { "breakdown": "$virt_traffic_type", "breakdown_type": "event" },
  "trendsFilter": { "display": "ActionsBarValue" }
}

Which bots / operators are hitting us

Filter to bots and break down by name (or $virt_bot_operator for company-level):

{
  "kind": "TrendsQuery",
  "dateRange": { "date_from": "-30d" },
  "series": [{ "kind": "EventsNode", "event": "$pageview", "math": "total" }],
  "properties": [{ "key": "$virt_is_bot", "value": ["true"], "operator": "exact", "type": "event" }],
  "breakdownFilter": { "breakdown": "$virt_bot_name", "breakdown_type": "event", "breakdown_limit": 25 },
  "trendsFilter": { "display": "ActionsBarValue" }
}

Measure AI-agent traffic specifically

Filter $virt_traffic_type exact AI Agent, break down by $virt_bot_operator to see which tools (OpenAI, Anthropic, Perplexity, …) read your site and which pages they hit.

Raw SQL equivalents

-- human pageviews only
SELECT count() AS human_pageviews
FROM events
WHERE event = '$pageview'
    AND NOT isLikelyBot(coalesce(nullIf(properties.$raw_user_agent, ''), properties.$user_agent))

-- top bots by hits
SELECT
    getBotName(coalesce(nullIf(properties.$raw_user_agent, ''), properties.$user_agent)) AS bot,
    getBotOperator(coalesce(nullIf(properties.$raw_user_agent, ''), properties.$user_agent)) AS operator,
    count() AS hits
FROM events
WHERE event = '$pageview'
    AND isLikelyBot(coalesce(nullIf(properties.$raw_user_agent, ''), properties.$user_agent))
GROUP BY bot, operator
ORDER BY hits DESC

Seeing bots that don't run JavaScript

Most crawlers and AI agents never execute JS, so posthog-js never fires a $pageview for them — they're invisible to client-side analytics. To measure them, the project must forward server access logs as $http_log events carrying $raw_user_agent. If a user asks "why don't I see GPTBot when I know it's crawling us?", the answer is almost always: no $http_log ingestion. Point them at server-side capture (the Vercel logs source, an edge worker, or the capture API) before building bot insights.

Gotchas

  • Needs a captured user agent. Classification is computed at query time from the event's $raw_user_agent / $user_agent, so it works on any historical event — there's no need to restrict dateRange.date_from. The one requirement is that a user agent was captured; events from sources that never set one can't be classified (and empty UAs fall through to Automation / no_user_agent, below).
  • isLikelyBot is "likely". Detection is a user-agent heuristic — some bots spoof real browser UAs, and some legit tools use bot-like ones. Treat it as best-effort, not ground truth.
  • Empty user agent = bot. Requests with no UA (server-to-server, misconfigured SDKs) classify as Automation / no_user_agent, so isLikelyBot returns true.
  • Don't silently drop the host filter. If the user is scoped to one domain, inherit $host in properties — leaving it out changes the answer.
  • Bot definitions evolve. The detected-bot list changes over time, so re-running the same query later can classify older events differently.

Frequently asked questions about Bot Traffic Filtering

Similar skills