-
Notifications
You must be signed in to change notification settings - Fork 0
Negative Keyword Conflict Cleaner
Script: scripts/cid_negative_conflict_cleaner.js
Level: Single account (CID) — see MCC Version for manager accounts
What it does: Detects and removes negative keywords that literally block your active (positive) keywords, in one run.

A conflict exists when a negative keyword's text would block one of your enabled keywords, per Google's literal negative matching rules:
| Negative Match Type | Blocks a keyword when… |
|---|---|
| EXACT | the keyword text is identical |
| PHRASE | the negative appears as an ordered word sequence inside the keyword |
| BROAD | ALL words of the negative appear anywhere in the keyword, any order |
Negatives never use close variants — plurals, misspellings, and apostrophe differences do not match. (That's correct behavior, and it's also why a pure conflict checker misses blocked queries — see the Blocked Search Term Recovery script for that.)
-
Account-level negatives — queried from the
ACCOUNT_LEVEL_NEGATIVE_KEYWORDSshared set viashared_criterionGAQL, removed viaAdsApp.mutate()with asharedCriterionOperation - Campaign-level negatives — entity iterators, scoped to that campaign's keywords
- Ad group-level negatives — scoped to that ad group's keywords
- Shared negative keyword lists — scoped to keywords in attached campaigns
-
DRY_RUN— report-only mode; always run this first -
MAX_BLOCKED_BEFORE_SKIP(default 25) — a negative blocking 25+ keywords is almost always an intentional brand blocker or traffic sculptor; it's logged as SKIPPED instead of removed - Per-level removal toggles — shared lists affect every attached campaign, so that toggle deserves extra thought
- Complete audit log — every row records level, scope, negative text, match type, sample blocked keywords, and the action taken, so any removal can be reversed manually in seconds
In well-architected accounts, many "conflicts" are deliberate traffic sculpting — brand negated out of non-brand, exact terms negated out of broad campaigns to route queries, campaign negatives steering traffic between ad groups. Before running with DRY_RUN: false, review the report and ask of each row: is this stale, or is this architecture? The 25-keyword skip threshold catches the most obvious sculptors, but nothing beats knowing your own account structure.
var CONFIG = {
DRY_RUN: true,
SPREADSHEET_URL: '',
REMOVE_ACCOUNT_LEVEL: true,
REMOVE_CAMPAIGN_LEVEL: true,
REMOVE_ADGROUP_LEVEL: true,
REMOVE_FROM_SHARED_LISTS: false,
MAX_BLOCKED_BEFORE_SKIP: 25
};By AHMEEGO™ / It All Started With A Idea LLC. Discuss in r/ppc_.