Knowing how users interact with your app is essential for making educated product decisions. But with user privacy being a foremost concern, big tech analytics frameworks are out of the picture. So the question is: can Matomo's open source user analytics be a viable alternative?

Building an app as an indie developer often feels like a guessing game. You are building something based on your own ideas and mental models, but the app’s users might think totally different. Bridging that gap is what enables good product decisions, yet most of what you can do after release is to hope for the best and actively try to get feedback.

Direct user feedback is incredibly valuable and offers a look at the product through the user’s eyes, but is also entirely subjective and best supported by hard evidence – actual data about how users interact with the app. So the real qestion: how do you get that data?

User Analytics Frameworks

Google’s definition of a ‘free’ service

Both of my iOS apps (Timist, a Pomodoro-inspired time tracker and Leap Habits, a habit tracker) are entirely ‘local’ apps, with all data only persisted on device. That is a very intentional decision: I don’t feel comfortable implementing something like Google’s Firebase as a cloud storage and handing all of my user’s data over to Google, and I also don’t (yet) really want to carry the burden of setting up my own servers to handle user data. The apps work great locally and I’d love to consider a privacy-conscious sync solution like Apple’s CloudKit in the future.

That also means I have no information about how users are actually using my apps. So with actual user (content) data out of the picture, I started looking into analytics frameworks. Almost every major player offers its own:

  • Microsoft has Azure App Insights
  • Google has Google Analytics for Firebase
  • Yahoo has Flurry
  • Amazon has AWS Pinpoint
  • Facebook has Facebook Analytics

The fact that most of these are free means these solutions are probably either data-generation or customer-acquisition machines for their parent companies. I cannot (with good conscience) tell my users that I care about privacy and then implement SDKs from Facebook or Yahoo. All of these store data on their servers and can do whatever they wish with it. I’m also not a fan of integrating giant (closed source) SDKs in my apps. Who knows what they are doing in the background, and even without malicious intent, Facebook’s SDK alone has managed to crash half of the App Store’s apps twice this year.

Which leaves me with a range of other private companies and a few open source solutions. The SaaS-solutions get expensive really quickly and still own all of your data. Since I like to do these things myself, Matomo (formerly Piwik) seemed like the most promising alternative: open-source, completely free when self-hosted, with a thriving community and open-source SDKs for all major platforms.

Matomo comes with a lot of built-in reports, good-looking dashboards, and stores all of its data on a database of your choosing. The one downside: like its competitors, Matomo is primarily a web analytics framework. When using it as a mobile analytics solution, it still delivers most of the value, but some metrics are simply not available by default (Matomo cannot segment data by users) while others can simply be ignored (SEO, acquisition and where your users come from are important metrics for the web, but on iOS all users are ‘direct entry’ - they open the app).

Overall, Matomo felt like the best bet. I’ve been running Matomo as my main user analytics framework since January 2020 and I’m loving it. Watching the real-time feed is kind of addicting (so much so that I had to remove Matomo from my phone’s main homescreen), I gained some great insights into how my apps are being used and was able to make more informed decisions. The major redesign of Timist earlier this year was heavily influenced by the data I got from Matomo.

So, this post is about what I’ve learned so far: installation and hosting, integrating Matomo into your app, what and how to track, using Matomo’s dashboards and all the other stuff. Let’s get started.

Installation and Hosting

Enterprise Cloud-Hosting vs. a thirty minute setup on AWS

Matomo (formerly ‘Piwik’) is a PHP-based web app, that is completely open source and free to use when self-hosted. ‘Enterprise’-editions with additional features and a cloud hosted service keep the lights on, with prices starting at 19 EUR/month (although I would already be in the ~50 EUR/mo tier with my usage). I went the self-hosted route, since A) it’s a lot cheaper and B) I can customize it to my liking and make sure none of the data is available to any third parties. I also liked the challenge of setting it up myself, since running servers is something I’m not inherently familiar with.

