Chapter 3: User Analytics & Funnels

March 2, 2026 · View on GitHub

Welcome to Chapter 3: User Analytics & Funnels. In this part of PostHog Tutorial: Open Source Product Analytics Platform, you will build an intuitive mental model first, then move into concrete implementation details and practical production tradeoffs.

In Chapter 2, you built a solid event tracking layer with clean naming conventions, rich properties, and identity resolution. Raw events are the ingredients; analytics is the recipe. This chapter shows you how to turn those ingredients into funnels, retention curves, user paths, and trend analyses that drive real product decisions.

By the end of this chapter you will be able to answer questions like "Where do users drop off during onboarding?", "How many users come back after week one?", and "Which acquisition channel produces the most paying customers?"

What You Will Learn

  • Build conversion funnels and diagnose drop-offs
  • Measure retention with cohort-based tables
  • Map user journeys with the Paths visualization
  • Create trend insights for KPIs like DAU, WAU, and MAU
  • Segment every analysis by cohort, property, or experiment variant

The PostHog Insights Engine

PostHog provides five core insight types. Each answers a different class of question.

flowchart TD
    Q[Product Question]
    Q --> T["How is metric X changing over time?"]
    Q --> F["Where do users drop off?"]
    Q --> R["Do users come back?"]
    Q --> P["What paths do users take?"]
    Q --> L["What does the distribution look like?"]

    T --> Trends
    F --> Funnels
    R --> Retention
    P --> Paths
    L --> Stickiness

    classDef question fill:#e1f5fe,stroke:#01579b
    classDef insight fill:#e8f5e8,stroke:#1b5e20

    class Q,T,F,R,P,L question
    class Trends,Funnels,Retention,Paths,Stickiness insight
Insight TypePrimary QuestionExample
TrendsHow does a metric change over time?Daily signups this month
FunnelsWhere do users drop off in a sequence?Signup-to-first-project conversion
RetentionDo users come back after an initial action?Week-over-week app opens
PathsWhat do users do before/after an event?Pages visited before checkout
StickinessHow many days/weeks do users perform an action?Days per week a user opens the app

Building Conversion Funnels

Funnels are the most actionable insight type for product teams. They show exactly where users abandon a multi-step flow.

Designing Funnel Steps

A funnel is a sequence of events that represents a desired user journey. Each step narrows the population to users who completed that action.

flowchart LR
    A["visited_pricing<br/>100%"] --> B["started_checkout<br/>42%"]
    B --> C["entered_payment<br/>31%"]
    C --> D["completed_purchase<br/>24%"]

    classDef full fill:#e8f5e8,stroke:#1b5e20
    classDef mid fill:#fff3e0,stroke:#ef6c00
    classDef low fill:#ffebee,stroke:#c62828

    class A full
    class B mid
    class C,D low

Creating a Funnel in the UI

  1. Navigate to Insights and click New Insight
  2. Select Funnels as the insight type
  3. Add steps in order:
    • Step 1: visited_pricing
    • Step 2: started_checkout
    • Step 3: entered_payment
    • Step 4: completed_purchase
  4. Set the conversion window (e.g., 7 days)
  5. Add breakdowns by plan, $browser, or utm_source
  6. Save and add to a dashboard

Creating a Funnel via the API

import { PostHog } from 'posthog-node'

const client = new PostHog('YOUR_API_KEY', {
  host: 'https://app.posthog.com'
})

// Query a funnel programmatically via the PostHog API
const response = await fetch('https://app.posthog.com/api/projects/YOUR_PROJECT_ID/insights/funnel/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_PERSONAL_API_KEY'
  },
  body: JSON.stringify({
    events: [
      { id: 'visited_pricing', order: 0 },
      { id: 'started_checkout', order: 1 },
      { id: 'entered_payment', order: 2 },
      { id: 'completed_purchase', order: 3 }
    ],
    funnel_window_days: 7,
    breakdown: 'plan',
    date_from: '-30d'
  })
})

const funnelData = await response.json()
console.log('Overall conversion:', funnelData.result)
import requests

response = requests.post(
    'https://app.posthog.com/api/projects/YOUR_PROJECT_ID/insights/funnel/',
    headers={
        'Content-Type': 'application/json',
        'Authorization': 'Bearer YOUR_PERSONAL_API_KEY'
    },
    json={
        'events': [
            {'id': 'visited_pricing', 'order': 0},
            {'id': 'started_checkout', 'order': 1},
            {'id': 'entered_payment', 'order': 2},
            {'id': 'completed_purchase', 'order': 3},
        ],
        'funnel_window_days': 7,
        'breakdown': 'plan',
        'date_from': '-30d',
    }
)

funnel_data = response.json()
for step in funnel_data['result']:
    print(f"Step {step['order']}: {step['name']} - {step['count']} users")

