GuidesMay 14, 202613 min read

Migrating from Google Analytics 4 to JustAnalytics: A Step-by-Step 2026 Guide

Migrating from Google Analytics 4 to JustAnalytics: A Step-by-Step 2026 Guide

The moment I decided we were done with GA4 happened on a Wednesday afternoon in the Explore tab. I'd built the same funnel report three times in two weeks because the previous one kept... vanishing. Or unsharing itself. Or rendering with sampled data that disagreed with the unsampled version by 22%. Our head of growth sent a Slack message that just said "why is the number different again" and I didn't have a good answer.

We migrated the marketing site to JustAnalytics over the next six weeks. This is the writeup — the playbook I'd hand someone on day one, not the marketing version, the version that includes the parts that bit us.

Disclaimer up front: we ourselves stayed on GA4 too long. About eighteen months too long. Embarrassingly long, actually — I'd tell clients to migrate and then go back to my desk and not do it myself. Inertia is a hell of a drug, and "the data is already in there" feels like a real argument right up until you try to actually use the data.

Why migrate off GA4 in the first place

You probably know the reasons or you wouldn't be here, but it's worth being honest about which ones actually matter.

Complexity is the biggest. GA4 replaced a clean session-based model with an event-based one, and the property settings UI grew about forty new dimensions in the process. A basic "signups by source last month" report? Four-step Explore configuration. I've watched our marketing team open GA4, click around for ninety seconds, give up, and then Slack the data team to pull the number from BigQuery instead. That's not a tool working. That's a tool actively creating work.

Sampling is second. Cross the 10M-events-per-month line and standard reports get sampled — unsampled requires GA360, which starts at $50k/year. Most B2B SaaS sites don't need 360, but they do cross that event threshold the second they add a few custom events on a busy page.

Cookie consent banners are the third, and the one nobody talks about in dollar terms. We measured this on our own site: visitors who hit the cookie banner converted 11% less often than visitors on a no-banner control. That's not consent fatigue, that's literally people clicking "reject all" or closing the tab. Going cookieless got those conversions back.

The fourth reason is the one nobody admits out loud — GA4 is unpleasant to use. It's ugly. The UI is slow. The mental model is confusing. After fourteen years of Google Analytics being the default, "the default" finally lost the case for itself. And frankly, Google seems fine with that.

What's portable, what isn't

Before you start, get clear on what's coming with you and what isn't. This part hurts if you skip it.

Portable: tracking setup (UTM tags, campaign parameters), event names (with mapping), custom dimensions as event properties, and goal/conversion definitions (rebuilt, but the logic transfers).

Not portable: historical aggregated data, GA4-specific dimensions like "engaged sessions," audience definitions (those are Google Ads-specific anyway), and Looker Studio reports pulling directly from GA4.

The historical-data one is where teams keep tripping. You can't replay six years of GA4 numbers into JustAnalytics — the data models differ, and forcing it gives you numbers that look right and are quietly wrong. Don't do it.

What we did instead: ran one final BigQuery export, dropped it into a Postgres archive, built a simple "historical" tab in our internal dashboard. Nobody has actually opened it in five months.

Step 1: Parallel-track for 30 to 60 days

Don't cut over cold. Run both at once, compare, then switch.

Install JustAnalytics alongside GA4 so every page sends to both systems. On most sites that's two script tags in the head, no GTM dance needed.

<!-- Existing GA4 tag (leave as-is during parallel) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXX');
</script>

<!-- New JustAnalytics tag -->
<script
  defer
  data-site="your-site-id"
  src="https://cdn.justanalytics.app/script.js">
</script>

That's it for installation. Now leave it alone for at least four weeks and watch the dashboards diverge.

They will diverge. JustAnalytics will almost always show 10-25% fewer pageviews than GA4 — not because one tool is broken, but because GA4 models statistically-inferred pageviews to compensate for consent rejections and adblocked traffic, while JustAnalytics only counts what it actually receives. The "true" number lives somewhere between the two. What you want is consistency, not absolute matching.

What to do during the parallel window:

  1. Pick three key reports (total sessions, signups, paid traffic).
  2. Pull both numbers weekly and record the delta.
  3. After three weeks the delta should stabilize.
  4. Once stable, you've got your conversion factor and you're ready to cut over.

If the delta keeps changing week to week, you've got a tracking bug — usually a page that only fires one tag. Fix it before cutover.

Step 2: Map your custom events

GA4 names events with snake_case (sign_up, purchase, view_item). JustAnalytics uses the same convention but the parameter model is flatter — properties go directly on the event, no nested items array.

Here's a Next.js conversion of a typical GA4 purchase event:

