Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Adding Negative Keywords in Shopping Ad group.

73 views
Skip to first unread message

Zargham Ahmad

unread,
Jan 29, 2025, 3:45:02 AMJan 29
to Google Ads Scripts Forum
this is my script, i want to add negative keywords in ad group level. it's running fine, adding keywords in sheet but not add negative keywords in Ad Group. 


/**
 * Google Ads Script to Evaluate Search Terms and Manage Negative Keywords for Shopping Campaigns at the Ad Group Level
 *
 * Objective:
 * - Add irrelevant search terms as negative keywords to a specific Ad Group within Shopping campaigns.
 * - Irrelevant terms are those that DO NOT contain ANY of the specified keywords.
 * - Ensure negative keywords appear bracketed (e.g., [keyword]) in the UI as exact.
 * - Log added negative keywords to a Google Sheet for Shopping campaigns.
 *
 * Author: ManagingSEO
 *
 */

function main() {
  const TARGET_AD_GROUP_ID = "***********"; // 
  const SET_OF_TERMS = [
    "black velvet", "reginula",
    "frydek", "variegated", "micholitziana",
    "polly", "amazonica"
  ];
  const GOOGLE_SHEET_URL = "**********************"; 

  const DATE_RANGE = {
    startDate: getFormattedDate(-3),
    endDate: getFormattedDate(0),
  };

  const SHOPPING_SHEET_NAME = "Shopping Campaign Log";
  const MAX_SEARCH_TERMS = 10000;

  const shoppingSheet = getOrCreateSheet(GOOGLE_SHEET_URL, SHOPPING_SHEET_NAME);

  // Fetch search terms
  const searchTerms = fetchSearchTerms(DATE_RANGE, MAX_SEARCH_TERMS, TARGET_AD_GROUP_ID);

  let negativeKeywordsAdded = 0;
  let searchTermsProcessed = 0;

  searchTerms.forEach(termObj => {
    const searchTerm = termObj.text.toLowerCase();
    searchTermsProcessed++;

    // Check if the search term contains ANY of the specified terms.
    const hasAnyTerm = SET_OF_TERMS.some(t => searchTerm.includes(t.toLowerCase()));

    // If the term DOES NOT include ANY of the terms, add it as a negative.
    if (!hasAnyTerm) {
      addNegativeKeywordToAdGroup(termObj, shoppingSheet, TARGET_AD_GROUP_ID);
      negativeKeywordsAdded++;
    }
  });

  Logger.log("Total Search Terms Processed: " + searchTermsProcessed);
  Logger.log("Total Negative Keywords Added: " + negativeKeywordsAdded);
}

/**
 * Fetches search terms for the specified date range and filters by Ad Group ID.
 */
function fetchSearchTerms(dateRange, maxTerms, adGroupId) {
  const query =
    "SELECT Query, CampaignId, AdGroupId " +
    "FROM SEARCH_QUERY_PERFORMANCE_REPORT " +
    "WHERE Impressions > 0 " +
    "AND AdGroupId = '" + adGroupId + "' " + // Filter by Ad Group ID
    "DURING " + dateRange.startDate + "," + dateRange.endDate;

  const report = AdsApp.report(query);
  const rows = report.rows();
  const searchTerms = [];

  while (rows.hasNext() && searchTerms.length < maxTerms) {
    const row = rows.next();
    const campaignId = row["CampaignId"];
    const campaignType = getCampaignTypeById(campaignId);

    // Only include Shopping campaigns
    if (campaignType === "SHOPPING") {
      searchTerms.push({
        text: row["Query"],
        campaignId: campaignId,
        adGroupId: row["AdGroupId"],
        campaignType: campaignType,
      });
    }
  }
  return searchTerms;
}

/**
 * Fetch the type of a campaign (only Shopping campaigns are needed).
 */
function getCampaignTypeById(campaignId) {
  const shoppingCampaign = AdsApp.shoppingCampaigns()
    .withIds([campaignId])
    .get();
  if (shoppingCampaign.hasNext()) {
    return "SHOPPING";
  }

  return "UNKNOWN";
}

/**
 * Adds a negative keyword in bracketed form (for EXACT in UI) to the specified Ad Group
 * and logs it to the specified sheet.
 */
