Claude-code-plugins-plus-skills posthog-data-handling
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/posthog-pack/skills/posthog-data-handling" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-posthog-data-handling && rm -rf "$T"
manifest:
plugins/saas-packs/posthog-pack/skills/posthog-data-handling/SKILL.mdsource content
PostHog Data Handling
Overview
Privacy-safe analytics with PostHog. Covers property sanitization to strip PII before events leave the browser, consent-based tracking (opt-in/opt-out), GDPR data subject access requests and deletion, and PostHog's built-in privacy controls (IP masking, session recording masking).
Prerequisites
- PostHog project (Cloud or self-hosted)
and/orposthog-js
installedposthog-node- Privacy policy covering analytics data collection
- Cookie consent mechanism (e.g., CookieConsent banner)
Instructions
Step 1: Privacy-Safe Initialization
import posthog from 'posthog-js'; posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { api_host: 'https://us.i.posthog.com', // Disable autocapture to control exactly what's captured autocapture: false, // Respect browser Do Not Track setting respect_dnt: true, // Don't capture until user consents opt_out_capturing_by_default: false, // Set true for opt-in model // Sanitize ALL properties before they leave the browser sanitize_properties: (properties, eventName) => { // Remove IP address delete properties['$ip']; // Remove potentially identifying properties delete properties['$device_id']; // Redact URLs containing tokens or auth info if (properties['$current_url']) { properties['$current_url'] = properties['$current_url'] .replace(/token=[^&]+/g, 'token=[REDACTED]') .replace(/key=[^&]+/g, 'key=[REDACTED]') .replace(/session=[^&]+/g, 'session=[REDACTED]'); } // Redact referrer tokens if (properties['$referrer']) { properties['$referrer'] = properties['$referrer'] .replace(/token=[^&]+/g, 'token=[REDACTED]'); } return properties; }, // Session recording privacy session_recording: { maskAllInputs: true, // Mask all input fields maskTextSelector: '.pii-data', // Mask specific elements }, });
Step 2: Consent-Based Tracking
// Cookie consent integration interface ConsentState { analytics: boolean; functional: boolean; marketing: boolean; } export function handleConsentChange(consent: ConsentState) { if (consent.analytics) { // User opted in — start capturing posthog.opt_in_capturing(); } else { // User opted out — stop capturing and clear local data posthog.opt_out_capturing(); posthog.reset(); // Clears distinct_id, device_id, session data } } // Check consent before identifying (PII) export function identifyWithConsent( userId: string, properties: Record<string, any>, hasAnalyticsConsent: boolean ) { if (!hasAnalyticsConsent) return; // Only send non-PII properties by default const safeProperties: Record<string, any> = { plan: properties.plan, signup_date: properties.signupDate, account_type: properties.accountType, // Do NOT include: email, name, phone, address }; posthog.identify(userId, safeProperties); } // On page load: restore consent state export function restoreConsent() { const consent = getCookieConsent(); // Your consent mechanism if (consent?.analytics === false) { posthog.opt_out_capturing(); } }
Step 3: GDPR Data Subject Access Request (SAR)
// Find a person by email and export their data async function handleSubjectAccessRequest(email: string) { const personalKey = process.env.POSTHOG_PERSONAL_API_KEY!; const projectId = process.env.POSTHOG_PROJECT_ID!; // 1. Find the person by email property const searchResponse = await fetch( `https://app.posthog.com/api/projects/${projectId}/persons/?properties=[{"key":"email","value":"${encodeURIComponent(email)}","type":"person"}]`, { headers: { Authorization: `Bearer ${personalKey}` } } ); const searchData = await searchResponse.json(); if (!searchData.results?.length) { return { found: false, message: 'No person found with that email' }; } const person = searchData.results[0]; const distinctId = person.distinct_ids[0]; // 2. Export their events (strip PII from export) const eventsResponse = await fetch( `https://app.posthog.com/api/projects/${projectId}/query/`, { method: 'POST', headers: { Authorization: `Bearer ${personalKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ query: { kind: 'HogQLQuery', query: `SELECT event, timestamp, properties FROM events WHERE distinct_id = '${distinctId}' ORDER BY timestamp DESC LIMIT 1000`, }, }), } ); const eventsData = await eventsResponse.json(); return { found: true, person: { distinct_ids: person.distinct_ids, properties: person.properties, created_at: person.created_at, }, events_count: eventsData.results?.length || 0, events: eventsData.results, }; }
Step 4: GDPR Right to Erasure (Data Deletion)
// Delete a person and all their events async function handleDeletionRequest(email: string) { const personalKey = process.env.POSTHOG_PERSONAL_API_KEY!; const projectId = process.env.POSTHOG_PROJECT_ID!; // 1. Find the person const searchResponse = await fetch( `https://app.posthog.com/api/projects/${projectId}/persons/?properties=[{"key":"email","value":"${encodeURIComponent(email)}","type":"person"}]`, { headers: { Authorization: `Bearer ${personalKey}` } } ); const searchData = await searchResponse.json(); if (!searchData.results?.length) { return { deleted: false, reason: 'Person not found' }; } const personId = searchData.results[0].id; // 2. Delete the person (PostHog also deletes associated events) const deleteResponse = await fetch( `https://app.posthog.com/api/projects/${projectId}/persons/${personId}/`, { method: 'DELETE', headers: { Authorization: `Bearer ${personalKey}` }, } ); if (!deleteResponse.ok) { throw new Error(`Deletion failed: ${deleteResponse.status}`); } return { deleted: true, personId, timestamp: new Date().toISOString(), }; }
Step 5: Property Filtering for Data Exports
// Strip PII from HogQL query results before exporting const BLOCKED_PROPERTIES = ['$ip', 'email', 'phone', 'name', 'address', 'ssn']; async function safeExport(hogql: string) { const response = await fetch( `https://app.posthog.com/api/projects/${process.env.POSTHOG_PROJECT_ID}/query/`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.POSTHOG_PERSONAL_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ query: { kind: 'HogQLQuery', query: hogql } }), } ); const data = await response.json(); // Remove blocked columns from results if (data.columns && data.results) { const blockedIndexes = new Set( data.columns.map((col: string, i: number) => BLOCKED_PROPERTIES.some(b => col.toLowerCase().includes(b)) ? i : -1 ).filter((i: number) => i >= 0) ); data.columns = data.columns.filter((_: string, i: number) => !blockedIndexes.has(i)); data.results = data.results.map((row: any[]) => row.filter((_: any, i: number) => !blockedIndexes.has(i)) ); } return data; }
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| PII in autocapture events | Form data captured automatically | Disable autocapture, use manual capture |
| IP address in events | Not stripped by sanitize_properties | Add |
| Consent not persisted | opt_out state lost on reload | Store consent in cookie, call opt_out on load |
| Deletion API returns 404 | Wrong person ID or already deleted | Search by email first, check response |
| Session recordings show PII | Text not masked | Add and |
GDPR Compliance Checklist
-
strips PII before events leave browsersanitize_properties - Consent mechanism with
/opt_in_capturingopt_out_capturing -
in PostHog initrespect_dnt: true - Session recording masks all inputs
- Subject Access Request handler implemented
- Data Deletion handler implemented
- Privacy policy updated to mention PostHog analytics
Output
- Privacy-safe PostHog initialization with property sanitization
- Consent-based tracking with opt-in/opt-out
- GDPR Subject Access Request handler
- GDPR Data Deletion handler
- PII-safe data export function