Funnel Configuration Options

OptionDescriptionRecommended Value
Conversion windowMax time between first and last step7-14 days for SaaS onboarding
BreakdownSplit results by a propertyplan, utm_source, $browser
Exclusion stepsEvents that disqualify a useraccount_deleted, unsubscribed
Funnel orderStrict (exact order) vs. unorderedStrict for checkout flows
AggregationCount unique users or eventsUnique users for conversion rates

Diagnosing Drop-Offs

When a funnel shows a large drop between steps, investigate with these techniques:

  1. Click the drop-off bar to see the list of users who did not convert
  2. Watch their session recordings (covered in Chapter 4) to see what they did instead
  3. Add a breakdown by device, browser, or geography to find platform-specific issues
  4. Check the median time between steps -- a very long gap may indicate confusion
  5. Create a Path analysis starting from the drop-off point to see where users went

Retention Analysis

Retention answers the question: "After users do X for the first time, how many come back and do Y?" It is the single most important metric for product-market fit.

Retention Table Structure

A retention table shows cohorts (rows) and time periods (columns). Each cell is the percentage of the cohort that returned.

Cohort (Week)Week 0Week 1Week 2Week 3Week 4
Jan 6100%38%29%24%21%
Jan 13100%41%32%26%--
Jan 20100%35%27%----
Jan 27100%39%------

Creating Retention Insights

In the PostHog UI:

  1. Navigate to Insights and select Retention
  2. Set the start event (e.g., signed_up)
  3. Set the return event (e.g., app_opened or project_created)
  4. Choose the period (Day, Week, or Month)
  5. Optionally filter by cohort, plan, or experiment variant

Retention Patterns to Watch For

flowchart TD
    subgraph Healthy
        H1["Week 0: 100%"] --> H2["Week 1: 45%"]
        H2 --> H3["Week 4: 30%"]
        H3 --> H4["Week 8: 28%"]
    end

    subgraph Leaky["Leaky Bucket"]
        L1["Week 0: 100%"] --> L2["Week 1: 20%"]
        L2 --> L3["Week 4: 5%"]
        L3 --> L4["Week 8: 1%"]
    end

    subgraph Improving
        I1["Week 0: 100%"] --> I2["Week 1: 25%"]
        I2 --> I3["Week 4: 22%"]
        I3 --> I4["Week 8: 28%"]
    end

    classDef good fill:#e8f5e8,stroke:#1b5e20
    classDef bad fill:#ffebee,stroke:#c62828
    classDef neutral fill:#fff3e0,stroke:#ef6c00

    class H1,H2,H3,H4 good
    class L1,L2,L3,L4 bad
    class I1,I2,I3,I4 neutral
PatternShapeAction
HealthyCurve flattens above 20-30%Maintain; focus on growth
Leaky bucketSteep decline, never flattensFix activation; improve onboarding
ImprovingLater cohorts retain betterKeep shipping; recent changes are working
Smile curveDrops then risesUsers rediscover value; investigate why

User Paths (Journey Analysis)

Paths show the actual sequences of pages or events users follow. Unlike funnels, which test a specific hypothesis, paths help you discover unknown patterns.

Path Analysis Types

Path TypeStarting PointUse Case
Paths after eventA specific eventWhat do users do after signing up?
Paths before eventA specific eventWhat leads users to upgrade?
Full pathsAny entry pointGeneral navigation patterns

Creating a Path Analysis

// Query paths via the API
const response = await fetch(
  'https://app.posthog.com/api/projects/YOUR_PROJECT_ID/insights/path/',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_PERSONAL_API_KEY'
    },
    body: JSON.stringify({
      path_type: 'paths_after',          // or 'paths_before'
      start_point: 'signed_up',
      step_limit: 5,                     // max depth
      date_from: '-30d',
      include_event_types: ['custom_event', '$pageview'],
      exclude_events: ['$autocapture'],  // reduce noise
      min_edge_weight: 10                // hide rare paths
    })
  }
)

Reading Path Visualizations

Paths produce a Sankey-style diagram. Focus on:

  • Thick branches: popular paths most users follow
  • Dead ends: paths that lead to no further action (possible frustration)
  • Loops: users going back and forth (possible confusion)
  • Short paths to conversion: the "happy path" you want to optimize for

Trend Analysis

Trends are the simplest and most frequently used insight type. They plot a metric over time.

Common Trend Metrics

MetricEventAggregationUse
DAUapp_openedUnique users / dayDaily engagement
WAUapp_openedUnique users / weekWeekly engagement
Signupssigned_upTotal count / dayGrowth rate
Revenue eventsinvoice_paidSum of amount_cents / dayRevenue tracking
Errorsapi_error_occurredTotal count / hourReliability monitoring