// Old GA4 event
gtag('event', 'purchase', {
  transaction_id: order.id,
  value: order.total,
  currency: 'USD',
  items: [{
    item_id: 'plan_pro',
    item_name: 'Pro Plan',
    price: 49,
    quantity: 1,
  }],
});

// New JustAnalytics event
window.ja?.('event', 'purchase', {
  order_id: order.id,
  revenue_usd: order.total,
  plan_id: 'plan_pro',
  plan_name: 'Pro Plan',
});

Two honest things about this. First, you'll lose the multi-item nesting if your purchase events ship multiple line items per transaction — flatten to one event per item. Second, normalize currencies at the source. We standardized on USD and let the API handle exchange rates.

Build a mapping table before you touch any code. Ours was a Google Sheet, one row per GA4 event, columns for the new name and parameter rename. Forty-three events, mapped in an afternoon. Six were duplicates we'd accumulated over the years — embarrassing cruft, honestly — and we just deleted them instead of migrating. Nobody noticed.

Step 3: Rebuild your dashboards

This is the step everyone underestimates. We underestimated it. Allow a week, maybe more if you're perfectionist about dashboards (I am, unfortunately).

Audit the GA4 reports you actually look at. Be ruthless — most teams have between twelve and fifteen "saved reports" and check three of them. Identify the three. Rebuild those first in JustAnalytics.

// Pulling a custom report via the JustAnalytics API
const response = await fetch('https://api.justanalytics.app/v1/reports', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.JA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    site_id: 'your-site-id',
    metric: 'unique_visitors',
    dimension: 'utm_source',
    date_range: { start: '2026-04-14', end: '2026-05-14' },
    filters: [{ event: 'sign_up' }],
  }),
});
const { rows } = await response.json();

The JustAnalytics web UI handles most of what you'd build in Looker Studio without the SQL. The shift in mindset: stop thinking in "sessions." JustAnalytics counts unique visitors per day and events; everything else is derived.

Loop in finance early if revenue reporting was going to GA4. The hand-off is short — they'll want to know the new numbers reconcile with Stripe. They do, and it's almost irritating how much cleaner they are than GA4's numbers ever were. We'd been debugging Stripe-vs-GA4 discrepancies for two years. Gone.

Step 4: Cutover and archive GA4

You've parallel-tracked for forty-five days. The deltas are stable. The dashboards are rebuilt. Your team has been looking at the JustAnalytics dashboard for two weeks and nobody's complaining. Cutover day.

The cutover itself is anticlimactic:

  1. Remove the GA4 script tags from the site (or set the GTM tag to paused).
  2. Trigger one final BigQuery export of all GA4 historical data.
  3. Set the GA4 property to "view-only" — don't delete it, you may need it for audits.
  4. Update your privacy policy to remove the Google Analytics mention.
  5. Tell your team in Slack that the migration is done and they can stop looking at two dashboards.

For step 2, the export query we ran one last time:

-- final GA4 archive export to local storage
EXPORT DATA OPTIONS(
  uri='gs://your-bucket/ga4-archive/*.parquet',
  format='PARQUET',
  overwrite=true
) AS
SELECT
  event_date,
  event_name,
  event_params,
  user_pseudo_id,
  traffic_source.source AS utm_source,
  traffic_source.medium AS utm_medium
