What it does
Pulls keyword-level impressions, clicks, CTR and cost from Google Ads, splits each keyword into Brand or Non-Brand by matching its text against your brand terms, and writes two tabs to a Google Sheet: a Daily Report (one Brand and one Non-Brand row per date) and a Daily Trend (date rolled up with spend percentages). On first run it backfills from a HISTORICAL_START date to yesterday; after that it runs for yesterday only, driven by a Status cell on a Setup tab. Only Search campaigns whose name contains your campaign prefix are included.
The code
/**
* ============================================================
* GOOGLE ADS BRAND VS NON-BRAND DAILY REPORT
* ============================================================
*
* Splits keyword performance into Brand vs Non-Brand and writes a
* daily report + daily trend to Google Sheets.
*
* INITIAL RUN: Backfills from HISTORICAL_START to yesterday.
* AFTER INITIAL RUN: Runs for yesterday only (driven by a Setup sheet
* Status cell = BACKFILL_COMPLETED).
*
* CAMPAIGNS: Search campaigns whose name contains CAMPAIGN_NAME_PREFIX.
* Enabled + paused campaigns and keywords included.
*
* BRAND: keywords containing any BRAND_TERMS substring.
*
* METRICS: Impressions, Clicks, CTR, Cost.
*
* SHEETS: Daily Report | Daily Trend | Setup
* ============================================================
*/
/* ============================================================
MAIN FUNCTION
============================================================ */
function main() {
// ---- CONFIG: set these for your account -------------------
var SHEET_URL = 'YOUR_GOOGLE_SHEET_URL'; // target spreadsheet
var REPORT_TAB = 'Daily Report';
var TREND_TAB = 'Daily Trend';
var SETUP_TAB = 'Setup';
var HISTORICAL_START = 'YYYY-MM-DD'; // e.g. '2026-06-01'
var CAMPAIGN_NAME_PREFIX = 'YOUR_CAMPAIGN_PREFIX'; // e.g. 'brand_search'
var BRAND_TERMS = [ // keywords that count as brand
'yourbrand',
'your brand'
];
// -----------------------------------------------------------
var ss = SpreadsheetApp.openByUrl(SHEET_URL);
var report = getOrCreateSheet(ss, REPORT_TAB);
var trend = getOrCreateSheet(ss, TREND_TAB);
var setup = getOrCreateSheet(ss, SETUP_TAB);
// ==========================================================
// CREATE DAILY REPORT HEADER
// ==========================================================
if (report.getLastRow() === 0) {
report.appendRow([
'Date', 'Keyword Type', 'Impressions', 'Clicks', 'CTR', 'Cost'
]);
}
// ==========================================================
// GET YESTERDAY
// ==========================================================
var yesterday = getYesterday();
// ==========================================================
// DETERMINE RUN MODE
// ==========================================================
var status = setup.getRange('B1').getValue();
var startDate;
var endDate;
if (status !== 'BACKFILL_COMPLETED') {
// First run: HISTORICAL_START -> yesterday
startDate = HISTORICAL_START;
endDate = yesterday;
Logger.log('MODE: HISTORICAL BACKFILL');
Logger.log('DATE RANGE: ' + startDate + ' -> ' + endDate);
} else {
// Future runs: yesterday only
startDate = yesterday;
endDate = yesterday;
Logger.log('MODE: DAILY UPDATE');
Logger.log('DATE: ' + yesterday);
}
// ==========================================================
// GET GOOGLE ADS DATA
// ==========================================================
var data = getAdsData(startDate, endDate, CAMPAIGN_NAME_PREFIX, BRAND_TERMS);
// ==========================================================
// WRITE DAILY REPORT
// ==========================================================
writeReport(report, data);
// ==========================================================
// MARK BACKFILL COMPLETE
// ==========================================================
if (status !== 'BACKFILL_COMPLETED') {
setup.getRange('A1').setValue('Status');
setup.getRange('B1').setValue('BACKFILL_COMPLETED');
setup.getRange('A2').setValue('Historical Start');
setup.getRange('B2').setValue(HISTORICAL_START);
setup.getRange('A3').setValue('Backfill Through');
setup.getRange('B3').setValue(yesterday);
setup.getRange('A4').setValue('Daily Mode');
setup.getRange('B4').setValue('Yesterday');
setup.getRange('A1:B4').setFontWeight('bold');
}
// ==========================================================
// BUILD DAILY TREND
// ==========================================================
buildTrend(report, trend);
// ==========================================================
// FORMAT SHEETS
// ==========================================================
formatReport(report);
formatTrend(trend);
Logger.log('====================================');
Logger.log('REPORT COMPLETED SUCCESSFULLY');
Logger.log('====================================');
}
/* ============================================================
GET OR CREATE SHEET
============================================================ */
function getOrCreateSheet(ss, name) {
var sheet = ss.getSheetByName(name);
if (!sheet) sheet = ss.insertSheet(name);
return sheet;
}
/* ============================================================
GET YESTERDAY
============================================================ */
function getYesterday() {
var timezone = AdsApp.currentAccount().getTimeZone();
var date = new Date();
date.setDate(date.getDate() - 1);
return Utilities.formatDate(date, timezone, 'yyyy-MM-dd');
}
/* ============================================================
GET GOOGLE ADS DATA
============================================================ */
function getAdsData(startDate, endDate, campaignNameCondition, brandTerms) {
var data = {};
// NOTE: No campaign.status / ad_group_criterion.status filter, so
// enabled + paused campaigns and keywords are all included.
var query =
'SELECT ' +
'segments.date, ' +
'campaign.name, ' +
'campaign.advertising_channel_type, ' +
'ad_group_criterion.keyword.text, ' +
'metrics.impressions, ' +
'metrics.clicks, ' +
'metrics.cost_micros ' +
'FROM keyword_view ' +
'WHERE segments.date BETWEEN "' + startDate + '" AND "' + endDate + '"';
Logger.log('Running Google Ads query...');
var rows = AdsApp.search(query);
var totalRows = 0;
var includedRows = 0;
while (rows.hasNext()) {
var row = rows.next();
totalRows++;
// SEARCH CAMPAIGNS ONLY
if (row.campaign.advertisingChannelType !== 'SEARCH') continue;
// CAMPAIGN NAME CONDITION
var campaignName = row.campaign.name.toLowerCase();
if (campaignName.indexOf(campaignNameCondition.toLowerCase()) === -1) continue;
var date = row.segments.date;
var keyword = '';
if (row.adGroupCriterion && row.adGroupCriterion.keyword && row.adGroupCriterion.keyword.text) {
keyword = row.adGroupCriterion.keyword.text.toLowerCase();
}
var impressions = Number(row.metrics.impressions || 0);
var clicks = Number(row.metrics.clicks || 0);
var cost = Number(row.metrics.costMicros || 0) / 1000000;
if (!data[date]) {
data[date] = {
brandImpressions: 0, brandClicks: 0, brandCost: 0,
nonBrandImpressions: 0, nonBrandClicks: 0, nonBrandCost: 0
};
}
var isBrand = false;
for (var i = 0; i < brandTerms.length; i++) {
if (keyword.indexOf(brandTerms[i]) !== -1) { isBrand = true; break; }
}
if (isBrand) {
data[date].brandImpressions += impressions;
data[date].brandClicks += clicks;
data[date].brandCost += cost;
} else {
data[date].nonBrandImpressions += impressions;
data[date].nonBrandClicks += clicks;
data[date].nonBrandCost += cost;
}
includedRows++;
}
Logger.log('Rows processed: ' + totalRows);
Logger.log('Rows included: ' + includedRows);
return data;
}
/* ============================================================
WRITE DAILY REPORT
============================================================ */
function writeReport(sheet, data) {
var dates = Object.keys(data);
dates.sort();
if (dates.length === 0) { Logger.log('No matching data found.'); return; }
var existing = {};
var lastRow = sheet.getLastRow();
if (lastRow > 1) {
var values = sheet.getRange(2, 1, lastRow - 1, 1).getValues();
var timezone = AdsApp.currentAccount().getTimeZone();
for (var i = 0; i < values.length; i++) {
var existingDate = values[i][0];
if (existingDate instanceof Date) {
existingDate = Utilities.formatDate(existingDate, timezone, 'yyyy-MM-dd');
}
if (existingDate) existing[String(existingDate)] = true;
}
}
for (var j = 0; j < dates.length; j++) {
var date = dates[j];
if (existing[date]) continue;
var d = data[date];
var brandCTR = d.brandImpressions > 0 ? d.brandClicks / d.brandImpressions : 0;
var nonBrandCTR = d.nonBrandImpressions > 0 ? d.nonBrandClicks / d.nonBrandImpressions : 0;
sheet.appendRow([date, 'Brand', d.brandImpressions, d.brandClicks, brandCTR, d.brandCost]);
sheet.appendRow([date, 'Non-Brand', d.nonBrandImpressions, d.nonBrandClicks, nonBrandCTR, d.nonBrandCost]);
existing[date] = true;
}
}
/* ============================================================
BUILD DAILY TREND
============================================================ */
function buildTrend(report, trend) {
trend.clear();
trend.appendRow([
'Date', 'Brand Impressions', 'Non-Brand Impressions',
'Brand Clicks', 'Non-Brand Clicks', 'Brand CTR', 'Non-Brand CTR',
'Brand Cost', 'Non-Brand Cost', 'Total Cost',
'Brand Spend %', 'Non-Brand Spend %'
]);
var lastRow = report.getLastRow();
if (lastRow <= 1) return;
var values = report.getRange(2, 1, lastRow - 1, 6).getValues();
var grouped = {};
var timezone = AdsApp.currentAccount().getTimeZone();
for (var i = 0; i < values.length; i++) {
var date = values[i][0];
if (date instanceof Date) date = Utilities.formatDate(date, timezone, 'yyyy-MM-dd');
date = String(date);
var type = String(values[i][1]);
var impressions = Number(values[i][2]) || 0;
var clicks = Number(values[i][3]) || 0;
var cost = Number(values[i][5]) || 0;
if (!grouped[date]) {
grouped[date] = {
brandImpressions: 0, nonBrandImpressions: 0,
brandClicks: 0, nonBrandClicks: 0,
brandCost: 0, nonBrandCost: 0
};
}
if (type === 'Brand') {
grouped[date].brandImpressions += impressions;
grouped[date].brandClicks += clicks;
grouped[date].brandCost += cost;
} else if (type === 'Non-Brand') {
grouped[date].nonBrandImpressions += impressions;
grouped[date].nonBrandClicks += clicks;
grouped[date].nonBrandCost += cost;
}
}
var dates = Object.keys(grouped);
dates.sort();
for (var j = 0; j < dates.length; j++) {
var d = grouped[dates[j]];
var brandCTR = d.brandImpressions > 0 ? d.brandClicks / d.brandImpressions : 0;
var nonBrandCTR = d.nonBrandImpressions > 0 ? d.nonBrandClicks / d.nonBrandImpressions : 0;
var totalCost = d.brandCost + d.nonBrandCost;
var brandSpendPercentage = totalCost > 0 ? d.brandCost / totalCost : 0;
var nonBrandSpendPercentage = totalCost > 0 ? d.nonBrandCost / totalCost : 0;
trend.appendRow([
dates[j], d.brandImpressions, d.nonBrandImpressions,
d.brandClicks, d.nonBrandClicks, brandCTR, nonBrandCTR,
d.brandCost, d.nonBrandCost, totalCost,
brandSpendPercentage, nonBrandSpendPercentage
]);
}
}
/* ============================================================
FORMAT DAILY REPORT
============================================================ */
function formatReport(sheet) {
var lastRow = sheet.getLastRow();
if (lastRow <= 1) return;
sheet.getRange(1, 1, 1, 6).setFontWeight('bold');
sheet.getRange(2, 5, lastRow - 1, 1).setNumberFormat('0.00%');
sheet.getRange(2, 6, lastRow - 1, 1).setNumberFormat('#,##0.00');
sheet.autoResizeColumns(1, 6);
}
/* ============================================================
FORMAT DAILY TREND
============================================================ */
function formatTrend(sheet) {
var lastRow = sheet.getLastRow();
if (lastRow <= 1) return;
sheet.getRange(1, 1, 1, 12).setFontWeight('bold');
sheet.getRange(2, 6, lastRow - 1, 2).setNumberFormat('0.00%');
sheet.getRange(2, 8, lastRow - 1, 3).setNumberFormat('#,##0.00');
sheet.getRange(2, 11, lastRow - 1, 2).setNumberFormat('0.00%');
sheet.autoResizeColumns(1, 12);
}
When to use this
Run this when you want to see how much of your Search spend is going to brand terms versus non-brand terms, when checking whether brand campaigns are cannibalizing non-brand budget, or as a scheduled daily report so you can watch brand spend share over time without opening the Ads UI.
Prerequisites
- Script access at the account or MCC level (Tools & Settings > Bulk Actions > Scripts).
- A Google Sheet you own, with the spreadsheet URL to paste into CONFIG.
- Set CONFIG.SHEET_URL, CONFIG.CAMPAIGN_NAME_PREFIX and CONFIG.BRAND_TERMS for your account.
How to implement
- 1
Create a new script
Tools & Settings > Bulk Actions > Scripts > + New Script. Paste the full file.
- 2
Configure the CONFIG block
Set SHEET_URL to your spreadsheet, CAMPAIGN_NAME_PREFIX to the text that appears in your Search campaign names, and BRAND_TERMS to the list of your brand keywords. Set HISTORICAL_START to the earliest date you want to backfill from.
- 3
Run once to backfill
Run the script manually. The first run writes history from HISTORICAL_START through yesterday and marks the Setup sheet Status as BACKFILL_COMPLETED.
- 4
Schedule it
Set a daily schedule (clock icon). Each run after the first writes yesterday's Brand and Non-Brand rows only, and rebuilds the Daily Trend tab.
Caveats
Only Search campaigns whose name contains the campaign prefix are included; other campaign types and channels are ignored. Brand/non-brand classification is substring-based, so a keyword containing any brand term counts as brand. Backfill is driven by a single Status cell, so if the Setup sheet is deleted or the cell is cleared, the next run will backfill from HISTORICAL_START again. Cost is converted from cost_micros to the account currency.