I first started on an Azure VPS before moving all of my stuff over to AWS (for reasons that had nothing to do with the Matomo instance). In terms of sizing, Matomo works great with the smaller instances on both Azure and AWS: 1 vCPU and 1 GB of RAM and Matomo runs without breaking a sweat for my current usage, which amounts to roughly 100k pageviews or 10k visits in a month. On Azure, Matomo lived on a B1S VPS (~USD7/mo) and currently it runs on an AWS Lightsail VPS (~USD5/mo). That is with both MySQL and Matomo operating on the same VPS. At some point, I might move the database to a separate instance, but for now - it just works and the data is not mission critical.

Since all of the smaller VPS’ on Azure and AWS are ‘burstable’, meaning they can only run at high load for a short period of time, I’d err on the larger VPS size. Matomo has a fairly low base-load when simply receiving data, but spins up considerably when crunching the raw data and generating reports. I’ve had it run into memory limits a few times on the 512MB RAM instances while generating reports for timeframes of 3 months or more. You might not even get through set-up on the smallest instances, since you might burn all of your ‘burst’-credits during installation. Your mileage may vary, but I would consider 1vCPU + 1GB RAM as a good starting point, which can then easily be scaled as needed.

Server load when running Matomo on an AWS Lightsail server.
Server load for my Matomo instance, running on a 1vCPU Lightsail server. It rarely ever runs into the burstable zone, so it should have some headroom for future growth.

There are plenty of great tutorials for setting up Matomo on Ubuntu, so here’s the gist of it:

  1. Create new VPS with Ubuntu 18.04
  2. The usual apt update/upgrade dance
  3. Install MySQL and PHP
  4. Install Nginx, Certbot and set up HTTPS (make sure to open Port 443 in your VPS configuration)
  5. Download Matomo with wget and copy it to the target directory
  6. Configure Nginx for Matomo
  7. Log into MySQL, create a database and a user for Matomo
  8. Go to you brand new Matomo instance and finish the installation in you browser
  9. Create your first property to track in Matomo and implement the SDK in your app - and you’re done.

Matomo supports multiple properties and I have all of my websites and iOS apps reporting to the same instance. Like Google Analytics, installing the tracker in web properties is as simple as copying the necessary HTML and scripts into <head>. For iOS apps, a bit more setup is required.

Integrating Matomo into an iOS app

Singletons make the app go round

Matomo has SDKs for most major platforms and iOS is no exception. I have no personal experience with any SDK other than the one for iOS, but all seem to have fairly active contributors and the iOS SDK feels very polished.

On iOS, the SDK comes with one main class MatomoTracker, which can be passed around the app and is used to track all major interactions: page views, events, goals and e-commerce. There’s also search and campaign tracking, although I’ve never found these to be necessary for my particular usage. In addition to the interactions, the MatomoTracker can be configured with a custom user ID and multiple custom dimensions, which are tracked for every visit.

I decided not to pass the main MatomoTracker class around my app and building a wrapper TrackerManager class as an abstraction layer. It’s good practice not to integrate third-party code to tightly, since it allows me to easily switch out Matomo for any other tracking framework, and secondly, it enabled me to build a bunch of convenience functions and predefined values around it. For example, tracking goals in Matomo requires passing a numeric goal ID, which I substituted with a custom function that simply takes an enum and allows me to use named goals.

My custom TrackerManager does the following:

  1. Initializes the MatomoTracker with a custom User ID (a UUID String saved to UserDefaults), setting a range of custom dimensions (device type, region, app version) and configuring it based on the user’s privacy settings (full tracking vs. heartbeat)
  2. Tracking pageviews with the page name
  3. Tracking custom events with category, action, name and numeric value (using predefined enums for categories and actions)
  4. Tracking purchases by passing a custom SKProduct abstraction
  5. Tracking goals (using a goal enum)
  6. Firing off a manual dispatch of recorded interactions if the app is moving to the background or being terminated (triggered from SceneDelegate)

Another advantage of a custom TrackerManager is that I can integrate custom opt-out handling: if the user opts out of analytics, I don’t record any of the user’s interaction and only fire off a single “heartbeat” pageview per session. All views and events are still reported to the TrackerManager in the app, but only the first event triggers a dispatch with a “heartbeat” that actually leaves the device. That way I still get accurate numbers for DAU/MAU, retention, goal conversion and custom dimensions (e.g. app version) while respecting the user’s choice.

