Switching from Google Analytics to Matomo (f.k.a. Piwik) on WordPress

It’s a new decade, time to leave Google Analytics.

A big part of me wants to say screw it, just get rid of analytics altogether. But I find it interesting. I’ve never used it to decide what to write, and I don’t think I ever will, but it’s just fascinating to find out what makes the rounds. I’ll never know why a short post about repairing my mom’s straw bag was my most popular post for years, but I’m glad to know a lot of people checked it out.

So I decided to keep my Google Analytics property in place and just locked it down as much as I could. I adjusted the script to respect users’ Do Not Track browser settings (Paul Fawkesley has a short article about how to do this). I also configured Google Analytics to anonymise IP addresses, and I deliberately disabled Data Collection for Advertising Features, Demographics and Interest Reports, User-ID, and all data-sharing settings. I also set a low data retention policy to make sure old data would get deleted.

None of this changed the fact that I was still sharing data with Google.

Because of that, “switch to Matomo” has been a long-standing item on my TODO list. Matomo, formerly known as Piwik, is a powerful web analytics platform that can be self-hosted. This means that all of the data can stay on your hosting / server. Matomo offers similar privacy controls as those listed above for Google Analytics, meaning that you can anonymise IP addresses and set an appropriate data retention policy. But you don’t have to worry about things like data collection for advertising since Matomo is not an advertiser.

Every other time I’ve tried to tackle the switch, I’ve ended up putting it down since I couldn’t face the task. It would have been fine, Matomo’s documentation is good, but it probably would have taken me a day to get it all done cleanly on my host (NFSN).

I had another poke around at it yesterday and behold, Matomo Analytics for WordPress. Looks like they released the beta version about three months ago.

Matomo Analytics for WordPress is a WordPress plugin that sets up Matomo in the same hosting instance as your website. It’s not quite one-click, but it’s pretty close. I’m really pleased with it so far.

Important note: If it is absolutely critical that you keep receiving accurate analytics for your site, it might be best to wait until Matomo Analytics for WordPress is out of beta. Or just complete Matomo’s standard installation.

Setting up Matomo Analytics for WordPress

To set up Matomo Analytics for WordPress, I followed the instructions from Matomo’s blog post and their related FAQs. This is just a bit of further detail based on my experience.

0. Check your backups setup

Matomo Analytics for WordPress keeps your analytics data in the same database as your website. Because of that – and because it is super important in general! – you better make sure you’ve got a good backup system in place. Personally, I use VaultPress for peace of mind.

1. Install the plugin

Currently, the Matomo Analytics for WordPress download link resides in a blog post. Download the plugin zip file and then install it by manually uploading the file in the WordPress admin area. Activate the plugin once it has installed.

2. Check the system report and fix any errors or warnings

Navigate to the System Report under the new Matomo Analytics menu item to ensure that Matomo is compatible with your site setup. You’ll want to fix any warnings or errors to ensure that it runs smoothly.

I ran in to two warnings and an error. My memory_limit and max_allowed_packet were under the recommended values, and zlip.output_compression was not turned off. To fix this, I tweaked my php.ini file to turn off zlip.output_compression and raise the memory_limit value. To adjust the max_allowed_packet value, I changed this setting in my host’s admin area.

Every host is a bit different, so others may find that they have to make the necessary adjustments elsewhere. When in doubt, get in touch with your hosting provider.

3. Adjust the plugin settings

Under Matomo Analytics → Settings, check that the configuration is as you would prefer. For me, that meant excluding all users from tracking and excluding my own IP address.

On Google Analytics, I used to have a lot of trouble with referrer spam and set up a pretty lengthy filter to keep them out. With Matomo, referrer spam is automatically kept out of reports via blacklisting according to one of their blogposts on the subject. It looks like I shouldn’t have to worry about spam as long as I keep Matomo up-to-date.

4. Adjust Matomo settings

In the full Matomo reporting dashboard (see Matomo Analytics → Reporting), click the cog in the upper-right corner to access Matomo settings. Check that the configurations are as you prefer.

My adjustments were almost entirely privacy-related, specifically:

  • Anonymise the last two bytes of visitors’ IP addresses
  • Use the anonymised IP instead of the full IP for enriched data
  • Replace User IDs with hashed pseudonyms
  • Delete raw data older than 180 days old, scheduled once a week

Since my Matomo setup is self-hosted, that last point is critical to help me manage the size of my database.

