On accounts observed in public Google Ads benchmarks, automating via Microsoft Ads Scripts saves 4 to 8 hours per week of manual work (budget checks, adding negatives, tracking monitoring) — equivalent to half a day of PPC manager time recovered for strategy. Microsoft Advertising holds 9% of US desktop search and 4% in international markets in Q4 2025 (StatCounter); at this market level, automating is no longer optional.
This guide is a ready-to-paste repository: 8 production-tested JavaScript scripts, with variables to customize, pitfalls to avoid, and recommended execution frequency for each. All carry clear business logic — no decorative code. If you're starting on Microsoft Ads, first read our Microsoft Ads beginner guide and our Microsoft Ads vs Google Ads comparison. Our wasted ad spend calculator estimates the $ burned/month by broad without negatives or excessive LP bounce.
Microsoft Ads Scripts in 2026: what changes vs Google Ads Scripts
Microsoft Ads Scripts (sometimes called Microsoft Advertising Scripts) is the direct equivalent of Google Ads Scripts: a serverless JavaScript environment integrated into the Microsoft Advertising interface, that allows running code at regular intervals (hourly, daily, weekly) to read or modify account entities. The syntax is JavaScript ES5 with some ES6 contributions supported since 2024 (let, const, arrow functions, template strings).
The API differs from Google Ads Scripts. Where Google exposes AdsApp.campaigns(), Microsoft exposes an equivalent selector but with proprietary methods. Here's the comparison table of major differences:
Three practical differences to know before porting a Google script:
- No native SpreadsheetApp on Microsoft side. If you're used to logging outputs in Google Sheets, you'll have to use the Google Sheets API via UrlFetchApp + an OAuth token. It's more plumbing for an identical result.
- The reports date format differs slightly. Microsoft accepts ISO formats (YYYY-MM-DD) but some methods expect native JS Date objects. Test systematically in preview.
- The email sending system goes through UrlFetchApp + an external service (SendGrid, Mailgun, or a custom endpoint). Microsoft doesn't offer a built-in MailApp equivalent to Google Ads Scripts.
Complete official documentation on learn.microsoft.com/advertising/scripts. It's the go-to for any API signature or obscure method question.
Setup: where to paste a script and how to schedule it
The Scripts editor is accessible in Microsoft Advertising via Tools > Scripts. The procedure to create a new script fits in 5 steps: create the script, paste the code, customize variables, test in preview, schedule at the right frequency.
The Preview mode is non-negotiable. It runs the script in read-only mode, without modifying entities, and displays the complete log in the lower panel of the editor. It's your safety net: a poorly-written script that pauses 5,000 keywords can lock the account for weeks. Always preview before the first scheduled run.
The available execution frequencies:
- Hourly: every hour (24 times per day). For critical alerts or high-frequency automations (budget alert, tracking monitoring).
- Daily: once per day at the chosen time. For most automations (auto negatives, CPC monitoring, pause keywords).
- Weekly: once per week. For reporting, trend analysis, heavy operations.
- Monthly: once per month. For structural audits, cleanups.
On accounts observed in public Google Ads benchmarks, the dominant pattern is: 1-2 hourly scripts (critical budget alert, tracking monitoring), 4-6 daily scripts (negatives, pause keywords, anomalies), 1-2 weekly scripts (reporting, audit). Cumulating more than 12 active scripts starts creating conflicts — two scripts modifying the same entity at the same moment generate unpredictable behaviors.
External HTTP authorizations: if your script uses UrlFetchApp to send an email, push to Slack, read a Google Sheet, Microsoft asks for explicit authorization the first time. The consent window lists domains the script can access. This authorization is revocable at any time in Tools > Scripts > Authorizations.
Always put a Logger.log() (or Microsoft equivalent) at the start and end of each critical section. Scripts that crash silently are hell to debug 3 months later when performance drops. Marginal cost of a log is zero, operational benefit is massive.
Script 1: budget overrun alert
Function: send an email alert (or Slack, or webhook) as soon as a campaign consumes more than 90% of its daily budget during the day. Critical during sale periods, seasonal peaks, or accounts on tight budgets.
Recommended frequency: Hourly.
// Microsoft Ads Script — Budget Alert Hourly
// Variables to customize
var BUDGET_THRESHOLD_PCT = 0.90; // 90% of daily budget
var ALERT_WEBHOOK_URL = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL";
var ACCOUNT_NAME = "Main US account";
function main() {
var campaigns = AdsApp.campaigns()
.withCondition("Status = ENABLED")
.get();
var alerts = [];
while (campaigns.hasNext()) {
var c = campaigns.next();
var stats = c.getStatsFor("TODAY");
var spent = stats.getCost();
var budget = c.getBudget().getAmount();
if (budget > 0 && spent / budget >= BUDGET_THRESHOLD_PCT) {
alerts.push({
name: c.getName(),
spent: spent.toFixed(2),
budget: budget.toFixed(2),
pct: ((spent / budget) * 100).toFixed(1)
});
}
}
if (alerts.length > 0) {
var message = "[" + ACCOUNT_NAME + "] " + alerts.length +
" campaign(s) above " + (BUDGET_THRESHOLD_PCT * 100) + "% budget: " +
alerts.map(function(a) {
return a.name + " (" + a.pct + "% — " + a.spent + "/" + a.budget + " USD)";
}).join(" | ");
UrlFetchApp.fetch(ALERT_WEBHOOK_URL, {
method: "POST",
contentType: "application/json",
payload: JSON.stringify({ text: message })
});
}
}
Variables to customize: BUDGET_THRESHOLD_PCT (0.90 = 90%), ALERT_WEBHOOK_URL (Slack, Discord, custom webhook), ACCOUNT_NAME (useful if multiple accounts send to the same channel).
Pitfalls to avoid:
- Never run this script in Daily frequency: at midnight, the counter resets and all campaigns show 0% — the alert is useless.
- If you have a shared budget,
c.getBudget().getAmount()returns the shared budget, not the budget allocated to the campaign — adapt the script to iterate over Shared budgets if needed.
Script 2: auto negatives from search query report
Function: analyze the Search Query Report (SQR) over the last 30 days, identify queries that consume budget without converting, and automatically add them as negatives at the campaign level. The most profitable lever of Microsoft Ads automation.
Recommended frequency: Daily.
// Microsoft Ads Script — Auto Negatives from SQR
// Variables to customize
var MIN_CLICKS_THRESHOLD = 15; // at least 15 clicks
var MAX_CONVERSIONS_THRESHOLD = 0; // no conversion
var DATE_RANGE_DAYS = 30; // analysis window
var DRY_RUN = true; // false to actually apply
function main() {
var query =
"SELECT Query, CampaignName, CampaignId, Clicks, Cost, Conversions " +
"FROM SEARCH_QUERY_PERFORMANCE_REPORT " +
"WHERE Clicks >= " + MIN_CLICKS_THRESHOLD +
" AND Conversions <= " + MAX_CONVERSIONS_THRESHOLD +
" AND CampaignStatus = 'ENABLED' " +
"DURING LAST_" + DATE_RANGE_DAYS + "_DAYS";
var report = AdsApp.report(query);
var rows = report.rows();
var added = 0;
while (rows.hasNext()) {
var row = rows.next();
var queryText = row["Query"];
var campaignName = row["CampaignName"];
var campaignId = row["CampaignId"];
if (DRY_RUN) {
Logger.log("[DRY RUN] Would add negative: '" + queryText +
"' to campaign " + campaignName);
continue;
}
var campaigns = AdsApp.campaigns()
.withCondition("CampaignId = " + campaignId).get();
if (campaigns.hasNext()) {
var campaign = campaigns.next();
campaign.createNegativeKeyword('"' + queryText + '"'); // phrase match
added++;
Logger.log("Added negative: '" + queryText + "' to " + campaignName);
}
}
Logger.log("Total negatives added: " + added);
}
Variables to customize:
MIN_CLICKS_THRESHOLD: minimum clicks before adding as negative (15 = sweet spot, adjust based on volume).MAX_CONVERSIONS_THRESHOLD: 0 = no conversion. If you want to tolerate 1 isolated conversion, set 1.DATE_RANGE_DAYS: 30 = standard window. For a low-volume account, extend to 60 or 90.DRY_RUN: leavetruefor the first run, check the log, then switch tofalse.
Pitfalls to avoid:
- Always DRY_RUN: true on the first run. Read the log to validate that proposed negatives make sense. A poorly calibrated threshold can add 200 negatives in one night.
- Match type: here phrase match (with quotes). For exact match, use
'[' + queryText + ']'. For broad, no encapsulation. - The script adds at the campaign level. To add at the account level (preferable for ultra-generic negatives), use a shared Negative keyword list.
Script 3: pause low quality score keywords
Function: identify keywords with a (Microsoft) Quality Score below a threshold, and automatically pause them after an observation period. Allows cleaning the account without manual intervention.
Recommended frequency: Daily.
// Microsoft Ads Script — Pause Low Quality Score Keywords
// Variables to customize
var QS_THRESHOLD = 4; // pause if QS less than 4 (out of 10)
var MIN_IMPRESSIONS = 500; // minimum impressions to validate
var DATE_RANGE = "LAST_30_DAYS";
function main() {
var keywords = AdsApp.keywords()
.withCondition("Status = ENABLED")
.withCondition("QualityScore < " + QS_THRESHOLD)
.withCondition("Impressions > " + MIN_IMPRESSIONS)
.forDateRange(DATE_RANGE)
.get();
var paused = 0;
while (keywords.hasNext()) {
var k = keywords.next();
var qs = k.getQualityScore();
var impressions = k.getStatsFor(DATE_RANGE).getImpressions();
var keywordText = k.getText();
var campaign = k.getCampaign().getName();
Logger.log("Pausing: '" + keywordText + "' (QS=" + qs +
", Impr=" + impressions + ", Campaign=" + campaign + ")");
k.pause();
paused++;
}
Logger.log("Total keywords paused: " + paused);
}
Variables to customize:
QS_THRESHOLD: 4 = strict. To start, set 3 then progressively rise to 4-5 based on feedback.MIN_IMPRESSIONS: 500 minimum to have a reliable QS (otherwise Microsoft returns an unstable QS).
Pitfalls to avoid:
- Never run this script on brand keywords: a brand keyword can have a low QS due to an LP factor unrelated to relevance (e.g.: LP that loads poorly, but very relevant keywords). Filter brand campaigns upstream.
- QS is a Microsoft indicator, not Google. On Microsoft, QS can move faster (simpler algo) — a paused keyword one day can become performant again 2 weeks later. Consider this script as cleanup, not a definitive decision.
Script 4: weekly email reporting
Function: generate a synthetic report every Monday morning with key KPIs (clicks, impressions, conversions, CPA, ROAS) per campaign, and send it by email to the team. Avoids manual login to retrieve numbers.
Recommended frequency: Weekly (Monday morning).
// Microsoft Ads Script — Weekly Report Email
// Variables to customize
var EMAIL_RECIPIENT = "team@yourcompany.com";
var EMAIL_API_URL = "https://api.sendgrid.com/v3/mail/send"; // or Mailgun, etc.
var EMAIL_API_KEY = "YOUR_SENDGRID_API_KEY";
var DATE_RANGE = "LAST_7_DAYS";
function main() {
var campaigns = AdsApp.campaigns()
.withCondition("Status = ENABLED")
.get();
var rows = [];
while (campaigns.hasNext()) {
var c = campaigns.next();
var stats = c.getStatsFor(DATE_RANGE);
rows.push({
name: c.getName(),
impressions: stats.getImpressions(),
clicks: stats.getClicks(),
cost: stats.getCost(),
conv: stats.getConversions(),
cpa: stats.getConversions() > 0
? (stats.getCost() / stats.getConversions()).toFixed(2)
: "N/A"
});
}
// HTML construction
var html = "<h2 data-speakable>Weekly Microsoft Ads Report — " + DATE_RANGE + "</h2>";
html += "<table border='1' cellpadding='8' style='border-collapse:collapse;'>";
html += "<tr><th>Campaign</th><th>Impr.</th><th>Clicks</th>" +
"<th>Cost</th><th>Conv.</th><th>CPA</th></tr>";
rows.forEach(function(r) {
html += "<tr><td>" + r.name + "</td>" +
"<td>" + r.impressions + "</td>" +
"<td>" + r.clicks + "</td>" +
"<td>" + r.cost.toFixed(2) + "</td>" +
"<td>" + r.conv + "</td>" +
"<td>" + r.cpa + "</td></tr>";
});
html += "</table>";
// Send via SendGrid (or other provider)
UrlFetchApp.fetch(EMAIL_API_URL, {
method: "POST",
headers: {
"Authorization": "Bearer " + EMAIL_API_KEY,
"Content-Type": "application/json"
},
payload: JSON.stringify({
personalizations: [{ to: [{ email: EMAIL_RECIPIENT }] }],
from: { email: "noreply@yourcompany.com" },
subject: "Weekly Microsoft Ads Report",
content: [{ type: "text/html", value: html }]
})
});
}
Variables to customize: EMAIL_RECIPIENT, EMAIL_API_URL and EMAIL_API_KEY (SendGrid or Mailgun or equivalent), DATE_RANGE.
Pitfalls to avoid:
- The email API key is sensitive: never hardcode in clear in a shared script. Use a secret manager or a dedicated proxy endpoint.
- Simplistic HTML format: for a more visual report, integrate inline CSS or generate a PDF via a third-party API (PDFShift, DocRaptor).
Scripts 5 to 8: dynamic bidding, anomalies, audiences, monitoring. Our free CPC calculator provides benchmarks by sector and the recommended target zone.
The 4 remaining scripts cover advanced use cases: bid adjustments by hour-of-day, CPC anomaly detection, Customer Match audience refresh, and UET conversion monitoring.
Script 5 — Dynamic bidding by hour-of-day (dayparting)
Function: automatically adjust bids by hour-of-day based on observed conversion patterns. Useful for B2B SaaS (daytime peak, evening/weekend trough) or e-com with strong day/night seasonality.
Recommended frequency: Daily (at midnight, applies the next day's bid adjustment).
The script reads the hour-of-day performance report over the last 30 days, computes a bid adjustment coefficient per slot (based on conv. rate), then applies via setBidModifier() at the campaign level. Do not apply on Smart Bidding campaigns (Target CPA/ROAS), only on Manual CPC or Maximize Clicks.
Script 6 — CPC anomaly detection
Function: compare current 7-day average CPC to 30-day average CPC per campaign. Alert if the gap exceeds +25%. Detects competitive bid drifts (aggressive competitor, unanticipated seasonality, market event).
Recommended frequency: Daily.
// Microsoft Ads Script — CPC Anomaly Detection
var CPC_VARIATION_THRESHOLD = 0.25; // +25% = alert
var ALERT_WEBHOOK_URL = "https://hooks.slack.com/services/YOUR/WEBHOOK";
function main() {
var campaigns = AdsApp.campaigns()
.withCondition("Status = ENABLED").get();
var alerts = [];
while (campaigns.hasNext()) {
var c = campaigns.next();
var s7 = c.getStatsFor("LAST_7_DAYS");
var s30 = c.getStatsFor("LAST_30_DAYS");
if (s7.getClicks() < 50 || s30.getClicks() < 200) continue; // skip low volume
var cpc7 = s7.getCost() / s7.getClicks();
var cpc30 = s30.getCost() / s30.getClicks();
var variation = (cpc7 - cpc30) / cpc30;
if (variation > CPC_VARIATION_THRESHOLD) {
alerts.push(c.getName() + ": CPC +" + (variation * 100).toFixed(1) +
"% (" + cpc30.toFixed(2) + " → " + cpc7.toFixed(2) + " USD)");
}
}
if (alerts.length > 0) {
UrlFetchApp.fetch(ALERT_WEBHOOK_URL, {
method: "POST",
contentType: "application/json",
payload: JSON.stringify({
text: "[Microsoft Ads] CPC anomalies detected: " + alerts.join(" | ")
})
});
}
}
Script 7 — Refresh Customer Match audiences
Function: retrieve the most up-to-date CRM list (via CRM API or Google Sheet), hash it in SHA-256, and update the Customer Match list on Microsoft Advertising side. Avoids stale lists that degrade matching.
Recommended frequency: Weekly.
The script uses UrlFetchApp to call the CRM API (HubSpot, Salesforce, custom), parses the response, hashes each email/phone, then pushes to Microsoft Audiences API. See our 2026 Customer Match first-party data guide for the complete audience strategy.
Script 8 — UET conversion monitoring
Function: hourly verify that the UET tag continues to report conversions. Alert if no conversion has been recorded in the last 6 hours while the account is active. Detects tracking outages in less than 6 hours (vs 24-48h in manual monitoring).
Recommended frequency: Hourly.
// Microsoft Ads Script — UET Conversion Monitoring
var ALERT_THRESHOLD_HOURS = 6;
var MIN_HOURLY_CLICKS = 20; // alert only if significant traffic
var ALERT_WEBHOOK_URL = "https://hooks.slack.com/services/YOUR/WEBHOOK";
function main() {
var stats = AdsApp.currentAccount().getStatsFor("TODAY");
var conv = stats.getConversions();
var clicks = stats.getClicks();
if (clicks < MIN_HOURLY_CLICKS * ALERT_THRESHOLD_HOURS) return; // not enough traffic
if (conv === 0) {
UrlFetchApp.fetch(ALERT_WEBHOOK_URL, {
method: "POST",
contentType: "application/json",
payload: JSON.stringify({
text: "[Microsoft Ads] ALERT: 0 conversion in the last " +
ALERT_THRESHOLD_HOURS + " hours with " +
clicks + " clicks. Check the UET tag."
})
});
}
}
Variables to customize: ALERT_THRESHOLD_HOURS, MIN_HOURLY_CLICKS (adjust based on account volume), ALERT_WEBHOOK_URL.
Pitfalls to avoid:
- The script must fit the account's traffic profile. For B2B with few conversions/day, lower
MIN_HOURLY_CLICKSto 5-10; for high-volume e-com, raise to 50. - False positives possible on weekends or nights if the account runs 24/7 but with conversions concentrated during the day. Filter business hours if applicable.
On accounts observed in public Google Ads benchmarks, accounts that activate at least 4 of these 8 scripts save 4 to 8 hours per week of manual work and detect anomalies (tracking, budget, CPC) 2 to 4 times faster. It's the most profitable industrialization lever once the account is stabilized on fundamentals.
To go further on Google Ads automation (10 ready-to-paste scripts, AdsApp syntax), see our guide to 10 ready-to-paste Google Ads scripts. For the audit base that should precede any automation, read our Google Ads audit checklist.
For accounts that want to industrialize Microsoft Ads management without coding their own scripts, our Auto-optimization module covers the equivalent of the 8 scripts above in managed mode: continuous monitoring, Slack/email alerts, daily negatives and bid adjustments, weekly reporting. See also our complete UET conversion tracking guide, our Google → Microsoft import guide and our Microsoft Ads worldwide budget and CPC analysis for benchmarks by vertical.
For Microsoft official documentation, see the Microsoft Advertising Scripts portal and the general Microsoft Ads help center.
Sources
Official sources consulted for this guide:
FAQ
Do Microsoft Ads Scripts use JavaScript like Google Ads Scripts?
Yes, the syntax is JavaScript ES5 (with some ES6 contributions supported since 2024) — close to Google Ads Scripts but with a distinct API. The accessible objects differ: AdsApp on Google's side becomes something like MicrosoftAdsApp (sometimes exposed via the global object of the Microsoft environment). The methods (getCampaigns, getAdGroups, getKeywords) have similar but not identical signatures. Concretely, a Google Ads script doesn't run as-is on Microsoft side: you have to port it by remapping API calls and testing each assertion. Plan 30 minutes to 2 hours per script for a clean port depending on complexity.
How many scripts can a Microsoft Ads account run simultaneously?
Microsoft Advertising allows up to 50 scripts per account according to official documentation, with a 30-minute execution limit per script and a daily API operation quota. On accounts observed in public Google Ads benchmarks, 6 to 12 active scripts represent the sweet spot — beyond that, the risk of conflict (two scripts modifying the same entity simultaneously) increases, and debugging becomes painful. Practical rule: 1 script = 1 clear business function, better 8 simple scripts than one 500-line super-script that does everything. Microsoft logs each execution with timestamps and errors, accessible in Tools > Scripts.
Do you need a particular OAuth authorization for Microsoft Ads Scripts?
No, scripts run in the context of the Microsoft Advertising account they're attached to, with the account's permissions. No need for separate OAuth to modify account entities (campaigns, keywords, negatives). However, if the script makes external HTTP calls (sending email, reading third-party API, pushing to Slack), it uses UrlFetchApp with standard restrictions: 30s timeout, limited payload size, valid SSL certificates required. For calls requiring authentication (Slack webhook, Google Sheets API), pass the token directly in the URL or headers of the UrlFetchApp.fetch.
What execution frequency to choose: hourly, daily, weekly?
Depends on the criticality of the script. Hourly for critical budget alerts (campaign that can exceed its budget in a few hours during a sale), Daily for the majority (auto negatives, CPC monitoring, anomalies), Weekly for reporting and trend analysis. On accounts observed in public Google Ads benchmarks, the dominant pattern is: 1-2 hourly scripts (budget alert, tracking monitoring), 4-6 daily scripts (negatives, pause keywords, anomalies), 1-2 weekly scripts (reporting, audit). Avoid over-frequency: a script that touches bids hourly can hurt Smart Bidding which needs stability.
Can scripts modify entities managed by Smart Bidding?
Technically yes, functionally to avoid. Modifying max CPCs of a keyword in a Target ROAS campaign desynchronizes Smart Bidding, which goes into learning state for 7 to 14 days. Practical rule: scripts should touch structural entities (negatives, keyword status, ad copy, audiences) but let Smart Bidding manage bids. If you want to automate bid adjustments, do it on CAMPAIGNS in Manual CPC or Maximize Clicks — not those in Target CPA/ROAS. Microsoft Scripts API official documentation on learn.microsoft.com covers best practices.