Debug and inspect PostHog implementations on any website. Use this skill when a user wants to understand how PostHog is implemented on a page, troubleshoot tracking issues, verify configuration, check what events are being sent, or audit a PostHog setup. Works with Chrome DevTools MCP and Playwright MCP to inspect live websites.
npx skills add https://github.com/PostHog/skills --skill posthog-debugger
Inspect and debug PostHog implementations on any website using browser automation tools. This skill helps developers and product teams understand exactly how PostHog is configured and what data is being captured.
mcp__chrome-devtools__*) or Playwright MCP (mcp__playwright__*) tools.When a user asks to inspect a PostHog implementation:
?__posthog_debug=true appendedAfter navigating, take a snapshot and look for login indicators:
If you detect a login page or the user mentions the page requires auth:
"This page requires authentication. Please log in using the Chrome browser I just opened. Let me know when you're logged in and on the page you want to inspect."
mcp__chrome-devtools__navigate_page - Navigate to URLmcp__chrome-devtools__take_snapshot - Get page accessibility treemcp__chrome-devtools__evaluate_script - Run JavaScript to inspect PostHogmcp__chrome-devtools__list_network_requests - See network trafficmcp__chrome-devtools__get_network_request - Get request detailsmcp__chrome-devtools__list_console_messages - Check for errorsmcp__playwright__browser_navigate - Navigate to URLmcp__playwright__browser_snapshot - Get page snapshotmcp__playwright__browser_evaluate - Run JavaScriptmcp__playwright__browser_network_requests - See network trafficmcp__playwright__browser_console_messages - Check consoleAlways add ?__posthog_debug=true to the URL to enable PostHog's debug mode. This outputs detailed logs to the console.
https://example.com?__posthog_debug=truehttps://example.com?foo=bar&__posthog_debug=trueWorkflow:
/login, /signin, /auth, /ssoExecute JavaScript to inspect the posthog object on the window:
(() => {
if (typeof posthog === 'undefined') {
return { installed: false };
}
const ph = posthog;
return {
installed: true,
version: ph.version || ph.LIB_VERSION || 'unknown',
config: {
token: ph.config?.token || ph.get_config?.('token') || 'not accessible',
apiHost: ph.config?.api_host || ph.get_config?.('api_host') || 'not accessible',
autocapture: ph.config?.autocapture ?? ph.get_config?.('autocapture') ?? 'not accessible',
capturePageview: ph.config?.capture_pageview ?? ph.get_config?.('capture_pageview') ?? 'not accessible',
capturePageleave: ph.config?.capture_pageleave ?? ph.get_config?.('capture_pageleave') ?? 'not accessible',
sessionRecording: ph.config?.enable_recording_console_log !== undefined ||
ph.sessionRecording?.started ||
'check network',
persistence: ph.config?.persistence || ph.get_config?.('persistence') || 'not accessible',
debug: ph.config?.debug ?? ph.get_config?.('debug') ?? false
},
distinctId: ph.get_distinct_id?.() || 'not accessible',
sessionId: ph.get_session_id?.() || 'not accessible',
featureFlags: ph.getFeatureFlag ? Object.keys(ph.featureFlags?.flags || {}) : [],
activeFeatureFlags: ph.getFeatureFlag ?
Object.entries(ph.featureFlags?.flags || {})
.filter(([_, v]) => v)
.map(([k]) => k) : []
};
})()
If posthog is not on window, check for bundled implementations that use _POSTHOG_REMOTE_CONFIG:
(() => {
const remoteConfig = window._POSTHOG_REMOTE_CONFIG;
if (!remoteConfig) {
return { found: false };
}
const tokens = Object.keys(remoteConfig);
const configs = tokens.map(token => {
const cfg = remoteConfig[token]?.config || {};
return {
token,
hasFeatureFlags: cfg.hasFeatureFlags || false,
autocapture: !cfg.autocapture_opt_out,
sessionRecording: cfg.sessionRecording || false,
heatmaps: cfg.heatmaps || false,
surveys: cfg.surveys || false,
capturePerformance: cfg.capturePerformance || {},
defaultIdentifiedOnly: cfg.defaultIdentifiedOnly || false
};
});
return {
found: true,
bundled: true,
configs
};
})()
With ?__posthog_debug=true, PostHog outputs detailed logs. Use list_console_messages and look for [PostHog.js] entries:
Key messages to look for:
[PostHog.js] Persistence loaded - Shows persistence type (localStorage, sessionStorage, cookie)[PostHog.js] [Surveys] Surveys loaded successfully - Surveys module loaded[PostHog.js] [Surveys] flags response received, isSurveysEnabled: X - Whether surveys are enabled[PostHog.js] [SessionRecording] - Session recording status[PostHog.js] [WebExperiments] - Web experiments/feature flags[PostHog.js] set_config - Configuration changesImportant distinction:
A module can load but still be disabled. For example:
[PostHog.js] [Surveys] Surveys loaded successfully <- Module loaded
[PostHog.js] [Surveys] flags response received, isSurveysEnabled: false <- Feature disabled
Look for PostHog scripts in the page:
(() => {
const scripts = Array.from(document.querySelectorAll('script'));
const posthogScripts = scripts.filter(s =>
(s.src && (s.src.includes('posthog') || s.src.includes('ph.js'))) ||
(s.textContent && (s.textContent.includes('posthog.init') || s.textContent.includes('!function(t,e)')))
);
return {
found: posthogScripts.length > 0,
scripts: posthogScripts.map(s => ({
src: s.src || 'inline',
async: s.async,
defer: s.defer,
type: s.type || 'text/javascript'
}))
};
})()
Filter network requests to find PostHog traffic. PostHog may use:
Standard domains:
*.posthog.comus.i.posthog.com / eu.i.posthog.comus-assets.i.posthog.com / eu-assets.i.posthog.comCustom proxy domains (common patterns):
ph.yourcompany.comanalytics.yourcompany.comt.yourcompany.com/array/phc_ in the path (PostHog config)Endpoints to look for:
/e/ or /capture/ - Events/s/ - Session recording/decide/ or /flags/ - Feature flags/batch/ - Batched events/array/phc_*/config.js - Remote config (bundled implementations)/static/surveys.js - Surveys module/static/recorder.js - Session recording moduleHow to identify a custom proxy:
/flags/, /array/phc_)phc_ tokens in request URLs or bodiesRun diagnostics:
(() => {
const issues = [];
// Check if posthog exists
if (typeof posthog === 'undefined') {
issues.push('PostHog not found on window object');
return { issues };
}
// Check for multiple instances
if (window.__POSTHOG_INSTANCES__ && window.__POSTHOG_INSTANCES__.length > 1) {
issues.push('Multiple PostHog instances detected - may cause duplicate events');
}
// Check if initialized
if (!posthog.get_distinct_id || !posthog.get_distinct_id()) {
issues.push('PostHog may not be fully initialized');
}
// Check consent mode
if (posthog.has_opted_out_capturing && posthog.has_opted_out_capturing()) {
issues.push('User has opted out of tracking');
}
// Check for debug mode in production
const isDebug = posthog.config?.debug || posthog.get_config?.('debug');
const hostname = window.location.hostname;
if (isDebug && !hostname.includes('localhost') && !hostname.includes('127.0.0.1')) {
issues.push('Debug mode is enabled in production');
}
// Check autocapture
const autocapture = posthog.config?.autocapture ?? posthog.get_config?.('autocapture');
if (autocapture === false) {
issues.push('Autocapture is disabled - only manual events will be tracked');
}
return {
issues: issues.length > 0 ? issues : ['No issues detected'],
checksRun: true
};
})()
If debug mode is on or we can access the queue:
(() => {
if (typeof posthog === 'undefined') return { events: [] };
// Try to get queued events
const queue = posthog._requestQueue?.queue ||
posthog.requestQueue?.queue ||
[];
// Get recent events from persistence if available
const stored = posthog.persistence?.props?.$stored_events || [];
return {
queuedEvents: queue.length,
recentEventTypes: [...new Set([...queue, ...stored].map(e => e?.event || 'unknown').slice(0, 20))]
};
})()
Scan network requests and scripts to identify all analytics/tracking tools on the page. Use known patterns for named tools, and detect unknown tracking scripts as a fallback.
(() => {
const scripts = Array.from(document.querySelectorAll('script[src]')).map(s => s.src);
const hostname = window.location.hostname.replace('www.', '');
const knownTools = [];
const matchedScripts = new Set();
const patterns = {
'Google Analytics': /google-analytics\.com|gtag\/js/i,
'Google Tag Manager': /googletagmanager\.com\/gtm/i,
'Facebook Pixel': /connect\.facebook\.net.*fbevents/i,
'LinkedIn Insight': /snap\.licdn\.com/i,
'HubSpot': /js\.hs-scripts\.com|js\.hsforms\.com/i,
'Hotjar': /static\.hotjar\.com/i,
'Segment': /cdn\.segment\.com/i,
'Mixpanel': /cdn\.mxpnl\.com|mixpanel\.com/i,
'Amplitude': /cdn\.amplitude\.com/i,
'Heap': /heap-analytics\.com|heapanalytics\.com/i,
'Intercom': /widget\.intercom\.io|intercomcdn\.com/i,
'Drift': /js\.driftt\.com/i,
'Zendesk': /static\.zdassets\.com/i,
'Crisp': /client\.crisp\.chat/i,
'FullStory': /fullstory\.com\/s\/fs\.js/i,
'LogRocket': /cdn\.logrocket\.com/i,
'Sentry': /browser\.sentry-cdn\.com/i,
'Datadog': /datadoghq\.com/i,
'Snowplow': /cdn\.snowplow/i,
'Rudderstack': /cdn\.rudderlabs\.com/i,
'Clearbit': /tag\.clearbitscripts\.com/i,
'Dreamdata': /cdn\.dreamdata\.cloud/i,
'GrowthBook': /cdn\.growthbook\.io/i,
'LaunchDarkly': /sdk\.launchdarkly\.com/i,
'Optimizely': /cdn\.optimizely\.com/i,
'VWO': /dev\.visualwebsiteoptimizer\.com/i,
'Ahrefs': /analytics\.ahrefs\.com/i,
'AdRoll': /s\.adroll\.com/i,
'Factors.ai': /app\.factors\.ai/i,
'Vector': /cdn\.vector\.co/i,
'Leadfeeder': /sc\.lfeeder\.com/i,
'Pendo': /cdn\.pendo\.io/i,
'Chameleon': /fast\.chameleon\.io/i,
'Appcues': /fast\.appcues\.com/i,
'UserPilot': /js\.userpilot\.io/i,
'Mouseflow': /cdn\.mouseflow\.com/i,
'Lucky Orange': /tools\.luckyorange\.com/i,
'Crazy Egg': /script\.crazyegg\.com/i,
'Plausible': /plausible\.io\/js/i,
'Fathom': /cdn\.usefathom\.com/i,
'Simple Analytics': /scripts\.simpleanalyticscdn\.com/i,
'Matomo': /matomo\.js|piwik\.js/i,
'Klaviyo': /static\.klaviyo\.com/i,
'Customer.io': /track\.customer\.io/i,
'Braze': /sdk\.iad-\d+\.braze\.com/i,
'OneSignal': /cdn\.onesignal\.com/i,
'Insider': /insr\.io/i,
'Mutiny': /cdn\.mutinycdn\.com/i,
'Qualified': /js\.qualified\.com/i,
'Chilipiper': /js\.chilipiper\.com/i,
'Typekit': /use\.typekit\.net/i,
'Google Fonts': /fonts\.googleapis\.com/i,
'Cookiebot': /consent\.cookiebot\.com/i,
'OneTrust': /cdn\.cookielaw\.org/i,
'TrustArc': /consent\.trustarc\.com/i,
'Osano': /cmp\.osano\.com/i,
'Usercentrics': /app\.usercentrics\.eu/i,
'OpenLI/Legal Monster': /widgets\.legalmonster\.com|openli\.com/i,
'Nelio A/B Testing': /nelio-ab-testing/i,
'Mesh': /mesh-interactive|withmesh\.com/i,
'Reddit Pixel': /redditstatic\.com|rdt\.li/i,
'Webflow': /wdfl\.co|webflow\.com\/js/i,
'Dub.co': /dubcdn\.com|dub\.co/i
};
// Match known tools
for (const [name, pattern] of Object.entries(patterns)) {
for (const src of scripts) {
if (pattern.test(src)) {
knownTools.push(name);
matchedScripts.add(src);
break;
}
}
}
// Find unknown tracking scripts
const trackingKeywords = /track|analytics|pixel|tag|beacon|collect|measure|metric|event|telemetry|monitor/i;
const unknownScripts = scripts.filter(src => {
if (matchedScripts.has(src)) return false;
try {
const url = new URL(src);
const scriptHost = url.hostname.replace('www.', '');
// Skip same-domain scripts
if (scriptHost === hostname || scriptHost.endsWith('.' + hostname)) return false;
// Skip common CDNs that host non-tracking code
if (/unpkg\.com|jsdelivr\.net|cdnjs\.cloudflare\.com|ajax\.googleapis\.com/i.test(scriptHost)) return false;
// Include if it has tracking-like keywords or is from a third-party
return trackingKeywords.test(src) || true;
} catch {
return false;
}
});
// Extract just the domain from unknown scripts for cleaner output
const unknownDomains = [...new Set(unknownScripts.map(src => {
try {
return new URL(src).hostname;
} catch {
return src;
}
}))].sort();
return {
knownTools: [...new Set(knownTools)].sort(),
unknownScripts: unknownDomains
};
})()
After inspection, provide a structured summary. Be factual and concise - no commentary or recommendations unless asked. Use emojis to indicate status at a glance.
Status emojis:
## PostHog Implementation Summary
### Status
✅ Installed / ✅ Installed (bundled) / ❌ Not Found
### Implementation Type
[Global window.posthog / Bundled (not exposed globally) / Custom proxy]
### Configuration
- **Version:** [version]
- **API Host:** [host] ([US/EU Cloud] / [Custom proxy] / [Self-hosted])
- **Project Token:** [full token]
- **Persistence:** [localStorage/sessionStorage/cookie/memory]
### Features (Module Loaded → Feature Enabled)
| Feature | Module Loaded | Enabled |
|---------|---------------|---------|
| Autocapture | ✅ / ❌ | ✅ / ❌ |
| Session Recording | ✅ / ❌ | ✅ / ❌ |
| Surveys | ✅ / ❌ | ✅ / ❌ |
| Heatmaps | ✅ / ❌ | ✅ / ❌ |
| Feature Flags | ✅ / ❌ | ✅ / ❌ |
| Web Vitals | ✅ / ❌ | ✅ / ❌ |
### Identifiers
- **Distinct ID:** [id]
- **Session ID:** [id]
### Feature Flags
[List of active flags or "None loaded"]
### Network Activity
- Events endpoint: ✅ / ❌
- Session recording: ✅ / ❌
- Decide/flags endpoint: ✅ / ❌
- Surveys endpoint: ✅ / ❌
### Issues Found
✅ None detected
— or —
⚠️ [Issue description]
### Script Loading
- **Source:** [CDN/Self-hosted/NPM bundle/Custom proxy]
- **Loading:** [Async/Sync/Defer]
### Other Analytics Tools Detected
[List of recognized analytics, tracking, and marketing tools]
### Other External Scripts
[Third-party script domains not matched to known tools]
Run full inspection workflow and provide complete summary.
/e/ or /capture//s/ network requests/decide/ network requestRun the diagnostic checks and report findings.
Check the api_host configuration:
us.i.posthog.com = US Cloudeu.i.posthog.com = EU Cloudapp.posthog.com = Legacy (defaults to US)User: "How is PostHog implemented on https://example.com?"
Claude: "I'll navigate to example.com with debug mode enabled and inspect the PostHog implementation."
[Navigates to https://example.com?__posthog_debug=true]
[Runs JavaScript checks]
[Checks console for PostHog debug messages]
[Analyzes network requests]
Claude:
## PostHog Implementation Summary
### Status
✅ Installed
### Implementation Type
Global window.posthog
### Configuration
- **Version:** 1.96.1
- **API Host:** https://us.i.posthog.com (US Cloud)
- **Project Token:** phc_abc123def456ghi789jkl012mno345
- **Persistence:** localStorage+cookie
### Features (Module Loaded → Feature Enabled)
| Feature | Module Loaded | Enabled |
|---------|---------------|---------|
| Autocapture | ✅ | ✅ |
| Session Recording | ✅ | ✅ |
| Surveys | ✅ | ❌ |
| Heatmaps | ❌ | ❌ |
| Feature Flags | ✅ | ✅ |
| Web Vitals | ✅ | ✅ |
### Identifiers
- **Distinct ID:** 018d4f2a-1234-7abc-def0-123456789abc
- **Session ID:** 018d4f2a-5678-7xyz-abc0-987654321xyz
### Feature Flags
- `new-checkout-flow` ✅
- `beta-features` ❌
### Network Activity
- Events endpoint: ✅
- Session recording: ✅
- Decide/flags endpoint: ✅
- Surveys endpoint: ✅
### Issues Found
✅ None detected
### Script Loading
- **Source:** CDN (us-assets.i.posthog.com)
- **Loading:** Async
### Other Analytics Tools Detected
- Google Tag Manager
- Google Analytics
- HubSpot
### Other External Scripts
- cdn.example-vendor.com
Example 2: Authenticated page with custom proxy
User: "Check PostHog on https://app.example.com/dashboard"
Claude: "I'll navigate to that page with debug mode enabled."
[Navigates to https://app.example.com/dashboard?__posthog_debug=true]
[Takes snapshot, sees login page]
Claude: "I've opened the page but it requires login. Please log in using the Chrome browser I just opened. Let me know when you're on the dashboard."
User: "I'm logged in"
Claude: [Takes new snapshot, verifies authenticated, runs inspection]
## PostHog Implementation Summary
### Status
✅ Installed (bundled)
### Implementation Type
Bundled (not exposed globally) via custom proxy
### Configuration
- **Version:** 1.257.2
- **API Host:** ph.example.com (Custom proxy)
- **Project Token:** phc_abc123def456ghi789jkl012mno345
- **Persistence:** sessionStorage
### Features (Module Loaded → Feature Enabled)
| Feature | Module Loaded | Enabled |
|---------|---------------|---------|
| Autocapture | ✅ | ❌ |
| Session Recording | ❌ | ❌ |
| Surveys | ✅ | ❌ |
| Heatmaps | ❌ | ❌ |
| Feature Flags | ✅ | ✅ |
| Web Vitals | ✅ | ❌ |
### Identifiers
- **Distinct ID:** (not accessible - bundled)
- **Session ID:** (not accessible - bundled)
### Network Activity
- Events endpoint: ✅
- Session recording: ❌
- Decide/flags endpoint: ✅
- Surveys endpoint: ✅ (module loaded, feature disabled)
### Issues Found
✅ None detected
### Script Loading
- **Source:** Custom proxy (ph.example.com)
- **Loading:** Bundled in app
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
Use this skill to query your Google NotebookLM notebooks directly from Claude Code for source-grounded, citation-backed answers from Gemini. Browser automation, library management, persistent auth. Drastically reduced hallucinations through document-only responses.
Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.
Automate Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify, etc.) using agent-browser via Chrome DevTools Protocol. Use when the user needs to interact with an Electron app, automate a desktop app, connect to a running app, control a native app, or test an Electron application. Triggers include "automate Slack app", "control VS Code", "interact with Discord app", "test this Electron app", "connect to desktop app", or any task requiring automation of a native Electron application.
Use when the task requires automating a real browser from the terminal (navigation, form filling, snapshots, screenshots, data extraction, UI-flow debugging) via `playwright-cli` or the bundled wrapper script.
Use when the user explicitly asks for a desktop or system screenshot (full screen, specific app or window, or a pixel region), or when tool-specific capture capabilities are unavailable and an OS-level capture is needed.
A fast Rust-based headless browser automation CLI with Node.js fallback that enables AI agents to navigate, click, type, and snapshot pages via structured commands.
| 所有联网操作必须通过此 skill 处理,包括:搜索、网页抓取、登录后操作、动态页面交互等。 触发场景:用户要求搜索信息、查看网页内容、访问需要登录的网站、操作网页界面、抓取社交媒体内容(小红书、微博、推特等)、读取动态渲染页面、以及任何需要真实浏览器环境的网络任务。
Take posthog/posthog-debugger from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.