Back to Blog

I Replaced GA4 with a Google Sheet

No analytics dashboard. Full lead attribution. ROAS in 10 seconds.

April 2026 · 6 min read

Every freelancer has been there. You open GA4 to answer one simple question — "did that LinkedIn post bring me any clients?" — and twenty minutes later you're still configuring a custom report, comparing session sources to conversion paths, and wondering why the numbers don't match what you remember.

GA4 is a powerful tool built for teams with dedicated analysts. I'm one person. The only metric I actually care about is: which channel brought me a paying client?

So I built something different. A contact form that logs every lead directly to a Google Sheet, organized by month, with full UTM attribution. I can calculate ROAS in ten seconds without opening a single dashboard.

Here's how it works and why I think more freelancers should do this.


The stack

Three pieces, all free:

  • Netlify Functions — a serverless function that receives the form submission
  • Google Apps Script — a web app that writes to Google Sheets
  • Resend — sends a notification email to me and a confirmation email to the lead

No SDK. No service accounts. No OAuth flows. Just three fetch calls.


Why Google Apps Script and not the Sheets API?

The Google Sheets API requires a service account, a private key stored as an environment variable, and a 300-line npm package. I tried it. It worked — until it didn't, and debugging JWT authentication errors at midnight is not something I want to do again.

Google Apps Script is the secret weapon nobody talks about. You open your spreadsheet, go to Extensions → Apps Script, and you have a full backend that can read and write to Sheets, Calendar, Gmail, and Drive — natively, without credentials, with ten lines of code.

You deploy it as a web app with public POST access, and you get a URL. That's your API endpoint.

function doPost(e) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  const data = JSON.parse(e.postData.contents);
  sheet.appendRow([data.timestamp, data.name, data.email, ...]);
  return ContentService
    .createTextOutput(JSON.stringify({ success: true }))
    .setMimeType(ContentService.MimeType.JSON);
}

That's it. No npm install. No API keys. Zapier charges €50/month for workflows you can replicate with this in an afternoon. I paid €0. I smiled.


The Netlify function

The contact form on my site POSTs to a Netlify serverless function at /.netlify/functions/submit-form. The function does three things in sequence:

  1. Calls the Apps Script URL to log the lead to Sheets
  2. Calls Resend to send me a notification email with all the lead data
  3. Calls Resend again to send the lead an automatic confirmation

The whole thing is about 80 lines of vanilla JavaScript. No framework, no dependencies, just fetch.

One thing I got wrong the first time: I wasn't checking the response status from Resend. The function was returning { success: true } even when Resend returned a 422 error. Always check res.ok and log the body — otherwise failures are completely invisible.


UTM tracking and the ROAS calculation

The real value of this system is attribution. Every ad I run, every LinkedIn post, every email campaign gets a tagged URL:

leonardocassone.com/contact?utm_source=linkedin&utm_medium=social&utm_campaign=apr-2026

The form reads these parameters from the URL and sends them with the submission. The Google Sheet stores them alongside the lead's name, email, and message.

Now when I want to calculate ROAS for a campaign, the workflow is:

  1. Open the sheet for the month
  2. Filter by UTM campaign
  3. Add a "Deal Value" column manually when a lead converts
  4. Divide total deal value by ad spend

Ten seconds. No funnels, no attribution models, no discrepancies between sessions and conversions. Just a list of actual humans who contacted me and what they were worth.


Monthly tabs, automatic

The Apps Script creates a new tab for each month automatically — "April 2026", "May 2026" — with the header row already in place. I never have to touch the spreadsheet manually.

Six months from now I'll have a clean archive: one tab per month, every lead with full context, filterable by source. That's more useful to me than any GA4 report I've ever tried to build.


When does this stop being enough?

This system is perfect for freelancers and small teams where the goal is lead attribution — understanding which channels bring clients.

It doesn't replace analytics for funnel analysis. If you want to know why 80% of visitors leave your pricing page without converting, you need behavioral data — heatmaps, scroll depth, click tracking. That's GA4 territory, or Hotjar, or Microsoft Clarity.

But if you're a freelancer asking "did my last ad campaign pay off?" — open your sheet, count the leads, multiply by average deal size, divide by spend. Done.


The AI part

I didn't write most of this system by hand. I built it in a conversation with Claude Code — describing what I wanted, iterating on the function, debugging the Apps Script authorization flow in real time.

The interesting thing wasn't that the AI wrote the code. It's that having a conversation about what I actually needed forced me to simplify. Every time I described a requirement, the response was "you can do that with a fetch call." No SDK needed. No OAuth flow. No npm package.

The complexity I was about to add was complexity I didn't need.

That's the real lesson here. Not the stack — the question. What do you actually need to know about your leads? Answer that first, then pick the simplest possible tool to get it.

For me, the answer fit in a Google Sheet.

Total cost: €0. Tools replaced: GA4 (for lead attribution), Zapier (for automation), and half a dozen SaaS tools I was considering. Sometimes the best stack is the one you already have — you just need to know how to wire it together.

You don't need to pay for tools. You need to understand what you actually need.

© 2026 Leonardo Cassone. All rights reserved.

Privacy Policy||

    This site uses cookies for anonymous analytics. Learn more