Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

README.md

Search Term Intent Classifier + Auto-Negative Suggester

Pulls the last N days of search-term data, classifies every term by intent (Brand / Competitor / Informational / Transactional / Navigational / Unclear), flags the obvious negative-keyword candidates, and either lists them for your review (dry run) or applies them to a shared negative-keyword list (live mode).

Solves the broad-match-with-smart-bidding control problem: Google now matches your ads to far more search queries than the keyword you actually bid on, and the only practical lever you have is negative keywords. This script automates the review.

Why this exists

Broad match + Smart Bidding is the default for most new campaigns in 2026. It pushes traffic toward intent Google decides is similar to your bid keyword, but "similar" is generous — informational queries, navigational queries, and outright off-topic terms slip through. Manually reviewing the Search Terms report every week is tedious and the obvious patterns are the same every account: "how to", "what is", "login", "support".

This script encodes those patterns and lets you act on them in bulk. Paid SaaS tools (Negator.io, ShopStory) charge $29–$199/mo for this. You get it for free, with full control over the rules.

Two safety levels

Every run reports what it would do. Only when you explicitly set DRY_RUN = false does the script touch your account.

  • DRY RUN (default) — pulls search terms, classifies, writes to Sheet, emails a digest of suggestions. Nothing is added to your campaigns.
  • LIVE — same as above, plus adds the suggested terms (EXACT match by default) to a shared negative-keyword list named Aumlytics Auto Negatives (or whatever you set NEGATIVE_LIST_NAME to). The list is auto-created if missing. You then apply that list to the campaigns you want protected. No campaign-level or ad-group-level negatives are ever created — everything goes through the shared list so it's audit-friendly and instantly reversible.

Two flavors

File When to use
single-account.js One Google Ads account.
mcc.js Manager / MCC account — runs across all child accounts (or a labeled subset) in parallel and applies negatives per child.

Intent classification

A search term gets the first intent it matches, in this priority order:

  1. Brand — contains any term from CONFIG.BRAND_TERMS (your brand name, common misspellings). Never flagged as a negative, even if it never converts.
  2. Competitor — contains any term from CONFIG.COMPETITOR_TERMS. By default not flagged either (most B2B accounts WANT competitor traffic). Enable RULES.competitor_block if you don't.
  3. Navigational — contains "login", "support", "contact", "phone number", etc. Users looking for help, not to buy. Almost always a waste in PPC.
  4. Informational — contains "how to", "what is", "tutorial", "guide", "vs", etc. Top-of-funnel research, rarely converts on first click.
  5. Transactional — contains "buy", "price", "near me", "for sale", etc. Never flagged — these are your highest-intent terms.
  6. Unclear — no pattern matched. Default for everything that isn't obviously one of the above.

The pattern lists are intentionally short and English-language by default — edit them in CONFIG to fit your account's language and vocabulary.

Suggestion rules (in CONFIG.RULES)

Rule Triggers when Default
informational_no_conv Intent = Informational, cost ≥ $25, conversions = 0 Enabled
unclear_waste Intent = Unclear, cost ≥ $50, conversions = 0 Enabled
navigational_any Intent = Navigational, clicks ≥ 3 Enabled
competitor_block Intent = Competitor, clicks ≥ 1 Disabled — most accounts want competitor bids

Tune the thresholds to your account size. A $500/day account should use much higher minCost values than a $30/day account.

Setup — single account

  1. Open the account → Tools → Bulk actions → Scripts → +
  2. Paste single-account.js
  3. Edit CONFIG:
    • EMAIL — required
    • BRAND_TERMS — your brand variants. Don't skip this — without it your brand traffic gets misclassified as Informational/Unclear and could be suggested as a negative.
    • COMPETITOR_TERMS — optional but recommended for cleaner classification
    • RULES.*.minCost and minClicks — tune to your account size
  4. Click Preview. Read the log. Authorize.
  5. Click Run with DRY_RUN = true. Read the email digest carefully.
  6. Open the Sheet, filter Suggested Negative = Yes, scan the terms. Anything that looks wrong → tune your CONFIG patterns and re-run.
  7. Only after at least 1–2 dry-run cycles you trust: flip DRY_RUN = false and run.
  8. Manually apply the shared negative list to your campaigns in the Google Ads UI (Tools → Shared library → Negative keyword lists → tick the list → Apply to campaigns). The script creates the list; YOU control which campaigns use it.
  9. Schedule weekly.

Setup — MCC

  1. From your MCC → Tools → Bulk actions → Scripts → +
  2. Paste mcc.js
  3. CONFIG.BRAND_TERMS and CONFIG.COMPETITOR_TERMS are shared across all child accounts in v1. If your MCC has accounts with very different brands, fork the script and key off acct.getName() inside processAccount().
  4. Use ACCOUNT_LABEL to limit scope to a labeled subset.
  5. Always start with DRY_RUN = true for the first run.
  6. Weekly cadence with LIVE mode is the common production pattern.

Required scopes

  • AdWords (read) — to query search terms
  • AdWords (write) — only used when DRY_RUN = false, to add negatives to a shared list. Not requested in dry run.
  • Spreadsheets — to write the output Sheet
  • Mail — to send the digest

If your Workspace admin restricts script scopes, dry-run mode needs only read + Sheets + Mail.

What gets added when DRY_RUN = false

For each unique suggested term (deduplicated by MATCH_TYPE:term):

  • A new entry in the shared negative keyword list named CONFIG.NEGATIVE_LIST_NAME
  • Match type from CONFIG.NEGATIVE_MATCH_TYPE (default EXACT)
  • Format: [search term] for EXACT, "search term" for PHRASE, plain text for BROAD

The script SKIPS:

  • Terms already in the list (duplicates)
  • Terms whose addNegativeKeyword call throws (logged in the digest)
  • Terms shorter than CONFIG.MIN_TERM_LENGTH (default 4 chars)

Reverting

The script never modifies campaigns directly. To revert:

  • Easy: Tools → Shared library → Negative keyword lists → Aumlytics Auto Negatives → un-apply from campaigns
  • Full clean: delete entries from the shared list manually, OR delete the list entirely

The shared list pattern means every negative the script adds is grouped in one place — no hunting through 50 campaigns for what changed.

Limitations of v1

  • Intent classification is keyword-pattern based. No NLP model, no LLM, no contextual understanding. A search like "best login solutions for enterprise" gets classified Navigational because it contains "login", even though it's clearly commercial. You'll see edge cases — tune the patterns or accept the noise.
  • Per-account brand/competitor lists in MCC mode require a fork. v1 uses one shared list across all child accounts.
  • No Phrase / Broad suggestion — defaults to EXACT for safety. You can switch with NEGATIVE_MATCH_TYPE but understand the implications first.
  • No mining of n-grams. Each search term is evaluated whole. If "how to install" appears 100x as part of longer queries but never as a 3-word query, the script won't suggest it as a negative.

License

MIT — see LICENSE in the parent repo.