The TrackerManager is then exposed as a singleton within the app (alternatively with dependency injection, if that’s your style). You’re now ready to track user behavior. But what to track?

Users, Views, Events, Goals and what else to track

Track all the things (but don’t be creepy)

When looking at different user analytics frameworks, there were three main properties I was interested in: user flow (how are users navigating through the app), user behavior (how are users interacting with the main features of the app) and user conversion (how do users get through the “funnel”, from initial launch to first meaningful interaction to purchase). Closely related to conversion is also purchase behavior (what are users buying), but with all of my apps currently centered around one main purchase this was more of an afterthought.

In addition to these three, there is also an aspect of general usage information. How many users does the app have, what device and app version are they using and so forth. I’ll go through all of them in detail:

User Flow

There and back again

Depending on how an app is set up, there are plenty of ways to navigate within. These are some fundamental questions that might be of interest:

  • What screens/views do users spend the most/least time in?
  • What screens/views do users visit the most/least?
  • Which way through the app do users take to arrive at a given destination?
  • On which screen/view do users leave the app (moving it to the background)?

Depending on the app’s structure and navigation stack, there are a variety of additional metrics, especially if the app is providing external content from one or multiple sources. The way Matomo implements this is by tracking pageviews with page URLs, the way a web analytics framework would track pages (e.g. $rootURL/posts/this-post/). For an iOS app, that translates to $bundleIdentifier/path/to/page, which is reported to the MatomoTracker as an array of strings. Using paths allows for a detailed reproduction of an apps navigation stack within Matomo.

For Leap Habits, my habit tracking iOS app, I implemented the TabBarController’s tabs as root pages and then follow those with subpages down the navigation stack. Every push or modal segue that goes further down gets added as an additional subpage to the pages array. Once a view is loaded (or appears, if it is not removed from the navigation stack after disappearance), it reports its current page stack to the TrackerManager 1.

Matomo page paths for iOS app.
In Leap Habits, the tab bar is the navigation root, from which every segue goes further down the navigation stack. The entire stack is reported to the Matomo SDK as an array of strings.

Matomo aggregates these for a timeframe of your choosing and groups the hierarchy based on the paths you have set. Most reports are centered around pageviews, with unique pageviews (only counted once per visit) and average time on page/view available as well. This report can also be generated without grouping if the hierarchy is not necessary.

Matomo pageviews report
If hierarchical pages are used, Matomo displays all views for the root page (e.g. 'Habits' in the fifth row) as well as grouped entries for all of its subpages (e.g. 'Habits' expanded in the first row with 'Detail', which then itself also has subpages).

Matomo offers a bunch of other reports in the pageviews category, most of which are web-focused. Entry pages and exit pages (first and last page of a visit) can be interesting, but depending on how you handle app state on termination and relaunch won’t deliver any revelations. One use case can be to check how often an onboarding screen becomes an exit page (whenever a user leaves the app during onboarding). Reports like Outlinks and Downloads simply won’t show anything, because there is nothing to track inside the app.

One final page-based report that I’ve found to be very valuable is Transitions, which shows all the incoming and outgoing transitions for any view. There’s also a full-featured user flow add-on available (starting at 79 EUR/year), which displays the entire navigation flow in a Sankey diagram, but I haven’t used it yet because the built-in transitions worked well for me.

Matomo transitions report
Transitions to and from the main 'Habits' screen in Leap Habits. Almost half are 'direct entry', since it's the first screen loaded on app launch.

Matomo’s page tracking and its reports deliver valuable information about how users move around in the app, and where they spend their time, all while respecting their privacy. It’s easy to get a good picture of user flow and make educated decisions on what might need additional work. The next step after user flow is user behavior: how are users interacting with the app’s main features?

User Behavior

And I thought that one feature was the most important

Depending on what your app is and what it does, capturing how users interact with its main features can deliver some fundamental insights. The way to do this is with events, Matomo’s second major entity after pageviews. Events can be defined, grouped and triggered by actions in the app and come with four properties: category (every event belongs to one), action (an event’s main identifier), name (a sub-identifier or configuration for an action) and value (a numeric value for any event).

