PO

Tools for configuring privacy-safe analytics, including data sanitization and GDPR compliance patterns.

Install

mkdir -p .claude/skills/posthog-data-handling && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/8723" && unzip -o skill.zip -d .claude/skills/posthog-data-handling && rm skill.zip

Installs to .claude/skills/posthog-data-handling

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

PostHog PII handling, GDPR compliance, consent management, data deletion,
73 charsno explicit “when” trigger
Advanced

Key capabilities

  • Sanitize properties to remove PII before transmission
  • Implement consent-based tracking flows
  • Execute GDPR data subject access requests
  • Perform GDPR right to erasure deletions
  • Mask session recording inputs and text

How it works

The skill uses property sanitization functions and consent-based SDK methods to ensure PII is stripped or blocked before data leaves the client.

Inputs & outputs

You give it
User consent state or deletion request email
You get back
Redacted analytics data or confirmed data erasure

When to use posthog-data-handling

  • Implement client-side PII redaction
  • Configure GDPR and CCPA consent flows
  • Enable IP masking for analytics data
  • Setup data subject access request workflows

About this skill

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

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

IssueCauseSolution
PII in autocapture eventsForm data captured automaticallyDisable autocapture, use manual capture
IP address in eventsNot stripped by sanitize_propertiesAdd delete properties['$ip']
Consent not persistedopt_out state lost on reloadStore consent in cookie, call opt_out on load
Deletion API returns 404Wrong person ID or already deletedSearch by email first, check response
Session recordings show PIIText not maskedAdd 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

When not to use it

  • When relying on default autocapture for sensitive forms
  • When failing to store consent state in cookies

Prerequisites

PostHog projectposthog-js or posthog-nodePrivacy policyCookie consent mechanism

Limitations

  • Autocapture may leak PII if not disabled
  • Deletion API requires finding person IDs first

How it compares

This approach proactively redacts sensitive data at the source rather than relying on post-capture filtering.

Compared to similar skills

posthog-data-handling side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
posthog-data-handling (this skill)026dReviewAdvanced
reviewing-nextjs-16-patterns118moReviewIntermediate
firebase206moNo flagsIntermediate
auth-patterns77moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

More by jeremylongshore

View all by jeremylongshore

analyzing-logs

jeremylongshore

Analyze application logs to detect performance issues, identify error patterns, and improve stability by extracting key insights.

14123

ollama-setup

jeremylongshore

Configure auto-configure Ollama when user needs local LLM deployment, free AI alternatives, or wants to eliminate hosted API costs. Trigger phrases: "install ollama", "local AI", "free LLM", "self-hosted AI", "replace OpenAI", "no API costs". Use when appropriate context detected. Trigger with relevant phrases based on skill purpose.

1167

backtesting-trading-strategies

jeremylongshore

Backtest crypto and traditional trading strategies against historical data. Calculates performance metrics (Sharpe, Sortino, max drawdown), generates equity curves, and optimizes strategy parameters. Use when user wants to test a trading strategy, validate signals, or compare approaches. Trigger with phrases like "backtest strategy", "test trading strategy", "historical performance", "simulate trades", "optimize parameters", or "validate signals".

1071

generating-database-seed-data

jeremylongshore

Process this skill enables AI assistant to generate realistic test data and database seed scripts for development and testing environments. it uses faker libraries to create realistic data, maintains relational integrity, and allows configurable data volumes. u... Use when working with databases or data models. Trigger with phrases like 'database', 'query', or 'schema'.

1033

cursor-codebase-indexing

jeremylongshore

Execute set up and optimize Cursor codebase indexing. Triggers on "cursor index setup", "codebase indexing", "index codebase", "cursor semantic search". Use when working with cursor codebase indexing functionality. Trigger with phrases like "cursor codebase indexing", "cursor indexing", "cursor".

885

testing-mobile-apps

jeremylongshore

Execute mobile app testing on iOS and Android devices/simulators. Use when performing specialized testing. Trigger with phrases like "test mobile app", "run iOS tests", or "validate Android functionality".

810

You might also like

reviewing-nextjs-16-patterns

djankies

Review code for Next.js 16 compliance - security patterns, caching, breaking changes. Use when reviewing Next.js code, preparing for migration, or auditing for violations.

11106

firebase

davila7

Firebase gives you a complete backend in minutes - auth, database, storage, functions, hosting. But the ease of setup hides real complexity. Security rules are your last line of defense, and they're often wrong. Firestore queries are limited, and you learn this after you've designed your data model. This skill covers Firebase Authentication, Firestore, Realtime Database, Cloud Functions, Cloud Storage, and Firebase Hosting. Key insight: Firebase is optimized for read-heavy, denormalized data. I

2050

auth-patterns

davepoon

This skill should be used when the user asks about "authentication in Next.js", "NextAuth", "Auth.js", "middleware auth", "protected routes", "session management", "JWT", "login flow", or needs guidance on implementing authentication and authorization in Next.js applications.

720

agentic-development

alinaqi

Build AI agents with Pydantic AI (Python) and Claude SDK (Node.js)

19

ai-model-web

TencentCloudBase

Use this skill when developing browser/Web applications (React/Vue/Angular, static websites, SPAs) that need AI capabilities. Features text generation (generateText) and streaming (streamText) via @cloudbase/js-sdk. Built-in models include Hunyuan (hunyuan-2.0-instruct-20251111 recommended) and DeepSeek (deepseek-v3.2 recommended). NOT for Node.js backend (use ai-model-nodejs), WeChat Mini Program (use ai-model-wechat), or image generation (Node SDK only).

13

middleware-protection

dadbodgeoff

Protect routes with Next.js middleware. Check authentication once, protect routes declaratively. Supports public routes, protected routes, and role-based access.

13

Search skills

Search the agent skills registry