posthog-data-handling
PostHog PII handling, GDPR compliance, consent management, data deletion, property sanitization, and privacy-safe analytics configuration. Trigger: "posthog data", "posthog PII", "posthog GDPR", "posthog data retention", "posthog privacy", "posthog CCPA", "posthog consent".
What this skill does
# 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)
- `posthog-js` and/or `posthog-node` installed
- Privacy policy covering analytics data collection
- Cookie consent mechanism (e.g., CookieConsent banner)
## Instructions
### Step 1: Privacy-Safe Initialization
```typescript
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
```typescript
// 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)
```typescript
// 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)
```typescript
// 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
```typescript
// 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 `delete properties['$ip']` |
| 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 `maskAllInputs: true` and `maskTextSelector` |
## GDPR Compliance Checklist
- [ ] `sanitize_properties` strips PII before events leave browsRelated in Data & Analytics
clawarr-suite
IncludedComprehensive management for self-hosted media stacks (Sonarr, Radarr, Lidarr, Readarr, Prowlarr, Bazarr, Overseerr, Plex, Tautulli, SABnzbd, Recyclarr, Unpackerr, Notifiarr, Maintainerr, Kometa, FlareSolverr). Deep library exploration, analytics, dashboard generation, content management, request handling, subtitle management, indexer control, download monitoring, quality profile sync, library cleanup automation, notification routing, collection/overlay management, and media tracker integration (Trakt, Letterboxd, Simkl).
querying-soql
IncludedSOQL query generation, optimization, and analysis with 100-point scoring. Use this skill when the user needs SOQL/SOSL authoring or optimization: natural-language-to-query generation, relationship queries, aggregates, query-plan analysis, and performance or safety improvements for Salesforce queries. TRIGGER when: user writes, optimizes, or debugs SOQL/SOSL queries, touches .soql files, or asks about relationship queries, aggregates, or query performance. DO NOT TRIGGER when: bulk data operations (use handling-sf-data), Apex DML logic (use generating-apex), or report/dashboard queries.
app-store-optimization
IncludedApp Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklists, and tracking ranking changes.
habit-flow
IncludedAI-powered atomic habit tracker with natural language logging, streak tracking, smart reminders, and coaching. Use for creating habits, logging completions naturally ("I meditated today"), viewing progress, and getting personalized coaching.
app-store-optimization
IncludedApp Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklists, and tracking ranking changes.
visualizing-data
IncludedBuilds dashboards, reports, and data-driven interfaces requiring charts, graphs, or visual analytics. Provides systematic framework for selecting appropriate visualizations based on data characteristics and analytical purpose. Includes 24+ visualization types organized by purpose (trends, comparisons, distributions, relationships, flows, hierarchies, geospatial), accessibility patterns (WCAG 2.1 AA compliance), colorblind-safe palettes, and performance optimization strategies. Use when creating visualizations, choosing chart types, displaying data graphically, or designing data interfaces.