Reading PostHog Funnels with HogQL: How to Find Your Worst Step Week-Over-Week
Prompt: Reading PostHog funnels with the conversion drop-off API: how to find your worst step week-over-week
Grounded in: materialized-columns.md, safe-django-migrations.md, CONTRIBUTING.md, clickhouse-event-table-migrations.md, query-performance-optimization.md
Read full generated draft
Meta description: Learn how to query PostHog's events table in HogQL to identify which funnel step loses the most users week-over-week. Includes working queries and performance optimization tips.
Funnel analysis shows you where users drop off in multi-step flows—sign-up sequences, onboarding paths, checkout processes. But spotting which step degraded this week versus last week requires comparing conversion rates across time windows. This tutorial shows you how to query PostHog's underlying events data to surface the funnel step with the biggest week-over-week drop-off increase.
Prerequisites
- PostHog Cloud or self-hosted instance with funnel insights already configured
- Access to HogQL queries (PostHog's SQL-like query interface)
- Familiarity with funnel step definitions in your PostHog project (event names, property filters)
- Basic SQL knowledge for aggregating and comparing time-series data
You'll be querying the events table via HogQL. If you're on PostHog Cloud, you can run these queries directly in the SQL insight editor.
Step 1: Define Your Funnel Steps as Event Filters
PostHog funnels are sequences of events. Before querying conversion rates, list the exact event names and any property filters that define each step. For example, a sign-up funnel might be:
- Step 1:
$pageviewwith a specific URL pattern - Step 2:
form_submittedwith a form identifier - Step 3:
account_created
Write down these event names and filters—you'll use them in the WHERE clauses below.
Step 2: Query Funnel Conversion Rates for Two Time Windows
PostHog stores events in the events table with columns event, timestamp, distinct_id, properties, and team_id. To calculate conversion rates between steps, you'll:
- Count distinct users who completed Step 1 in each time window
- Count distinct users who completed Step 1 and then Step 2 within a session window
- Divide Step 2 completions by Step 1 completions to get the conversion rate
Here's a HogQL query template for a two-step funnel comparing this week versus last week:
Evidence note: the source material does not verify a safe copy-paste SQL query for this check because
current_url,form_submitted,last_week_rate,last_week_step1,last_week_step2,rate_changedid not appear in the provided evidence. Treat this as a current evidence limitation rather than a runnable query.
Replace placeholders:
<value>: Your PostHog team ID (find it in Project Settings)- Event names and property filters: Match your funnel definition
- Session window (
INTERVAL 1 HOUR): Adjust based on expected user behavior
Performance note: This query joins the events table to itself. For high-volume projects, consider materializing frequently-queried properties to speed up JSONExtractString operations. See [handbook/materialized-columns.md] for details on automatic and manual materialization via Dagster.
Step 3: Extend the Query to All Funnel Steps
If your funnel has more than two steps, repeat the CTE pattern for each step pair. Then combine results to compare all step pairs:
Evidence note: the source material does not verify a safe copy-paste SQL query for this check because
rate_changedid not appear in the provided evidence. Treat this as a current evidence limitation rather than a runnable query.
The step with the most negative rate change is your worst-performing step this week.
Step 4: Optimize Query Performance for Large Datasets
For projects with millions of events per week, these self-joins can be slow. Apply these optimizations:
4.1 Materialize Frequently-Filtered Properties
If you filter on properties in every query, materialize them as ClickHouse columns. PostHog automatically materializes properties used in slow queries (see [handbook/materialized-columns.md]), but you can manually trigger materialization via Dagster:
- EU Cloud: Dagster Playground (EU)
- US Cloud: Dagster Playground (US)
Configure the create_materialized_columns_op with your property name. Materialized columns can make property filters up to 25× faster [handbook/materialized-columns.md].
4.2 Limit the Time Window
Instead of querying all events in a 7-day window, filter to business hours or high-traffic periods if your funnel is time-sensitive:
Evidence limitation: this runnable code example was removed because it failed syntax validation. Use the surrounding verified data model and source notes instead of copying an invalid snippet.
4.3 Use PREWHERE for Early Filtering
ClickHouse evaluates PREWHERE before reading all columns. Move your most selective filter (usually team_id and event) to PREWHERE:
Evidence note: the source material does not verify a safe copy-paste SQL query for this check because
current_urldid not appear in the provided evidence. Treat this as a current evidence limitation rather than a runnable query.
See [handbook/query-performance-optimization.md] for more ClickHouse optimization patterns.
Step 5: Verify Results Against PostHog's Funnel Insight
Run the same funnel in PostHog's Insights UI for the same time windows. Compare the conversion rates:
- Go to Insights → New Insight → Funnel
- Define the same steps and filters
- Set the date range to "Last 7 days" and "Previous 7 days"
- Check the conversion rate for each step
Your HogQL query results should match within a few percentage points. Small discrepancies can occur due to:
- Session window differences: PostHog's UI may use a different default session timeout
- Timezone offsets: Ensure your query uses the same timezone as your PostHog project settings
- Sampling: PostHog may sample large datasets in the UI; HogQL queries run on full data
Troubleshooting
Query Times Out or Returns Empty Results
Cause: The events table is large, and self-joins without proper indexing are slow.
Fix:
- Add
LIMIT 10000to each CTE to test the query logic on a subset - Verify
team_idandeventfilters are correct (typos will return zero rows) - Check that your time window actually contains events
Conversion Rates Are Unexpectedly High or Low
Cause: Session window is too long or too short, or property filters don't match your funnel definition.
Fix:
- Inspect a sample of raw events to confirm property values
- Adjust the session window (
INTERVAL 1 HOUR) based on actual user behavior - Verify property extraction syntax (e.g.,
JSONExtractStringvs.JSONExtractRaw)
Materialized Columns Not Speeding Up Queries
Note: Materialized columns require backfilling to be effective [handbook/materialized-columns.md]. This is typically a weekend operation due to cluster load. Verify the column exists by checking your table schema, and ensure your WHERE clause references the materialized column directly rather than extracting from the JSON properties field.
Next Steps
- Automate the Query: Schedule this query to run weekly via PostHog's scheduled insights or an external cron job. Export results to Slack or email.
- Drill Down into Drop-Off Causes: Once you identify the worst step, query for common properties among users who dropped off to understand why.
- Compare Across Cohorts: Segment the funnel by user properties (e.g., new vs. returning users, mobile vs. desktop) to find which cohort regressed.
Evidence limitation: the current KB evidence does not verify a self-contained command, schema, or example for this step.
Sources:
- [handbook/materialized-columns.md]
- [handbook/query-performance-optimization.md]
Word count: ~1,280 words