Negative keyword conflicts are one of the quietest ways to waste money in Google Ads.
You add a negative keyword to block irrelevant traffic. Completely reasonable. But somewhere in the account - maybe a shared list applied months ago, maybe a leftover from a campaign restructure - that negative is now blocking a keyword you’re actively bidding on.
Your ad never shows. You never know why.
For a single account, this is annoying. For an agency managing 20, 30, 50 accounts? It’s happening somewhere right now and you have no idea where.
In this guide, I’ll walk through what negative keyword conflicts actually are, the four most common ways they happen, a free Google Ads script you can install today, and how AI can detect these automatically across your entire account portfolio.
Let’s get into it.
What is a negative keyword conflict?
A negative keyword conflict happens when a negative keyword in your account blocks a positive keyword you’re actively bidding on.
The result: your ad is eligible to show, but it never does. Google sees the negative and suppresses the ad before it gets a chance to enter the auction.
Here’s a simple example. You’re bidding on the keyword “leather shoes.” Somewhere in your campaign negatives - or in a shared negative list applied to this campaign - you have “leather” set to broad match. Google sees the conflict and your “leather shoes” keyword gets blocked entirely. It’s generating zero impressions.
The tricky part is that Google won’t always tell you this is happening. The keyword shows as “Active” in your account.
How conflicts happen: the 4 most common causes
Conflicts rarely happen because someone made an obvious mistake. They happen because Google Ads accounts are living documents - campaigns get restructured, teams change, negative lists get copy-pasted from templates - and nobody audits the whole picture.
Here are the four situations that come up most often.
1. Shared negative lists applied without checking
Shared negative lists are useful. One list, applied across multiple campaigns - great for efficiency. The problem is when that list gets applied to a campaign it wasn’t designed for.
A common scenario: you build a master “irrelevant traffic” list that includes broad match “free” to block freebie-seekers. Then a new campaign launches for a client running a free trial offer. The list gets applied automatically. Now “free” is blocking “free trial software” - the campaign’s core keyword.
2. Copy-pasting industry negative keyword templates
There are hundreds of negative keyword lists online for every vertical. B2B SaaS, legal, healthcare, ecommerce. They’re tempting to use as a starting point.
The problem is that generic templates don’t know your account. Copy a B2B SaaS template that includes broad match “-software” and you’ll block your own software-related keywords. Nobody catches it because the keyword still shows as “Active.”
3. Old campaign structure, new campaigns
Accounts that have been running for 2–3+ years often have campaign-level negatives that made sense at the time - and now nobody remembers why they exist.
The account gets restructured. New ad groups are built. But the old campaign-level negatives stay in place, silently blocking terms in the new structure. This is especially common after agency handoffs, where the incoming team inherits negatives they didn’t create.
4. Multi-person teams working in silos
One manager reviews the search terms report and adds “training” as a negative to stop unqualified clicks. Six weeks later, a different manager adds “sales training” as a new positive keyword targeting a new service line.
Nobody connects the dots. The positive keyword sits there, active on paper, never showing an ad.
The manual audit process
Before reaching for a script or a tool, it’s worth knowing what you can catch manually. The manual process won’t scale to large accounts, but it gives you a clear mental model of what you’re looking for.
Step 1: Export your negative keywords - all three layers
Campaign and ad group negatives are in one place. Shared list negatives are in another. You need both.
For campaign and ad group negatives: Go to Keywords > Negative keywords in the left-hand menu. Click the download icon in the top right of the table and export to CSV.
For shared negative lists: Go to Tools > Shared Library > Exclusion lists > Negative keyword lists. Open each list and note the keywords - there’s no bulk export from the UI for shared lists, so you’ll need to check them manually or use a script to pull everything into one place.
This is one of the reasons the manual process breaks down on larger accounts. A script handles all three layers in a single output.
Step 2: Pull your positive keyword list
Navigate to Keywords > Search keywords and export your full list of active keywords. Filter for “Enabled” status only. There’s no point checking paused keywords - you’re looking for active conflicts that are costing you right now.
Step 3: Cross-reference for conflicts
This is the tedious part. You’re looking for cases where a word in your negative keyword list also appears in one of your positive keywords - and where the negative’s match type would block it.
A few things to check specifically:
- Any single-word broad match negatives (highest risk of blocking something unintended)
- Negatives in shared lists applied at campaign level (widest blast radius)
- Negatives added in the last 90 days (most likely to have been added carelessly during a reactive optimisation session)
Step 4: Check Google’s Recommendations tab
Google Ads will sometimes surface negative keyword conflicts under Recommendations in the left-hand menu. Look for a recommendation labelled “Remove conflicting negative keywords.”
Don’t auto-apply this. Google’s conflict detection has known gaps - it checks ad group and campaign negatives but doesn’t always catch conflicts from shared negative lists. Review each recommendation manually before removing anything.
Step 5: Check impression volume on your keywords
Any active keyword sitting at zero or near-zero impressions for 30+ days is worth investigating. Low impressions can have other causes - low bids, tight audience, seasonal - but a conflict is always worth ruling out first.
Filter your keyword list by impressions (ascending) and work through the bottom of the list.
Free Google Ads script
The manual process works for small accounts. For anything with more than a few hundred keywords, you need a script.
Google published an official Negative Keyword Conflicts script . It works - but it has a significant flaw: it checks all campaigns, including ended and paused experiment campaigns. The result is a flood of false positives that make you want to ignore the alerts entirely. That defeats the purpose.
The script below fixes that. It only checks campaigns with an active serving status, so the output is clean and actionable. Install it, run it, and you’ll get a Google Sheet listing every active conflict in your account.
The script
// Negative Keyword Conflicts Script for Google Ads
// Checks active campaigns only - skips ended/paused experiments
// Tab 1: Conflicts report
// Tab 2: Keyword Performance (last 30 days)
//
// Setup:
// 1. Create a blank Google Sheet and copy its URL
// 2. Replace SPREADSHEET_URL below with your spreadsheet URL
// 3. Replace RECIPIENT_EMAIL with your email address
// 4. Click Authorize, then Preview to test, then Run
// 5. Schedule weekly via the Frequency column on the Scripts page
var CONFIG = {
SPREADSHEET_URL: 'YOUR_SPREADSHEET_URL_HERE',
RECIPIENT_EMAIL: 'YOUR_EMAIL_HERE',
SEND_EMAIL_IF_NO_CONFLICTS: false
};
function main() {
var spreadsheet = SpreadsheetApp.openByUrl(CONFIG.SPREADSHEET_URL);
// Tab 1: Conflicts
var conflictSheet = getOrCreateSheet(spreadsheet, 'Conflicts');
conflictSheet.clearContents();
conflictSheet.appendRow([
'Campaign', 'Ad Group',
'Positive Keyword', 'Positive Match Type',
'Blocking Negative', 'Negative Match Type',
'Negative Level', 'Negative List Name'
]);
var conflicts = findConflicts();
if (conflicts.length === 0) {
Logger.log('No conflicts found.');
if (CONFIG.SEND_EMAIL_IF_NO_CONFLICTS) {
sendEmail([], spreadsheet.getUrl());
}
} else {
for (var i = 0; i < conflicts.length; i++) {
var c = conflicts[i];
conflictSheet.appendRow([
c.campaign, c.adGroup,
c.positiveKeyword, c.positiveMatchType,
c.negativeKeyword, c.negativeMatchType,
c.negativeLevel, c.negativeListName
]);
}
Logger.log('Found ' + conflicts.length + ' conflict(s).');
sendEmail(conflicts, spreadsheet.getUrl());
}
// Tab 2: Keyword Performance (last 30 days)
var kwSheet = getOrCreateSheet(spreadsheet, 'Keyword Performance');
kwSheet.clearContents();
kwSheet.appendRow([
'Campaign', 'Ad Group', 'Keyword', 'Match Type',
'Impressions', 'Clicks', 'CTR', 'Cost',
'Conversions', 'Conv. Rate', 'CPA'
]);
writeKeywordReport(kwSheet);
Logger.log('Keyword performance report written.');
}
function writeKeywordReport(sheet) {
var perfQuery =
'SELECT campaign.name, ad_group.name,' +
' ad_group_criterion.keyword.text,' +
' ad_group_criterion.keyword.match_type,' +
' metrics.impressions, metrics.clicks,' +
' metrics.cost_micros,' +
' metrics.conversions' +
' FROM keyword_view' +
' WHERE campaign.status = ENABLED' +
' AND campaign.serving_status = SERVING' +
' AND ad_group.status = ENABLED' +
' AND ad_group_criterion.status = ENABLED' +
' AND segments.date DURING LAST_30_DAYS';
var iter = AdsApp.search(perfQuery);
while (iter.hasNext()) {
var row = iter.next();
if (!row || !row.campaign || !row.adGroup) { continue; }
if (!row.adGroupCriterion || !row.adGroupCriterion.keyword) { continue; }
var metrics = row.metrics || {};
var cost = (parseInt(metrics.costMicros, 10) || 0) / 1000000;
var clicks = parseInt(metrics.clicks, 10) || 0;
var impressions = parseInt(metrics.impressions, 10) || 0;
var conversions = parseFloat(metrics.conversions) || 0;
var ctr = impressions > 0 ? (clicks / impressions) : 0;
var convRate = clicks > 0 ? (conversions / clicks) : 0;
var cpa = conversions > 0 ? (cost / conversions) : 0;
sheet.appendRow([
row.campaign.name,
row.adGroup.name,
row.adGroupCriterion.keyword.text,
row.adGroupCriterion.keyword.match_type,
impressions,
clicks,
parseFloat(ctr.toFixed(4)),
parseFloat(cost.toFixed(2)),
parseFloat(conversions.toFixed(2)),
parseFloat(convRate.toFixed(4)),
parseFloat(cpa.toFixed(2))
]);
}
}
function findConflicts() {
var conflicts = [];
var keywordQuery =
'SELECT campaign.id, campaign.name, ad_group.id, ad_group.name,' +
' ad_group_criterion.keyword.text,' +
' ad_group_criterion.keyword.match_type' +
' FROM ad_group_criterion' +
' WHERE campaign.status = ENABLED' +
' AND campaign.serving_status = SERVING' +
' AND ad_group.status = ENABLED' +
' AND ad_group_criterion.status = ENABLED' +
' AND ad_group_criterion.type = KEYWORD' +
' AND ad_group_criterion.negative = false';
var positiveKeywords = [];
var kwIter = AdsApp.search(keywordQuery);
while (kwIter.hasNext()) {
var row = kwIter.next();
if (!row || !row.campaign || !row.adGroup) { continue; }
if (!row.adGroupCriterion || !row.adGroupCriterion.keyword) { continue; }
positiveKeywords.push({
campaignId: row.campaign.id,
campaignName: row.campaign.name,
adGroupId: row.adGroup.id,
adGroup: row.adGroup.name,
keyword: row.adGroupCriterion.keyword.text.toLowerCase(),
matchType: row.adGroupCriterion.keyword.match_type
});
}
var campaignNegQuery =
'SELECT campaign.id,' +
' campaign_criterion.keyword.text,' +
' campaign_criterion.keyword.match_type' +
' FROM campaign_criterion' +
' WHERE campaign.status = ENABLED' +
' AND campaign.serving_status = SERVING' +
' AND campaign_criterion.type = KEYWORD' +
' AND campaign_criterion.negative = true';
var campaignNegatives = [];
var negIter = AdsApp.search(campaignNegQuery);
while (negIter.hasNext()) {
var row = negIter.next();
if (!row || !row.campaign || !row.campaignCriterion) { continue; }
if (!row.campaignCriterion.keyword) { continue; }
campaignNegatives.push({
campaignId: row.campaign.id,
keyword: row.campaignCriterion.keyword.text.toLowerCase(),
matchType: row.campaignCriterion.keyword.match_type
});
}
var adGroupNegQuery =
'SELECT campaign.id, ad_group.id,' +
' ad_group_criterion.keyword.text,' +
' ad_group_criterion.keyword.match_type' +
' FROM ad_group_criterion' +
' WHERE campaign.status = ENABLED' +
' AND campaign.serving_status = SERVING' +
' AND ad_group.status = ENABLED' +
' AND ad_group_criterion.status = ENABLED' +
' AND ad_group_criterion.type = KEYWORD' +
' AND ad_group_criterion.negative = true';
var adGroupNegatives = [];
var agNegIter = AdsApp.search(adGroupNegQuery);
while (agNegIter.hasNext()) {
var row = agNegIter.next();
if (!row || !row.campaign || !row.adGroup || !row.adGroupCriterion) { continue; }
if (!row.adGroupCriterion.keyword) { continue; }
adGroupNegatives.push({
campaignId: row.campaign.id,
adGroupId: row.adGroup.id,
keyword: row.adGroupCriterion.keyword.text.toLowerCase(),
matchType: row.adGroupCriterion.keyword.match_type
});
}
var sharedSetQuery =
'SELECT campaign.id, shared_set.id, shared_set.name' +
' FROM campaign_shared_set' +
' WHERE campaign.status = ENABLED' +
' AND campaign.serving_status = SERVING' +
' AND shared_set.type = NEGATIVE_KEYWORDS';
var sharedSetMap = {};
var sharedSetIter = AdsApp.search(sharedSetQuery);
while (sharedSetIter.hasNext()) {
var row = sharedSetIter.next();
if (!row || !row.campaign || !row.sharedSet) { continue; }
var setId = row.sharedSet.id;
if (!sharedSetMap[setId]) {
sharedSetMap[setId] = { name: row.sharedSet.name, campaignIds: {} };
}
sharedSetMap[setId].campaignIds[row.campaign.id] = true;
}
var sharedCriterionQuery =
'SELECT shared_set.id,' +
' shared_criterion.keyword.text,' +
' shared_criterion.keyword.match_type' +
' FROM shared_criterion' +
' WHERE shared_criterion.type = KEYWORD';
var sharedNegatives = [];
var sharedIter = AdsApp.search(sharedCriterionQuery);
while (sharedIter.hasNext()) {
var row = sharedIter.next();
if (!row || !row.sharedCriterion || !row.sharedCriterion.keyword) { continue; }
if (!row.sharedSet) { continue; }
var setId = row.sharedSet.id;
if (!sharedSetMap[setId]) { continue; }
sharedNegatives.push({
setId: setId,
listName: sharedSetMap[setId].name,
campaignIds: sharedSetMap[setId].campaignIds,
keyword: row.sharedCriterion.keyword.text.toLowerCase(),
matchType: row.sharedCriterion.keyword.match_type
});
}
for (var i = 0; i < positiveKeywords.length; i++) {
var pos = positiveKeywords[i];
for (var j = 0; j < campaignNegatives.length; j++) {
var neg = campaignNegatives[j];
if (neg.campaignId !== pos.campaignId) { continue; }
if (isConflict(pos.keyword, neg.keyword, neg.matchType)) {
conflicts.push({
campaign: pos.campaignName,
adGroup: pos.adGroup,
positiveKeyword: pos.keyword,
positiveMatchType: pos.matchType,
negativeKeyword: neg.keyword,
negativeMatchType: neg.matchType,
negativeLevel: 'Campaign',
negativeListName: '-'
});
}
}
for (var j = 0; j < adGroupNegatives.length; j++) {
var neg = adGroupNegatives[j];
if (neg.campaignId !== pos.campaignId) { continue; }
if (neg.adGroupId !== pos.adGroupId) { continue; }
if (isConflict(pos.keyword, neg.keyword, neg.matchType)) {
conflicts.push({
campaign: pos.campaignName,
adGroup: pos.adGroup,
positiveKeyword: pos.keyword,
positiveMatchType: pos.matchType,
negativeKeyword: neg.keyword,
negativeMatchType: neg.matchType,
negativeLevel: 'Ad Group',
negativeListName: '-'
});
}
}
for (var k = 0; k < sharedNegatives.length; k++) {
var neg = sharedNegatives[k];
if (!neg.campaignIds[pos.campaignId]) { continue; }
if (isConflict(pos.keyword, neg.keyword, neg.matchType)) {
conflicts.push({
campaign: pos.campaignName,
adGroup: pos.adGroup,
positiveKeyword: pos.keyword,
positiveMatchType: pos.matchType,
negativeKeyword: neg.keyword,
negativeMatchType: neg.matchType,
negativeLevel: 'Shared List',
negativeListName: neg.listName
});
}
}
}
return conflicts;
}
function isConflict(positiveKw, negativeKw, negMatchType) {
var pos = positiveKw.toLowerCase();
var neg = negativeKw.toLowerCase();
if (negMatchType === 'BROAD') {
return pos.split(' ').indexOf(neg) !== - 1;
}
if (negMatchType === 'PHRASE') {
return pos.indexOf(neg) !== - 1;
}
if (negMatchType === 'EXACT') {
return pos === neg;
}
return false;
}
function sendEmail(conflicts, sheetUrl) {
if (!CONFIG.RECIPIENT_EMAIL) { return; }
var subject = conflicts.length > 0
? conflicts.length + ' negative keyword conflict(s) found'
: 'No negative keyword conflicts found';
var body = conflicts.length > 0
? 'Your account has ' + conflicts.length + ' conflict(s).\n\nView report: ' + sheetUrl
: 'No conflicts detected in your active campaigns.';
MailApp.sendEmail(CONFIG.RECIPIENT_EMAIL, subject, body);
}
function getOrCreateSheet(spreadsheet, name) {
return spreadsheet.getSheetByName(name)
|| spreadsheet.insertSheet(name);
} How to install
- In Google Ads, click Tools in the left-hand menu, then select Bulk Actions > Scripts
- Click the blue + button and select New Script
- Give it a name - something like “Negative Keyword Conflict Checker”
- Delete the placeholder code in the editor, then paste in the script above
- Replace
YOUR_SPREADSHEET_URL_HEREwith the URL of a Google Sheet you own - Replace
YOUR_EMAIL_HEREwith your email address - Click Authorize when prompted - this gives the script read access to your account
- Click Preview to do a dry run first. Check the Changes/Logs panel for output
- Once you’re happy it’s working, click Run
- To schedule it: on the Scripts page, hover over the Frequency column next to your script, click the pencil icon, and set it to Weekly
The output sheet will show you every conflict: which campaign, which ad group, which positive keyword is being blocked, which negative is blocking it, and whether it’s coming from a campaign-level negative or a shared list.
Watch out for shared list conflicts
That last column matters. If the conflict is coming from a shared list, removing it from one campaign removes it for every campaign using that list. Tread carefully.
Detect negative keyword conflicts automatically
Scripts are great. But they still require you to install them, check the output, and repeat the process across every account you manage.
If you’re an agency running 20+ accounts, that’s 20 scripts to maintain, 20 spreadsheets to monitor, and 20 opportunities to miss something. The manual overhead adds up fast.
This is where AI agents from PPC.io can change the equation.
An AI agent connected to your Google Ads account can scan for negative keyword conflicts on demand -no script installation, no spreadsheet setup. You ask it a question and it queries your account data directly.
The AI approach doesn’t replace understanding conflicts - you still need to know what you’re looking at to make the right call on each one. But for detection at scale, it’s faster than any manual process.
How to fix negative keyword conflicts
Finding the conflict is the hard part. Fixing it is usually straightforward - but there are a few decisions to make before you start deleting negatives.
First: Is the conflict intentional?
Some conflicts are deliberate. A common example: you use an exact match negative [-running shoes] alongside a phrase match positive “running shoes” to control which specific queries trigger your ad. That’s a structural choice, not a mistake.
Before removing any negative, ask yourself why it was added. If you can’t find a record of the reasoning, that’s a sign the account needs better change history documentation - but it doesn’t automatically mean the negative is wrong.
If the conflict is unintentional, here’s how to fix it
✅ Option 1: Remove the negative keyword
The straightforward fix when the negative is genuinely no longer needed. Go to Keywords > Negative keywords, find the keyword, and remove it. If it’s in a shared list, remove it from the list - but check what other campaigns use that list first.
✅ Option 2: Tighten the match type
If the negative is still useful but too broad, tighten it rather than deleting it. Switch from broad match to phrase match or exact match to reduce collateral damage. A broad match “-training” becomes a phrase match “-corporate training” - you keep the protection, you stop blocking “sales training.”
✅ Option 3: Move the negative to ad group level
If the negative is correctly blocking traffic in most of your campaigns but causing a conflict in one specific ad group, move it from campaign level down to the specific ad groups where it belongs. This takes more time but gives you more precise control.
❌ What not to do: auto-apply Google’s recommendations
Google will sometimes recommend removing conflicting negatives automatically. Don’t let it do this without reviewing each one. Google’s conflict detection misses shared list conflicts, and auto-applying can remove negatives that were protecting campaigns from wasting budget.
The agency problem: conflicts at scale
Everything above applies to a single account. For agencies, the problem compounds in ways that make manual auditing genuinely impractical.
Here’s what makes it harder at scale:
Shared negative lists across clients. If you use a master negative list across multiple client accounts - which is efficient and sensible - a single bad negative can cause conflicts across all of them simultaneously. One list, multiplied by 30 accounts.
Account handoffs. When you take on a new client, you inherit their negative keywords too. Conflicts from previous agencies are common. The incoming audit should always include a conflict check - but it often doesn’t.
Multiple team members. In agencies with more than two or three people working across accounts, keyword additions and negative additions happen in parallel. Without a process for checking conflicts before adding negatives, they accumulate silently.
The practical answer for agencies isn’t just running the script or the AI check once. It’s making conflict detection a regular part of account maintenance - built into your onboarding process for new clients, your monthly audit checklist, and your workflow when anyone adds new negative keywords.
Run conflict checks across every client account automatically
PPC.io can run this check across your entire account portfolio automatically, flagging conflicts before they have time to drain budget. It’s one of the audit checks we built specifically because this problem doesn’t get easier as you manage more accounts - it gets harder.
Conclusion
Negative keyword conflicts are fixable. The frustrating part is that they’re also invisible - active keywords that look fine in your account but are silently blocked from ever entering the auction.
The process is straightforward: audit your negative lists against your positive keywords, use the script above to catch what manual checks miss, and if you’re managing multiple accounts, use an AI agent to run this at scale.
The bigger habit to build is prevention. Before adding any new negative keyword - especially at campaign level or in a shared list - cross-reference it against your active keywords. Thirty seconds of checking now is worth hours of debugging later.
If you want to run this check across your Google Ads accounts without installing scripts or building spreadsheets, PPC.io’s AI agent handles it automatically.
Frequently Asked Questions
What is a negative keyword conflict in Google Ads?
A negative keyword conflict happens when a negative keyword in your account blocks a positive keyword you’re actively bidding on. The affected keyword shows as “Active” in your account but never enters the auction, generating zero impressions.
How do I find negative keyword conflicts in Google Ads?
There are three ways: (1) manually cross-reference your negative keyword exports against your active keywords, (2) check Google’s Recommendations tab for a “Remove conflicting negative keywords” suggestion, or (3) run a Google Ads script that automatically checks all active campaigns and outputs conflicts to a Google Sheet.
Do negative keywords override positive keywords?
Yes. In Google Ads, negative keywords always take precedence over positive keywords. If a negative keyword matches a search query that would also trigger a positive keyword, the negative wins and the ad does not show.
How do I fix a negative keyword conflict?
You have three options: remove the negative keyword entirely if it’s no longer needed; tighten its match type from broad to phrase or exact to reduce collateral blocking; or move it from campaign level to ad group level so it only applies where it’s actually useful.
Why does Google Ads show a keyword as active if it has a conflict?
Google Ads doesn’t always surface conflicts proactively. The keyword status reflects whether it’s enabled in your account -not whether it’s being blocked by a negative. Shared negative list conflicts in particular are often missed by Google’s own conflict detection tools.
How often should I audit for negative keyword conflicts?
At minimum, check for conflicts when onboarding new accounts, after any major campaign restructure, and whenever someone adds new negative keywords at the campaign level or to a shared list. For actively managed accounts, a monthly automated check using a script is a sensible baseline.