AdWords Quality Score Tracker Version 2.0 – Now with Labels

Google just made some very helpful additions to AdWords Scripts. One of them is that you can now use labels within scripts. To demonstrate how powerful this feature is, I’ve rewritten the Quality Score Tracker.

The first version required a spreadsheet with a list of keywords to track. It also required you to get the spreadsheet first. This new version relies on labels to mark keywords for tracking and the setup is much easier.

What the Script does

Just like the first version, this script tracks keyword quality scores over time. When run, the script goes through all keywords you’ve marked with a certain label and checks their quality scores. Changes are logged and can be emailed to you. The script also writes all values into a spreadsheet, so that over time you’ll get a complete history.

The recommended use is to run the script regularly for your top keywords to see changes over time. You can use it for a quick diagnosis, but you can also use the history in the spreadsheet for an in depth analysis later.

The Setup

As I mentioned, this one is much easier than before. Again, three steps are needed:

  1. Get the script
  2. Run the script once to create the label
  3. Mark all keywords you want tracked with the label

The Script

Go to your AdWords account and click Bulk operations (formerly Automation) and then Scripts on the left hand side of the AdWords interface. Next, click Create script. Copy and paste the following code:

var email_address = "YOUR_EMAIL_HERE"; // Change this to be notified of changes
var label_name = "Track QS";

