All documentation

Search performance

Google Search Console data for your site: what you rank for, which positions moved, and where a small change would pay.

What you see

Search performance reads your Google Search Console property: the searches that actually surfaced one of your pages in a Google result. "Query" on this screen is Search Console's own word for a literal search string typed into Google, not this product's tracked prompts against AI engines; the import that fills this screen asks Search Console for exactly the date, query, page and country dimensions it defines (api/services/integrations/google/reconciliation.py#import_gsc_daily_for_project).

The hero number is clicks for the selected window, with impressions, click-through rate, and average position stepped down beneath it (api/routers/integrations.py#gsc_search_performance). Click-through rate is the window's total clicks divided by its total impressions, not an average of the underlying rows' own rates, and average position is weighted by impressions the way Google itself aggregates it (#_ctr, #_round2). The backend keeps that figure to two decimal places; the screen itself shows one, everywhere a position appears on it (frontend/src/lib/format.js#formatPos). Because a lower position number is the better ranking, the opposite of clicks or impressions, the position card spells the direction out in words rather than leaving a signed number to misread: "2.0 positions better" means the average position fell by two (frontend/src/routes/(app)/performance/search/+page.svelte#posShift, frontend/src/lib/format.js#formatPosDelta).

A "Closest to page one" card beside the hero surfaces the top row of Quick wins below: among the queries within striking distance of page one, the one with the most impressions, not necessarily the one with the best position. topWin reads quickWins[0], and quick_wins sorts its candidates by impressions then clicks, using position only as the 4-to-20 admission filter described next (frontend/src/routes/(app)/performance/search/+page.svelte#topWin, api/services/integrations/google/gsc_search_metrics.py#quick_wins). Leading with impressions is a reasonable choice even so: it points at the near-miss with the most search demand behind it, the one worth acting on first even when a better-positioned query sits further down the list. The card links out to Prompts → Search Console opportunities, a related but separately computed list; see Suggestions and opportunities for its own rule rather than assuming it repeats this screen's.

Where your impressions rank

Every query with a measured position falls into exactly one of four bands, in a fixed order, labelled on screen as "1-3", "4-10", "11-20" and "21+", based on that query's own impressions-weighted average position for the window. Because that average is a continuous, often fractional number, the labels' low edges are not exact: the cutoffs are position 3 or better for the first band, then better than 3 up to and including 10, then better than 10 up to and including 20, then worse than 20, so a query averaging 3.4 lands in "4-10", not "1-3" (api/services/integrations/google/gsc_search_metrics.py#_BUCKETS, #position_bucket). A query with no position on record is left out of every band and out of the total the bands divide by, so the bar and the list beside it show a share of the impressions that had a ranked position, not a share of every impression in the window (#position_mix). The fourth band carries its own label on screen: "Position 21+, effectively unseen."

Quick wins and movers

Quick wins lists queries with an average position from 4.0 to 20.0, both ends inclusive, and at least 10 impressions in the window, ranked by impressions then clicks, capped at 10 rows (api/services/integrations/google/gsc_search_metrics.py#quick_wins, called with that floor and cap in api/routers/integrations.py#gsc_search_performance). That is a wider position band and a lighter bar than Search Console opportunities, which requires position 5 or better, at least 100 impressions, a match to a tracked prompt, and that same prompt's AI-engine citation rate to sit below 20 percent over the same window (api/services/integrations/google/reconciliation.py#_RECON_LOW_AI_VISIBILITY): the two lists answer different questions over different populations, not the same ranking under two names.

Movers diffs each query's clicks in the selected window against its clicks in the window immediately before it (the two are adjacent, though not quite the same width; see below for the exact day counts), keeping a query only where one of the two windows reached 5 clicks, then splits the result into gainers and losers, each capped at 8 rows (#compute_movers, called with that floor and cap in #gsc_search_performance). A query that fell to zero clicks still shows up as a loser rather than disappearing, because the comparison is a diff across both windows, not a filter on the current one.

The trend, and the explorer beneath it

The daily trend draws clicks and impressions on their own separate scales, so it shows shape rather than one shared magnitude. It renders once the window is wider than 3 days, which every preset (7, 28, or 90) and any manually set ?days= of 4 or more satisfies: the series is zero-filled for every calendar day in the window whether or not that day has actually been imported yet, so a property with a single imported day still draws a full-width chart, one real point against the rest at zero (frontend/src/routes/(app)/performance/search/+page.svelte#hasTrend, api/services/integrations/google/gsc_search_metrics.py#build_daily_series). Beneath it, three tabs, queries, pages, and query-to-page pairs, each list up to 100 rows ranked by clicks, with that row's own impressions, click-through rate, and average position alongside it. The one Change column compares clicks against the prior window; the other figures carry no delta (#gscRows, api/routers/integrations.py#gsc_search_performance).

The window, and how it catches up with Google

Left alone, this screen opens on the last 28 days. You can switch it to 7, 28, or 90 days, or set ?days= yourself higher still; 180 is the ceiling this screen enforces regardless of what the URL asks for (frontend/src/routes/(app)/performance/search/+page.server.js#windowDays). Every change figure, the hero's clicks, each explorer row, and the queries feeding Movers, compares that window against the window immediately before it: the two are adjacent, with no gap and no overlap, but not quite the same width. The current window runs from window_days days ago through today, both ends inclusive, which is window_days + 1 days; the prior window covers exactly window_days days, ending the moment the current one starts. At the default 28-day setting that is 29 days of current data against 28 days of prior data, one day wider; the same window_days + 1 is why the trend chart above draws 29 daily buckets for that same default, not 28 (api/routers/integrations.py#gsc_search_performance).

The import behind this screen does not treat Google's reporting lag as a special case: on every scheduled run it re-pulls the last 7 days plus today, 8 calendar days inclusive, and overwrites what is already stored, so a day that first lands incomplete gets corrected on a later run instead of needing separate lag-handling logic. The first time a property is mapped, the import instead pulls 181 days, from 180 days ago through today, both ends inclusive, in one run split into 26 chunks of up to 7 days each rather than a single request (api/services/integrations/google/reconciliation.py#import_gsc_daily_for_project, #_IMPORT_WINDOW_DAYS, #_BACKFILL_WINDOW_DAYS, #_BACKFILL_CHUNK_DAYS, #_backfill_windows). See Troubleshooting: Performance is empty for exactly when the recurring import runs and what a window that lands before your most recent import means for this screen.

Before you see data

Search performance needs Google Search Console connected and a property mapped to this project. With no connection, the screen prompts you to connect one; connected with nothing mapped, it prompts you to map a property instead; mapped with no impressions recorded yet for the selected window, it says so and points at the mapping to check, rather than showing an empty chart (frontend/src/routes/(app)/performance/+layout.server.js#gscConnected, frontend/src/routes/(app)/performance/search/+page.server.js#gscMapped, frontend/src/routes/(app)/performance/search/+page.svelte#gscHasData).

Last verified 2026-08-10

Start monitoring your AI visibility.

See how AI search engines talk about your brand.

Free to start. No credit card required.