How do these translate in practice? For Timist, my time-tracking-meets-Pomodoro app for iOS, users can create timers, for each of which time can be tracked in sessions. These sessions, depending on the timer’s properties, are either running until stopped manually, or alternate between Pomodoro-themed longer work and shorter break intervals.

Since Timist, like Leap Habits, persists all data locally on device, I have no information on how users are actually using these features. To give me a rough idea, I’ve implemented four categories of events in Timist: timer, session, tag (timers can be organized with tags) and app (everything else). Each of these categories have corresponding actions, e.g. timers can be created and deleted, sessions can be started, stopped, edited and so forth. In the app category, I track events like changing to alternate icons or the review prompt being triggered.

Deconstructing Timist's events for reporting
Matomo's events in Timist, deconstructed into category-action-name-value.

The actions are then configured with more detail using an event’s name property: a session-start action can be either infinite (a regular session without an end time), a work or break interval, depending on which kind of session is started. Using different names for session-start actions lets Matomo group these as different start-actions in the session-category, so I can figure out if and how the Pomodoro system is used. In addition the session’s length is set as the event’s value.

Matomo events report
The Timist sessions-category in Matomo, expanded to show the corresponding actions. Shown are total events and event value, which is the session length in minutes.

Matomo displays events depending on their category, action or name, and allows you to group them along a secondary dimension. Reports are centered around the event count as a total sum or a chart with development over time, as well as the sum/min/max/avg of the optional event value.

Matomo events report showing actions
The session-start action in Timist with its grouped event names as 'sub-actions'. Most people are using regular timers, the work intervals are roughly 40 minutes and the break interval 6.5 minutes.

Other events in the app are tracked in a similar fashion, and the “category > action > name + value”-system allows for a lot of flexibility. Since events don’t have to be predefined in Matomo, but can simply be named and triggered from the tracking client, it was fairly easy for example to add tracking for tags or alternate icons (app > alternateIcons > $iconName). An advantage of using a custom wrapper class is that you’ll be able to have custom functions for event tracking that take action/category/name enums as parameters. That way you can use Xcode’s autocompletion when adding an event somewhere in the app, prevent typos for identifiers, and can keep all tracking-related concerns in the TrackerManager.

So that’s events and user behavior. The last major dimension in Matomo is goals and focuses on user conversion.

Goals and Conversion

All the way through the funnel

Conversion is getting users through major milestones in their user journey: all the way from first discovery of the app to becoming a paying, long-term, customer. I’ve written a bit about a mobile app funnel here, along with the struggles that Timist had with conversion and user retention. Quoting from that post, a funnel for mobile apps might look roughly like this:

  1. Problem (users need to have a problem/need that they want to have addressed)
  2. Discovery (the product needs to show up in place where they might look for a solution, e.g. App Store search or Google)
  3. Download (an app enticing enough to be downloaded)
  4. First launch (a surprising amount of users never get from download to first launch)
  5. First meaningful interaction (trying out the apps main feature, e.g. adding and starting a timer for Timist)
  6. Returning user (using the app more than a couple of times)
  7. Experienced user (one who understands the app and its value proposition well enough to even evaluate an upgrade or a purchase)
  8. Paying customer (user who had a need that required a purchase and actually spent the money)

Funnels will look different depending on the app and its business model, but for the purpose of this post, and tracking conversion with Matomo, this one should serve as a useful framework. The first three steps are well outside of being trackable with Matomo, so the focus is on the latter five stages (four to eight and fist launch to paying customer).

Within these five stages can be plenty of potential goals. Every potential goal also comes with a potential exit, so the main reason to track goals in the first place is to get an understanding of how far users get through the funnel, and to develop and trial optimizations to help them get further.

So, which goals to track? Anything that represents a user’s step further into their user journey and is likely bringing them closer to becoming a paying and long-term customer. For example, here’s a selection of goals I’ve used for Timist in stages 4-8:

  1. First launch: first launch of the app, completion of onboarding, granted access to system permissions (e.g. notifications), …
  2. First meaningful interaction: first timer created, first session started, first review of data in analytics, …
  3. Returning user: user has returned (second visit), certain number of actions or visits, App Store review request triggered, …
  4. Experienced user: 30+ number of visits and actions, use of "power user"-features (e.g. in Timist: tags, advanced session system, export), ...
  5. Paying customer: started trial, purchased subscription or IAP, …

