agentsclimarketplace

Growth google ads

Skill andredezzy/maccing/skills/growth-google-ads

Use when managing Google Ads campaigns, creating ads, optimizing campaigns, checking metrics, or automating Google Ads via Scripts. Triggers on "google ads", "google ads campaign", "create campaign", "ad campaign", "google ads scripts", "check google ads", "google ads metrics", "campaign performance", "AdsApp", "automation", "adspower".From its SKILL.md

Install
npx -y skills add andredezzy/maccing --skill growth-google-ads

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

24.5 KB, as published. Nobody here has run it

Google Ads Management

Scripts-first approach to Google Ads. Google Ads Scripts run inside the Google Ads UI with zero setup — no developer token, no OAuth, no approval. Use Scripts for everything. Browser automation is only needed to apply Google's built-in recommendations.

Project data lives in .maccing/growth/google-ads/<account>/README.md. This file contains only generic, reusable platform knowledge.

Iron Laws

0. MANDATORY: Read Project Context First

BEFORE ANY ACTION, ALWAYS READ THE PROJECT README.
.maccing/growth/google-ads/<account>/README.md contains current state:
campaign IDs, budgets, negatives, performance, pending actions.
Without reading it, you WILL operate on stale data.

1. Google Ads Scripts First, Always

ALWAYS USE GOOGLE ADS SCRIPTS FOR WRITE AND READ OPERATIONS.
THE BROWSER IS ONLY FOR APPLYING GOOGLE RECOMMENDATIONS.

Priority order:

  1. Google Ads Scripts (AdsApp.mutate / AdsApp.search) — create ads, add keywords, read metrics, manage campaigns. Reliable, requires zero approval, works immediately.
  2. Google Ads MCP (growth-google-ads MCP server) — secondary option when developer token is approved. Same power as Scripts but callable directly from Claude.
  3. The operator's own browser — only for applying Google's built-in recommendations (the Recommendations page cards). There is no API for this. Drive it with aside-browser: the Recommendations page runs inside the operator's authenticated Google session, so the traffic has to carry their identity.

NEVER guide the user through manual Google Ads UI steps when Scripts can do it.

2. Self-Improving Skill

After every session: update patterns, note new script techniques, record gotchas.

3. Exact UI Paths for Manual Actions

WHEN GUIDING ANY MANUAL ACTION, PROVIDE THE FULL CLICK PATH.
NEVER say "go to Conversions". ALWAYS say the exact sequence.

Every manual instruction MUST include:

  • The exact sidebar item (e.g., "Metas")
  • Every sub-menu and click in order (e.g., "→ Conversões → Resumo")
  • The exact element to click (e.g., "→ clica em 'Editar meta' ao lado de 'Visualização de página'")
  • The exact toggle/button (e.g., "→ muda 'Otimização de ações' de Principal para Secundário")
  • The save action (e.g., "→ Salvar")

Format: Sidebar Item → Sub-menu → Page Element → Action → Confirm

Conversion goals UI structure (confirmed 2026-05-05):

  • Sidebar: Metas (🏆 icon) → Conversões (dropdown) → Resumo
  • Page shows goals grouped by category
  • Each group has: Campanhas count, Principais ações count, Status, "Editar meta" button
  • Click "Editar meta" → expands to show individual conversion actions
  • Each action shows: name, Otimização de ações (Principal/Secundário), Origem, Todas as conv, Status
  • Change Principal/Secundário here — NOT via a toggle on the action detail page
  • metrics.conversions takes days to recalculate after changing Primary→Secondary

Conventions & Best Practices

Naming Conventions

EntityFormatExample
CampaignBrand - Product - Channel - GeoAcme Brand - Search - PH/PK/BD
Ad Group[Theme] - [Match Type]Pricing Keywords - Broad
Script fileNN-kebab-description.js33-campaign-optimize.js
Negative keywordLowercase, broad match defaultwithout paying

Ad Group Organization

Split ad groups by match type for granular control:

  • Broad match group: Discovery keywords, wider reach, lower CPC
  • Exact match group: High-intent terms from search term reports, tailored RSA copy
  • Each ad group gets its own RSA with copy tailored to that audience segment
  • Never mix match types in the same ad group — makes bid optimization impossible

RSA Best Practices (Confirmed Working)

  • 15 headlines + 4 descriptions for maximum ad strength
  • Headlines must cover diverse angles: what it is, action, community, CTA, benefit, differentiator
  • Always count characters before creation: headlines ≤ 30, descriptions ≤ 90
  • Path1/Path2 should match the ad group theme
  • Ad strength POOR = Google suppresses impressions. Aim for GOOD or EXCELLENT.
  • One ad group can have 2-3 RSAs for A/B testing — remove underperformers (0 impressions)

Keyword Strategy

  • Broad match: Use for discovery. Monitor search terms report weekly. Negative aggressively.
  • Exact match: Graduate winning search terms from broad campaigns to exact match in dedicated ad groups.
  • Negatives at campaign level: Prevents all ad groups in the campaign from triggering on those terms.
  • Quality over CTR: High CTR on low-intent keywords wastes budget. Evaluate keywords by downstream funnel conversion, not just click metrics.

Multi-Step Script Pattern (Confirmed Working)

A single script can chain multiple operation types reliably:

1. campaignCriterionOperation.create (negatives) via mutateAll
2. GAQL query → adGroupCriterionOperation.update (pause keywords) via mutateAll
3. adGroupCriterionOperation.create (add keywords) via mutateAll
4. adGroupAdOperation.remove (delete ad) via mutate
5. adGroupAdOperation.create (new RSA) via mutate
6. adGroupOperation.update (rename) via mutate
7. adGroupOperation.create (new ad group) via mutate

Use AdsApp.mutateAll() for batch (arrays), AdsApp.mutate() for single operations. Chain sequentially. If step N fails, return to stop the script.

GAQL Gotchas

FieldIssueFix
quality_info.qualityScoreTypeError if undefinedDon't include in SELECT, query separately
ad_relevance, landing_page_experienceInvalid in Scripts APIRemove from queries
metrics.conversionsInflated after Primary→Secondary changeWait 7+ days for recalculation
REGEXP_MATCHWorks for keyword text filteringUse for pattern-based queries
metrics.conversions with conversion_actionPROHIBITED_METRIC errorUse all_conversions or query from campaign with segments.conversion_action_name

Negative Keyword Conflicts

Broad match negatives can silently block positive keywords. Example: negative "jobs from home" (BROAD) blocks positive "online jobs from home". Google shows this in Recommendations → "Remover palavras-chave negativas em conflito" but does NOT prevent serving — it just wastes budget on zero impressions for those keywords.

Always audit: after adding negatives, cross-check against positive keywords. A broad negative matches any query containing those words in any order.

Funnel-Aware Negative Keywords

When the business model has a free tier → paid conversion funnel:

  • Negative the zero-spend phrases — "without paying", "no cost", "free money", "no money needed" — these users state up front that they will not pay. Do NOT negate bare "free": the free tier is the top of your own funnel.
  • High CTR ≠ high value. Evaluate keywords by downstream conversion (purchase), not clicks
  • Search terms with high downstream CVR → promote to exact match

Scripts Workflow

How to run a script

  1. Claude reads the template from the plugin's skills/growth-google-ads/scripts/ directory

  2. Claude fills in CONFIG variables (IDs, ad copy, keywords) from the project README

  3. Claude gives the script to the user using this EXACT format:

    Script: script-name Arquivo: .maccing/growth/google-ads/<account>/scripts/NN-script-name.js Copiar: cat .maccing/growth/google-ads/<account>/scripts/NN-script-name.js | pbcopy

    ALWAYS provide all three lines. Never skip any.

  4. User opens Google Ads → Ferramentas → Scripts → Novo script

  5. User names the script, pastes content, clicks Executar (NOT Visualizar — Preview is read-only and blocks all mutations)

  6. For read scripts: user copies the Logger output back to Claude

  7. For write scripts: script logs success/failure per operation

When to use Scripts vs MCP vs the browser

TaskScriptsMCP (when approved)Browser
Create RSA ads✅ AdsApp.mutate✅❌ Angular overlay
Add keywords✅ AdsApp.mutateAll✅⚠️ FAB works but fragile
Read metrics✅ AdsApp.search GAQL✅ GAQL✅ innerText
Apply recommendations❌ No API❌✅ Only way
Create conversion actions✅ AdsApp.mutate✅❌ Blocked
Pause/resume campaigns✅ AdsApp.mutate✅✅ Status toggle
Remove negative keywords✅ AdsApp.mutate✅❌ No bulk UI
Update ad URLs✅ AdsApp.mutate✅❌
Add negative keywords✅ AdsApp.mutateAll✅⚠️ Campaign picker broken
Modify conversion goals❌ ALL approaches fail❌ Same API❌
Remove campaigns❌ Campaigns can only be PAUSED✅✅
Create sitelinks✅ AdsApp.mutate✅❌ 16-field form corrupts

AdsApp.mutate() — What Works and What Doesn't (Confirmed May 2026)

Works:

  • campaignOperation create/update (name, status, budget, bidding) — confirmed
  • adGroupCriterionOperation update (pause/enable keywords) — confirmed
  • campaignCriterionOperation create (add negative keywords) — confirmed
  • campaignCriterionOperation remove (delete negative keywords) — confirmed
  • adGroupAdOperation create (create RSA ads) — confirmed
  • adGroupAdOperation remove (delete ads) — confirmed
  • conversionActionOperation create (create new conversion action) — confirmed
  • adGroupOperation create/update (create ad group, rename) — confirmed
  • assetOperation create (sitelinks) — confirmed
  • campaignAssetOperation create/remove (link/unlink sitelinks to campaigns) — confirmed
  • assetOperation remove → NOT supported. Assets can only be unlinked via campaignAssetOperation.remove

Also works (confirmed 2026-05-12):

  • conversionActionOperation update name — confirmed (renamed 4 conversion actions in single mutateAll)
  • campaignCriterionOperation create with location (add geo targets) — confirmed
  • campaignBudgetOperation update amountMicros — confirmed
  • campaign.finalUrlSuffix for UTM parameters — confirmed (set on 3 campaigns, verified via GAQL query)
  • Campaigns with status REMOVED cannot be updated (name, status, anything) → "The operation is not allowed for removed resources"

GAQL gotcha (confirmed 2026-05-13):

  • geographic_view queries require campaign.status in SELECT clause when filtering by campaign.status in WHERE — otherwise EXPECTED_REFERENCED_FIELD_IN_SELECT_CLAUSE error

Does NOT work:

  • conversionActionOperation update/remove on WEBPAGE_CODELESS type → MUTATE_NOT_ALLOWED
  • conversionActionOperation update status to DISABLED → generic error
  • customerConversionGoalOperation (update biddable) → generic error
  • campaignConversionGoalOperation (update biddable) → generic error
  • campaignOperation with conversionGoalCampaignConfig → generic error
  • campaignOperation.remove → generic error. Campaigns can only be paused.
  • Any operation on REMOVED campaigns → "not allowed for removed resources"

Classic AdsApp API (always works as fallback):

var campaigns = AdsApp.campaigns().withCondition("campaign.id = 123").get();
campaigns.next().enable();  // or .pause()
// NOTE: .remove() does NOT exist on campaign objects in Google Ads Scripts

// Bidding strategy change (classic API only, mutate update fails):
campaigns.next().bidding().setStrategy("TARGET_SPEND", { cpcBidCeiling: 2.50 });
// TARGET_SPEND = Maximize Clicks. "MAXIMIZE_CLICKS" is NOT valid.

