Skip to content
Back to skills

Posthog Data Handling

ASecurity

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".

  • 4 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 12, 2026
developmenttypescriptnodeapi

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 12, 2026

npx -y skills add aibot88/sec_skill_store --skill posthog-data-handling --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Posthog Data Handling?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Posthog Data Handling
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aibot88-posthog-data-handling/badge)](https://www.skillsdirectory.com/skills/aibot88-posthog-data-handling)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: posthog-data-handling
description: |
  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".
allowed-tools: Read, Write, Edit
version: 1.0.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
compatible-with: claude-code, codex, openclaw
tags: [saas, posthog, compliance]
---

# 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 browser
- [ ] Consent mechanism with `opt_in_capturing` / `opt_out_capturing`
- [ ] `respect_dnt: true` in PostHog init
- [ ] 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

## Resources

- [PostHog Privacy Controls](https://posthog.com/docs/privacy)
- [PostHog GDPR Compliance](https://posthog.com/docs/privacy/gdpr-compliance)
- [PostHog Persons API](https://posthog.com/docs/api/persons)
- [PostHog Data Collection Controls](https://posthog.com/docs/privacy/data-collection)

Files in this skill

  • SKILL.md9.1 KB
  • source.json837 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.