5. Adjust privacy notice and privacy policy

Make sure your privacy notice and privacy policy reflect the changes you’re making. I’m not going to go in to detail on this since everyone’s specifics are a bit different, but I will point out Matomo’s useful privacy notice and GDPR compliance articles.

I moved the privacy notice from a sticky footer to the menu due to the way that my theme is configured, and I adjusted my privacy policy to remove Google Analytics and add information about Matomo and its cookies.

6. Remove Google Analytics and enable Matomo

To remove Google Analytics tracking, you have to remove the GA script that is usually at the top or bottom of every page on your site. To enable Matomo within the WordPress plugin, you have to adjust the settings under the Tracking tab in Matomo Analytics → Settings.

I decided not to use Matomo’s default tracking code because I wanted it to do a little more. This is the tracking code I wrote to ensure that Do Not Track browser settings are respected and to make sure that each infinite scroll page load is tracked correctly. This was added in the plugin admin area, positioned in the footer.

const dnt = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
let _paq = window._paq || [];

// If Do Not Track is not enabled
if (dnt != "1" && dnt != "yes") {

  // Set up the default tracking info
  _paq.push(["trackPageView"]);
  _paq.push(["enableLinkTracking"]);
  _paq.push([
    "setTrackerUrl",
    "//piperhaywood.com/wp-content/plugins/matomo/app/matomo.php"
  ]);
  _paq.push(["setSiteId", "1"]);
  const d = document,
    g = d.createElement("script"),
    s = d.getElementsByTagName("script")[0];
  g.type = "text/javascript";
  g.async = true;
  g.defer = true;
  g.src = "//piperhaywood.com/wp-content/uploads/matomo/matomo.js";
  s.parentNode.insertBefore(g, s);

  // Register each append of infinite scroll
  if (typeof infScroll !== "undefined") {
    infScroll.on("append", function(response, path, items) {
      _paq.push(["setCustomUrl", path]);
      _paq.push(["setDocumentTitle", response.title]);
      _paq.push(["trackPageView"]);
    });
  }

} else {

  // If DNT is enabled, opt the user out
  _paq.push(["optUserOut"]);
}

7. Add an opt-out for users

Matomo’s WordPress plugin comes with an opt-out shortcode. This lets website administrators easily insert an automated opt-out form anywhere that WordPress shortcodes are supported on their site, for example in a footer widget.

It’s admirable that they’ve included this option, but the downside is that it is pretty limited. The texts aren’t editable, though they do have an issue open to use the WordPress translations system which would allow text editing. Also, the shortcode opt-out message is displayed in an iframe that has space all the way around it, preventing it from inheriting the site styles. They have an issue open about this as well, so it may change in the future.

Since this is a message you see on every single page, I want it to look exactly the way it should. This means straight quotes instead of curly quotes, messaging that is in keeping with the tone of my website, and styles that are appropriate for the placement of the opt-out message.

Instead of using the out-of-the-box opt-out shortcode, I made a custom opt-out system with HTML, CSS, and JavaScript.

The HTML below went in to the widget that is shown at the base of my menu. This HTML includes all three messages for opted-in, opted-out, and Do Not Track viewers.

<div id="matomo" class="matomo">

  <div class="matomo__opted-in">
    <p>Matomo analytics tracking is enabled. If you would prefer not to be included and want to opt-out of Matomo, please click the link below.</p>
    <a href="#" class="matomo__button matomo__button--opt-out">Disable tracking</a>
  </div>

  <div class="matomo__opted-out">
    <p>Matomo analytics tracking is disabled. If you are willing to be included, please click the link below.</p>
    <a href="#" class="matomo__button matomo__button--opt-in">Allow tracking</a>
  </div>

  <div class="matomo__dnt">
    <p>Matomo analytics tracking is disabled since the “Do Not Track” feature is enabled in your browser settings.</p>
  </div>

</div>

I then used WordPress’s Customiser to add the necessary CSS that would hide and show the appropriate texts.

.matomo {
	display: none;
}

.matomo--enabled {
	display: block;
}

.matomo__opted-in,
.matomo__opted-out,
.matomo__dnt {
  display: none;
}

.matomo--enabled.matomo--opted-in .matomo__opted-in {
  display: block;
}

.matomo--enabled.matomo--opted-out .matomo__opted-out {
  display: block;
}

.matomo--enabled.matomo--dnt .matomo__dnt {
  display: block;
}

