Google Ads offline conversion tracking via Google Sheets: no paid CRM needed

Written by

Abdul Kayium

Published Jul 28, 2026 comments

When this approach fits (and when it doesn't)

What you'll build

How the setup works

Let's implement it

Google Ads offline conversion tracking via Google Sheets

Save Lead Data website to Google Sheets

Step 1: collect data in web GTM

New to the data layer?

function() { // ---- CONFIG ---- // Leave as '' to output UTC (works for every account, recommended). // Or set an IANA zone for this container, e.g. 'America/Toronto', 'Asia/Dhaka'. var TIMEZONE = ''; // ---- GET TIMESTAMP ---- // Reads ?timestamp= from URL, else uses current time. // Change this line if your timestamp lives somewhere else. function getParam(p) { var m = new RegExp('[?&]' + p + '=([^&]*)').exec(location.search); return m && decodeURIComponent(m[1].replace(/\+/g, ' ')); } var raw = getParam('timestamp'); var ts = raw ? Number(raw) : Date.now(); if (!ts || isNaN(ts)) ts = Date.now(); // seconds vs milliseconds, auto-detect if (String(Math.floor(ts)).length <= 10) ts = ts * 1000; var d = new Date(ts); function pad(n) { return ('0' + n).slice(-2); } // ---- UTC PATH ---- if (!TIMEZONE) { return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1) + '-' + pad(d.getUTCDate()) + ' ' + pad(d.getUTCHours()) + ':' + pad(d.getUTCMinutes()) + ':' + pad(d.getUTCSeconds()) + '+00:00'; } // ---- TIMEZONE PATH (auto DST) ---- try { var parts = new Intl.DateTimeFormat('en-CA', { timeZone: TIMEZONE, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).formatToParts(d); var map = {}; for (var i = 0; i < parts.length; i++) map[parts[i].type] = parts[i].value; if (map.hour === '24') map.hour = '00'; // correct offset for that exact date (handles -04:00 vs -05:00) var asUTC = new Date(d.toLocaleString('en-US', { timeZone: 'UTC' })); var asTZ = new Date(d.toLocaleString('en-US', { timeZone: TIMEZONE })); var offMin = Math.round((asTZ - asUTC) / 60000); var sign = offMin < 0 ? '-' : '+'; var abs = Math.abs(offMin); return map.year + '-' + map.month + '-' + map.day + ' ' + map.hour + ':' + map.minute + ':' + map.second + sign + pad(Math.floor(abs / 60)) + ':' + pad(abs % 60); } catch (e) { // if zone is invalid, fall back to UTC so nothing breaks return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1) + '-' + pad(d.getUTCDate()) + ' ' + pad(d.getUTCHours()) + ':' + pad(d.getUTCMinutes()) + ':' + pad(d.getUTCSeconds()) + '+00:00'; } }

Note:

Step 2: send data from web GTM to server GTM

Step 3: create variables and triggers in server GTM

Note:

Step 5: connect and send data to Google Sheets

Set up Google Sheets automation

Marketing Qualified Lead (MQL) Automation

=ARRAYFORMULA( FILTER( SUBSTITUTE( IMPORTRANGE("PASTE_YOUR_CRM_SHEET_URL_HERE", "Sheet1!D2:J"), "Inquiry", "Marketing Qualified (OCT)" ), INDEX( IMPORTRANGE("PASTE_YOUR_CRM_SHEET_URL_HERE", "Sheet1!A2:A"), 0 ) = "Marketing Qualified" ) )

Automatically upload offline conversions to Google Ads

Step 1: create a Google Ads Conversion Action

Click on Skip this step and set up a data source later…
Click on This data was collected and is being shared with Google in compliance with Google's EU user consent policy and Customer data policies, as applicable
Click on Done

Step 2: schedule the Google Sheets upload

Before you finish

Conclusion

FAQ

Want to join the server side?

Try for free

Written by

Abdul Kayium

Content writer

Kayium is a tracking specialist and official Stape partner with 500+ implementations. He fixes complex attribution problems for ecommerce and B2B brands so advertisers can trust their numbers.

Was this article helpful?

Comments

Recommended for you

Turn better tracking into better marketing performance

Try for free