Facebook Ads vs boosted posts β which should you use? We compare targeting, cost...
Read MoreCustom Google Ads Script Development
Custom Google Ads Script Development
Bespoke automation scripts that save you time, cut wasted spend, and scale your campaigns beyond what manual management can achieve.
Google Ads scripts are one of the most powerful yet underused features in the platform. They let you automate repetitive tasks, build custom alerts, generate advanced reports, and optimise bids at a scale that would be impossible manually. Our team builds bespoke scripts tailored to your exact requirements.

Discuss Your Script
Talk to our scripting team about automating your campaigns
0115 727 0038

Unlock the Full Power of Google Ads Scripts
Google Ads gives you access to a built-in scripting engine that can automate almost anything in your account. The problem is that most businesses never touch it because it requires JavaScript knowledge and a deep understanding of the Google Ads API. That is where we come in.
Our developers have built hundreds of custom scripts for businesses across the UK β from simple budget alerts to complex bid algorithms that adjust every hour based on weather, stock levels, or competitor pricing. Every script is built from scratch to solve your specific challenge, tested thoroughly, and deployed with full documentation.
The results speak for themselves. Clients using our custom scripts typically see 15-30% improvement in ROAS within the first quarter, simply by eliminating waste and reacting to performance signals faster than any human could. Scripts run 24/7, never take a day off, and execute in milliseconds.
Whether you need a one-off script to solve a specific problem or an ongoing automation suite that runs your campaigns on autopilot, our team can build it. We work with accounts of all sizes, from Β£500/month local businesses to six-figure enterprise campaigns.
Get a free script consultation for your Google Ads account
0115 727 0038
What We Build
From simple alerts to complex automation suites, here are the four main areas where our scripts deliver the biggest impact on your Google Ads performance.

Automated Bid Management
Custom bid scripts that adjust your CPCs based on time of day, device, location, conversion rate, and any other signal that matters to your business. React to performance changes in real time instead of waiting for your next manual review.

Custom Reporting & Alerts
Automated reports delivered to your inbox, Slack, or Google Sheets. Set up anomaly detection that alerts you instantly when spend spikes, conversions drop, or any metric moves outside your defined thresholds. Never miss a problem again.

Feed & Campaign Automation
Auto-generate campaigns, ad groups, and ads from product feeds, spreadsheets, or databases. Ideal for e-commerce businesses with large inventories or service businesses with multiple locations that need dynamic campaign structures.

Budget Pacing & Optimisation
Scripts that monitor your daily and monthly spend in real time, automatically adjusting bids or pausing campaigns to prevent overspend. Perfect for agencies managing multiple client budgets or businesses with strict monthly caps.
Automated reports delivered to your inbox, Slack, or Google Sheets. Set up anomaly detection that alerts you instantly when spend spikes, conversions drop, or any metric moves outside your defined thresholds. Never miss a problem again.

Talk to our scripting team about automating your campaigns
0115 727 0038

