Automattic / Automattic/jetpack

Stats: click/download tracking misses multisite /files/ and custom uploads paths (only matches /wp-content/uploads)

Open
#49,478 1 comment 0 reactions 0 assignees View on GitHub
Bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.