The way to track these with Matomo is through goals, each of which can be triggered from the client. All of these are also trackable with pageviews and events, but goals are listed as a separate entity specifically for this purpose. You can create a goal in the Matomo web interface, either based on a condition (which is mostly useless, as I’ll soon discuss) or to be triggered manually by the client (which is what you’ll probably need for mobile analytics). Every goal comes with a numeric id, which I mapped to an enum:int in my custom TrackerManager.

This is also where using a custom TrackerManager instead of the SKD’s native MatomoTracker comes with additional benefits — and where Matomo’s web heritage definitely is an issue. For Matomo, goal conversion is linked to visits, meaning any goal can be converted once or multiple times per visit. That might make sense on the web, but for an app, goal conversion is (mostly) user-based and not visit-based. So for a lot of goals, you’ll need to persist goal conversion locally to make sure it’ll only trigger once (e.g. save the first completion of a goal to UserDefaults and only send it to Matomo the first time, e.g. for creating a first timer in Timist).

The visit-based goal conversion unfortunately also makes Matomo’s analytics less useful in that regard. A goal report will always compare the goal completions to all other visits without a completion for that specific goal. For a ‘Completed Onboarding’ goal, that conversion rate is a fairly useless statistic, since the question that really matters is what percentage of users (total or new users) have completed that goal. Total completions are still quite insightful however, and the facts that 1) all the data is in your database and 2) can always be exported directly from the Matomo dashboard means all relative goal conversions require is exporting and doing some Excel analysis.

Matomo goal conversion
Goal conversion for Leap Habits 'Completed Onboarding' goal for two weeks this Summer. The conversion rate is not really useful here, but the totals are still a good proxy, and for detailed analysis the data can always be exported.

Another interesting report that Matomo offers is the number of days and visits until a goal is first converted. Especially for goals in the later stages, this can be a quite insightful. How long it takes on average for a user to first use one of the advanced operations (e.g. exporting data to CSV) or to discover less prominent features can be a good indicator how well the app is educating its users.

So that’s goals. What else?

General Usage and Custom Variables

Hello, I’m looking for up and to the right

As an indie developer, I’m not really interested hockey stick growth and would much rather build something meaningful over time. Still, I prefer my usage graphs to be of the up-and-to-the-right persuasion instead of hover-and-decline.
So… how do I know how I’m doing?

For all of its shortcomings, App Store Connect really does provide me with most of what I need to know: are new people discovering the app (downloads), are they actually using it (active devices and retention) and do some of them find it useful enough to part with their money (sales) - all of that is already covered.

I like looking at numbers and statistics probably a bit too much though, so the fact that Matomo also gives you a general idea of Daily Active Users (DAU) and Monthly Active Users (MAU) is a nice bonus. The core metric in Matomo is a visit, so aggregated visit counts for days and months are comparable to total daily and monthly sessions. Since the TrackerManager is initialized with a locally persisted and consistent userID, Matomo can also group visit data by users, which enables reports that are akin to DAU/MAU statistics.

Matomo visitor count showing daily active users
Matomo's visitor reports with vists and users enabled. The result is akin to daily active users in Leap Habits for 30 days. Still cannot believe I got featured in the US App Store 🥳.

Another feature in Matomo is custom variables, which allows you to transmit additional information with every visit. These can be used to transmit any String and I’ve been using it for things like device type or the current app version. The latter is a critical for segmenting visit data (another neat feature to conditionally segment visit data by Matomo properties like visits or actions, including custom variables) by app version after a significant update to analyze its effects2.

Displaying iOS app version in Matomo with custom variables
Visits by app version for Leap Habits in Matomo. Again, I'd much rather see these segmented by users, but as far as I know that's not possible at the moment. Also in this shot: 2020.6.1, the version of shame. Because apparently someone shipped 2020.6 without proper testing (it was me).

