Automattic / Automattic/jetpack
Stats: click/download tracking misses multisite /files/ and custom uploads paths (only matches /wp-content/uploads)
- Dominant language
- PHP
- Stars
- 1.8k
- Forks
- 898
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 774
Description
### Impacted plugin
Jetpack
### Quick summary
> < />!NOTE< />
**AI-assisted investigation, reviewed before posting.** Findings below were produced by an AI agent (Claude) querying a live Jetpack-connected site's Stats and reading the `automattic/jetpack-stats` tracker source. Reviewed before filing; please sanity-check the specifics.
### Summary
The Stats client-side **click tracker only treats same-host links as trackable file/download clicks when the URL contains the literal string** `/wp-content/uploads`. Sites that relocate their uploads directory — most notably **WordPress multisite installs that serve uploads from** `/files/` (the `ms_files_rewriting` mechanism), and any site using a custom `UPLOADS` **constant** or `upload_path` **/** `upload_url_path` option — therefore get **no click/download tracking for their media files at all**. Internal navigation is (correctly) ignored, but so are genuine file downloads, because they don't match the hardcoded path.
### Where the behavior lives
`Tracking_Pixel::enqueue_stats_script()` (package `automattic/jetpack-stats`) enqueues the WP.com-served tracker `https://stats.wp.com/e-{YEARWEEK}.js` and bootstraps it with `_stq.push(["clickTrackerInit", blog, post])`. The tracker's click handler contains:
```js
// a(e) === true when the link is same-host
if ( a(e) && !e.href.includes("/wp-content/uploads") ) return; // not tracked
...
window._stq.push(["click", { s:"2", u:e.href, r:..., b:blog, p:post }]); // c.gif beacon
```
So the heuristic is: *track same-host links only if they look like a default-location upload.* The path is hardcoded to `/wp-content/uploads` and does not account for relocated uploads.
### Steps to reproduce
### Steps to reproduce
1. Use a Jetpack-connected **multisite** install (or any site with relocated uploads), where attachment URLs resolve to `https://example.org/files/…` rather than `…/wp-content/uploads/…`.
2. Add a normal download link to a media file, e.g. `Download`.
3. Click it repeatedly from the front end (logged out).
4. Check **Stats → Clicks** (or query `WPCOM_Stats::get_clicks()`).
### What I expected
The `/files/…/kit.zip` clicks to be recorded, the same way `/wp-content/uploads/…/kit.zip` clicks are on a single-site default install.
### What actually happens
Nothing is recorded for the `/files/` link — the same-host guard short-circuits because the URL doesn't contain `/wp-content/uploads`. The download is invisible to Stats.
### Site owner impact
Fewer than 20% of the total website/platform users
### Severity
Minor
### What other impact(s) does this issue have?
No revenue impact
### If a workaround is available, please outline it here.
### Real-world evidence
On a live Jetpack-connected multisite (blog serves uploads from `/files/`):
* 7 existing PDF attachments (`/files/2020/…`–`/files/2022/…`) appear in **no** Clicks report across day/week/month/year windows.
* Confirmed same-host non-file URLs *do* otherwise surface in the Clicks report, so the report itself works — it's specifically the upload-path match that fails.
* (Separately, the dedicated **File Downloads** report is unavailable on non-Simple sites — see Related — so Clicks is the only available channel, which makes this gap more impactful.)
### Impact
Any multisite or relocated-uploads Jetpack site silently loses **all** automatic download/media-click tracking. WordPress multisite (`/files/`) is a first-class, officially supported configuration, so this is not an exotic edge case. There is no front-end workaround via the documented API; the only mitigation is manually pushing `_stq.push(["click", {…}])` per link, which bypasses the guard but requires custom JS on every download link.
### Suggested fix
Don't hardcode `/wp-content/uploads`. Derive the site's actual uploads path server-side (e.g. the path component of `wp_get_upload_dir()['baseurl']`) and pass it into the tracker config so the click handler can match the real upload location. Concretely, `clickTrackerInit` (or the stats array) could carry an `uploads_path` (or a small list, to cover both the default and a relocated path), and the guard would test against that instead of a fixed string. This keeps internal-navigation links untracked while correctly capturing file downloads regardless of where uploads live.
### Related
* [Automattic/jetpack#17172]() — "Jetpack Stats does not track outbound link clicks on AMP pages" (sibling click-tracker coverage gap).
* Automattic/wp-calypso#36197 — "Stats: File Downloads section not present on atomic sites" (the dedicated File Downloads report is Simple-only, which is why Clicks is the only channel here).
* [https://github.com/WordPress/Learn/pull/3496]()
### Environment
* Jetpack Stats package: `automattic/jetpack-stats` (`Tracking_Pixel`), tracker `stats.wp.com/e-*.js`.
* Site type: Jetpack-connected WordPress **multisite**, uploads served from `/files/`.
### Platform (Simple and/or Atomic)
Self-hosted
Contributor guide
Research direction
Start at Tracking_Pixel::enqueue_stats_script() in the automattic/jetpack-stats package, then inspect the WP.com-served e-*.js tracker around clickTrackerInit and its /wp-content/uploads guard. Verify how the uploads URL can be passed into the tracker without losing same-host filtering. Done means relocated /files/ and custom upload links are recorded while ordinary internal navigation remains untracked; no test file is named in the issue.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, php, wordpress
- Domain
- analytics, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100