Martez

Why your Matomo Campaigns report is empty (and why the URL says gad_source=1)

Two campaign-tracking symptoms come up constantly in Matomo 5.x: the Campaigns report stays empty even though tagged links get visits, and the visit log shows ?gad_source=1 instead of your UTM parameters. Neither is usually a bug. Here's the mechanism behind each and exactly how we fix them.

You tagged your links, the visits are clearly landing, and Acquisition → Campaigns is still empty. Or you open a visit in the log and the landing page reads /pricing?gad_source=1, with none of the utm_source and utm_campaign values you appended. Just that one stray parameter clinging to the URL. Both look like Matomo quietly threw your campaign data away.

It usually didn't. The empty report is almost always a tracking-side config problem you can find in about thirty seconds, and the gad_source=1 URL is two normal behaviours happening at once. Different problems, different fixes, so we'll take them one at a time.

TL;DR

If your Campaigns report is empty, open the matomo.php request in DevTools and read its url= value. If your utm_* params aren't in there, the problem is on the page or in the tracker, not in Matomo's reports: usually disableCampaignParameters in the snippet, setCustomUrl sending a bare path, or a utm_* param on an exclusion list. If they are in there, it's a Matomo-side setting. The gad_source=1 is a separate, harmless thing: a Google Ads parameter Matomo doesn't recognise, so it lingers in your stored URLs until you add it to the excluded parameters.

Symptom 1: the Campaigns report is empty

When tagged links get traffic but the Campaigns report stays blank, the campaign parameters aren't reaching Matomo's tracker. Before you touch any Matomo setting, confirm that in the browser. It tells you which side of the wire the problem is on.

Open your landing page with a real campaign URL, open DevTools (F12) → Network, and filter for matomo.php (or whatever your tracker endpoint is). Click the tracking request and look at the url= parameter it sent:

  • If url= is missing your utm_* / mtm_* params, the tracker never saw them. The problem is on the page, in the tracking code, or in something cleaning the URL before Matomo reads it.
  • If url= contains them but the report is still empty, the tracker is fine and the issue is on the Matomo side: a reporting or config setting.

That one check splits the whole problem in half. Here's what to look at on each side.

When the params are missing from url=

disableCampaignParameters is in your tracking code. Matomo 5.1 added disableCampaignParameters as a privacy switch. When it's present, the JavaScript tracker deliberately strips campaign parameters out of the request before sending it, so campaigns are never recorded:

js
// If this line is in your snippet, campaign params never reach Matomo.
_paq.push(['disableCampaignParameters']);

This is the single easiest cause to miss, because everything else in the snippet looks correct. Grep your tracking template and your tag manager for disableCampaignParameters and pull it out unless a privacy requirement genuinely needs it. There's no recovering campaign data for visits tracked while it was active, because the parameters never left the browser. Fix the code first, then move on.

setCustomUrl is dropping the query string. On SPA and React/Next/Vue sites, it's common to push the path by hand. If you send only the pathname, you throw the campaign params away before trackPageView fires:

js
// Wrong: discards ?utm_source=... entirely
_paq.push(['setCustomUrl', window.location.pathname]);
 
// Right: keeps the full URL, query string included
_paq.push(['setCustomUrl', window.location.href]);

Your campaign params are on the excluded list. This is Matomo's own first answer in the official "missing campaign links" FAQ, and it bites people who got aggressive with URL cleanup. Check two places: Administration → Websites → Manage → Edit → Excluded Parameters for the site, and the Matomo Tag Manager configuration variable's advanced "Set query params to exclude from URL" field if you track through MTM. If a utm_* param is sitting in either of those, Matomo strips it from the URL before it can be read as a campaign.

On self-hosted installs, check exclude_requests too, but know it's a different thing. The [Tracker] exclude_requests setting in config/config.ini.php doesn't clean up query parameters; it discards whole tracking requests that match an expression (for example url=@utm_). It's rarely the cause, but if someone wrote a rule that happens to match your campaign landing pages, those hits never get recorded at all, which looks identical to an empty Campaigns report.

Something upstream is cleaning the URL. A Cloudflare "Cache Everything" rule, a reverse-proxy rewrite that returns 200 while dropping the query string, or a consent-management script that "tidies" the URL before the tracker runs will all remove the params without leaving a trace in your code. If you self-host behind a proxy and see other oddities too, our note on reverse-proxy 502s and header handling covers the same class of "the proxy ate part of the request" problem.

When the params are present but the report is still empty

If url= already carries your utm_* values, the tracker is doing its job and the problem is on the Matomo side. Work through these roughly in order:

  • Right site and date range. The dull one that catches everybody at least once: the report is empty because the wrong site is selected, or the date range predates the campaign.
  • Archiving. Reports populate from archived data, so a recent campaign may not appear until an archive run completes. Trigger one before you conclude the data isn't there. If your archiving isn't running on a schedule at all, our cron archiving setup covers that.
  • Custom campaign parameter overrides. If someone changed which parameters Matomo treats as the campaign, the defaults quietly stop working. That can live in [Tracker] or [MarketingCampaignsReporting] config, or in the JavaScript via setCampaignNameKey / setCampaignKeywordKey. Make sure your links and Matomo agree on which parameter carries the campaign name and keyword.
  • Privacy or compliance mode. A CNIL-style compliance configuration can discard campaign values as they arrive. If you turned one on for legal reasons, an empty Campaigns report may be working as designed.
  • The Marketing Campaigns Reporting plugin, but only if you're missing the richer reports, not all campaign data. Matomo core already recognises utm_campaign, utm_source, and utm_medium for the campaign name and utm_term for the keyword. The free Marketing Campaigns Reporting plugin adds separate reports for campaign name, source, medium, keyword, content, and campaign ID, including utm_content and utm_id. It's a Marketplace install on self-hosted Matomo, not bundled with core, so confirm it's installed and active if the basic name and keyword data shows up but the channel breakdown doesn't.