And finally, I adjusted the analytics script to add and remove the necessary classes on click.

function setOptOutText(elem) {
  _paq.push([
    function() {
      if (this.isUserOptedOut()) {
        elem.classList.remove("matomo--opted-in");
        elem.classList.add("matomo--opted-out");
      } else {
        elem.classList.add("matomo--opted-in");
        elem.classList.remove("matomo--opted-out");
      }
    }
  ]);
}

const elem = document.querySelector("#matomo");
const dnt = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
let _paq = window._paq || [];

// If Do Not Track is not enabled
if (dnt != "1" && dnt != "yes") {

  // Set up the default tracking info (see plugin to get the appropriate code)
  _paq.push(["trackPageView"]);
  _paq.push(["enableLinkTracking"]);
  _paq.push([
    "setTrackerUrl",
    "//piperhaywood.com/wp-content/plugins/matomo/app/matomo.php"
  ]);
  _paq.push(["setSiteId", "1"]);
  const d = document,
    g = d.createElement("script"),
    s = d.getElementsByTagName("script")[0];
  g.type = "text/javascript";
  g.async = true;
  g.defer = true;
  g.src = "//piperhaywood.com/wp-content/uploads/matomo/matomo.js";
  s.parentNode.insertBefore(g, s);

  // Set up event listeners for the opt in/out links
  const optOut = document.querySelector(".matomo__button--opt-out");
  optOut.addEventListener("click", function(e) {
    e.preventDefault();
    _paq.push(["optUserOut"]);
    setOptOutText(elem);
  });
  const optIn = document.querySelector(".matomo__button--opt-in");
  optIn.addEventListener("click", function(e) {
    e.preventDefault();
    _paq.push(["forgetUserOptOut"]);
    setOptOutText(elem);
  });

  // Register each append of infinite scroll
  if (typeof infScroll !== "undefined") {
    infScroll.on("append", function(response, path, items) {
      _paq.push(["setCustomUrl", path]);
      _paq.push(["setDocumentTitle", response.title]);
      _paq.push(["trackPageView"]);
    });
  }

  // Set text
  setOptOutText(elem);

} else {

  // If DNT is enabled, opt the user out
  _paq.push(["optUserOut"]);

  // Set the DNT class to show the DNT message
  elem.classList.add("matomo--dnt");
}

// Show the Matomo message area
elem.classList.add("matomo--enabled");

Note that you could integrate this sort of thing directly in to a WordPress theme, but I opted to use the WordPress Customiser and the Matomo plugin to implement it so that I keep personal stuff out of my theme.

Import old Google Analytics data using Matomo’s Google Analytics Importer for WordPress

I want to completely get rid of Google Analytics, so this is a necessary TODO. Setting up Matomo’s Google Analytics Importer is a bit more complicated than setting up Matomo for WordPress since it involves setting up OAuth credentials to access a few Google APIs. As of right now, the tool imports the data in to a separate Matomo site (see related issue on GitHub).

Matomo’s documentation on how to import Google Analytics data is pretty strong. This is a summarised outline of the steps:

  1. Install Google Analytics Importer plugin (in WordPress, see Matomo Analytics → Marketplace)
  2. Check the system report to ensure that the hosting is compatible (should not need to set up cron archiving since this uses the WordPress cronjob)
  3. Import the data using the Matomo admin or the command line
  4. Anonymise the imported data
  5. Cleanup; delete the data from Google and make sure there aren’t any traces of GA-related scripts or CSS on the site

I gave this a try and got hung up on step 3, the data import. I tried importing via the admin page, but the import job indicated that it was permanently hung up. I cancelled the job and then tried importing via the command line and got an HTML response saying (in part):

<h4>Your access to this site has been limited</h4>
<p>Your access to this service has been temporarily limited. Please try again in a few minutes. (HTTP response code 503)</p>
<p>Reason: <span style="color: #c10000;">Manual block by administrator</span></p>

Something related to the security plugin Wordfence is blocking the import, likelyl some firewall settings. I tried a bunch of different techniques to whitelist IPs and to try to identify the reason for the block, but I couldn’t figure it out. I could just disable Wordfence and give it a shot but I’m very wary of doing that…

I think I’m just going to try the import locally with Wordfence disabled, and then if it works I’ll just import that database. We’ll see.


I’m going to keep an eye on this setup since Matomo Analytics for WordPress is still in beta, but I’ve got high hopes! If nothing else, I’ve taken a big step in the right direction.