FROM `your-project.analytics_XXXXXX.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20200101' AND '20260513';

The privacy policy update is the one teams forget. If you were running GA4, your policy probably names it explicitly. Now you can replace that section with something much shorter:

We use JustAnalytics, a cookieless privacy-first analytics service, to count pageviews and measure visitor patterns. JustAnalytics does not use cookies, does not store personal identifiers, and processes data in the European Union.

If your legal team wants a longer version, the JustAnalytics privacy summary page has copy you can adapt.

Common pitfalls (we've hit most of these)

UTM tags going AWOL on cutover. GA4 keeps a thirty-minute session window where reloads remember the original UTM tags. JustAnalytics does the same, but the window is six minutes. If you do a lot of pre-page-load redirects from paid campaigns, you may see attribution dip on cutover. Fix: make sure UTM tags are on the final landing URL, not just the click URL.

Attribution windows differ. GA4's default looks back 90 days, JustAnalytics uses 30. For SaaS with long sales cycles, this can make paid channels look 30% less efficient on day one. I learned this the hard way. Reconcile before your CMO sees it on a Friday afternoon and schedules a "quick sync" for Monday.

Adblock-driven undercount on technical audiences. Developer-focused sites should expect another 5-10% dip beyond what you saw in parallel — adblockers are more common in that audience. Fix is to proxy the script through your own subdomain. Drop this into next.config.js:

// next.config.js — proxy the JustAnalytics script through your own domain
module.exports = {
  async rewrites() {
    return [
      { source: '/ja/script.js', destination: 'https://cdn.justanalytics.app/script.js' },
      { source: '/ja/event',     destination: 'https://api.justanalytics.app/event' },
    ];
  },
};

Then update the script tag's src to /ja/script.js and the data attribute data-api="/ja/event". We saw a 13% lift in tracked pageviews on a developer-tools site after proxying — the Next.js 15 App Router setup we wrote up covers the proxy pattern in more detail.

Ad fraud still happens, and it lies to your dashboard. Switching analytics doesn't change the fact that bot traffic and click fraud are quietly inflating your numbers — they just inflate a different dashboard now. We run ClickzProtect alongside JustAnalytics for exactly this reason; the click fraud filter sits at the ad-platform level, so the dirty traffic never reaches analytics in the first place. If you're paying for meaningful ad spend, this is the biggest leak to plug after the migration.

Multi-account testing gets messy without proper tooling. If QA involves verifying tracking across logged-in, logged-out, free, and paid states, a regular browser will pollute everything with prior cookies. We use JustBrowser for isolated profiles per scenario — same machine, ten clean browser contexts.

90-day cost comparison

The numbers from our own migration, rounded:

  • GA4 + GA360 quote for our event volume: $52,000/year for 360.
  • GA4 standard (what we were on): $0/year, but Explore reports sampled past 10M events.
  • JustAnalytics Growth plan at 2M pageviews/month: $1,176/year ($98/month).

The 360 quote was the internal breaking point. Fifty-two thousand dollars. For analytics. We weren't going to do it, so we'd been living with sampling and pretending that was fine. JustAnalytics gave us unsampled data at 2.2% of that cost. Hard to argue with.

The hidden cost was consultant time. Two contractors who specialized in GA4 had been billing us about $14k/year combined to keep reports running. Gone after the migration. That savings paid for cutover labor twice over inside ninety days.

For a feature-level side-by-side, our JustAnalytics vs Plausible vs Fathom comparison covers the three cookieless options — short version, all three save you the same dollars vs GA4, and the choice is about API depth and team preference more than price.

That's the migration. Forty-five days of parallel tracking, a week of dashboard rebuild, an afternoon of cutover. Could've done it eighteen months earlier, but here we are. More guides like this on the JustAnalytics blog.

Frequently Asked Questions

Can I import my historical GA4 data into JustAnalytics?

Not really, and you'll save yourself a week of frustration by accepting that early. Aggregated GA4 numbers (sessions, users, page-level pageviews) can be exported via BigQuery and stored as a flat archive, but you can't replay them into JustAnalytics as if they were tracked there originally — the data models don't line up. What we recommend instead: export GA4 to BigQuery or a CSV one final time, keep it as your read-only history, and treat the JustAnalytics dashboard as a fresh start dated from your cutover day. Most teams stop looking at the GA4 archive within two months.

How long should the parallel-tracking window run?

Thirty to sixty days, depending on traffic. If your site does over 100k pageviews a month, thirty days is plenty — you'll have enough volume to spot discrepancies in the first week. Lower-traffic sites need closer to sixty days because day-of-week and weekly cycles take longer to show up. The point of parallel tracking isn't to validate that JustAnalytics is "correct" — it's to make sure you understand why the numbers differ from GA4, so nobody panics when the dashboard shows 18% fewer pageviews on cutover day.

Will I lose attribution data on existing paid campaigns?

Your UTM tags keep working — JustAnalytics reads the same utm_source, utm_medium, utm_campaign parameters GA4 does. What changes is the attribution window. GA4 defaults to data-driven attribution with a 90-day lookback, JustAnalytics uses last-non-direct-click with a 30-day window. So a conversion that GA4 credited to a Facebook ad someone clicked 60 days ago will show as direct in JustAnalytics. Most teams find this is actually closer to the truth, but if your sales cycle is long, run them in parallel until you've reconciled the difference.

Do I need to keep Google Tag Manager after the migration?

Not for analytics. JustAnalytics ships a single script tag that handles pageviews and custom events directly — no tag container needed. If GTM is only running for GA4 and the JustAnalytics tag, you can remove it entirely and your site will load measurably faster. Keep GTM if it's managing other tags (Meta pixel, LinkedIn Insight, ad conversion tags) that genuinely need a container. We removed GTM from our marketing site during the migration and shaved 340ms off our Largest Contentful Paint.

JP
JustAnalytics Platform TeamContributor

Author at JustAnalytics.

Related posts