CTR gap by position
Upload the Queries export from Search Console and this builds your site's own click-through curve by position, then lists the queries whose CTR sits well below it, with the clicks that gap represents. The curve comes from your own queries, not from a published average, and a gap only counts if it survives a binomial noise check.
Your file never leaves your browser. It is read by JavaScript in this tab; there is no upload, and this page's security policy blocks fetch, XHR, WebSocket, EventSource and beacon calls and form posts.
Where you earn fewer clicks than your own average
Your curve, not an industry one
A published average CTR by position describes other people's results pages. This curve is weighted by impressions from your own queries and smoothed so it never rises with position. If your file is too thin to draw one, the tool says so and uses nothing else.
A noise guard on every gap
A query counts only with enough impressions, a CTR clearly below the curve, and a CTR that is still below it at the top of its binomial interval. The "at least" figure is the part of the gap that is not counting noise.
Brand terms taken out
Type your brand terms and branded queries leave the curve and the list, so they do not make ordinary queries look low. With none typed, the result says the curve includes them.
What to upload
In Search Console, choose your property, open Performance, then Search results, and pick a
date range; a longer one gives the curve more impressions to stand on. Open the Queries tab, make
sure Clicks, Impressions, CTR and Average position are switched on (click a metric's tile above the chart to show it), and
use the Export button, then Download CSV. Upload the ZIP, or the Queries file inside it. Google says a
value shown as ~ or - in the report is a zero in the download.
A site needs real volume for this: a point on the curve needs at least 1,000 impressions from at least 5 queries of 100 impressions or more within one position, with no one query holding more than half of them, and at least 4 positions need one. If your file does not have that, the tool tells you how far short it is and draws nothing. A table with a Page column as well as a Query column is not read here.
Finding the page for a query. A Queries export has no page column. In the Performance report, click the query in the Queries table, which filters the whole report to it, then open the Pages tab to see which URLs were shown for it.
What the file cannot tell you
- A gap is a place to look, not a diagnosis. A query can sit below your average CTR for its position because its searchers want an answer the results page already gives, because of what else is on that page, or because your title and snippet do not match what they typed. Google's page on the report's dimensions suggests looking for queries with high impressions and low CTR as opportunities to improve a page title and description. The file does not say which of these applies.
- AI features are in the same table. Google says sites that appear in AI features such as AI Overviews and AI Mode are reported in the Performance report within the Web search type. A query whose results page has an AI answer and one whose page has none are in the same file, and the file cannot separate them.
- The curve is an average of your queries. It is drawn from the average position of each query, and Google says a position number can mean different things in different situations. A query's average hides its spread, and the curve says nothing about a single result page.
- The table is cut at 1,000 rows and rare queries are missing. Google says the download is "truncated to 1,000 rows of representative examples of your data" and does not say which rows, and it omits anonymized (rare) queries from the Queries table. The curve is built from the rows present. A table of exactly 1,000 rows may have been cut, and the file cannot say whether it was.
- The noise guard covers counting noise only. It allows for the luck of a few hundred impressions. It does not allow for queries being different in kind. And because many queries are checked, a few pass by chance: the result prints how many if the curve were exact, and the real number is higher, because the curve is estimated from the same queries and because intent and results-page features add differences the guard cannot see.
- The lowest CTRs are partly bad luck. Picking the queries furthest below the curve picks some whose CTR was low by chance, so their gaps tend to shrink on another export. That is why each gap has an "at least" figure beside it.
- Each query is part of its own curve. The curve at a position includes the queries sitting there, so a query that holds a large share of its position's impressions pulls the curve toward itself and can look closer to it than it is.
- A gap is not a forecast. The click gap is what the query's impressions would have yielded at your own average CTR for its position. It is not a promise that any change gets a query there.
- Zeros and dates. A position of 0 is "no position recorded" and is left out. The figures cover the whole range you exported, and dates in Search Console are Pacific Time.
Sources, read on 21 Oct 2026: Google's help pages Export data directly from a Search Console report, What are impressions, position, and clicks?, Troubleshooting data discrepancies, Common tasks and use cases and Dimensions and data groupings, and Google Search Central's AI features and your website.
What happens to your file
Nothing leaves your browser. The file is read by JavaScript running in this tab and parsed in memory; it is not uploaded, there is no copy on a server, and nothing from it is written to a cookie or to local storage (the theme switch remembers light or dark and nothing else). The page loads no analytics and no third-party scripts.
You do not have to take our word for it. Open your browser's developer tools, choose the Network tab, run an analysis, and
watch it make no request. Two things back that up. This page carries a content security policy, readable in its source,
that blocks fetch, XHR, WebSocket, EventSource and beacon calls (connect-src 'none') and form posts
(form-action 'none'), and allows images only as inline data: URIs. A content security policy
does not cover every browser feature (WebRTC, for one), so the second check matters: the project's tests read this page's
own script and every module it imports, and fail on any of the browser's network APIs.
The site-wide script penlid.js, which every page loads, contains network calls for the signed-in app's
account switcher. This page never calls it, and a test checks that those calls sit only inside the switcher.
The brand terms you type stay in the page too. The one thing that leaves this page's memory is what you ask for by clicking: the CSV of the queries you see.
Methods: the rules and thresholds
Each number below is a constant in public/assets/lib/queries.js (the read-size limit is in tool-ui.js). The project's tests compare the numbers quoted here with those constants; that is a check on the numbers, and not on every sentence. They are judgement calls, and they are the owner's to change.
- Reading the file
- A table is recognised by its columns, not its name: a column of queries, and columns of clicks, impressions and position. Only a Queries table is read. A choice of files totalling more than 64 MB is not opened. A row with a negative or fractional clicks or impressions value, or with more clicks than impressions, is not used, and is counted. A row with an empty clicks or impressions cell is not used either, and is counted: Search Console writes 0 and never a blank, and reading a blank as 0 would make a query look as though nobody clicked it. A row whose position or CTR is not a number keeps its clicks and impressions and has no position. Every row not used is counted in a sentence at the top of the result. A row with no query text, or labelled "Grand total", is skipped. A query that appears more than once, ignoring case, has its rows added together and is named at the top; the position of a merged query is its rows' positions weighted by their impressions. CTR is clicks divided by impressions, worked out from the two counts.
- Brand terms
- One term per line, or separated by commas or semicolons; at most 100 terms, each at most 80 characters. By whole word, a term needs at least 2 characters; inside words, at least 3, or 2 for a term written wholly in Han, Kana, Hangul, Thai, Lao, Khmer or Myanmar script. Case and accents are folded away, and a query matches when its words contain the term's words, in order and next to each other. An apostrophe is tried both ways, so "macys" and "macy's" find each other. A query that matches a term is left out of the curve and the list, and the number left out is printed.
- The curve
- Queries with a position are grouped by whole position, 1 up to 21 in buckets 1 position wide (a position of 20.9 is in the last one; 21 and over is not on the curve). A bucket's CTR is its clicks divided by its impressions, which weights each query by its impressions. A bucket draws a point only with at least 1,000 impressions from at least 5 queries that each have at least 100 impressions, and with no one query holding more than 50% of them, so that no point is a single query's own CTR; any other bucket is drawn hollow and judges nothing. At least 4 points are needed or no curve is drawn. The points are then smoothed with a weighted fit that never lets the curve rise with position (pool adjacent violators, weighted by impressions), placed at each bucket's impression-weighted average position. Between two points the curve is joined on a log scale; beyond the first and last it is flat.
- Which queries are judged
- A query is judged only with at least 100 impressions, at a position inside the drawn range and in a bucket that drew a point. The others are counted, by reason, in a sentence above the result.
- The click gap and the noise guard
- Click gap = impressions × (the curve's CTR at the query's position minus the query's CTR). A query is listed only when all three hold: it has at least 100 impressions; its CTR is at most 75% of the curve's value; and the top of its Wilson interval at 2.576 standard errors is still below the curve's value. The "at least" figure is impressions × (the curve's value minus that top). 2.576 standard errors is 99.5% one-sided, so about 0.5% of queries sitting exactly on the curve would pass the third test by chance, and the page prints that count for the queries it judged. It is stricter than the usual 1.96 because hundreds of queries are checked at once. The curve is estimated from the same queries it judges, so that count is a floor: the real number of queries passing by chance is somewhat higher. Listed queries are ordered by click gap, largest first.
- The 1,000-row table
- A table of exactly 1,000 rows (Google's documented maximum) gets the warning that it may have been cut off, and that the file cannot say whether it was. A table with more than 1,000 rows is not called cut off.