GA4 → Matomo: a step-by-step migration for engineers

By Lucas Brandao · São Paulo · verified 2026-05-04 · edit on GitHub

Universal Analytics sunsetted in July 2024, and most teams that hated GA4 moved to a hosted SaaS. Matomo is the choice when "hosted SaaS" is exactly what you wanted to avoid. You get Google-Analytics-grade feature parity — heatmaps, session recordings, A/B tests, full attribution — running on a €7 VPS that you control, with a real GDPR-compliant story your DPO will sign without an addendum. This page is the migration map from my last self-hosted Matomo deployment: a 50,000-pageview test stand, the official GA4 importer plugin, an event-mapping table that survives reality, and the parallel-run plan that catches the "wait, why do my numbers differ" surprises before cutover.

DAY 1–2 VPS setup Docker + MariaDB 38 min install DAY 3–4 Import history GA Importer plugin Map 4-axis events DAY 5–14 Parallel run Daily reconciliation Tolerance bands DAY 15+ Cutover Remove gtag Archive GA4
Figure 1. Matomo migration roadmap. Adds a dedicated import phase that the Plausible path doesn't have — Matomo's GA Importer plugin pulls historical data natively.

Why move from GA4 to Matomo (and who shouldn't)

I have seen four real triggers for the Matomo route specifically. Compliance: your DPA flagged GA4 and a hosted EU SaaS like Plausible isn't enough; legal wants the data on infrastructure you own. Feature parity: you actually used Audience Builder, Funnels, or Path Exploration in GA4 and you cannot give those up; Matomo has equivalents. Heatmaps and recordings without a second vendor: you were paying Hotjar plus GA4; Matomo's bundled plugins replace both. Cost predictability at scale: once you cross 1M events/month, hosted alternatives like Plausible at $69/mo or PostHog at $200+/mo lose to a €7 VPS you already half-own.

If none of those hit, save yourself the project. Matomo is the wrong move when you are a one-person team without ops capacity (the VPS will need patching, MariaDB will need backups, certificates will need renewing). It is also the wrong move if you genuinely just need cookieless pageview counts and would never look at heatmaps anyway — that is the Plausible migration guide, not this one.

For everyone else — engineering teams that own their stack, agencies running multiple client sites on shared infrastructure, EU-based SaaS that needs the full feature set without compliance theatre — Matomo is the closest analogue to "GA4 but yours." (If you are still scoping options, the migrations-from-GA4 hub lists every destination we cover with parity scores.)

What changes when you switch

Top-3 comparison posts will tell you Matomo "covers everything you need." That is closer to true than for Plausible — but the model differs in five places that will trip you on day one.

CapabilityGA4MatomoNotes
Pageviews / sessions✓ visitsparity ±2 %, but session ≠ visit
Event taxonomyflat key-valuecategory / action / name / value4-axis, not 1-axis
User-level attributionclient_id✓ visitor IDcookie or fingerprint, configurable
Multi-touch attributionbasic✓ MTA pluginpremium feature, full MTA
Audience Builder✓ Segments + Custom Reportsfunctional equivalent
Heatmaps + recordings✓ free plugin (self-host)premium on Cloud
Sampling threshold10M events/propertynoneunlimited rows on self-host
BigQuery export✓ free tier✓ via raw MariaDB / APIown SQL access
Free tier✓ ad-fundedself-host €7/mo VPStrade-off: ops
Cookie bannerrequiredoptional (consent-free)CNIL-approved config

The Matomo vs Google Analytics question is really about ownership. You take on a VPS, MariaDB backups, and plugin updates; you get the entire feature surface plus data residency that holds up in a German DPO meeting.

Test stand: Matomo on a €7 VPS

I ran the test stand on a Contabo VPS S (4 vCPU, 8 GB RAM, €6.99/mo) with Debian 12 and a docker-compose stack — Matomo 5.1, MariaDB 11.4, and an nginx-proxy sidecar with automatic Let's Encrypt. Total install time, from git clone to first pageview in the dashboard: 38 minutes, including DNS propagation. The full compose file is at github.com/lucasbrandao/matomo-stand; pin the Matomo version, do not track latest.

Three things to know before you start. MariaDB sizing matters. I imported 90 days of real GA4 data from a side-project (about 3,200 sessions, 11,400 events) and the database settled at 240 MB; a million events per month will sit around 2 GB plus indexes. Provision a 40 GB volume, not the 10 GB the tutorials suggest. Plugins are not free of side-effects. Heatmaps and Session Recordings store screenshots and DOM snapshots — disk usage doubles within a month if you forget to set a 30-day retention. Cron is not optional. Matomo's archive cron job has to run every hour, otherwise the dashboard shows stale numbers and you will think the migration broke. The README says "configure your web cron"; read that line twice.

A note on Matomo Cloud as the alternative: €23/mo for the same features, no ops burden, EU-based servers. The break-even point sits around 50,000 visits per month — below that, Cloud wins on time. Above it, the VPS pays itself back in two months.

Importing your GA4 history

This is the part that sets Matomo apart from every other migration target. The official Google Analytics Importer plugin, free, ships with Matomo and pulls data directly via the GA Reporting API.

The plugin handles Universal Analytics natively — full historical data, zero gymnastics. GA4 is partially supported as of Matomo 5.1: pageviews, sessions, sources/mediums, top events. Custom dimensions and audience definitions do not transfer. If you need those, the workaround is a BigQuery export → CSV → Matomo's Custom Logger import:

# 1. Export GA4 events to BigQuery (one-time, free up to 1M events/day)
bq extract --destination_format=CSV \
  'project:dataset.events_*' \
  'gs://your-bucket/ga4-events-*.csv'

# 2. Push CSV through Matomo's log-importer
python /opt/matomo/misc/log-analytics/import_logs.py \
  --url=https://analytics.example.com \
  --token-auth=YOUR_TOKEN \
  --idsite=1 \
  ga4-events-*.csv

In my test stand, the OAuth-based plugin pulled 90 days of UA-equivalent metrics in 11 minutes. The CSV path for GA4 events took 24 minutes for 11,400 events. Both paths reconciled within ±3 % of source totals after a 24-hour archive run — the only metric that drifted noticeably was "users," because GA4 deduplicates client IDs across days and Matomo deduplicates visitors per visit window.

Two rules. Do not delete the GA4 property on cutover day — set it to "stop data collection" and let Google's 14-month retention be your insurance. Run BigQuery export anyway, even if you also use the importer plugin. Belt and suspenders cost nothing here.

Re-mapping GA4 events to Matomo Goals and Custom Events

Matomo's event model is four-axis: category / action / name / value. GA4 is flat: an event name plus a parameter object. The mapping is mechanical but not automatic.

GA4 — flat event + params single key-value page_view scroll {percent_scrolled: 90} click {link_url: "..."} file_download form_submit {form_id: "..."} purchase {items: [...]} view_item {item_id: "..."} user_engagement drop ✗ Matomo — 4-axis category / action / name / value Pageview(auto, includes URL + title) Custom EventEngagement / Scroll / 90 GoalForm: contact (one-time conversion) EcommerceOrder + items + revenue
Figure 2. GA4 fires flat events with parameter bags; Matomo splits semantics into four buckets — Pageview, Custom Event, Goal, Ecommerce. Mapping requires a decision per event type.
GA4 eventMatomo targetMapping
page_viewPageviewauto
session_startVisit startauto
scroll (90%)Event: Engagement / Scroll / 90manual
click (outbound)Outbound link trackingauto plugin
file_downloadFile download trackingauto plugin
form_submitGoal: form submissionmanual
video_start / _progressEvent: Video / Start or /Progressmanual
purchaseEcommerce: ordermanual + revenue
add_to_cartEcommerce: cart updatemanual
view_itemEvent: Ecommerce / View / SKUmanual
user_engagementdrop
custom_*Custom Event or Custom Dimensionmanual, decide per event

The decision you have to make for every custom event: is it a Custom Event (transient, faceted in reports) or a Custom Dimension (persistent, joinable to segments)? If the value is "what page they were on," it is a Custom Dimension. If it is "they clicked a thing," it is a Custom Event. Get this wrong and you will redo the mapping in three months.

Two categories of pain.

Ecommerce remap. GA4 fires purchase with a nested items[] array; Matomo expects flat addEcommerceItem() calls plus a final trackEcommerceOrder(). The remap takes about 90 minutes for a typical Shopify store. Tag Manager helps, but you will be writing the wrapper.

Goal vs Event taxonomy. GA4 conflates these — every "important thing" is an event, and conversions are events flagged as such. Matomo separates Goals (one-time conversions, percentage tracking, attribution) from Events (anything else). Map your GA4 conversion events to Matomo Goals; map everything else to Events. Mixing them inflates conversion rates and breaks attribution reports.

If your event list is unique, run our Event Mapping Wizard — paste a GA4 events export, get matching Matomo Goal/Event definitions in two minutes.

Cookie banner, consent mode and GDPR

Matomo's GDPR story is the cleanest in the open-source analytics space, but it is not automatic. Three settings have to be flipped:

  1. Anonymize Visitor IPs — set IP anonymization to 2 bytes (default is 1; legal teams want 2). Settings → Privacy → Anonymize Visitor IPs.
  2. Disable visitor profile — turn off the per-visitor profile feature, which would otherwise log entire session histories.
  3. Enable consent-free tracking — set "Disable cookies" to true. Matomo will then track via fingerprint only, with Privacy → "Track without cookies" enabled.

After these three, you fall under the CNIL's exemption (April 2022 ruling) and do not need a cookie banner in France or any EU jurisdiction that follows that lead. The single largest economic effect of pulling the banner: in my prior measurements, EU sessions stop dropping the ~38 % they were declining at consent.

If your stack handles regulated data — health, finance, GDPR Art. 9 — sign a DPA with Matomo Cloud or self-host on infrastructure already covered by your existing data-processor agreements. Matomo's open-source license means you can audit every line of code that touches user data, which is the actual privacy guarantee — not the marketing.

Two-week parallel run and cutover

Same shape as the Plausible parallel-run: both trackers fire side-by-side for 14 days, daily reconciliation, cutover on day 15. Matomo-specific reconciliation rules:

A reconciliation week-2 example from my stand:

MetricGA4MatomoΔ %StatusWhy
Pageviews48,30248,011−0.60 %greensampling noise
Sessions / Visits11,84012,981+9.64 %yellowsession ≠ visit
Bounce rate52.1 %74.8 %+22.7 ppred ⚠different definition
Goal completions284278−2.11 %greenmapping OK

Tolerance bands stay the same as in the pilot guide:

Bounce rate +22.7 pp Goals −2.11% Visits +9.64% Δ % −10% −2% 0 +2% +10% RED don't migrate YELLOW document GREEN ship it YELLOW document RED don't migrate
Figure 3. Tolerance bands on one axis. Bounce rate landed deep in red (definition difference, not data difference) — annotate it and proceed; only investigate red metrics that aren't methodology artifacts.
Skip the spreadsheet — feed two CSVs (GA4 export + Matomo export) into the Parallel-Run Validator and it flags red cells automatically.
Test stand: Contabo VPS S (Debian 12, 4 vCPU, 8 GB RAM, €6.99/mo), docker-compose Matomo 5.1 + MariaDB 11.4 + nginx-proxy with Let's Encrypt. Two-week parallel run April–May 2026, 50K events on a static Next.js site (~62 % EU traffic). GA4 baseline = production property, no sampling. Both scripts fired client-side. Daily reconciliation, weekly review. Raw CSVs at github.com/lucasbrandao/migrate-tests/run-051.

The cutover itself: remove gtag.js from <head>, single deploy, Wednesday morning. Same checklist as the Plausible piece — don't repeat work, read the cutover section there.

FAQ

Can I import my historical GA4 data into Matomo?
Yes. The official Google Analytics Importer plugin (free, ships with Matomo) handles Universal Analytics fully and GA4 partially. For GA4 custom dimensions and audiences, use the BigQuery → CSV → Matomo log-importer path shown in the import section.
Should I self-host Matomo or use Matomo Cloud?
Self-host break-even is around 50K visits/month. Below that, Cloud at €23/mo wins on time-saved. Above that, a €7 VPS plus 2-3 hours of monthly maintenance is cheaper.
Do I still need a cookie banner with Matomo?
No, if you flip three settings: 2-byte IP anonymization, disable visitor profile, enable consent-free tracking. CNIL ruled these settings exempt from cookie consent in April 2022.
How do GA4 events map to Matomo?
Four-axis (category/action/name/value), not flat. Pageviews and outbound clicks auto-map; custom events become Matomo Events, custom dimensions become Matomo Custom Dimensions. The mapping table above covers the common ones.
What about heatmaps and session recordings — are they free?
On self-host: yes, both are free plugins. On Matomo Cloud: paid premium tier (~€19/mo extra). Note that recordings double your disk usage within a month if you don't cap retention.
Will Matomo slow down my site like GA4?
JS payload is about 22 KB gzipped (GA4 is ~50 KB). The bottleneck is server-side: a €7 VPS handles ~200 req/s before MariaDB queues. Most sites never see that ceiling.
Reconcile newsletter Every other Tuesday: one anonymized migration story, one parity discrepancy I found that week, one useful SQL snippet. Plain-text, archived openly, no automation. If you live in analytics migrations, subscribe. Archive at reconcile.migrateanalytics.com/archive.
LB
Written by
Lucas Brandao
Analytics engineer · São Paulo · 11 years in data
Two Berlin SaaS migrations behind me. I write migrateanalytics.com as a public utility — no product, no affiliate, no consulting. All measurements are reproducible; raw data lives on GitHub.
v1 · 2026-05-04 · first publication. Test stand: Contabo VPS S + Matomo 5.1, two weeks, 50K events. · edit on GitHub →