How to conduct a digital marketing audit?

Running digital marketing audits for big brands like BMW, Domino’s, Watsons and many more taught me this:

Most digital marketing efforts waste between 15–30% of their budget silently. Not because of bad strategy; because nobody audited the infrastructure.

A significant portion of this waste can be caught by lightweight scripts before it costs you a cent. Especially for Google Ads.

Audit in the context of digital marketing…

The word Audit comes from the Latin audīre, meaning “to hear.” The noun form auditus meant “a hearing” — and crucially, in medieval accounting practice, financial accounts were literally read aloud to a magistrate or official who would hear them and confirm they were correct. That oral act of verification gave us auditus compoti (“a hearing of accounts”), which then entered English around the 15th century as audit.

This is why we still talk about auditors “reviewing” books, but the original act was acoustic. The same Proto-Indo-European root *au- (“to perceive, apprehend”) also gave us audio, audience, auditorium, and audible — all words where hearing is central.

In this context, a digital marketing audit is a hearing of digital marketing accounts.

An audit requires measurable criteria against which something is evaluated. Without that, it’s just an opinion.

So “your social posts suck” is a take. But “your average engagement rate is 0.4% against an industry benchmark of 2.1%” — that’s audit territory. Same observation, completely different epistemic standing.

Digital marketing audit requires quantitative work and objectivity.

A proper digital marketing audit has to define what it’s measuring before it measures it — otherwise you’re just retrofitting judgment with numbers. The criteria come first, the assessment follows.

This is why digital marketing audits tend to cluster around the channels that are most instrumented:

  • Paid media (PPC, paid social) — highly auditable, everything is tracked
  • SEO — rankings, crawlability, backlink profiles, Core Web Vitals
  • Email — open rates, CTR, list health, deliverability
  • Website/CRO — traffic, bounce, conversion funnels

And why “social media content quality” is the hardest to audit honestly — aesthetics and tone resist quantification, so most audits either skip it or proxy it through engagement metrics, which is the right move.

Google ads are the easiest to audit – despite black boxes.

Google offers a unique tool that no other self-service platform does: Google Ads scripts module.

What makes this module unique is that you don’t need external libraries or third-party tools to set up. You don’t need to go through API application processes or authentication. If you have access to the Google Ads account itself, you already have access to the script environment.

When conducting a digital marketing audit, especially for large accounts, increased manual work produces human errors. The most efficient way to tackle this in the long term is introduce scripts.

And as of 2026 March, the date this post was updated, it’s also possible …it’s also possible to connect Google Ads to well-known LLMs via MCP — including Gemini, Claude, and ChatGPT — currently in read-only mode, making it ideal for audit and analysis workflows.

Note: Grok and DeepSeek aren’t plug-and-play with Google Ads MCP just yet — but in this industry, that could change any week.

A simple Google Ads script to start your own digital marketing audit for your e-commerce, for Google search ads.

The following script investigates search terms in 10% chunks. Suggested use for the reference ROAS is your break-even ROAS.

It also gives you the top 10 search terms with impressions and profitability metrics.

function main() {
  const referenceRoas = 1; // Change this value as needed, using break-even ROAS is suggested 
  const bucketSize = 0.1; // 10% chunks
  
  const report = AdsApp.report(
    'SELECT Query, Impressions, Clicks, Cost, Conversions, ConversionValue ' +
    'FROM SEARCH_QUERY_PERFORMANCE_REPORT ' +
    'WHERE CampaignStatus = ENABLED ' +
    'AND Cost > 0 ' +
    'DURING LAST_30_DAYS'
  );

  const rows = [];
  let totalCost = 0;
  const roasBuckets = {};
  
  // Generate bucket ranges dynamically
  const numBuckets = Math.ceil(referenceRoas / bucketSize);
  for (let i = 0; i < numBuckets; i++) {
    const lower = referenceRoas - (i + 1) * bucketSize;
    const upper = referenceRoas - i * bucketSize;
    const key = `${lower.toFixed(1)}-${upper.toFixed(1)}`;
    roasBuckets[key] = 0;
  }

  const reportRows = report.rows();
  while (reportRows.hasNext()) {
    const row = reportRows.next();
    const cost = parseFloat(row['Cost']) || 0;
    const conversionValue = parseFloat(row['ConversionValue']) || 0;
    const roas = cost > 0 ? conversionValue / cost : 0;
    
    rows.push(row);
    totalCost += cost;
    
    // Find which bucket this ROAS falls into
    for (let i = 0; i < numBuckets; i++) {
      const lower = referenceRoas - (i + 1) * bucketSize;
      const upper = referenceRoas - i * bucketSize;
      if (roas >= lower && roas < upper) {
        const key = `${lower.toFixed(1)}-${upper.toFixed(1)}`;
        roasBuckets[key] += cost;
        break;
      }
    }
  }

  Logger.log(`Total search terms with cost > 0: ${rows.length}`);
  Logger.log(`Total cost: ${totalCost.toFixed(2)}`);
  Logger.log(`Reference ROAS: ${referenceRoas}`);
  Logger.log('');
  Logger.log('ROAS Bucket Breakdown:');
  
  Object.keys(roasBuckets).forEach(bucket => {
    const bucketCost = roasBuckets[bucket];
    const percentage = totalCost > 0 ? ((bucketCost / totalCost) * 100).toFixed(2) : 0;
    Logger.log(`ROAS ${bucket}: ${bucketCost.toFixed(2)}, ${percentage}%`);
  });

  // Sort by impressions descending
  rows.sort((a, b) => b['Impressions'] - a['Impressions']);

  // Log top 10 only
  Logger.log('');
  Logger.log('Top 10 by Impressions:');
  rows.slice(0, 10).forEach(row => {
    const cost = parseFloat(row['Cost']) || 0;
    const conversionValue = parseFloat(row['ConversionValue']) || 0;
    const roas = cost > 0 ? (conversionValue / cost).toFixed(2) : 'N/A';
    
    Logger.log(`Query: ${row['Query']}, Impressions: ${row['Impressions']}, Clicks: ${row['Clicks']}, Cost: ${row['Cost']}, ConversionValue: ${row['ConversionValue']}, ROAS: ${roas}`);
  });
}
About the author: