Google Ads Query
Setup
Check prerequisites before querying:
Script path resolution: Before running any script, determine the correct base path. Check in this order:
.claude/skills/google-ads-query/scripts/— project-local install (run from repository root)~/.claude/skills/google-ads-query/scripts/— global install
Use whichever path exists. All script paths below use the project-local form; substitute the global path if that's where scripts are found.
- Virtual environment:
.claude/skills/google-ads-query/scripts/.venv/must exist - Config file:
google-ads.yamlmust be findable (see Config section)
First-time setup
# 1. Install dependencies
bash .claude/skills/google-ads-query/scripts/setup.sh
# 2. Create google-ads.yaml in project root or ~/.config/google-ads-query/
# (see Config section for template)
# 3. Test (direct argument is fine here — no special characters)
gads "SELECT customer.id FROM customer"
Claude Code sandbox
When running inside the Claude Code sandbox, network access is restricted. Add the following domains to ~/.claude/settings.json under sandbox.network.allowedDomains:
Setup (pip install):
pypi.orgfiles.pythonhosted.org
Runtime (Google Ads API):
googleads.googleapis.comoauth2.googleapis.com
Config
The script searches for google-ads.yaml in this order:
--configCLI parameter (explicit path)./google-ads.yaml(project root)~/.config/google-ads-query/google-ads.yaml(user-level)
Template (google-ads.yaml):
# Service account authentication:
developer_token: "YOUR_TOKEN"
json_key_file_path: "/path/to/service-account.json"
customer_id: "YOUR_CUSTOMER_ID"
use_proto_plus: true
# OR OAuth2 authentication:
# developer_token: "YOUR_TOKEN"
# client_id: "YOUR_CLIENT_ID.apps.googleusercontent.com"
# client_secret: "YOUR_SECRET"
# refresh_token: "1//YOUR_REFRESH_TOKEN"
# customer_id: "YOUR_CUSTOMER_ID"
# use_proto_plus: true
# Optional (manager accounts):
# login_customer_id: "MANAGER_CUSTOMER_ID"
Usage
Use the gads alias for queries:
alias gads='.claude/skills/google-ads-query/scripts/.venv/bin/python3 .claude/skills/google-ads-query/scripts/query.py'
IMPORTANT: Always pipe queries via stdin. Use echo "QUERY" | gads - for all queries. This avoids shell escaping failures with dots in field names, single quotes in WHERE clauses, and other GAQL punctuation. Direct argument passing (gads "QUERY") is fragile and should only be used for trivial test queries like gads "SELECT customer.id FROM customer".
# Standard query (pipe via stdin to avoid shell escaping issues)
echo "SELECT campaign.name, metrics.clicks FROM campaign" | gads -
# Query with single quotes in WHERE clause
echo "SELECT campaign.name FROM campaign WHERE campaign.status != 'REMOVED'" | gads -
# Override customer ID
echo "SELECT campaign.name FROM campaign" | gads --customer-id 9876543210 -
# Explicit config path
echo "SELECT campaign.name FROM campaign" | gads --config /path/to/google-ads.yaml -
# Direct argument (only for trivial test queries with no special characters)
gads "SELECT customer.id FROM customer"
Direct argument mode: Passing the query as a positional argument (gads "QUERY") works only for simple queries with no single quotes, dots in WHERE clauses, or other special GAQL characters. For all real queries, use stdin (echo "..." | gads -).
Output: JSON array to stdout. Enums returned as names (e.g., "ENABLED", not 2).
Errors: JSON to stderr + exit code 1.
Field Notes
These fields may cause issues depending on API version:
campaign.start_date,campaign.end_date— may be unrecognizedconversion_action.type— can return cryptic errorsconversion_action.tag_snippets— repeated composite, may fail in some versions
The script handles repeated fields (both scalar and composite) correctly via protobuf serialization. If a query fails, remove suspected fields one at a time to isolate the issue.
Query Patterns
Campaign overview
echo "SELECT campaign.id, campaign.name, campaign.status,
campaign.advertising_channel_type, campaign.bidding_strategy_type,
metrics.impressions, metrics.clicks, metrics.cost_micros,
metrics.conversions, metrics.all_conversions
FROM campaign
WHERE campaign.status != 'REMOVED'" | gads -
Conversion actions (full list)
echo "SELECT conversion_action.id, conversion_action.name,
conversion_action.status, conversion_action.category,
conversion_action.origin, conversion_action.counting_type,
conversion_action.include_in_conversions_metric,
conversion_action.primary_for_goal
FROM conversion_action" | gads -
Campaign conversion goals
echo "SELECT campaign.id, campaign.name,
campaign_conversion_goal.category, campaign_conversion_goal.origin,
campaign_conversion_goal.biddable
FROM campaign_conversion_goal
WHERE campaign.id = CAMPAIGN_ID" | gads -
Conversion goal config (account vs campaign level)
echo "SELECT campaign.id, campaign.name,
conversion_goal_campaign_config.goal_config_level
FROM conversion_goal_campaign_config" | gads -
Metrics with date range
echo "SELECT campaign.name, metrics.impressions, metrics.clicks,
metrics.cost_micros, metrics.conversions, segments.date
FROM campaign
WHERE segments.date >= 'YYYY-MM-DD' AND segments.date <= 'YYYY-MM-DD'
AND campaign.status != 'REMOVED'" | gads -
Ad group details
echo "SELECT ad_group.id, ad_group.name, ad_group.status,
ad_group.cpc_bid_micros, campaign.name
FROM ad_group
WHERE campaign.status != 'REMOVED'" | gads -
Keywords
echo "SELECT ad_group_criterion.keyword.text,
ad_group_criterion.keyword.match_type,
ad_group_criterion.status, metrics.clicks,
metrics.impressions, metrics.cost_micros
FROM keyword_view
WHERE campaign.status != 'REMOVED'" | gads -
Search terms report
echo "SELECT search_term_view.search_term, metrics.clicks,
metrics.impressions, metrics.cost_micros, metrics.conversions
FROM search_term_view
WHERE segments.date >= 'YYYY-MM-DD' AND segments.date <= 'YYYY-MM-DD'" | gads -
Enum Reference
The script returns enum values as human-readable names (e.g., "ENABLED", "MAXIMIZE_CONVERSIONS"), so you typically don't need to decode numeric codes. For raw numeric references, see references/enums.md.
Key enum values for GAQL WHERE clauses (these use string names):
| Field | WHERE value | Meaning |
|---|---|---|
| campaign.status | 'ENABLED' | Active campaign |
| campaign.status | 'PAUSED' | Paused campaign |
| campaign.status | 'REMOVED' | Deleted campaign |
Conversion Tracking Audit
- Query customer settings — confirm
conversion_tracking_statusandauto_tagging_enabled - Query all
conversion_action— list actions, check status, primary_for_goal, include_in_conversions_metric - Query
campaign_conversion_goalfor the campaign — verify biddable goals match campaign objective - Query
conversion_goal_campaign_config— check if campaign uses campaign-level or account-level goals - If GA4-imported conversions: verify GA4 tag fires on target pages (recommend Google Tag Assistant)
- Check metrics: compare
metrics.conversions(primary only) vsmetrics.all_conversions(includes secondary)
Notes
metrics.cost_microsis in micros (divide by 1,000,000 for currency)- GAQL WHERE uses string enum values (
'REMOVED'), not numeric codes segments.daterequires both>=and<=bounds- Campaign-level goals override account-level defaults when
goal_config_level= CAMPAIGN