AdsApp.mutate() return values:

  • result.isSuccessful() — boolean
  • result.getErrorMessages() — array of strings
  • NO method to get the created resource name (getReturnValue() and getReturnedResourceName() both don't exist)
  • To reference created entities: use temp resource names with mutateAll (see below)

Temp resource names (confirmed working): Chain dependent creates in a single mutateAll batch using negative IDs:

var ops = [
  { campaignBudgetOperation: { create: { resourceName: "customers/CID/campaignBudgets/-1", ... } } },
  { campaignOperation: { create: { resourceName: "customers/CID/campaigns/-2", campaignBudget: "customers/CID/campaignBudgets/-1", ... } } },
  { adGroupOperation: { create: { resourceName: "customers/CID/adGroups/-3", campaign: "customers/CID/campaigns/-2", ... } } },
  { adGroupCriterionOperation: { create: { adGroup: "customers/CID/adGroups/-3", keyword: {...} } } },
  { adGroupAdOperation: { create: { adGroup: "customers/CID/adGroups/-3", ad: {...} } } }
];
AdsApp.mutateAll(ops); // All resolved in one batch

Then query by name to get real IDs: SELECT campaign.id FROM campaign WHERE campaign.name = '...'

Workaround: WEBPAGE_CODELESS Page View Conversions

WEBPAGE_CODELESS conversion actions are completely immutable via any API. Cannot disable, remove, change status, or modify biddable/primaryForGoal. This is a hard Google platform restriction.

Solution: Add send_page_view: false to gtag('config', ...) in the website code. This stops the codeless conversion from firing at the source.

Manual UI: Conversion Goal Management

Changing primaryForGoal / otimização de ações CANNOT be done via Scripts or API. Must be done manually:

Metas (sidebar, ícone troféu) → Conversões → Resumo
→ clica no nome da conversão (texto azul)
→ página de detalhes abre com abas "Detalhes" e "Configurações"
→ clica "Editar configurações" (botão azul, canto inferior direito da seção Configurações)
→ seção "Otimização de ações" expande com 2 radio buttons:
   ○ "Ação primária utilizada para otimização de lances"
   ● "Ação secundária não utilizada para otimização de lances"
→ seleciona a opção desejada → Salvar

Campaign Creation via Scripts

Required Fields (confirmed)

campaignOperation: {
  create: {
    name: "...",
    status: "PAUSED",
    advertisingChannelType: "SEARCH",
    campaignBudget: budgetResourceName,
    maximizeConversions: {},  // NOT biddingStrategyType
    containsEuPoliticalAdvertising: "DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING",  // REQUIRED
    networkSettings: {
      targetGoogleSearch: true,
      targetSearchNetwork: false,
      targetContentNetwork: false
    }
  }
}

Campaign Creation Gotchas

  • biddingStrategyType: "MAXIMIZE_CONVERSIONS" does NOT work → use maximizeConversions: {} object
  • maximizeClicks does NOT exist in v23 → use targetSpend: { cpcBidCeilingMicros: "1500000" } for Maximize Clicks
  • containsEuPoliticalAdvertising is REQUIRED even for non-EU targeting
  • Correct enum value: "DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING" (not "ADS", not boolean)
  • Budget must have explicitlyShared: false for Maximize Conversions bidding
  • Campaign name conflict: campaignOperation.create fails if a PAUSED campaign with same name exists
  • Campaigns cannot be deleted — only paused. They stay in the account forever.

Sitelink Creation (confirmed)

finalUrls belongs to the Asset object (sibling of sitelinkAsset), NOT inside sitelinkAsset.

// WRONG
{ sitelinkAsset: { linkText: "...", finalUrls: ["url"] } }

// RIGHT
{ sitelinkAsset: { linkText: "...", description1: "...", description2: "..." }, finalUrls: ["url"] }

Sitelink Policy: Common Disapprovals

TriggerPolicyExample
"win", "winning" in descriptionsDeclarações não confiáveis"Play and win daily"
Money-outcome terms on landing pageMISLEADING_CONTENTpage copy promising "profit sharing"
"profit", "returns", "guaranteed"Unreliable claims"Daily profit sharing"

Fix pattern: sitelink assets are immutable — create a new compliant asset, unlink old from campaigns, link new. Old asset stays in account (cannot be deleted).

Ad Copy Limits

ElementMax Length
Headline30 chars
Description90 chars
Path115 chars
Path215 chars

ALWAYS count characters before creating RSA ads. The API gives generic "Too long" error.

GA4 Setup (Generic Flow)

Create GA4 Property

analytics.google.com (NOT ads.google.com)
→ Create property with correct timezone + currency
→ Configure web data stream
→ Copy Measurement ID (format: G-XXXXXXXXXX)

Create Measurement Protocol API Secret

analytics.google.com → Admin → Property → Data Streams → click stream
→ Events section → Measurement Protocol API secrets → Create

Link GA4 to Google Ads

analytics.google.com → Admin → Product links → Google Ads → Link
→ Select Google Ads account → Confirm

Server-Side Conversion Tracking (Generic Pattern)

Architecture

Frontend (GA4 tag) ──cross-domain linker──▶ App (GA4 tag)
                                              │
                                    captures gaClientId + gaSessionId
                                              │
                                              ▼
                                    Backend: UserAttribution record
                                              │
                            ConversionTrackingListener
                              USER_REGISTERED → GA4 MP "sign_up"
                              CONTRACT_ACTIVATED → GA4 MP "purchase" + value
                                              │
                                              ▼
                                    GA4 ──linked──▶ Google Ads

Key Technical Facts

  • gclid is NOT a GA4 MP field. Attribution via session stitching (client_id + session_id)
  • engagement_time_msec: 100 required in every MP event or GA4 may ignore it
  • transaction_id in purchase events = deduplication key
  • GA4 MP production endpoint always returns 2xx (no error feedback). Use debug endpoint for testing
  • Debug endpoint: https://www.google-analytics.com/debug/mp/collect
  • client_id: use gtag('get', 'G-XXX', 'client_id', cb) API instead of parsing _ga cookie
  • session_id goes in event params, NOT top-level body
  • Max 72 hours backdating for events

Restricted-Category Policy Learnings

Note: the enforcement patterns below are practitioner-observed, not official Google policy. Verify against ads.google.com policy before acting.

  • "Binary" in investment context = permanent ban trigger (binary options are banned)
  • "Profit sharing" + "join/refer" = recruitment-scheme flag
  • "Automated trading" triggers review for financial licensing
  • Landing page MUST have: legal business name, physical address, registration number, T&C, Privacy Policy, risk disclaimers
  • Google requires financial services verification for India, but NOT for PH/PK/BD (specific program name "G2RS" is a practitioner-observed term, not confirmed in official Google Ads documentation)
  • Safe ad copy pattern: focus on TOOL (platform, app), not OUTCOME (returns, profits)
  • Include "Capital at risk" in ad copy — Google favors ads with embedded disclaimers
  • Campaign with disapproval history should be abandoned — create new campaign with clean history
  • MISLEADING_CONTENT is caused by landing PAGE CONTENT, not by domain name

MISLEADING_CONTENT Triggers (Confirmed)

Landing-page terms that trigger it: "trading", "investment", "deposit", "portfolio", "returns", "profit", "earnings", "withdrawal"

Terms the classifier treats as neutral: "community", "membership", "operations", "progress tracking", "participants"

Ad copy triggers: "earn", "income", "get paid", "profit sharing", "daily returns", "trusted", "proven track record", "no hidden fees", "grow your money", "secure your future"

Wizard Flow (Manual Campaign Creation)

  1. Start: click "+" (Criar) → Campanha
  2. Objective: select card (Vendas / Leads / Tráfego) → Continuar
  3. Type: Pesquisar → check "Visitas ao site" → enter URL → Continuar
  4. Name: enter campaign name → Continuar
  5. Bidding: leave Maximize Conversions → Avançar
  6. Settings: uncheck "Rede de Display"; add locations; add languages → Avançar
  7. AI Max: uncheck BOTH "Personalização do texto" AND "Expansão de URL final" → Avançar
  8. Keywords + Ads: fill URL final; paste keywords; open RSA editor → add headlines + descriptions → Concluído → Avançar
  9. Budget: set daily budget → Avançar
  10. Publish: fix any issues → Publicar campanha

Critical: each step only saves on "Avançar". Clicking sidebar steps loses data.

MCP Setup (Generic)

Required credentials:

  1. Developer token from MCC → Ferramentas → Central de API
  2. Google Cloud OAuth client ID + secret (Desktop Application type, Google Ads API enabled)
  3. OAuth refresh token via browser auth flow

Browser patterns (for Recommendations only)

Driven with aside-browser. Its REPL exposes page directly and holds no state between invocations, so each run attaches its own tab and finishes its work in one call.

Apply a recommendation card

await page.goto("https://ads.google.com/aw/recommendations?ocid=<ACCOUNT_ID>");
await page.waitForLoadState("networkidle");

await page.evaluate(() => {
  const options = document.querySelectorAll("[role=option]");
  for (const opt of options) {
    if (opt.textContent?.includes("TARGET_RECOMMENDATION_TEXT")) {
      const btns = opt.querySelectorAll("[role=button], button");
      for (const btn of btns) {
        if (btn.textContent?.trim() === "Aplicar") {
          btn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
          break;
        }
      }
      break;
    }
  }
});
await page.locator('role=button[name="Aplicar"] >> nth=1').click({ force: true });

Key rule: never use native click()

Google Ads wraps everything in acx-overlay-container. Always use page.evaluate(() => element.click()) or dispatchEvent.

URL Map

Base: https://ads.google.com — replace <ocid> with the account's Customer ID.

DestinationPath
Overview/aw/overview?ocid=<ocid>
Campaigns list/aw/campaigns?ocid=<ocid>
Ad Groups/aw/adgroups?ocid=<ocid>
Ads list/aw/ads?ocid=<ocid>
Keywords/aw/keywords?ocid=<ocid>
Campaign Settings/aw/campaignsettings?ocid=<ocid>
Conversions/aw/conversions?ocid=<ocid>
Account (Verification)/aw/policy/account?ocid=<ocid>
Sitelink Extension/aw/adextensions/new?ocid=<ocid>&placeholderType=1&assetFieldType=31
Callout Extension/aw/adextensions/new?ocid=<ocid>&placeholderType=17&assetFieldType=32
Search Terms Report/aw/keywords/searchterms?ocid=<ocid>
Negative Keywords/aw/keywords/negative?ocid=<ocid>
Recommendations/aw/recommendations?ocid=<ocid>
API Center (MCC only)/aw/apicenter
Manager Accountshttps://ads.google.com/home/tools/manager-accounts/

Geo Target Constants

CountryID
Bangladesh2050
India2356
Indonesia2360
Malaysia2458
Pakistan2586
Philippines2608
Thailand2764

Intent → Reference Routing Table

IntentReferenceUse for
Automating Google Ads: Scripts/API-first, the narrow UI carve-out, official-surface decision treereferences/automation.mdChoosing API vs browser vs operator for Google Ads tasks; staying undetectable when browser is needed

Available Script Templates

All templates in .maccing/growth/google-ads/<account>/scripts/ (copy from plugin skills/growth-google-ads/scripts/).

ScriptDescription
read-full-audit.jsFull account audit: campaigns, ad groups, ads, keywords, search terms, conversions
read-campaign-performance.jsCampaign metrics + budget + bidding strategy
read-keyword-performance.jsKeywords with Quality Score breakdown
read-search-terms.jsSearch queries with wasted spend and converting term filters
read-conversion-actions.jsAll conversion actions with attribution settings
read-ad-details.jsRSA headlines/descriptions with pin positions, ad strength
write-create-rsa.jsCreate RSA in existing ad group (PAUSED state)
write-add-keywords.jsAdd keywords via AdsApp.mutateAll
write-add-negatives.jsCampaign-level negative keywords
write-pause-campaign.jsPause or resume campaign
write-update-ad-url.jsUpdate Final URL of an existing ad
write-create-conversion.jsCreate new conversion action

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.