function main() {
  var labelIterator = AdWordsApp.labels().withCondition("Name = '" + label_name + "'").get();
  // If there is no label, this is the first time this script is running
    // Create the spreadsheet
    var spreadsheet = SpreadsheetApp.create("AdWords Quality Score Tracker");
    Logger.log("Spreadsheet for QS history created: " + spreadsheet.getUrl());
    var sheet = spreadsheet.getActiveSheet();
    sheet.setName("QS history");
    // Put in the table headings
    sheet.getRange(1, 1, 1, 6).setValues([["Date", "Campaign", "AdGroup", "Keyword", "Quality Score", "Change"]]);
    sheet.getRange(1, 1, 1, 6).setFontWeight("bold");
    // Create the label and save the spreadsheet's URL in the description
    AdWordsApp.createLabel(label_name, "Marks Keywords for QS tracking. Results are here: " + spreadsheet.getUrl() + " (keep URL in this description)", "#339999");
    Logger.log("Label '" + label_name + "' has been created. Apply this label to all keywords want to track. Then run the script again.");
    for(i = 1; i <= 10; i++){
      AdWordsApp.createLabel("QS: " + i, "Used for QS comparison.", "#ffffff");
    Logger.log("Ten additional labels ('QS: 1' to 'QS: 10') have been created. Those are needed by the script to compare old and new Quality Scores later. You can just ignore those.");
  // There is a label so get it and get the spreadsheet's URL from its description
  label =;
  var matches = new RegExp(' (http(s?)://[^ ]+) ').exec(label.getDescription());
  if (!matches || !matches[1]) {
    throw "Couldn't get spreadsheet URL from label description: " + label.getDescription();
  var spreadsheetUrl = matches[1];
  var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
  var alert_text = new Array();
  var history = new Array();
  var currentTime = new Date();
  var today = (currentTime.getMonth() + 1) + "/" + currentTime.getDate() + "/" + currentTime.getFullYear();
  var keywordIterator = label.keywords().get();
  var line_counter = 0;
  while (keywordIterator.hasNext()) {
    var keyword =;
    var current_quality_score = keyword.getQualityScore();
    keywordLabelsIterator = keyword.labels().withCondition("Name STARTS_WITH 'QS: '").get();
      keyword_label =;
      var matches = new RegExp('QS: ([0-9]+)$').exec(keyword_label.getName());
      old_quality_score = matches[1];
      old_quality_score = 0;
    // For the history also note the change or whether this keyword is new
    if(old_quality_score > 0) var change = current_quality_score - old_quality_score;
    else var change = "NEW";
    var row = [today, keyword.getCampaign().getName(), keyword.getAdGroup().getName(), keyword.getText(), current_quality_score, change];
    // If there is a previously tracked quality score and it's different from the current one...
    if(old_quality_score > 0 && current_quality_score != old_quality_score){
      // Make a note of this to log it and possibly send it via email later
      alert_text.push(current_quality_score + "\t" + old_quality_score + "\t" + change + "\t" + keyword.getText());
      // Remove the old label
    // Store the current QS for the next time by using a label
    keyword.applyLabel("QS: " + current_quality_score);
  if(line_counter == 0){
    Logger.log("Couldn't find any keywords marked for quality score tracking. To mark keywords for tracking, apply the label '" + label_name + "' to those keywords.");
  Logger.log("Tracked " + line_counter + " keyword quality scores. To select different keywords for tracking, apply the label '" + label_name + "' to those keywords.");
  // Store history
  var history_sheet = spreadsheet.getSheetByName('QS history');
  history_sheet.getRange(history_sheet.getLastRow()+1, 1, history.length, 6).setValues(history);  
  // If there are notes for alerts then prepare a message to log and possibly send via email
    var message = "The following quality score changes were discovered:\nNew\tOld\tChange\tKeyword\n";
    for(i = 0; i < alert_text.length; i++){
      message += alert_text[i] + "\n";
    // Also include a link to the spreadsheet
    message += "\n"
      + "The complete history is available at "
      + spreadsheet.getUrl();
    // If there is an email address send out a notification
    if(email_address && email_address != "YOUR_EMAIL_HERE"){
      MailApp.sendEmail(email_address, "Quality Score Tracker: Changes detected", message);

If you want to receive notifications, replace YOUR_EMAIL_HERE with your email address. If you don’t want any notifications, just ignore this.

Name the script “Quality Score Tracker v2” and click Save.

The First Run

Click the button Run script now. AdWords will ask you to authorize the script before it can be executed. The first run takes about ten seconds.

The script will now create the spreadsheet where the history will be logged. The spreadsheet’s URL can be found in the logs, after the first run. The script will also create some labels. The label Track QS will be needed for the next step. The other ten labels (QS: 1 to QS: 10) are needed by the script and should be ignored.

Apply the label Track QS to select keywords for quality score trackingThe Keywords

Go back to your campaigns and hit F5 to reload the AdWords interface (otherwise the newly created labels won’t be available). Go to the keyword column and select the keywords you want tracked. Apply the label Track QS to those keywords.

You can always come back to this and remove the label or apply it to new keywords at any time.

Using the Script

To run the script, click the button Run script now. You can also schedule the script to run regularly.

Execution takes a while, especially if it’s the first time you track new keywords. You don’t have to wait around for the script to finish. If changes are found, the script sends an email notification and also logs the notification (to be found under View details in the script logs). The first time the script runs it naturally won’t be able to find any changed quality scores.

If at any point you want to change the set of tracked keywords, simply use the label Track QS to change it.

To analyze the quality score history of your keywords, use the spreadsheet. Its URL can be found in the logs after the script has been run, but it will also be emailed to you (if you provided your address). You can also find the URL in the label’s description.

There’s nothing you can break in the spreadsheet, but I’d recommend to copy the contents into a new sheet if you want to play with the data. To focus on changes over time, filter out the zeros in the Change column.

Limitations and Warnings

Scripts have gotten a lot faster and it wouldn’t be a problem to track hundreds, maybe thousands of keywords with it. You could also run the script hourly. If you do it, you might go crazy.

We’ve done a lot of testing and tracking with keyword quality scores and found that they change quite frequently. Most changes don’t mean anything and you won’t be able to see any difference in performance. Chances are that too much tracking will give you a lot to worry about.

So my advice is still to just track the ten or twenty most important keywords and do it once a week or once a month.

For Scripts Developers: A new Feature

If you’ve written scripts before you might have noticed that this one does something that previously wasn’t possible with AdWords Scripts: It stores data in the account.

This is especially helpful for the spreadsheet’s URL. Previously there was no way for a script to “remember” information like this between runs: If you wanted to use the same spreadsheet twice you had to put its URL into the script’s code. That’s why you had to get the spreadsheet and save its URL in the first version.

In this version the script just checks if there a label called Track QS. If not, it creates the spreadsheet and then the label, storing the spreadsheet’s URL in the label’s description. Afterwards, the script just gets the label and with it the spreadsheet.

This way of storing information opens up new possibilities for AdWords Scripts. You could use labels to store information directly (as done with the ten QS labels), or you could do it indirectly, by storing URL’s in label descriptions. Of course, usually there won’t be a reason not to write a URL directly into the script, but it’s a nice trick if you want to share a script and make the setup as easy as possible.

It would also be possible to use label descriptions to store some other pieces of data, like the last time a script was executed. However, with room for only 200 characters, the storage capabilities of label descriptions are very limited.

Minor Update 7 / 7 / 2014

Changed the way the spreadsheet is opened from byId to byUrl. If the script works fine, there’s no need to upgrade.

About Martin Roettgerding
Martin Roettgerding is the head of SEM at SEO/SEM agency Bloofusion Germany. On Twitter he goes by the name @bloomarty, and you can find him regularly on #ppcchat.

  • Auke Jongbloed

    Great work Martin,

    Love the label solution. It is for me a great starting point in having a script with a more weighted overall score for campaigns and adgroups. I have found a great post from Frederick Vallaeys on (

    I would like to alter the script in showing me a historical quality score on campaign and adgroups. Not a real developer so it will take me some efford :)

    Thanks for posting this script.


  • pusche

    great stuff, thanks ! gonna run some tests on limitations in the next days… :)

  • Rick

    Call me crazy.. but I can’t seem to find a URL to access the data after the initial run + subsequent runs. Help!

    • Martin Roettgerding

      Hi Rick,
      I tried several times, but I can’t reproduce the error. Someone on Twitter reported something similar yesterday.

      Could you paste the description of the label “Track QS”? The spreadsheet’s URL should be in there, but apparently something has gone wrong so that the script can’t read it. With the description I should be able to find out what went wrong and fix the script.

      Sorry for the trouble…

      • Rick

        Hi there,

        Thanks for following up.

        Actually – this seemed to have fixed itself. What I did was: a) sign in and out of the account. b) wait for a period of time (overnight).

        Checked the label’s description and viola – it’s there (whereas it wasn’t before). Woohoo!

  • Hervé – Easy Internet Marketing

    THX for the great work Martin! Very usefull stuff!

  • Russ Savage

    I started a blog that focuses on free scripting help. I try to publish a new script at least once a week. Let me know if you have any ideas.

  • searchengineman

    This is an incredibly useful script. Thank you for taking the time to share and post this script over here. I found your article via PPCHero. I’m looking forward to seeing the changes in one of our campaigns (Only 30 keywords). If fluctuations are that varied..

    Is there a repository or collection of useful Adwords Scripts out there?
    I’m sure there must be some some solutions to problems we’ve never even thought about!

    It’s time like these I wish I was a coder.

  • Aurelien

    Awesome script! Thanks a lot!

  • Marc Pearson

    Thanks Martin! This is my first time tracking keywords, wish me luck.

  • Justin

    Great script! Thank you so much, this is a real time saver.
    I was wondering, is there a way to add additional labels to this script to track multiple groups of keywords?

  • John Seal

    Martin, the script is amazing!!! Thanks for providing a great way to track Quality Score. I am no developer and I’d like to add the average position to the tracking script. Can you coach me on the best way to do that?

  • David

    Hey there Martin,

    thanks a lot for your script and the understandable instruction! It looks like an easy but efficient method to track your QS. Plus I am completly new into AdWords scripts and eager to learn more about them.

    All the best,


  • David from Hungary

    Hi Martin,

    I see that you are very good in this AdWords script thing.

    Maybe you can help me:
    I manage campaigns for an online travel agency and I would like to create a script which checks hourly an online spreadsheet to see if some offer changed (simply we give one cell for each destinations ad to check whether the cell content is different or not), if it is different, the script repleaces the text for the new offer.

    I think it is pretty simple compared to this quality score tracker script.

    Thank you

  • Peter


    This looks great however I am getting “Couldn’t get spreadsheet ID from label description: (line 30)”.

    I have probably missed a step but can’t quite see the wood for the trees! Any help would be most welcome.

    • Goran Giertz

      @Peter, where you able to sort out this problem. I am getting the same one?

      • Martin Roettgerding

        Looks like the spreadsheet hasn’t been stored in the label’s description. Maybe there was a problem with creating the spreadsheet…

        Try this: Delete the label “Track QS” and the ten QS labels, then run the script again. This should reset everything and hopefully all will work on the second run.

  • Tim

    I was able to get the script set up and running on a weekly schedule. It runs fine, and I can view results in the log details as well as the spreadsheet, but am not receiving emails despite entering my email address in both YOUR_EMAIL_HERE sections (I did double check that I’d entered it properly, as well as checking the spam folder). Any idea what may be causing the email to not go through? Not a huge deal as I can still view results in the browser, but was curious if others have had issues and had any insight to troubleshoot/solve. My knowledge of coding is very much on the basic end.

  • Martin Roettgerding

    Hi Tim,
    This should work…
    The email comes from the AdWords account’s email address. Maybe you have an automated rule in your email program that sorts out those mails?

    Oh, wait – both YOUR_EMAIL_HERE sections? The second YOUR_EMAIL_HERE in line 88 shouldn’t be changed. This part checks whether the email address is different from the string “YOUR_EMAIL_HERE”. Just change it back and you should be fine.

  • Roy Smid

    Hi Martin,

    Respect, this is great work! It has the keywordlevel detail I did not find in other scripts, but mostly the label and email function are very usefull!

    Keep up the good work.



  • Maroesja te Hennepe

    Hi Martin,

    Great Script! Thanks a lot. Is there a possibility to track the CTR next to the Quality score? As the CTR has a huge influence on the CTR it would be good to know if these changed together with the QS.

    Thanks in Advance!


    • Martin Roettgerding

      Hi Maroesja,

      You could add a column and track CTR, but the problem is that CTR refers to a time period whereas Quality Score refers to “now”.

      Another problem is that CTR alone doesn’t mean much. In connection to Quality Score you’d have to normalize by position, but all you’ll get from AdWords is average position. You’d also have to look at CTR on Google, but not on Search Partners or other networks. (From experience I know that analysts will gladly ignore the unsolvable first problem and simply forget to account for the second one.)

      However, since all other stats are available all the time, there’s no need to track them like QS. You can just pull historical CTR, average position and all the other stuff at any time.

      • Maroesja te Hennepe

        Hi Martin!

        Thanks for the quick responce!
        Indeed with the historical data it would work as well.

        Kind Regards,


  • http://Greatstuff Margo

    This one is phenomenal! I have waited for so long and tracking QS manually in excel! I love it.

  • Guest

    Is anyone having issues with this script now? I have been using it and variations of it for over a year now and just recently have been experiencing issues.

    When adding the script to a new account the initial run works, creating all 11 labels and the google spreadsheet. The issue is when I run the script again after tagging keywords. When I do this I get the following error:
    “Couldn’t get spreadsheet ID from label description: Marks Keywords for QS tracking. Results are here:{spreadsheet id removed for postin} (keep URL in this description) (line 35)”

    • Martin Röttgerding

      This was sometimes a problem in an old version of the script. I updated the script above in July to fix this. If you are having this problem, please get the code again and copy it over your existing script (you can leave lines 1 and 2).

      Let me know if there are still any problems.

  • alexpeerenboom

    Thank you for this script. It runs successfully the first to apply the labels. However, each time I run it after that I get the following error: “The label QS: null does not exist. (line 65)”

    What could this be?

    • Martin Röttgerding

      Hi Alex,
      That one is new… instead of “null” there should be the keyword’s current quality score. This would mean that the script couldn’t get a quality score from that keyword… or that the keyword simply doesn’t have one. Can you check if there’s anything special about the keyword in question? Can you get a quality score for this keyword manuelly (via status bubble or QS column)?

  • Jonny Walsh

    Love the idea of this script, however when i try to run i get the following error in the log Document 1ODMBC73xHOg4o_BDuT5jFYfmji1J4gtcTbo6oqFNt10 is missing (perhaps it was deleted?) (line 33)

    • Lee

      I’m getting this same problem – anybody found a way round this?

  • Marya Conzalyes

    Nice Script, I hoped it worked for me but it didn’t. I got error “Couldn’t get spreadsheet URL from label description”

    • Max

      I have the same issue.

  • Redline Digital Marketing

    Great script. However, weighted quality score is much more important to me. Have you developed anything to generate this weighted average?

  • Eric Hitchman

    Hey Martin,

    Wanted to follow up on a question from below. With the two main issues “normalizing for average position” and the “search partners” problem in mind, would you agree that at the least, pulling in impressions adds value as it relates to a ‘weighted quality score’?

    Either way, could you give some guidance on how to simply add in a column to the spreadsheet, “impressions” as well as pulling in the corresponding value for the day? If the script were to run at say, 10pm every evening, I think I would be satisfied knowing that although the impression counts wouldn’t be perfectly representative (the last KW that runs may have accrued a few more impressions than the first keyword, while the script runs) it would be close enough to help with some weightings.

    What do you think?


    • Martin Röttgerding

      Hi Eric,
      Adding impressions should be easy to do. Basically you would have to add the impression stats to the row in line 55:
      var row = […, change, keyword.getStatsFor(“TODAY”).getImpressions() ];

      Then in line 75, change the 6 to a 7.

      That should be it. I haven’t tested this, though…

      As for the merit of a weighted QS: I have my doubts that keywords QS even says much, so weighting it doesn’t add much value for me. This age old debate aside, this script only pulls in some keywords and not all of your keywords. If you are looking for such a thing as account QS, this only takes you so far. There are other scripts for QS tracking out there try to go after account QS. I believe Frederick Vallaeys has published such a script somewhere.

  • Eric Hitchman

    Another quick question… Am I right in saying that if I wanted to store the google sheet within a specific directory on a drive account, I could simply copy & paste the sheet/workbook that the script creates to the new location, and manually change the “Track QS” label description to include the NEW locations sheet URL?

    Hope that’s clear, let me know what you think!

    • Martin Röttgerding

      Yes, this should be no problem.

      Good night from Germany


  • RobMarsland

    Thanks for the useful script! I had too many campaigns to manually go through and label the keywords of, so to speed things up and only record all active keywords, replaced line 38:

    var keywordIterator = AdWordsApp.keywords()
    .withCondition(‘CampaignName STARTS_WITH(“0[13]-“)’)
    .withCondition(‘Status = ENABLED’)
    .withCondition(‘AdGroupStatus = ENABLED’)
    .withCondition(‘CampaignStatus = ENABLED’)

  • Daniel Zrust

    Hi Martin,
    Great post. I have 1 million KWs in the account – a lot of longtail KWs. Could we run this script only on KWs which received more than 0 impressions on that day? (that would be few hundred KWs max)

    I assume the condition would be somehow similar to the post below…


    • Martin Röttgerding

      Hey Daniel,
      Yes, that should be no problem. You could switch out line 38, where the keywords are selected by the label. Change that to

      var keywordIterator = AdWordsApp.keywords().withCondition(“Impressions > 0”).forDateRange(“YESTERDAY”).get();

      I haven’t tested this, but this could be enough…
      I’m not sure how long this will take for a run, depends on the number of keywords. 30 minutes may not be enough…

  • Diana

    Hey Martin

    Great script! I was wondering how would I tweak the code so that every single time there is a change in the code it creates a new column? I want to be able to keep a historical record of the data.

    Thank you!

    • Martin Röttgerding

      Hi Diana,
      I’m not sure what you mean by changes in the code. If you are making changes to the code you could just manually add a line to the spreadsheet (like “made code change here”) … but I don’t understand why you would need to make notes of code changes…

      • Diana

        Hey Martin,

        I was referring to the Change column. I wanted to create a new Change column every single time the Quality Score changed so I can create a sparkline in Google Sheets and track it’s up and down progress. :) Did that make sense?

        • Martin Röttgerding

          Hi Diana,
          I think I finally got it. This would require some work to rewrite parts of the script. Right it just writes line after line, which is fairly simple. To do this with columns there would need to be some columns for keyword, campaign, and adgroup first. Then, whenever the script runs, it would have to see if there’s already a line for the current keyword and put the new QS in the right line, but in a new column. You’d probably want to track all QS, not just changes, in order to avoid gaps in your sparklines.

          Can be done, no problem, but you’d have to invest some work.

  • kostas

    hi martin

    Preview completed (00:05)



    Change toType of changeCurrent valueNew value (if run now)Track QSAdd Label—-QS: 1Add Label—-QS: 2Add Label—-QS: 3Add Label—-QS: 4Add Label—-QS: 5Add Label—-QS: 6Add Label—-QS: 7Add Label—-QS: 8Add Label—-

    And i puted spreadsheet there:

    // There is a label so get it and get the spreadsheet’s URL from its description
    label =;
    var matches = new RegExp(‘ ([^ ]+) ‘).exec(label.getDescription());
    if (!matches || !matches[1]) {
    throw “Couldn’t get spreadsheet URL from label description: ” + label.getDescription();

    i go to campaing to see the labels but there are not exict in the list can you help me martin

    • Martin Röttgerding

      Hi Kostas,
      Don’t the spreadsheet URL in the script. Just copy it again and run it. It takes care of the spreadsheet automatically.

      • kostas

        hi martin
        thank you for your answer i will check it :)

      • Jenny Londa

        i copied and pasted the original and ran it again but still getting the same error:

        Couldn’t get spreadsheet URL from label description: (line 30)

        • Martin Röttgerding

          Hi Jenny,
          I can’t diagnose the problem from afar… the best + easiest solution should be to start over: Delete the labels from your account (at least the track QS label), get the script (don’t make any changes after line 2) and run it again.

          • Tony

            Hi Martin,

            I also get this error, attached:

            “Couldn’t get spreadsheet URL from label description: (line 30)”

            I have removed the labels from the keywords.

            I logged out and back in, and ran the script again.

            And get the error again.

            Any help is gratefully appreciated.

          • Martin Röttgerding

            Hi Tony,
            Could you paste the label’s description here? You can find it in AdWords under “Labels”. The label’s name is “Check QS”. With this I should hopefully be able to see the problem…

          • Tony

            Hi Martin,

            Thanks for your fast reply!

            The label I applied to keywords, following the instructions, is:

            Track QS

          • Martin Röttgerding

            Hi Tony,
            Yes I know 😉 But I need the label’s full description. There should be a URL to a spreadsheet included. Could you paste the (complete) description?

          • Tony
          • Tony

            And line 2 of the script shows:

            var label_name = “Track QS”;

          • Tony

            Hi Martin,

            Just wondering if that is the information you are asking for?

            Many thanks,
            – Tony

          • Martin Röttgerding

            Hi Tony,
            The complete description from the label (not just the URL) would be perfect. In any case it’ll take a few days I’m afraid… sorry, I’m a bit under water this week…

          • Tony

            No problem Martin, no rush :-)

            Can tell me exactly where to get the complete description from the label? I don’t think I have followed your instructions properly.

  • Anrich Brummer

    Hi Martin,

    First of all thanks for sharing this great script! So silly how difficult it is to track this metric at scale, you would think the adwords team could come up with something.

    I am having timeout issues with the script (most likely because I am attempting to run it across too many kewords), any suggestions on how I could resolve the issue? I guess it could break up my campaigns into buckets and run the scripts across smaller groups of keywords, but this makes managing & checking everything a bit tricky.

    I am also getting an error message when it tries to make changes > “This label is already associated with some of the selections.Attempted new value: QS: 3”

    I have a feeling its because I adjusted the script using a suggestion from one of the comments on this blog from RobMarsland (thanks for sharing Rob):
    Thanks for the useful script! I had too many campaigns to manually go through and label the keywords of, so to speed things up and only record all active keywords, replaced line 38:

    var keywordIterator = AdWordsApp.keywords()
    .withCondition(‘Status = ENABLED’)
    .withCondition(‘AdGroupStatus = ENABLED’)
    .withCondition(‘CampaignStatus = ENABLED’)



    • Martin Röttgerding

      Hi Anrich,
      Since I published the script there have been some new features. It might be possible to speed this up, but it would require to rewrite a bigger part of the script. I’d like to give it a try but I can’t promise anything… let’s see what happens on the weekend.

      • Anrich Brummer

        Hi Martin,
        Thanks for responding, I can see some others have had the same issue but it might be a bit difficult to solve. Once again thanks for sharing this great script!

  • James Gonzales

    Hello Martin,
    I really appreciate the script. It will save us time to record the QS for all keywords. Unfortunately, I’ve been getting error every time I run the script. The initial run is always successful. Since I have thousands of keywords, I always get the following error:

    “Exceeded maximum execution time”.

    I understand that AdWords will cancel automatically if it will exceed more than 30 minutes.

    Execution time limits
    AdWords scripts
    AdWords scripts for advertiser accounts can execute for a maximum of 30 minutes, after which they will be cancelled. All of the changes made before the script was cancelled will be applied.


    How can I solve this issue? Thank You!