Google Search Console
This feature is exclusive for Premium users!
The Google Search Console module transforms the Search Console bulk data export into analysis-ready tables. It unions one or more properties, flags branded and question queries, and builds five SEO reports on top: daily trends, query cannibalization, query and page movers and shakers, and a CTR-by-position benchmark.
Key Featuresβ
- Multiple Properties: Union the bulk exports of several Search Console properties (also from other GCP projects), each with its own friendly name
- Branded Query Flag: Mark queries that contain your brand - exact terms, regex patterns, or phonetic (SOUNDEX) matching to catch misspellings
- Question Query Flag: Mark queries that start with a question word (what, how, why, ...), with per-property word lists for other languages
- Normalized Queries:
query_normalized(lowercase, punctuation removed) so casing and punctuation variants group together - SEO Reports: Daily trends with 14/28/56-day averages, cannibalization, movers and shakers per query and per page, and CTR underperformers against your own CTR curve
- Export Monitoring: Export delays and retries per property and day
- Incremental Processing: Only new export days are loaded into the base tables
Search Console Bulk Data Exportβ
- Set up the bulk data export for every property you want to include. Google creates a dataset (default name
searchconsole) with theExportLog,searchdata_site_impressionandsearchdata_url_impressiontables. - The export starts from the day you enable it - Search Console does not backfill history.
- Every listed dataset must be in the same BigQuery region as your GA4Dataform datasets.
Limitationsβ
- Search Console exports land 2-3 days late. The reports anchor their windows on the latest exported
data_dateof each property, not on the current date. - Anonymized queries (rare queries Google hides for privacy) have no query text, so they can't be flagged as branded or question queries.
is_branded_queryandis_question_queryare set when a day is first loaded. Changing brand terms or question words doesn't update history - see Applying changes to history.- CTR and position differ a lot between search types (WEB, IMAGE, VIDEO, NEWS, DISCOVER). The reports default to
WEBonly. If you include more search types, filter on a singlesearch_typein your dashboard instead of summing across them.
Configurationβ
The module uses a YAML configuration file: includes/custom/modules/google_search_console/config.yaml
# set by Superform Labs team to indicate changes
version: 1
# true/false to toggle this feature, default is false
enabled: true
question_words:
- "what"
- "where"
- "when"
- "why"
- "who"
- "how"
# ... see the full default list in the template
gsc_sources:
- dataset: 'searchconsole'
gsc_property_name: 'Main Site (EN)'
brand_terms:
- "ga4dataform"
- "ga4 dataform"
brand_terms_regex:
- "ga4.?data.?form"
brand_terms_soundex:
- "dataform"
- gcp_project: 'my-other-project'
dataset: 'searchconsole_es'
gsc_property_name: 'Spanish Site'
question_words:
- "quΓ©"
- "dΓ³nde"
- "cΓ³mo"
Each report also has its own settings block - see Report Settings.
Configuration Optionsβ
Essential Settingsβ
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Turns the module on |
gsc_sources | array | [] | The Search Console export datasets to read - at least one is required when the module is enabled |
question_words | array | English list (what, where, when, why, who, how, do, does, is, are, was, did, if, will, can, could) | Words that mark a query as a question when they are its first word (case-insensitive) |
Data Sources (gsc_sources)β
Each entry describes one Search Console property export. Multiple entries are unioned into the same tables, told apart by source_dataset and gsc_property_name.
| Option | Required | Description |
|---|---|---|
dataset | Yes | The bulk export dataset name |
gcp_project | No | Project of the dataset - falls back to the current project |
gsc_property_name | No | Friendly label, written to the gsc_property_name column. Falls back to project.dataset |
brand_terms | No | See Branded queries |
brand_terms_regex | No | See Branded queries |
brand_terms_soundex | No | See Branded queries |
question_words | No | Replaces the module-level question_words for this property only - see Question queries |
Branded Queriesβ
is_branded_query is TRUE when any of the three brand options of the property matches:
| Option | Matching |
|---|---|
brand_terms | Whole words, case-insensitive. dataform matches "ga4 dataform docs", not "dataforms" |
brand_terms_regex | Regex patterns, case-insensitive, anywhere in the query |
brand_terms_soundex | Phonetic: a query is branded if any of its words sounds like a term. dataform also matches "dataphorm". Use single words. SOUNDEX is coarse - check the results before you rely on it |
Anonymized queries have no query text, so they are never flagged as branded.
Question Queriesβ
is_question_query is TRUE when the first word of the query matches a question word (case-insensitive, punctuation ignored).
- The module-level
question_wordslist applies to every property. The config template ships an English list. - A property's own
question_wordslist replaces the module list for that property - for example for a Spanish site. - Only single words work - a two-word starter like "por quΓ©" never matches.
question_words: []on a property turns question detection off for it.
Applying Changes to Historyβ
is_branded_query and is_question_query are set when a day is first loaded. After you add a property or change brand terms or question words, update history with one of:
- a full refresh of the module
- reprocessing of the affected date range with the tag
module_google_search_console
Report Settingsβ
Each report except the daily trends report has its own settings block. The blocks are plain objects, so you can override one field without repeating the rest:
cannibalization_report:
lookback_days: 30
Set enabled: false in a block to turn off only that report.
search_types restricts a report to specific search types (WEB, IMAGE, VIDEO, NEWS, DISCOVER). The config template sets ["WEB"] for every report. [] means all search types. CTR and position differ a lot between search types - if you include more than one, filter on a single search_type in your dashboard.
cannibalization_reportβ
| Option | Default | Description |
|---|---|---|
enabled | true | Builds this report |
lookback_days | 90 | Days scanned, counted back from the latest export date |
exclude_branded_queries | true | Skip branded queries - several pages ranking for your brand is rarely cannibalization |
max_urls_per_query | 5 | Competing URLs listed per query (competing_url_count is never capped) |
search_types | ["WEB"] | Search types to include |
movers_shakers_queries_reportβ
| Option | Default | Description |
|---|---|---|
enabled | true | Builds this report |
period_days | 28 | Length of each comparison window (last 28 days vs the 28 days before) |
exclude_branded_queries | true | Skip branded queries |
min_impressions | 10 | Minimum impressions in the larger of the two periods |
search_types | ["WEB"] | Search types to include |
movers_shakers_pages_reportβ
| Option | Default | Description |
|---|---|---|
enabled | true | Builds this report |
period_days | 28 | Length of each comparison window |
exclude_branded_queries | false | Skip branded queries. Anonymized queries can't be checked, so branded searches inside them still count |
exclude_anonymous_queries | false | Drop anonymized queries from the page totals |
min_impressions | 10 | Minimum impressions in the larger of the two periods |
search_types | ["WEB"] | Search types to include |
ctr_benchmark_reportβ
| Option | Default | Description |
|---|---|---|
enabled | true | Builds this report |
lookback_days | 90 | Days scanned - longer, so each position has enough data |
exclude_branded_queries | true | Skip branded queries (they get much higher CTR) |
min_impressions | 5 | Minimum impressions for a query to enter the curve and the output |
min_bucket_sample_size | 100 | Minimum impressions in a position bucket before its expected_ctr is trusted |
underperformance_threshold | 0.5 | A query is flagged when its CTR is below this fraction of the expected CTR |
search_types | ["WEB"] | Search types to include |
GCS Exportβ
The GCS_EXPORT block overrides the global cross-cloud export settings for this module only.
Table Structuresβ
Base Tablesβ
The base tables are incremental and partitioned by data_date. They keep the grain of the bulk export and add:
| Column | Description |
|---|---|
source_dataset | The export dataset the row came from (project.dataset) |
gsc_property_name | The friendly property name from the configuration |
query_normalized | The query in lowercase, with punctuation collapsed to single spaces - use it to group and join |
is_branded_query | The query matched a brand term |
is_question_query | The query starts with a question word |
Build custom analysis on these tables when the reports don't cover your question.
google_search_console_site_impression (incremental)β
Granularity: Same as the export's searchdata_site_impression - per property, date, query, country, device and search type
Property-level search data. Average position = SUM(sum_top_position) / SUM(impressions) + 1.
google_search_console_url_impression (incremental)β
Granularity: Same as the export's searchdata_url_impression - per property, date, URL, query, country, device, search type and search appearance
URL-level search data, including the search appearance flags (is_review_snippet, is_video, is_merchant_listings, ...). Average position = SUM(sum_position) / SUM(impressions) + 1.
Reportsβ
The reports are full rebuilds on each run, read from the base tables. To run only the reports, use the tag module_google_search_console_reports.
Every report window ends at the latest exported data_date of each property, not at the current date, so the 2-3 day export delay never cuts a window short.
google_search_console_site_impression_report (table)β
Granularity: One row per date, property, branded flag and search type
Daily impressions, clicks, average position and CTR, each with 14, 28 and 56-day trailing averages. Use it for trend charts and to split branded from non-branded traffic. Anonymized queries form their own row with is_branded_query = NULL.
google_search_console_cannibalization_report (table)β
Granularity: One row per property and query
Queries where 2 or more pages get impressions for the same search term. Shows the true number of competing URLs (competing_url_count), the query totals, and the top competing URLs with their clicks, impressions and average position (urls array).
google_search_console_movers_shakers_queries_report (table)β
Granularity: One row per property, query and search type
Compares the last period_days to the period before: impressions, clicks, average position and position bucket (top 3, first page (4-10), second page (11+)) for both periods, plus the absolute and percentage changes. New queries and queries that stopped ranking appear with 0 in one period. A negative position_change is an improvement.
google_search_console_movers_shakers_pages_report (table)β
Granularity: One row per property, URL and search type
The same comparison as the query report, per page, summed over every query that landed on the page. It includes branded and anonymized queries by default, so its totals come closest to the page totals in the Search Console UI.
google_search_console_ctr_benchmark_report (table)β
Granularity: One row per property and query
Builds your site's own expected CTR for each position from its history, then compares each query against it. performance_vs_expected is actual CTR / expected CTR. is_underperforming flags queries below underperformance_threshold. These are usually snippet problems: a weak title or meta description, or a SERP feature that takes the clicks.
Export Monitoringβ
google_search_console_exportlog (incremental)β
Granularity: One row per published export (retries included)
The export log of all configured properties. Incremental, not partitioned.
google_search_console_export_delays (view)β
Granularity: One row per property and date
How many hours after the end of the day (Pacific Time) each export arrived, and how many times it was retried.
Common Use Casesβ
- Branded vs non-branded trends: Track organic growth without brand demand hiding the signal
- Cannibalization clean-up: Find pages that compete for the same query and consolidate or re-target them
- Weekly SEO review: Spot the queries and pages that gained or lost the most since the previous period
- Snippet optimization: Rewrite titles and descriptions of queries that underperform the CTR of their position
- Content ideas: Use question queries (
is_question_query) to find topics for FAQ and how-to content
Data Studio Templateβ
We do not (yet) have a Data Studio template for this module.