Trusted by Hundreds of UK Businesses
Local SEO Checklist for Small Businesses in 2026
A practical local SEO checklist for small businesses in 2026. Optimise your Goog...
Read MoreHow AI Is Changing Google Ads in 2026
Discover how AI is transforming Google Ads in 2026 with smart bidding, Performan...
Read MoreGet a free script consultation for your Google Ads account
0115 727 0038
Free Google Ads Scripts
Copy and paste these scripts directly into your Google Ads account. No charge, no sign-up required.
Search Query Clean-Up Script
Automatically find wasteful search terms and add them as negative keywords
This script analyses your search term report over the past 30 days and identifies terms that have received clicks but generated zero conversions. It automatically adds them as negative keywords at campaign level, saving you from paying for irrelevant traffic.
How to use: Copy the script below, go to Google Ads > Tools > Scripts, paste it in, update the CONFIG settings at the top, and schedule it to run weekly.
- Looks back 30 days by default (configurable)
- Only flags terms with 5+ clicks and zero conversions
- Adds as exact match negatives at campaign level
- Sends you an email summary of what was added
// Search Query Clean-Up Script // Free script by DPOM | https://www.dpom.co.uk // Custom Google Ads scripts: https://www.dpom.co.uk/google-ads-scripts/ // Finds search terms with clicks but no conversions and adds as negatives // === CONFIG === var DAYS_TO_LOOK_BACK = 30; var MIN_CLICKS = 5; var MIN_COST = 10; // minimum spend in account currency var EMAIL = 'your@email.com'; // change to your email // === END CONFIG === function main() { var dateRange = getDateRange(DAYS_TO_LOOK_BACK); var report = AdsApp.report( 'SELECT Query, CampaignName, CampaignId, Clicks, Cost, Conversions ' + 'FROM SEARCH_QUERY_PERFORMANCE_REPORT ' + 'WHERE Conversions = 0 AND Clicks >= ' + MIN_CLICKS + ' AND Cost >= ' + (MIN_COST * 1000000) + ' DURING ' + dateRange ); var rows = report.rows(); var negatives = []; while (rows.hasNext()) { var row = rows.next(); var query = row['Query']; var campaignName = row['CampaignName']; var clicks = parseInt(row['Clicks']); var cost = parseFloat(row['Cost']) / 1000000; var campaigns = AdsApp.campaigns() .withCondition('Name = "' + campaignName + '"') .get(); if (campaigns.hasNext()) { var campaign = campaigns.next(); campaign.createNegativeKeyword('[' + query + ']'); negatives.push({query: query, campaign: campaignName, clicks: clicks, cost: cost.toFixed(2)}); } } if (negatives.length > 0) { var body = 'Added ' + negatives.length + ' negative keywords:\n\n'; negatives.forEach(function(n) { body += n.query + ' | ' + n.campaign + ' | ' + n.clicks + ' clicks | Β£' + n.cost + '\n'; }); MailApp.sendEmail(EMAIL, 'Search Query Clean-Up: ' + negatives.length + ' negatives added', body); } Logger.log('Done. Added ' + negatives.length + ' negative keywords.'); } function getDateRange(days) { var end = new Date(); var start = new Date(); start.setDate(start.getDate() - days); return formatDate(start) + ',' + formatDate(end); } function formatDate(d) { var y = d.getFullYear(); var m = (d.getMonth() + 1).toString().padStart(2, '0'); var day = d.getDate().toString().padStart(2, '0'); return y + m + day; }
Quality Score Tracker
Log keyword quality scores to Google Sheets daily and track changes over time
Quality Score directly affects your CPCs and ad position, but Google Ads does not show you historical data. This script logs your keyword quality scores to a Google Sheet every day, so you can track improvements and spot problems.
How to use: Create a Google Sheet, copy its URL into the CONFIG section, then schedule this script to run daily.
- Logs keyword, campaign, quality score, expected CTR, ad relevance, and landing page experience
- Adds a new row each day per keyword
- Only tracks keywords with impressions (ignores paused/zero-volume)
// Quality Score Tracker // Free script by DPOM | https://www.dpom.co.uk // Custom Google Ads scripts: https://www.dpom.co.uk/google-ads-scripts/ // Logs quality scores to Google Sheets daily // === CONFIG === var SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit'; var SHEET_NAME = 'Quality Scores'; var MIN_IMPRESSIONS = 10; // === END CONFIG === function main() { var ss = SpreadsheetApp.openByUrl(SPREADSHEET_URL); var sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { sheet = ss.insertSheet(SHEET_NAME); sheet.appendRow(['Date', 'Campaign', 'Ad Group', 'Keyword', 'Quality Score', 'Expected CTR', 'Ad Relevance', 'Landing Page']); } var today = Utilities.formatDate(new Date(), AdsApp.currentAccount().getTimeZone(), 'yyyy-MM-dd'); var keywords = AdsApp.keywords() .withCondition('Status = ENABLED') .withCondition('CampaignStatus = ENABLED') .withCondition('AdGroupStatus = ENABLED') .forDateRange('LAST_30_DAYS') .withCondition('Impressions >= ' + MIN_IMPRESSIONS) .get(); var rows = []; while (keywords.hasNext()) { var kw = keywords.next(); var qs = kw.qualityScore(); rows.push([ today, kw.getCampaign().getName(), kw.getAdGroup().getName(), kw.getText(), qs || 'N/A', kw.getQualityScoreComponent('EXPECTED_CTR') || 'N/A', kw.getQualityScoreComponent('AD_RELEVANCE') || 'N/A', kw.getQualityScoreComponent('LANDING_PAGE_EXP') || 'N/A' ]); } if (rows.length > 0) { sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows); } Logger.log('Logged ' + rows.length + ' keywords.'); }
Broken URL Checker
Scan all your ad and keyword URLs for 404 errors and broken links
Broken landing pages waste your ad spend and frustrate potential customers. This script checks every final URL in your active ads and keywords, and emails you a report of any that return errors (404, 500, timeouts, etc.).
How to use: Paste into Google Ads Scripts, set your email, and schedule to run daily or weekly.
- Checks all active ad final URLs and keyword final URLs
- Detects 404, 500, redirect loops, and timeout errors
- Emails a summary only when problems are found
// Broken URL Checker // Free script by DPOM | https://www.dpom.co.uk // Custom Google Ads scripts: https://www.dpom.co.uk/google-ads-scripts/ // Checks all ad URLs for errors var EMAIL = 'your@email.com'; function main() { var urls = {}; var errors = []; // Collect URLs from active ads var ads = AdsApp.ads() .withCondition('Status = ENABLED') .withCondition('CampaignStatus = ENABLED') .withCondition('AdGroupStatus = ENABLED') .get(); while (ads.hasNext()) { var ad = ads.next(); var url = ad.urls().getFinalUrl(); if (url && !urls[url]) { urls[url] = ad.getAdGroup().getCampaign().getName() + ' > ' + ad.getAdGroup().getName(); } } // Check each URL var urlList = Object.keys(urls); Logger.log('Checking ' + urlList.length + ' unique URLs...'); for (var i = 0; i < urlList.length; i++) { try { var response = UrlFetchApp.fetch(urlList[i], { muteHttpExceptions: true, followRedirects: true, validateHttpsCertificates: false }); var code = response.getResponseCode(); if (code >= 400) { errors.push({url: urlList[i], code: code, location: urls[urlList[i]]}); } } catch (e) { errors.push({url: urlList[i], code: 'TIMEOUT/ERROR', location: urls[urlList[i]]}); } } if (errors.length > 0) { var body = 'Found ' + errors.length + ' broken URLs:\n\n'; errors.forEach(function(e) { body += '[' + e.code + '] ' + e.url + '\n Location: ' + e.location + '\n\n'; }); MailApp.sendEmail(EMAIL, 'Google Ads: ' + errors.length + ' Broken URLs Found', body); Logger.log('Sent alert: ' + errors.length + ' broken URLs'); } else { Logger.log('All ' + urlList.length + ' URLs are working correctly.'); } }
Monthly Budget Pacing Alert
Get email alerts when campaigns are over or under-spending against monthly targets
This script calculates where each campaign should be in terms of monthly spend based on the current date, then alerts you if any campaign is more than 15% over or under pace. Prevents end-of-month surprises.
How to use: Set your monthly budgets in the CONFIG object (campaign name: monthly budget), then schedule to run daily.
- Compares actual spend against expected pace for the month
- Configurable alert threshold (default 15%)
- Emails only when a campaign is off-pace
// Monthly Budget Pacing Alert // Free script by DPOM | https://www.dpom.co.uk // Custom Google Ads scripts: https://www.dpom.co.uk/google-ads-scripts/ // === CONFIG === var MONTHLY_BUDGETS = { 'Campaign Name 1': 1000, 'Campaign Name 2': 500, 'Campaign Name 3': 2000 }; var ALERT_THRESHOLD = 0.15; // 15% over/under var EMAIL = 'your@email.com'; // === END CONFIG === function main() { var today = new Date(); var dayOfMonth = today.getDate(); var daysInMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0).getDate(); var pacePercent = dayOfMonth / daysInMonth; var alerts = []; for (var name in MONTHLY_BUDGETS) { var monthlyBudget = MONTHLY_BUDGETS[name]; var expectedSpend = monthlyBudget * pacePercent; var campaigns = AdsApp.campaigns() .withCondition('Name = "' + name + '"') .forDateRange('THIS_MONTH') .get(); if (campaigns.hasNext()) { var stats = campaigns.next().getStatsFor('THIS_MONTH'); var actualSpend = stats.getCost(); var diff = (actualSpend - expectedSpend) / expectedSpend; if (Math.abs(diff) > ALERT_THRESHOLD) { var status = diff > 0 ? 'OVERSPENDING' : 'UNDERSPENDING'; alerts.push(name + ': ' + status + ' | Spent: Β£' + actualSpend.toFixed(2) + ' | Expected: Β£' + expectedSpend.toFixed(2) + ' | Diff: ' + (diff * 100).toFixed(1) + '%'); } } } if (alerts.length > 0) { var body = 'Budget pacing alerts (Day ' + dayOfMonth + ' of ' + daysInMonth + '):\n\n'; body += alerts.join('\n'); MailApp.sendEmail(EMAIL, 'Budget Pacing Alert: ' + alerts.length + ' campaigns off-pace', body); } Logger.log(alerts.length + ' alerts sent.'); }
Low CTR Ad Pauser
Automatically pause underperforming ads that drag down your account quality
Running ads with a low click-through rate hurts your quality score and wastes impressions. This script identifies ads that have had enough impressions to judge but are underperforming, and pauses them automatically. It only pauses when at least one other ad in the same ad group is still active.
How to use: Set your minimum CTR threshold and impression count, then schedule weekly.
- Only pauses ads with statistically significant data (1,000+ impressions by default)
- Checks that at least one other active ad remains in the ad group
- Sends a summary email of paused ads
// Low CTR Ad Pauser // Free script by DPOM | https://www.dpom.co.uk // Custom Google Ads scripts: https://www.dpom.co.uk/google-ads-scripts/ // === CONFIG === var MIN_CTR = 0.02; // 2% minimum CTR var MIN_IMPRESSIONS = 1000; var DATE_RANGE = 'LAST_30_DAYS'; var EMAIL = 'your@email.com'; // === END CONFIG === function main() { var paused = []; var ads = AdsApp.ads() .withCondition('Status = ENABLED') .withCondition('CampaignStatus = ENABLED') .withCondition('AdGroupStatus = ENABLED') .withCondition('Impressions >= ' + MIN_IMPRESSIONS) .withCondition('Ctr < ' + MIN_CTR) .forDateRange(DATE_RANGE) .get(); while (ads.hasNext()) { var ad = ads.next(); var adGroup = ad.getAdGroup(); // Check other active ads exist in this ad group var otherAds = adGroup.ads() .withCondition('Status = ENABLED') .get(); var activeCount = 0; while (otherAds.hasNext()) { otherAds.next(); activeCount++; } if (activeCount > 1) { var stats = ad.getStatsFor(DATE_RANGE); ad.pause(); paused.push({ campaign: adGroup.getCampaign().getName(), adGroup: adGroup.getName(), headline: ad.getHeadlinePart1(), ctr: (stats.getCtr() * 100).toFixed(2) + '%', impressions: stats.getImpressions() }); } } if (paused.length > 0) { var body = 'Paused ' + paused.length + ' low-CTR ads:\n\n'; paused.forEach(function(p) { body += p.headline + ' | ' + p.campaign + ' > ' + p.adGroup + ' | CTR: ' + p.ctr + ' | ' + p.impressions + ' impr.\n'; }); MailApp.sendEmail(EMAIL, 'Low CTR Ad Pauser: ' + paused.length + ' ads paused', body); } Logger.log('Paused ' + paused.length + ' ads.'); }
Competitor Auction Insights Tracker
Log your competitors' impression share, overlap rate, and position data to Google Sheets daily
Google only shows you a snapshot of auction insights. This script captures competitor data every single day and logs it to a Google Sheet, so you can see trends over weeks and months. Spot when a new competitor enters the market, when someone ramps up spend, or when you are losing ground.
What you get:
- Daily log of every competitor's impression share, overlap rate, position above rate, and outranking share
- Historical trends you can chart in Google Sheets
- Early warning when a competitor significantly increases activity
- Data broken down by campaign for granular insights
// Competitor Auction Insights Tracker // Free script by DPOM | https://www.dpom.co.uk // Custom Google Ads scripts: https://www.dpom.co.uk/google-ads-scripts/ // === CONFIG === var SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit'; var SHEET_NAME = 'Auction Insights'; var DAYS = 7; // look-back window for each snapshot // === END CONFIG === function main() { var ss = SpreadsheetApp.openByUrl(SPREADSHEET_URL); var sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { sheet = ss.insertSheet(SHEET_NAME); sheet.appendRow(['Date', 'Campaign', 'Competitor', 'Impression Share', 'Overlap Rate', 'Position Above Rate', 'Outranking Share', 'Top of Page Rate']); } var today = Utilities.formatDate(new Date(), AdsApp.currentAccount().getTimeZone(), 'yyyy-MM-dd'); var report = AdsApp.report( 'SELECT CampaignName, Domain, SearchImpressionShare, ' + 'SearchOverlapRate, SearchPositionAboveRate, ' + 'SearchOutrankingShare, SearchTopImpressionShare ' + 'FROM AUCTION_INSIGHTS_REPORT ' + 'DURING LAST_' + DAYS + '_DAYS' ); var rows = report.rows(); var output = []; while (rows.hasNext()) { var row = rows.next(); var domain = row['Domain']; // Skip your own domain if (domain === 'Your domain') continue; output.push([ today, row['CampaignName'], domain, row['SearchImpressionShare'], row['SearchOverlapRate'], row['SearchPositionAboveRate'], row['SearchOutrankingShare'], row['SearchTopImpressionShare'] ]); } if (output.length > 0) { sheet.getRange(sheet.getLastRow() + 1, 1, output.length, output[0].length).setValues(output); } Logger.log('Logged ' + output.length + ' competitor rows for ' + today); }
Time-of-Day Bid Adjuster
Automatically set hourly bid adjustments based on your actual conversion data
This is a powerful script that analyses your conversion data by hour of day over the past 90 days, calculates the optimal bid modifier for each hour, and applies the adjustments to your ad schedule automatically. Hours that convert well get boosted, hours that waste money get reduced.
Why this matters: Most accounts have huge performance swings throughout the day. A lead gen business might convert 3x better at 9am than 9pm, but most advertisers never set up hour-by-hour bid adjustments because it takes too long to maintain manually. This script does it for you.
- Analyses 90 days of hourly conversion data per campaign
- Calculates bid multipliers based on each hour's conversion rate vs. average
- Applies adjustments with configurable caps (default -50% to +30%)
- Logs all changes to a Google Sheet for transparency
// Time-of-Day Bid Adjuster // Free script by DPOM | https://www.dpom.co.uk // Custom Google Ads scripts: https://www.dpom.co.uk/google-ads-scripts/ // === CONFIG === var SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit'; var MAX_BID_INCREASE = 0.30; // +30% max var MAX_BID_DECREASE = -0.50; // -50% max var MIN_CLICKS_PER_HOUR = 20; // need enough data to judge var CAMPAIGN_LABEL = 'HourlyBids'; // only applies to labelled campaigns // === END CONFIG === function main() { var campaigns = AdsApp.campaigns() .withCondition('Status = ENABLED') .withCondition('LabelNames CONTAINS_ANY ["' + CAMPAIGN_LABEL + '"]') .get(); var ss = SpreadsheetApp.openByUrl(SPREADSHEET_URL); var logSheet = ss.getSheetByName('Hourly Bid Log') || ss.insertSheet('Hourly Bid Log'); while (campaigns.hasNext()) { var campaign = campaigns.next(); var name = campaign.getName(); // Pull hourly performance data var report = AdsApp.report( 'SELECT HourOfDay, Clicks, Conversions, Cost, ConversionRate ' + 'FROM CAMPAIGN_PERFORMANCE_REPORT ' + 'WHERE CampaignName = "' + name + '" ' + 'DURING LAST_90_DAYS' ); var hourlyData = {}; var totalConvRate = 0; var totalClicks = 0; var totalConversions = 0; var rows = report.rows(); while (rows.hasNext()) { var row = rows.next(); var hour = parseInt(row['HourOfDay']); var clicks = parseInt(row['Clicks']); var conversions = parseFloat(row['Conversions']); if (!hourlyData[hour]) hourlyData[hour] = {clicks: 0, conversions: 0}; hourlyData[hour].clicks += clicks; hourlyData[hour].conversions += conversions; totalClicks += clicks; totalConversions += conversions; } var avgConvRate = totalClicks > 0 ? totalConversions / totalClicks : 0; if (avgConvRate === 0) continue; // Calculate and apply bid adjustments var adSchedule = campaign.targeting().adSchedules().get(); var schedules = {}; while (adSchedule.hasNext()) { var sched = adSchedule.next(); schedules[sched.getStartHour()] = sched; } for (var hour = 0; hour < 24; hour++) { var data = hourlyData[hour]; if (!data || data.clicks < MIN_CLICKS_PER_HOUR) continue; var hourConvRate = data.conversions / data.clicks; var modifier = (hourConvRate - avgConvRate) / avgConvRate; // Cap the modifier modifier = Math.max(MAX_BID_DECREASE, Math.min(MAX_BID_INCREASE, modifier)); modifier = Math.round(modifier * 100) / 100; if (schedules[hour]) { schedules[hour].setBidModifier(1 + modifier); } logSheet.appendRow([ new Date(), name, hour + ':00', data.clicks, data.conversions, (hourConvRate * 100).toFixed(2) + '%', (modifier * 100).toFixed(1) + '%' ]); } Logger.log('Updated hourly bids for: ' + name); } }
Negative Keyword Conflict Detector
Find where your negative keywords are accidentally blocking your own active keywords
One of the most common hidden problems in Google Ads accounts is negative keywords that conflict with your active keywords. You might be blocking your own traffic without realising it. This script cross-references every negative keyword against your active keywords and flags any conflicts.
Common causes: Shared negative keyword lists applied too broadly, negative keywords added at campaign level that conflict with other ad groups, or broad match negatives that accidentally match your exact keywords.
- Checks campaign-level and ad-group-level negatives
- Checks shared negative keyword lists
- Handles exact, phrase, and broad match logic
- Emails a clear report of all conflicts found
// Negative Keyword Conflict Detector // Free script by DPOM | https://www.dpom.co.uk // Custom Google Ads scripts: https://www.dpom.co.uk/google-ads-scripts/ var EMAIL = 'your@email.com'; function main() { var conflicts = []; var campaigns = AdsApp.campaigns() .withCondition('Status = ENABLED') .get(); while (campaigns.hasNext()) { var campaign = campaigns.next(); var campaignName = campaign.getName(); // Collect campaign-level negatives var negatives = []; var campNegs = campaign.negativeKeywords().get(); while (campNegs.hasNext()) { var neg = campNegs.next(); negatives.push({ text: neg.getText().toLowerCase(), matchType: neg.getMatchType(), level: 'Campaign' }); } // Check each ad group's keywords against negatives var adGroups = campaign.adGroups() .withCondition('Status = ENABLED') .get(); while (adGroups.hasNext()) { var adGroup = adGroups.next(); var agName = adGroup.getName(); // Also collect ad group level negatives var agNegs = adGroup.negativeKeywords().get(); var allNegs = negatives.slice(); while (agNegs.hasNext()) { var neg = agNegs.next(); allNegs.push({ text: neg.getText().toLowerCase(), matchType: neg.getMatchType(), level: 'Ad Group' }); } // Check active keywords var keywords = adGroup.keywords() .withCondition('Status = ENABLED') .get(); while (keywords.hasNext()) { var kw = keywords.next(); var kwText = kw.getText().toLowerCase(); for (var i = 0; i < allNegs.length; i++) { var neg = allNegs[i]; var isConflict = false; if (neg.matchType === 'EXACT' && kwText === neg.text) { isConflict = true; } else if (neg.matchType === 'PHRASE' && kwText.indexOf(neg.text) !== -1) { isConflict = true; } else if (neg.matchType === 'BROAD') { var negWords = neg.text.split(' '); var allPresent = negWords.every(function(w) { return kwText.indexOf(w) !== -1; }); if (allPresent) isConflict = true; } if (isConflict) { conflicts.push({ campaign: campaignName, adGroup: agName, keyword: kw.getText(), negative: neg.text, negMatch: neg.matchType, negLevel: neg.level }); } } } } } if (conflicts.length > 0) { var body = 'Found ' + conflicts.length + ' negative keyword conflicts:\n\n'; conflicts.forEach(function(c) { body += 'Keyword: ' + c.keyword + '\n'; body += ' Blocked by: [' + c.negMatch + '] ' + c.negative + ' (' + c.negLevel + ' level)\n'; body += ' Location: ' + c.campaign + ' > ' + c.adGroup + '\n\n'; }); MailApp.sendEmail(EMAIL, 'Negative Keyword Conflicts: ' + conflicts.length + ' found', body); } Logger.log(conflicts.length + ' conflicts found.'); }
Account Anomaly Detector
Multi-metric anomaly detection that alerts you when anything unusual happens in your account
This script compares yesterday's performance against the previous 14-day average for every campaign in your account. If any metric deviates beyond your threshold, you get an immediate email alert. Catches problems that would otherwise go unnoticed for days - sudden CPC spikes, conversion drops, CTR crashes, or unexpected spend surges.
Metrics monitored: Cost, clicks, impressions, CTR, CPC, conversions, conversion rate, and cost per conversion. Each has an independent alert threshold.
- Compares yesterday vs. 14-day rolling average
- Configurable sensitivity per metric
- Only alerts on statistically meaningful deviations
- Single daily email with all anomalies grouped by campaign
// Account Anomaly Detector // Free script by DPOM | https://www.dpom.co.uk // Custom Google Ads scripts: https://www.dpom.co.uk/google-ads-scripts/ // === CONFIG === var EMAIL = 'your@email.com'; var LOOKBACK_DAYS = 14; var THRESHOLDS = { Cost: 0.40, // alert if spend 40% above/below average Clicks: 0.35, Impressions: 0.40, Ctr: 0.30, AverageCpc: 0.30, Conversions: 0.40, ConversionRate: 0.35, CostPerConversion: 0.40 }; var MIN_BASELINE_CLICKS = 50; // ignore low-volume campaigns // === END CONFIG === function main() { var anomalies = []; var yesterday = getDateString(1); var baseStart = getDateString(LOOKBACK_DAYS + 1); var baseEnd = getDateString(2); var campaigns = AdsApp.campaigns() .withCondition('Status = ENABLED') .get(); while (campaigns.hasNext()) { var campaign = campaigns.next(); var name = campaign.getName(); // Get baseline average var baseStats = campaign.getStatsFor(baseStart, baseEnd); var baseClicks = baseStats.getClicks(); if (baseClicks < MIN_BASELINE_CLICKS) continue; var baseline = { Cost: baseStats.getCost() / LOOKBACK_DAYS, Clicks: baseStats.getClicks() / LOOKBACK_DAYS, Impressions: baseStats.getImpressions() / LOOKBACK_DAYS, Ctr: baseStats.getCtr(), AverageCpc: baseStats.getAverageCpc(), Conversions: baseStats.getConversions() / LOOKBACK_DAYS, ConversionRate: baseStats.getConversionRate(), CostPerConversion: baseStats.getConversions() > 0 ? baseStats.getCost() / baseStats.getConversions() : 0 }; // Get yesterday's data var ydayStats = campaign.getStatsFor(yesterday, yesterday); var actual = { Cost: ydayStats.getCost(), Clicks: ydayStats.getClicks(), Impressions: ydayStats.getImpressions(), Ctr: ydayStats.getCtr(), AverageCpc: ydayStats.getAverageCpc(), Conversions: ydayStats.getConversions(), ConversionRate: ydayStats.getConversionRate(), CostPerConversion: ydayStats.getConversions() > 0 ? ydayStats.getCost() / ydayStats.getConversions() : 0 }; // Compare each metric var campAnomalies = []; for (var metric in THRESHOLDS) { if (baseline[metric] === 0) continue; var deviation = (actual[metric] - baseline[metric]) / baseline[metric]; if (Math.abs(deviation) >= THRESHOLDS[metric]) { var direction = deviation > 0 ? 'UP' : 'DOWN'; campAnomalies.push( metric + ': ' + direction + ' ' + (Math.abs(deviation) * 100).toFixed(1) + '% ' + '(yesterday: ' + formatVal(metric, actual[metric]) + ' vs avg: ' + formatVal(metric, baseline[metric]) + ')' ); } } if (campAnomalies.length > 0) { anomalies.push({campaign: name, issues: campAnomalies}); } } if (anomalies.length > 0) { var body = 'Account anomalies detected for ' + yesterday + ':\n\n'; anomalies.forEach(function(a) { body += 'Campaign: ' + a.campaign + '\n'; a.issues.forEach(function(i) { body += ' - ' + i + '\n'; }); body += '\n'; }); MailApp.sendEmail(EMAIL, 'Google Ads Anomaly Alert: ' + anomalies.length + ' campaigns', body); } Logger.log(anomalies.length + ' campaigns with anomalies.'); } function formatVal(metric, val) { if (metric === 'Ctr' || metric === 'ConversionRate') return (val * 100).toFixed(2) + '%'; if (metric === 'Cost' || metric === 'AverageCpc' || metric === 'CostPerConversion') return 'Β£' + val.toFixed(2); return Math.round(val).toString(); } function getDateString(daysAgo) { var d = new Date(); d.setDate(d.getDate() - daysAgo); return Utilities.formatDate(d, AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd'); }
Wasted Spend Report
Calculate exactly how much money your account is wasting and where it is going
How much of your Google Ads budget is actually being wasted? This script calculates your total wasted spend across five categories and emails you a detailed breakdown. It is like a free audit that runs automatically every week.
What it analyses:
- Non-converting keywords: Keywords with spend but zero conversions in 60 days
- Low Quality Score: Spend on keywords with QS of 4 or below (you are overpaying per click)
- Poor search terms: Search queries that triggered your ads but are clearly irrelevant
- Underperforming ads: Ads with CTR below 2% that are dragging down quality
- Total waste: Combined figure across all categories with percentage of total spend
// Wasted Spend Report // Free script by DPOM | https://www.dpom.co.uk // Custom Google Ads scripts: https://www.dpom.co.uk/google-ads-scripts/ // === CONFIG === var EMAIL = 'your@email.com'; var DAYS = 60; var LOW_QS_THRESHOLD = 4; var LOW_CTR_THRESHOLD = 0.02; // === END CONFIG === function main() { var dateRange = getDateRange(DAYS); var waste = { nonConverting: {cost: 0, items: []}, lowQS: {cost: 0, items: []}, lowCTR: {cost: 0, items: []}, }; var totalSpend = 0; // 1. Non-converting keywords var keywords = AdsApp.keywords() .withCondition('Status = ENABLED') .withCondition('Clicks > 0') .withCondition('Conversions = 0') .forDateRange(dateRange) .orderBy('Cost DESC') .withLimit(50) .get(); while (keywords.hasNext()) { var kw = keywords.next(); var stats = kw.getStatsFor(dateRange); var cost = stats.getCost(); waste.nonConverting.cost += cost; if (waste.nonConverting.items.length < 10) { waste.nonConverting.items.push( kw.getText() + ' | Β£' + cost.toFixed(2) + ' | ' + stats.getClicks() + ' clicks'); } } // 2. Low Quality Score keywords var lowQS = AdsApp.keywords() .withCondition('Status = ENABLED') .withCondition('QualityScore <= ' + LOW_QS_THRESHOLD) .withCondition('Clicks > 0') .forDateRange(dateRange) .orderBy('Cost DESC') .withLimit(50) .get(); while (lowQS.hasNext()) { var kw = lowQS.next(); var stats = kw.getStatsFor(dateRange); var cost = stats.getCost(); waste.lowQS.cost += cost; if (waste.lowQS.items.length < 10) { waste.lowQS.items.push( kw.getText() + ' | QS: ' + kw.qualityScore() + ' | Β£' + cost.toFixed(2)); } } // 3. Low CTR ads var ads = AdsApp.ads() .withCondition('Status = ENABLED') .withCondition('Impressions > 500') .withCondition('Ctr < ' + LOW_CTR_THRESHOLD) .forDateRange(dateRange) .orderBy('Cost DESC') .withLimit(20) .get(); while (ads.hasNext()) { var ad = ads.next(); var stats = ad.getStatsFor(dateRange); waste.lowCTR.cost += stats.getCost(); } // Get total account spend var accountStats = AdsApp.currentAccount().getStatsFor(dateRange); totalSpend = accountStats.getCost(); var totalWaste = waste.nonConverting.cost + waste.lowQS.cost + waste.lowCTR.cost; var wastePercent = totalSpend > 0 ? (totalWaste / totalSpend * 100).toFixed(1) : 0; // Build report var body = 'WASTED SPEND REPORT - Last ' + DAYS + ' days\n'; body += '==========================================\n\n'; body += 'Total Account Spend: Β£' + totalSpend.toFixed(2) + '\n'; body += 'Estimated Waste: Β£' + totalWaste.toFixed(2) + ' (' + wastePercent + '%)\n\n'; body += 'NON-CONVERTING KEYWORDS: Β£' + waste.nonConverting.cost.toFixed(2) + '\n'; waste.nonConverting.items.forEach(function(i) { body += ' ' + i + '\n'; }); body += '\nLOW QUALITY SCORE: Β£' + waste.lowQS.cost.toFixed(2) + '\n'; waste.lowQS.items.forEach(function(i) { body += ' ' + i + '\n'; }); body += '\nLOW CTR ADS: Β£' + waste.lowCTR.cost.toFixed(2) + '\n'; MailApp.sendEmail(EMAIL, 'Wasted Spend Report: Β£' + totalWaste.toFixed(2) + ' (' + wastePercent + '%) over ' + DAYS + ' days', body); Logger.log('Report sent. Waste: Β£' + totalWaste.toFixed(2)); } function getDateRange(days) { var end = new Date(); var start = new Date(); start.setDate(start.getDate() - days); return Utilities.formatDate(start, AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd') + ',' + Utilities.formatDate(end, AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd'); }
Free scripts by DPOM - Custom Google Ads Script Development for UK Businesses
Talk to our scripting team about automating your campaigns
0115 727 0038
Why Choose DPOM for Script Development?
Our scripting team combines deep Google Ads platform expertise with strong software development skills. We have built automation solutions for over 200 Google Ads accounts, from simple alerts to full campaign management suites. Every script is built to your exact specification, thoroughly tested, and comes with full documentation and support.
FreeConsultation
Tell us what you want to automate and we will assess your account for free, identifying the best scripting opportunities and quick wins you can implement immediately.
DedicatedScript Developer
You work directly with the developer building your scripts. No account managers in between, no Chinese whispers. Direct access to the person who writes your code.
FixedProject Pricing
Every script project has a fixed price agreed upfront. No hourly rates, no scope creep surprises. You know exactly what you are paying before we write a single line of code.
OngoingSupport Plans
Need ongoing script maintenance, updates, or new automations each month? Our rolling support plans give you priority access to our scripting team with no long-term commitment.
FullDocumentation
Every script comes with clear, plain-English documentation explaining what it does, how it works, and how to adjust settings. You are never locked in to us for future changes.
Tested &Monitored
Every script is tested against your live account data before deployment. We monitor execution logs for the first 30 days and fix any issues at no extra cost.
Google AdsAPI Experts
Our developers work with the Google Ads API and Google Ads Scripts engine daily. We know the platform inside out, including its quirks, limits, and undocumented features.
FastTurnaround
Most scripts are built and deployed within 5-10 working days. Urgent requests can be fast-tracked. We know you want results quickly, not in three months.
Script development is available as a standalone service or as part of our Google Ads management packages. We also offer SEO services, click fraud protection, and social media advertising.
Get a free script consultation for your Google Ads account
0115 727 0038
Google Ads Scripts FAQ
Got questions about Google Ads scripts and how they could help your business? Here are the most common questions we get asked. If you need more detail, just get in touch.
Google Ads scripts are pieces of JavaScript code that run inside your Google Ads account. They can read your campaign data, make changes automatically, send alerts, generate reports, and connect to external services like Google Sheets, Slack, or your CRM. Think of them as a tireless assistant that works 24/7 inside your ad account.
Not at all. We build, install, and configure everything for you. Each script comes with documentation so you understand what it does, and we can adjust any settings through a simple Google Sheet interface if needed. You never need to touch code.
Simple scripts like budget alerts or URL checkers typically start from around Β£200. More complex automation such as dynamic bidding algorithms or feed-based campaign builders range from Β£500 to Β£1,500 depending on complexity. Every project is quoted upfront with a fixed price – no surprises.
We always test scripts in preview mode first using your real account data without making any actual changes. Once you are happy with the results, we switch to live mode. Scripts also include safety limits and checks to prevent unintended changes, and you can pause or remove any script instantly.
Yes. While scripts cannot directly override Smart Bidding CPCs, they are incredibly useful alongside automated strategies for budget management, reporting, anomaly detection, campaign structure changes, negative keyword management, and much more. Many of our most popular scripts complement Smart Bidding perfectly.
Most scripts are built and deployed within 5 to 10 working days. Simple alerts and reports can be done in 2-3 days. Complex projects with multiple integrations may take 2-3 weeks. We will give you a clear timeline upfront before starting any work.
Every script includes error handling and email notifications if anything unexpected happens. We monitor execution logs for the first 30 days after deployment and fix any issues free of charge. After that, our support plans provide ongoing monitoring and updates.
Yes – scroll down this page to find our free script library. We have published several commonly requested scripts that you can copy and use in your own Google Ads account right now, completely free. These give you a taste of what scripting can do before committing to a custom project.
Sectors
We have built custom Google Ads scripts for businesses across a wide range of sectors. Whatever your industry, our team can create automation solutions tailored to your specific challenges and goals.
- B2B
- Retail
- Software & SaaS
- Legal
- Healthcare
- Financial
We work extensively with business 2 business to provide lead generation campaigns that are both scalable and profitable.
We know that its leads you need and for a price that enables you to be profitable. To achieve this, you need to track every phone call, email and form submission on your website – and this will be the very first thing we do.
From there, we can track every lead you get and optimise around elements that provide the greatest returns.
We work with e-commerce websites to increase and profitability with targeted campaigns. Using Google/Bing Shopping ads, SEO and Social Media, we design campaigns to increase customers for retailers.
Where Google Ads is concerned, product listing ads are key (Google Shopping.) Optimising your product feed the only way to maximise visibility, sales and profitability and unlike many agencies, we do this as standard.
Campaigns for software (including SaaS) providers have a unique set of challenges, for example, If your model is based on monthly recurring revenue a different approach may be required than if you offer stand-alone software.
We design campaigns to increase brand awareness, demo sign-ups and purchases to work with your other marketing campaigns.
We also work with market places such as Capterra (Gartner) and others to help you fill your sales funnel.
Whether it’s personal injury or conveyancing, our experience within the legal sector uniquely positions us to provide scalable lead generation campaigns from highly targeted visitors.
We know from experience that for law firms in some areas, it takes considerable time for a customer to deliver ROI and we design both lead generation and brand awareness campaigns to maximise your incoming leads.
Healthcare companies face unique challenges when it comes to digital advertising including, restrictions based around ad types and content which if infringed, could lead to disapproved ads. We know how to navigate the pitfalls.
We’ve worked with banks, car finance companies, insurance providers and more to provide a scalable and profitable source of leads.
Whether it’s leads for car sales or finance applications you require, we’ve done it all and know how to deliver results.

Blog
Follow ourΒ Digital Marketing BlogΒ to find out about the latest digital marketing trends, insights and tips!
Facebook Ads vs Boosted Posts β What Actually Works
Facebook Ads vs boosted posts β which should you use? We compare targeting, cost...
Read MoreLocal SEO Checklist for Small Businesses in 2026
A practical local SEO checklist for small businesses in 2026. Optimise your Goog...
Read MoreHow AI Is Changing Google Ads in 2026
Discover how AI is transforming Google Ads in 2026 with smart bidding, Performan...
Read MoreWhy Every Small Business Needs a Blog in 2026
Discover why blogging is essential for small business growth in 2026. SEO be...
Read More



