Path cleaning
Contents
Path cleaning rules let you normalize dynamic URLs into consistent patterns, reducing the cardinality of your path data. This makes your web analytics paths, entry paths, exit paths, outbound clicks, and path breakdowns more readable and actionable.
For example, URLs like /user/123, /user/456, and /user/789 can all be cleaned into /user/:id so they appear as a single row in your path tables.
Path cleaning is available on paid plans. If you're on the free plan, you need to add a credit card to access this feature. See our pricing page for details.
Why use path cleaning?
Many websites include dynamic segments in URLs — user IDs, product slugs, session tokens, or other unique values. Without path cleaning, each unique URL shows up as a separate row, making it hard to see meaningful patterns.
Common examples of noisy URLs:
| Original URL | Problem |
|---|---|
/user/123/profile | User ID creates a unique row per user |
/product/blue-widget-v2 | Product slug creates a unique row per product |
/merchant/8421/dashboard | Merchant ID fragments your dashboard data |
/post/2024/01/my-blog-post | Date and slug make each post a separate entry |
Path cleaning solves this by using regex to match these patterns and replace them with a readable alias.
Configuring path cleaning rules
Path cleaning rules are configured globally in your project settings. Once configured, they apply across both web analytics and product analytics.
Each rule has two parts:
- Regex – a regular expression that matches the URL patterns you want to combine.
- Alias – the replacement for the matched portion of the URL. This is usually a fixed placeholder like
/user/:id, but it can also reuse part of the matched URL.
When a URL matches the regex, the matched portion is replaced with the alias. The rest of the URL remains unchanged.
Rules run against whichever URL property is being cleaned. That's a path like /user/123/profile for the web analytics tiles, but a full URL like https://example.com/user/123/profile for a Paths insight or a breakdown by $current_url – so a pattern anchored with ^/ matches the first and not the second. Test a rule against a sample URL in project settings before you save it, or write one that handles both.
Examples
Here are common path cleaning patterns:
User IDs in URLs
To combine all user profile pages into a single path:
- Regex:
\/user\/\d+ - Alias:
/user/:id
This turns /user/123/profile and /user/456/profile into /user/:id/profile.
Product pages with slugs
To combine all product pages:
- Regex:
\/product\/[a-z0-9-]+ - Alias:
/product/:slug
This turns /product/blue-widget and /product/red-gadget into /product/:slug.
Merchant dashboards
To combine merchant-specific dashboards:
- Regex:
\/merchant\/\d+\/dashboard$ - Alias:
/merchant/dashboard
This turns https://example.com/merchant/12345/dashboard and https://example.com/merchant/6789/dashboard into https://example.com/merchant/dashboard.
Reusing part of the matched URL
Because only the matched portion is replaced, a fixed alias already handles a segment that varies. The /user/:id rule above cleans both /user/123/profile and /user/456/settings, because /profile and /settings sit outside the match and are left alone.
You need a capture group when the part you want to keep sits inside the match – when the regex has to reach past it to get to whatever you're removing. Wrap that part in parentheses and reference it from the alias with \1.
Group deep pages by their section
To collapse everything below the second path segment into one row per section:
- Regex:
^(/[^/]+/[^/]+)/.*$ - Alias:
\1
| Original path | Cleaned path |
|---|---|
/docs/product-analytics/trends | /docs/product-analytics |
/docs/product-analytics/funnels | /docs/product-analytics |
/docs/web-analytics/dashboard | /docs/web-analytics |
The regex has to run to the end of the path to match the part it's dropping, which means it also matches the two segments you want to keep. \1 puts them back. A fixed alias can't do this, because the text to keep is different on every row. Paths shorter than three segments don't match at all, so /docs is left as it is.
Keep the path, drop a trailing fragment
To strip an in-page fragment, but only from paths that are exactly three segments deep:
- Regex:
^(/[^/#]+/[^/#]+/[^/#]+)/#.*$ - Alias:
\1
| Original path | Cleaned path |
|---|---|
/reports/2024/summary/#revenue | /reports/2024/summary |
/reports/2024/summary/#costs | /reports/2024/summary |
/reports/2023/detail/#revenue | /reports/2023/detail |
Here the capture group does double duty: it keeps the path, and spelling out the three segments is what limits the rule to that shape. A rule that matched only /#... would strip fragments from every path on the site.
Matching both paths and full URLs
Both rules above are anchored with ^/, so they only match a path. Don't fix that by dropping the anchor – an unanchored pattern happily matches the hostname as if it were a path segment. The section rule without its ^ turns https://example.com/docs/product-analytics/trends into https://example.com/docs, because example.com gets counted as the first segment.
Make the scheme and host an optional group instead, and put it back with \1:
- Regex:
^(https?://[^/]+)?(/[^/]+/[^/]+)/.*$ - Alias:
\1\2
| Original URL | Cleaned URL |
|---|---|
/docs/product-analytics/trends | /docs/product-analytics |
https://example.com/docs/product-analytics/trends | https://example.com/docs/product-analytics |
When the value has no scheme and host, group 1 matches nothing and \1 substitutes an empty string, so one rule covers both.
Alias syntax
| Syntax | Meaning |
|---|---|
\1 to \9 | The first to ninth capture group, counted by opening parenthesis |
\0 | The whole matched string |
\\ | A literal backslash |
Everything else in the alias is treated as literal text. A few things to watch for:
$1does nothing. Aliases use\1, not the$1syntax you may know from JavaScript.$is always literal, so an alias of/user/$1cleans paths to the literal string/user/$1.- There's no
\10. Only\1to\9are capture groups.\10reads as group 1 followed by a literal0, so with a regex capturing123, an alias of/user/\10gives/user/1230. - The group has to exist. PostHog rejects a rule when you save it if the alias references a group that its regex doesn't define, such as
\2in a regex with only one group. - Double the backslash in JSON. In your project settings, type
\1. If you set rules through the API, JSON needs the backslash escaped:"alias": "/:locale\\1".
When to use capture groups
A capture group costs more per row than a fixed alias, because the regex engine has to track where each group starts and ends instead of only whether the pattern matched. It still pays off when it collapses several near-identical rules into one, since every rule runs against every row.
Anchor your regex where you can – ^ when you're matching a path, $ when the pattern runs to the end of the URL. Path cleaning replaces every match in the value, not just the first, so an unanchored pattern keeps scanning after it has found what you were looking for, and can match again somewhere you didn't intend.
Where path cleaning applies
Path cleaning rules apply to the following tiles on the web analytics dashboard:
- Top paths – pageview counts grouped by cleaned path
- Entry paths – first pageview of each session, grouped by cleaned path
- Exit paths – last pageview of each session, grouped by cleaned path
- Outbound clicks – external URLs users clicked to leave your site, grouped by cleaned URL
Path cleaning also applies when using trends insights or funnel insights with a URL or pathname breakdown.
Path cleaning also applies to funnel breakdowns by URL or pathname properties ($current_url, $pathname).
Rules are also applied by default to new Paths insights. When you create a new Paths insight, your global path cleaning rules are automatically applied. You can disable this by toggling off Apply global path URL cleaning in the insight settings if you prefer to see raw, uncleaned paths.