Quality Lens records the Quality Score of every keyword once a day, together with its three components, and shows what changed since the last run. Because Google only shows today’s score, the history this script builds is the only way to see trends — and to find the low-quality keywords that actually cost money.

How it works

Keywords (enabled only, or enabled and paused) are read with quality_info.quality_score, expected CTR, ad relevance and landing-page experience plus impressions and cost for the last days days. The previous snapshot is read from _snapshot and every keyword whose score moved by at least change_threshold lands in the Changes tab with old and new values. An impression-weighted and a simple average QS, the cost on QS ≤ 4 and its share are computed; the 25 most expensive low-quality keywords are listed. One history row per day is upserted, so re-running the same day does not duplicate.

What it writes to the Sheet

  • Summary — metrics window, keywords with a QS, impression-weighted and simple average QS, cost and share on QS ≤ 4, number of changes, distribution by QS, weakest component and the most expensive low-quality keywords.
  • Keywords — one row per keyword: campaign, ad group, keyword, match type, QS, expected CTR, ad relevance, landing-page experience, impressions, cost.
  • Changes — date, keyword, previous QS, QS, Δ and components for every move ≥ change_threshold.
  • _history — one row per day (kept for keep_history_days); _snapshot — yesterday’s scores for the diff.

Setup

  1. Create a new, empty Google Sheet with the Google account you will authorise the script with and copy its URL. Do not reuse a Sheet from another script — each Sheet is bound to one script and one Google Ads account.
  2. In Google Ads open Tools → Scripts, create a new script, paste the whole code and give it a name.
  3. Set CONFIG.spreadsheet_url to the Sheet URL. If you run the script from a manager (MCC) account, also set CONFIG.account_id to the client account ID (e.g. 123-456-7890); without it the script stops with a clear error.
  4. Optional: fill CONFIG.email (comma-separated recipients) and/or CONFIG.slack_webhook. Keep the webhook in the code only — it is never written to the Sheet.
  5. Authorise the script, run Preview once to check the log, then Run. The first run creates the tabs, the header rows and the _settings tab.
  6. Schedule it: daily, at 04:00 account time or later.
  7. Optional: download the Excel dashboard (button above), open its Read me tab and paste the Sheet tabs as described — the column layout matches the script output one-to-one.

Parameters

ParameterDescriptionDefault
spreadsheet_urlURL of the empty Google Sheet the script writes to.'YOUR_SPREADSHEET_URL'
emailComma-separated recipients; blank = no e-mail. Can be overridden in _settings.''
account_idClient account ID; required only when running from a manager (MCC) account.''
daysLength of the analysis window in days.30
enabled_onlytrue = enabled keywords only; false = enabled and paused.true
min_impressionsMinimum impressions in the window for a keyword to be included.1
campaign_containsOnly campaigns whose name contains this text.''
change_thresholdMinimum QS move (absolute) to appear in Changes and the e-mail.2
keep_history_daysDays of _history rows kept.400
max_rows_writtenRow cap per table; when exceeded a truncation note is added.5000
All values live in the CONFIG block at the top of the script; keys that also exist in the _settings tab can be changed there.

The _settings tab

On the first run the script creates a _settings tab (Key / Value / Notes). Values there override the matching CONFIG entries, so day-to-day tuning happens in the Sheet without editing code. Numbers must be written without thousands separators and with at most two decimals (0.5 or 0,5). The rows script and account mark the Sheet’s owner: if another script or another account opens the same Sheet, it stops before writing anything. Internal tabs starting with an underscore (_settings, _history, _snapshot, _state, _baseline) are language-independent, so switching between the EN/TR/DE builds keeps the stored state.

Notifications

If email is set, each run sends the weighted QS, the number of moves, the biggest drops and gains and the most expensive low-quality keywords with a link to the Sheet. There is no Slack option in this script.

Notes and limits

  • The dashboard’s 7-day comparison cards need at least 8 days of history; before that they show an error — expected, not a bug.
  • Old v1 snapshot keys are migrated automatically on the first run, so changes are not lost when upgrading.
  • Every report tab carries a small attribution cell (H1:K1, warning-only protection) and the code header keeps a copyright line; both may be removed by anyone who edits the source.