function addNegativeKeywordToAdGroup(termObj, sheet, adGroupId) {
    const adGroupIterator = AdsApp.shoppingAdGroups()
        .withIds([adGroupId])
        .get();

    if (adGroupIterator.hasNext()) {
        const adGroup = adGroupIterator.next();
        const campaign = adGroup.getCampaign();

        // Check if this negative keyword already exists in the Ad Group
        const existingNegatives = adGroup
            .negativeKeywords()
            .withCondition('KeywordText = "' + termObj.text + '"')
            .get();

        let keywordExistsWithMatchType = false;
        while (existingNegatives.hasNext()) {
            keywordExistsWithMatchType = true;
            break;
        }

        if (!keywordExistsWithMatchType) {
            try {
                // Force bracket notation so it appears in the UI as [keyword].
                const bracketedKeyword = "[" + termObj.text + "]";
                adGroup.createNegativeKeyword(bracketedKeyword);

                // Log the original text (not bracketed), but note the matchType as EXACT
                logNegativeKeyword(sheet, termObj.text, termObj.campaignType, "EXACT", campaign.getName(), adGroup.getName());
            } catch (e) {
                Logger.log(
                    'Error adding negative keyword "' + termObj.text + '" to ad group "' +
                    adGroup.getName() + '": ' + e
                );
            }
        }
    }
}

/**
 * Logs the added negative keyword to the specified Google Sheet.
 */
function logNegativeKeyword(sheet, keyword, campaignType, matchType, campaignName, adGroupName) {
    const existingData = sheet.getDataRange().getValues();

    const exists = existingData.some(row => {
      if (row.length < 6) {
        return false;
      }
      const rowKeyword = String(row[0]).toLowerCase();
      const rowCampaignType = String(row[1]).toLowerCase();
      const rowMatchType = String(row[2]).toLowerCase();
      const rowCampaignName = String(row[3]).toLowerCase();
      const rowAdGroupName = String(row[4]).toLowerCase();

      return (
        rowKeyword === keyword.toLowerCase() &&
        rowCampaignType === campaignType.toLowerCase() &&
        rowMatchType === matchType.toLowerCase() &&
        rowCampaignName === campaignName.toLowerCase() &&
        rowAdGroupName === adGroupName.toLowerCase()
      );
    });

    if (!exists) {
      sheet.appendRow([
        keyword,
        campaignType,
        matchType,
        campaignName,
        adGroupName,
        new Date(),
        "Added from script",
      ]);
    }
}

/**
 * Creates or gets a Google Sheet by name.
 */
function getOrCreateSheet(sheetUrl, sheetName) {
  const spreadsheet = SpreadsheetApp.openByUrl(sheetUrl);
  let sheet = spreadsheet.getSheetByName(sheetName);

  if (!sheet) {
    sheet = spreadsheet.insertSheet(sheetName);
    sheet.appendRow(["Keyword", "Campaign Type", "Match Type", "Campaign Name", "Ad Group Name", "Timestamp", "Notes"]);
  }

  return sheet;
}

/**
 * Returns a formatted date string in EEOCMMDD format.
 */
function getFormattedDate(offsetDays) {
  const date = new Date();
  date.setDate(date.getDate() + offsetDays);
  return date.toISOString().slice(0, 10).replace(/-/g, "");
}
Message has been deleted

Google Ads Scripts Forum Advisor

unread,
Jan 29, 2025, 9:35:01 AMJan 29
to adwords...@googlegroups.com
Hi,

Thank you for reaching out to the Google Ads Scripts Support team.

In order to assist further on the issue, could you please share the following details:
  • Google Ads account ID/CID
  • Name of the script
  • If the script uses a spreadsheet, refer to the shareable link on how to share the spreadsheet publicly.
You can share the requested details via Reply privately to the author option or a direct private reply to this email.
 
This message is in relation to case "ref:!00D1U01174p.!5004Q02vGyvi:ref" (ADR-00286251)

Thanks,
 
Google Logo Google Ads Scripts Team

Feedback
How was our support today?

rating1    rating2    rating3    rating4    rating5



Zargham Ahmad

unread,
Jan 31, 2025, 2:32:04 AMJan 31
to Google Ads Scripts Forum
Hi, 
thanks for your response. I can't reply privately. i only have option to reply all. 
Message has been deleted

Google Ads Scripts Forum Advisor

unread,
Jan 31, 2025, 3:39:39 AMJan 31
to adwords...@googlegroups.com

Hi,

I am removing your previous interaction with us from the forums as it contains PII data. 

By looking at the script, I could see that you are getting the “AdsApp.flush is not a function” error for all the keywords. The method “AdsApp.flush” is invalid in the Google Ads Scripts. I would suggest that you create a script based on methods available in the AdsApp.​NegativeKeyword, AdsApp.​NegativeKeywordSelector, and AdsApp.​KeywordBuilder documents. 

Please be informed that our support channel cannot be able to provide assistance for third party scripts. If you still face any issues on this, kindly contact the author of the script. 

Reply all
Reply to author
Forward
0 new messages