Since early 2020, when I fist started using Matomo, it has gotten a decent amount of meaningful updates. For example, in Timist, the first app that I tracked with Matomo, I implemented the device type as a separate custom variable, both because it is a good indicator for screen size and respective design decisions, but also because I’m just curious. Some time in the last months though, the Matomo folks updated its native device reporting to be a lot more accurate. So that’s what I use now. It’s always comforting to see any open-source project you are relying on to be in active development.

Matomo reporting for device, OS and screen resolution
Matomo reports for device type (still including a lot of 'Apple iPhone' - that's data prior to the improved device reporting), screen sizes and OS versions. Also interesting to see a lot of iOS 14 betas.

This is probably the part that App Store Connect covers well enough and where Matomo mostly adds a granularity for analytics nerds (like me). Users will upgrade to a new app version and (at some point) the current screen-size iPhone eventually anyways.

But since we’re all here for Matomo, where does all of this come together? In the dashboard.

The Matomo dashboard

Real-time addiction and visit logs

All the reports come together in Matomo’s dashboard. There’s the actual Dashboard, which can be customized with a range of widgets from all of the reports that Matomo offers. The main navigation is in the sidebar and fairly self-explanatory. The real-time log shows the most recent visits in real-time (*gasp*) with visits (folder icon), goal completions (flag pole) and events (whatever that third thing is supposed to be).

Matomo dashboard
The Matomo dashboard for Leap Habits with the real-time visits log, visits over time and location information.

For any user with a consistent userID, Matomo can create visitor profiles. These are a collection of all pageviews, events and goal completions for that specific user, including some additional information and custom dimensions.

Matomo visitor profile
Mock visitor profile in Matomo I created with the simulator. It includes information about the individual visit and its views/events/goals, as well as custom dimensions.

So that’s the dashboard. The easiest way to take a look at Matomo and its dashboard is with the free demo that Matomo provides.

Privacy and Bandwidth

Privacy by default

While Matomo is certainly a more privacy-conscious solution than something like Google Firebase, it is so mainly because the tracking data is not disclosed to third parties, but remains within the bounds of the user-developer relationship. That puts developers in control, and enables them to make choices that otherwise Google Firebase would have made for them.

One of these choices can (and should) be to collect as little information about the user as possible, while tracking as much as is necessary to get useful data. This is especially important for information that can even be remotely user-identifiable. Matomo provides a range of additional options that can make this easier.

  • IP address anonymization (the last two bytes are masked by default, e.g. 192.168.xxx.xxx)
  • Using only the anonymized IP address for geo location via IP
  • Regularly deleting old raw data (and only keeping the aggregated report data)
  • Pseudonomyzing userIDs with a salted hash (which in my case brings little additional benefit, since the userID is already specific to Matomo, created randomly on device and is used nowhere else)

I have all of these options enabled and would recommend you do the same. A word on geo location: Matomo comes with support for two popular IP to geo location databases (MaxMind and dbip), which will infer a rough location from the user’s (anonymized) IP address. That alone makes me slightly uncomfortable, but Matomo will only be able to generate location reports if a geo location database is installed. The lookup happens locally with a copy of the location database on the Matomo instance, and dbip also offers a country-level database, which I consider a decent privacy tradeoff (since the country data is already available from App Store Connect).

Another choice I still struggle with is making tracking explicitly opt-in, instead of opt-out. The compromise I’ve come to right now is that I can in good conscience enable tracking by default because 1) the data allows me to make more educated choices, which definitely does benefit the user in the long term, and 2) I strive to implement tracking in the most privacy-conscious way.

But still, I’m making a choice for the user.

One might consider the fact that (almost) every other prominent app is doing that anyways, and the fact that every app with a significant cloud component has access to all user data — but other people/companies doing something has never made for a good argument. I might experiment with presenting the opt-out during onboarding, or doing a better job at educating users about the tracking and telling them explicitly where they can opt out. After all, at least Leap Habits is on the way to a decent user base, which means even explicit opt-in will probably still yield a large enough sample size.

Other than these choices, collecting user data also comes with a certain responsibility (insert $SpiderManQuote). If you are deciding to store analytics data, you better make sure you store it in a safe place. I’m no security expert, so I made sure that 1) I don’t have any personally identifiable user information in the first place, and 2) I follow best practices in cloud infrastructure (HTTPS and SSL all the way, strong passwords, regular updates, using managed services wherever possible to let the experts figure this out).