Building a Trend in TypeScript

// Fetch a trend from the PostHog API
const trendResponse = await fetch(
  'https://app.posthog.com/api/projects/YOUR_PROJECT_ID/insights/trend/',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_PERSONAL_API_KEY'
    },
    body: JSON.stringify({
      events: [
        {
          id: 'signed_up',
          math: 'total',         // count every event
          name: 'Signups'
        },
        {
          id: 'app_opened',
          math: 'dau',           // unique users per day
          name: 'DAU'
        }
      ],
      date_from: '-30d',
      interval: 'day',
      display: 'ActionsLineGraph'
    })
  }
)

const trendData = await trendResponse.json()
for (const series of trendData.result) {
  console.log(`${series.label}: ${series.data}`)
}

Trend Formulas

PostHog supports formulas to create composite metrics:

# Activation rate
A / B  where A = "onboarding_completed" and B = "signed_up"

# WAU / MAU ratio (stickiness proxy)
A / B  where A = "app_opened" (unique/week) and B = "app_opened" (unique/month)

Stickiness Analysis

Stickiness measures how frequently users perform an action over a time window. It answers: "Out of the users who did X this month, how many did it on 1 day, 2 days, 3 days ... etc.?"

A healthy product has a stickiness distribution that skews right -- many users engage on multiple days rather than just one.

Days Active in MonthUsersPercentage
12,40030%
2-31,80022%
4-71,60020%
8-141,20015%
15+1,00013%

Segmentation and Breakdowns

Every insight type supports breakdowns -- splitting results by a property. Breakdowns transform a single number into an actionable comparison.

Useful Breakdown Properties

PropertyTypeReveals
planPersonConversion differences by plan
utm_sourceEventBest acquisition channels
$browserSystemPlatform-specific issues
$country_codeSystemGeographic patterns
experiment_variantPersonA/B test impact
company (group)GroupB2B account behavior

Cohort-Based Segmentation

Cohorts let you define reusable user segments for any analysis.

flowchart LR
    subgraph Definition
        A["Users who signed up<br/>in last 30 days"]
        B["Users on growth plan"]
        C["Users who completed<br/>onboarding"]
    end

    subgraph Usage
        D[Funnel Breakdown]
        E[Retention Filter]
        F[Trend Comparison]
    end

    A --> D
    A --> E
    B --> D
    B --> F
    C --> E
    C --> F

    classDef def fill:#e1f5fe,stroke:#01579b
    classDef use fill:#e8f5e8,stroke:#1b5e20

    class A,B,C def
    class D,E,F use

Creating Cohorts

In the PostHog UI:

  1. Navigate to Persons & Groups then Cohorts
  2. Click New Cohort
  3. Define criteria:
    • "Completed event onboarding_completed in the last 30 days"
    • AND "Person property plan equals growth"
  4. Name the cohort (e.g., "Activated Growth Users") and save
  5. Use it as a filter or breakdown in any insight
import requests

# Create a cohort via the API
response = requests.post(
    'https://app.posthog.com/api/projects/YOUR_PROJECT_ID/cohorts/',
    headers={
        'Content-Type': 'application/json',
        'Authorization': 'Bearer YOUR_PERSONAL_API_KEY'
    },
    json={
        'name': 'Activated Growth Users',
        'groups': [
            {
                'properties': [
                    {
                        'key': 'plan',
                        'value': 'growth',
                        'type': 'person'
                    }
                ]
            }
        ],
        'is_static': False
    }
)

cohort = response.json()
print(f"Created cohort: {cohort['name']} (ID: {cohort['id']})")

Lifecycle Analysis

Lifecycle categorizes your users into four groups for each time period:

CategoryDefinitionAction
NewFirst time performing the eventOptimize acquisition
ReturningPerformed the event this period and the previous periodCelebrate and learn from them
ResurrectingPerformed the event this period but not the previous periodUnderstand what brought them back
DormantPerformed the event in the previous period but not this periodWin them back with re-engagement

Lifecycle is especially useful for understanding the composition of your active user base. A growing product has more new and returning users than dormant.

Putting It All Together: An Analytics Workflow

Here is a practical workflow for investigating a drop in weekly active users:

flowchart TD
    A["WAU dropped 15%<br/>this week"] --> B["Check Trends:<br/>Which event declined?"]
    B --> C["app_opened down<br/>among free users"]
    C --> D["Check Retention:<br/>Is the Jan 20 cohort different?"]
    D --> E["Week-1 retention<br/>dropped from 38% to 25%"]
    E --> F["Check Funnel:<br/>Is onboarding broken?"]
    F --> G["Step 2→3 drop-off<br/>increased 12%"]
    G --> H["Watch Session Recordings<br/>of users who dropped off"]
    H --> I["Users confused by<br/>new onboarding step"]
    I --> J["Fix UX and run<br/>an A/B test"]

    classDef alert fill:#ffebee,stroke:#c62828
    classDef investigate fill:#fff3e0,stroke:#ef6c00
    classDef action fill:#e8f5e8,stroke:#1b5e20

    class A alert
    class B,C,D,E,F,G,H investigate
    class I,J action