This is the same "tracking fires but the report stays empty" situation we wrote up for ecommerce orders that don't show up. The request reaching the tracker and the report populating are two separate gates, and it helps to know which one you're stuck at before you start changing things.

Symptom 2: the URL shows gad_source=1 instead of your UTM params

This one isn't broken at all. It's two normal behaviours happening together.

Matomo strips recognised campaign params out of the stored page URL by design. Once utm_source, utm_campaign, and friends are digested into the Campaign dimension, Matomo drops them from the page URL it stores. That's intentional. It stops one page from fragmenting into dozens of variants (/pricing?utm_campaign=a, /pricing?utm_campaign=b, and so on) in your Pages report. Your attribution is fully intact at the visit level. The campaign is recorded; it's just no longer duplicated in the raw URL. This is expected behaviour, not data loss.

gad_source=1 comes from Google, and Matomo doesn't recognise it. Google Ads and Google's tags add their own parameters to ad-click landing URLs. gad_source is one of them; auto-tagging also adds the classic gclid click identifier, and you'll see gbraid or wbraid in specific measurement contexts. The detail that matters here is the same for all of them: none are Matomo campaign parameters, so Matomo never digests or strips them. That's why gad_source=1 is the one query param still hanging off the URL in your visit log. Nothing is removing your UTMs instead of showing gad_source. The UTMs were absorbed into the Campaign dimension, and gad_source just survived because nothing was looking for it.

  1. 1

    Landing URL

    utm_source, utm_campaign, gad_source

  2. 2

    Matomo tracker

    reads url= in matomo.php

recognised

Campaign dimension

source / medium / campaign

not recognised

Stays on the stored Page URL

/pricing?gad_source=1

One landing URL splits two ways: the UTM params become campaign data, the unrecognised Google param stays on the stored page URL.

So your campaign attribution still works. Check the Campaigns report and the visit-level referrer if you want to see it. The gad_source=1 is just unstripped Google noise sitting in your Pages report.

To get it out of Matomo's stored Page URLs and the Pages report, add it to your excluded parameters. This only changes what Matomo stores from now on. It doesn't rewrite the visitor's browser URL, touch your Google tags, or clean up URLs already sitting in your database:

text
gad_source
gclid
gbraid
wbraid

Per site, that's Administration → Websites → Manage → Edit → Excluded Parameters. To cover every site at once, use Administration → Websites → Settings → "Global list of Query URL parameters to exclude" and put one parameter per line. Either way, the rule only touches new traffic, so your historical Pages report won't tidy itself up.

If you run several properties, this is exactly the kind of setting that drifts out of sync between them. Our walkthrough of managing multiple sites, domains, and measurables goes into keeping per-site config aligned.

If you actually need per-campaign page detail

Some teams want to compare the same page across different campaigns, and the URL stripping seems to take that away. Don't fight it by un-stripping the params and re-polluting your Pages report. Use Segments instead, one per campaign name, plus the segment comparison toolbar in the report view. You get the per-campaign breakdown without splitting every page into a dozen URL variants. It's the clean version of what people are usually reaching for when they ask to keep the params on the URL.

What we'd actually do

Run the thirty-second DevTools check first, every time. Whether url= carries your campaign params decides everything downstream, and it stops you from changing Matomo settings to fix a problem that lives in the page. If the params are gone there, hunt the tracking side: disableCampaignParameters in the snippet, setCustomUrl sending a bare pathname, or a utm_* param sitting on an exclusion list.

For gad_source=1, add the Google parameters (gad_source, gclid, and friends) to your excluded list and leave the by-design UTM stripping alone. It's keeping your Pages report sane, not eating your data. And standardise on one prefix across all your links, either Matomo-native mtm_* or Google-style utm_*, so you're never debugging mixed attribution on top of everything else.

If JavaScript tracking keeps mangling your campaign URLs no matter what you do, whether that's a heavy CMP, a stubborn CDN, or an SPA you can't fully control, moving campaign attribution to the server-side HTTP Tracking API takes the browser out of the equation. It's more work, but the params arrive exactly as you send them.

Martez connects Matomo with Meta Ads and Google Ads so ROAS, CLV, and attribution sit next to your web analytics instead of in a separate spreadsheet. It's in private beta. Join the waitlist if that's relevant.

Your UTMs are only really missing if they're missing from the url= value sent to Matomo. If they're there, Matomo already pulled them into your campaign data, and gad_source is just an unrelated Google parameter waiting for a cleanup rule.