One burden users do have to carry is that the app is now constantly “phoning home” — which brings me to bandwidth use.

Bandwidth Use

A 56k modem’s worth

Having the app send tracking data to a server will obviously incur some bandwidth cost. To figure out the amount of data being transmitted, I reset the mobile data statistics on iOS, disabled WiFi completely and used the app for a couple of days. Leap Habits does not include (at the time of writing) any network component other than the Matomo SDK, so I attributed all mobile data to tracking.

In my tests, a day of fairly regular use will consume between 50KB and 150KB of data. Over a month, that will probably end up in the range of 2-5MB. A good portion of that might be over WiFi, but even in the ‘worst case’ of 5MB per month over cellular - which is what Instagram will eat up in seconds with stories (or probably mere hours for its own analytics alone) - I feel like this slightly too high for an app that does not have a significant network component. Users do always have the chance to opt out of tracking, but I will try to find ways to reduce the data burden.

One solution might be to randomly set a cohort of users (e.g. half of the user-base) to heartbeat-tracking. That means goal conversion and purchases only, and still counting them as active users, but only firing off one call per session should cut the bandwidth use down to less than 20KB per day. It’s not perfect, but distributes the load across the the user base and should still yield statistically significant data.

So… Matomo. Yay or nay?

Closing thoughts

Privacy-conscious analyticis, effective and efficient

While this post is ostensibly about Matomo, the fundamental motivation is figuring out how users are interacting with your app, so you can build a better app. The only reason user analytics frameworks came up is because my iOS apps persist all data locally, which means I have no idea what is happening after App Store Connect reports a new download.

The question then is: do I have a better understanding of how users are interacting with my iOS apps?
Yes. Very much so.

After running Matomo as the user analytics framework in my apps for eight months, I can definitely say that the data Matomo generates has become an invaluable resource. I’m not sure I would advocate a solely data-driven decision process, but to me, the data is the hard evidence to support or challenge doing what ‘feels right’.

When looking for a mobile analytics solution, privacy was one of my main concerns and reason I went with Matomo. I wanted to make sure that I ‘own’ all the data, which in this case mostly means not disclosing it to any third parties. But it’s also a really great tool: open source, easy to set up, and comes with almost all important reports directly built in. If I had to sell it to management (which is me), I would describe it as both effective (gets the job done of both providing the data and respecting user’s privacy) and efficient (low-cost, simple to use, quick to set up).

Matomo’s limitations are mostly a consequence of it being a web analytics framework first and mobile analytics framework second. It requires some ‘jerry-rigging’, but not too much as to invalidate the efficiency argument. Since Matomo is all I’ve ever used, I might not know what I’m missing though.

In terms of performance and scaling, Matomo provides performance requirements on their website that go all the way up to 100M+ pageviews per month. I am still in the six-figure-pageviews bracket (with an apparently underpowered server), so I’d say there is some headroom. If anyone has experience with running large-scale Matomo instances, please let me know.

If you are a developer of mobile applications, iOS or Android, indie or studio, and looking for a user analytics framework, I would recommend you give Matomo a try. The self-hosted version is set up in less than an hour, and if you want to go the managed service route, the cloud-hosted offering from Matomo is priced very fairly. If you do give it a try, I’d love to hear about your experience.

And lastly, if you have any questions, recommendations, corrections, please do let me know. The easiest way to do that is on Twitter. I look forward to your feedback. Thanks for reading :)

  1. It goes without saying that any user-entered content within the app is strictly off limits. I want all data to be as anonymous as possible and only ever track pages with predefined names. 

  2. With Matomo’s origin being in web analytics, most metrics are grouped by visit cohorts, instead of user cohorts. As an example, an interesting metric might be the percentage of users using a certain device (size class) or using a specific version of the app. By default, Matomo can only display the number of visits (as in app sessions) per device or version, but not the number of users. Visits can be a close proxy for users, but these numbers still get distorted by “power users” with a high visit/session count. There’s probably ways to run SQL queries on the raw data, but I haven’t dug into the database schema yet. If I’m missing something, please let me know