Troubleshooting

ProblemCauseSolution
Funnel shows 0% conversionEvents not firing or wrong distinct_idVerify in Live Events; check ID consistency
Retention table is emptyStart event has no matches in date rangeExpand date range or check event name
Path visualization too noisyAutocapture events dominatingExclude $autocapture; increase min edge weight
Breakdown shows "Other" bucketToo many unique valuesReduce cardinality; bucket values
Numbers don't match other toolsDifferent attribution or dedup logicAlign on unique-user vs. total-event counting
Cohort size seems wrongInternal/test users includedFilter by is_test_user != true

Performance Considerations

  • Date range: Queries over very long date ranges (6+ months) on large datasets can be slow. Use shorter ranges for exploration, then save specific insights.
  • Breakdowns: Each breakdown multiplies query cost. Limit to 1-2 breakdowns at a time.
  • Sampling: PostHog supports sampling for exploratory analysis. Enable it in the query builder for faster iteration, then disable for final numbers.
  • Caching: Saved insights are cached. Dashboards refresh on a schedule rather than on every page load.

Security and Privacy

  • Filter internal traffic: Exclude your team's events using an is_internal person property or IP-based cohort.
  • Anonymize for sharing: When sharing insights with external stakeholders, ensure PII is not visible in person lists or recordings.
  • Access controls: Use PostHog's role-based access to limit who can see person-level data vs. aggregated insights.
  • Data retention: Set retention policies so old person-level data is purged while aggregated metrics remain.

Summary

User analytics transforms raw events into product intelligence. Funnels show where users abandon key flows. Retention tables reveal whether your product keeps users coming back. Paths uncover navigation patterns you did not expect. Trends track your KPIs over time. Stickiness and lifecycle round out the picture by showing engagement depth and user composition.

Key Takeaways

  1. Start with funnels for your core flows -- onboarding, checkout, and activation are the highest-leverage funnels to build first.
  2. Retention is the ultimate product-market fit metric -- if Week-4 retention is below 15-20%, focus on activation before growth.
  3. Paths reveal what you did not plan for -- use them to discover unexpected user behavior and new feature opportunities.
  4. Segment everything -- a single aggregate number hides the story. Break down by plan, source, and cohort to find the real insight.
  5. Connect quantitative and qualitative -- when a funnel shows a drop-off, watch the session recordings of those users to understand why.

Next Steps

You now know how to analyze user behavior quantitatively. But numbers only tell you what happened, not why. In Chapter 4: Session Recordings, you will learn how to watch real user sessions, identify UX friction, and connect qualitative observations to the funnels and retention curves you built here.


Built with insights from the PostHog project.

What Problem Does This Solve?

Most teams struggle here because the hard part is not writing more code, but deciding clear boundaries for classDef, fill, stroke so behavior stays predictable as complexity grows.

In practical terms, this chapter helps you avoid three common failures:

  • coupling core logic too tightly to one implementation path
  • missing the handoff boundaries between setup, execution, and validation
  • shipping changes without clear rollback or observability strategy

After working through this chapter, you should be able to reason about Chapter 3: User Analytics & Funnels as an operating subsystem inside PostHog Tutorial: Open Source Product Analytics Platform, with explicit contracts for inputs, state transitions, and outputs.

Use the implementation notes around Week, json, order as your checklist when adapting these patterns to your own repository.

How it Works Under the Hood

Under the hood, Chapter 3: User Analytics & Funnels usually follows a repeatable control path:

  1. Context bootstrap: initialize runtime config and prerequisites for classDef.
  2. Input normalization: shape incoming data so fill receives stable contracts.
  3. Core execution: run the main logic branch and propagate intermediate state through stroke.
  4. Policy and safety checks: enforce limits, auth scopes, and failure boundaries.
  5. Output composition: return canonical result payloads for downstream consumers.
  6. Operational telemetry: emit logs/metrics needed for debugging and performance tuning.

When debugging, walk this sequence in order and confirm each stage has explicit success/failure conditions.

Source Walkthrough

Use the following upstream sources to verify implementation details while reading this chapter:

  • View Repo Why it matters: authoritative reference on View Repo (github.com).

Suggested trace strategy:

  • search upstream code for classDef and fill to map concrete implementation paths
  • compare docs claims against actual runtime/config code before reusing patterns in production

Chapter Connections