Skip to content

N-gram analysis of search terms

Splits the search terms report into one-, two- and three-word tokens and sums cost and conversions across each. Words that spend without converting show up even when no single query does.

Try it

Shows an example of the output, not a run against your account.
Google Ads script
/**
 * Goodlabs Google Ads script: n-gram search term analysis
 * Schedule: Weekly on Mondays
 */
var CONFIG = {
  MIN_IMPRESSIONS: 50,
  MIN_COST: 20,
  SPREADSHEET_URL: "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit",
  DATE_RANGE: "LAST_30_DAYS"
};

function main() {
  Logger.log("Extracting search terms for N-Gram analysis...");
  var report = AdsApp.report(
    "SELECT search_term_view.search_term, metrics.impressions, metrics.clicks, " +
    "metrics.cost_micros, metrics.conversions, metrics.conversions_value " +
    "FROM search_term_view " +
    "WHERE metrics.impressions > " + CONFIG.MIN_IMPRESSIONS + " " +
    "AND segments.date DURING " + CONFIG.DATE_RANGE
  );

  var ngrams = {}; // token -> { imps, clicks, cost, convs, value }

  var rows = report.rows();
  while (rows.hasNext()) {
    var row = rows.next();
    var query = String(row["search_term_view.search_term"]).toLowerCase().trim();
    var imps = Number(row["metrics.impressions"] || 0);
    var clicks = Number(row["metrics.clicks"] || 0);
    var cost = Number(row["metrics.cost_micros"] || 0) / 1e6;
    var convs = Number(row["metrics.conversions"] || 0);
    var val = Number(row["metrics.conversions_value"] || 0);

    var words = query.split(/\s+/);
    
    for (var n = 1; n <= 3; n++) {
      for (var i = 0; i <= words.length - n; i++) {
        var gram = words.slice(i, i + n).join(" ");
        if (!ngrams[gram]) {
          ngrams[gram] = { gram: gram, words: n, imps: 0, clicks: 0, cost: 0, convs: 0, val: 0 };
        }
        ngrams[gram].imps += imps;
        ngrams[gram].clicks += clicks;
        ngrams[gram].cost += cost;
        ngrams[gram].convs += convs;
        ngrams[gram].val += val;
      }
    }
  }

  exportToSheet(ngrams);
}

function exportToSheet(ngrams) {
  var ss = SpreadsheetApp.openByUrl(CONFIG.SPREADSHEET_URL);
  var sheet = ss.getSheetByName("NGram_Analysis") || ss.insertSheet("NGram_Analysis");
  sheet.clear();
  sheet.appendRow(["N-Gram Token", "Words", "Impressions", "Clicks", "Cost", "Conversions", "CPA", "Conv. Rate", "ROAS", "Status"]);

  var output = [];
  for (var k in ngrams) {
    var item = ngrams[k];
    if (item.cost < CONFIG.MIN_COST) continue;
    var cpa = item.convs > 0 ? (item.cost / item.convs).toFixed(2) : "N/A";
    var cr = item.clicks > 0 ? ((item.convs / item.clicks) * 100).toFixed(2) + "%" : "0%";
    var roas = item.cost > 0 ? (item.val / item.cost).toFixed(2) : "0.00";
    var status = (item.convs === 0 && item.cost >= CONFIG.MIN_COST) ? "🚨 NEGATIVE CANDIDATE" : "OK";

    output.push([
      item.gram, item.words, item.imps, item.clicks,
      item.cost.toFixed(2), item.convs, cpa, cr, roas, status
    ]);
  }

  output.sort(function(a, b) { return parseFloat(b[4]) - parseFloat(a[4]); });

  if (output.length > 0) {
    sheet.getRange(2, 1, output.length, 10).setValues(output);
  }
  sheet.getRange(1, 1, 1, 10).setFontWeight("bold").setBackground("#f3f4f6");
  Logger.log("Exported " + output.length + " n-gram patterns to Google Sheets.");
}

How it works

  1. Pulls impressions, clicks, cost, conversions and value for the last 30 days from search_term_view, which has one row per search term and ad group, keeping rows with more than MIN_IMPRESSIONS impressions (50 by default).
  2. Splits each query into single words, two-word and three-word n-grams.
  3. Sums cost and conversions for every n-gram across all queries it appears in.
  4. Marks n-grams with zero conversions and cost above the minimum as negative candidates.
  5. Writes the table, CPA, conversion rate and ROAS included, to the NGram_Analysis tab of your Google Sheet.

Installation

  1. In Google Ads, open Tools > Bulk actions > Scripts.
  2. Paste the code into a new script.
  3. Put your Google Sheet URL in SPREADSHEET_URL.
  4. Authorize the script and run Preview.
  5. Schedule it weekly, Monday morning.

Questions

Why not just read the search terms report?

The report scores whole queries. A word like "free" can spend across dozens of queries that each look too small to act on; summed as one n-gram, it shows up. Lower MIN_IMPRESSIONS if your long tail sits under 50 impressions.

Need one adapted to your account?

We write Google Ads scripts, set up server-side GA4 and build reports that answer a specific question.