---
title: "Query"
description: "Filter Google search data and run SQL queries over the gscdump Store."
canonical_url: "https://gscdump.com/gscdump-cli/api/query"
last_updated: "2026-10-03T07:15:31.549Z"
---

# Query

## Filter expressions

`query` accepts prefix-encoded filter expressions for `--query`, `--page`, `--country`, `--device`, `--search-appearance`:

::table{tabindex="0"}
| Prefix    | Operator     |
| --------- | ------------ |
| (bare)    | equals       |
| `~foo`    | contains     |
| `!~foo`   | not contains |
| `re:foo`  | regex        |
| `!re:foo` | not regex    |
| `!foo`    | not equals   |
::

::pre{tabindex="0"}
```bash
# pages under /blog/ with brand mentions in the query
gscdump query --live --site example.com \
  --page '~/blog/' --query '~brand' --dimensions page,query
```
::

`--limit` and `--format` override saved `defaultLimit` and `defaultFormat` values.
Without saved values, queries use 1000 rows and JSON.
Limits must be positive integers. Formats must be `table`, `json`, or `csv`.

Rows come in click order, most clicks first. If `date` is a dimension, rows come in date order, oldest first.
Store queries, Hosted mode queries, and live queries use the same order, so `--limit` keeps the same rows from each source.

Without dates, a query reads the last 28 days that end on the newest synced day of the table it reads.
With `--live`, the window ends three days ago, Pacific time: the newest final GSC date.
`--start` alone runs to that end date. `--end` alone reads the 28 days that end on it.
Dates must use `YYYY-MM-DD`, and `--start` cannot follow `--end`.

`--page` accepts a path or a full URL. The Store compares paths, so `https://example.com/a` matches `/a`.
Live queries expand a path to the Site's origin. For a domain property, a path matches that path on any host.
Filtered dimensions choose the Store table too: `-d query --page /a` reads `page_queries`.
If no Store table holds every dimension and filter, the query fails and names `--live`.
If the matching table exists but has no synced rows, an authenticated query can answer live instead.
Check `meta.source` before describing a result as saved Store data.

::pre{tabindex="0"}
```bash
# Export query rows with a CSV header.
gscdump query --live --site example.com \
  --dimensions page,query --format csv --output rows.csv
```
::

For API request examples, see [the Search Analytics query guide](/learn-google-search-console/api/query-builder).

## SQL over the Store

`query --sql` runs DuckDB SQL over one view per Store table.
Run `query --schema` to list the views, their columns, and their date ranges.

::pre{tabindex="0"}
```bash
gscdump query --format json --sql "
  SELECT p.page, SUM(q.clicks) AS clicks, gsc_position(q.sum_position, q.impressions) AS position
  FROM pages p JOIN page_queries q USING (site, search_type, url, date)
  WHERE p.search_type = 'web' AND p.date >= DATE '2026-08-01'
  GROUP BY p.page ORDER BY clicks DESC LIMIT 20"
```
::

::table{tabindex="0"}
| Column         | Meaning                                                      |
| -------------- | ------------------------------------------------------------ |
| `site`         | The Site URL, such as `sc-domain:example.com`                |
| `search_type`  | `web`, `image`, `video`, `news`, `discover`, or `googleNews` |
| `url`, `page`  | The page path. `page` is the same value as `url`             |
| `sum_position` | Zero-based position multiplied by impressions                |
::

- The Store keeps every search type. Filter or group by `search_type`, or a `SUM` adds web, image, and Discover rows together.
- `gsc_position(sum_position, impressions)` returns the impression-weighted average position. Use it with `GROUP BY`.
- The views cover every Site in the Store. `--site` and `--type` narrow them.
- Dates return as `YYYY-MM-DD`. Integers return as numbers, and an integer past 2^53 returns as a string.
- If the SQL names a table with no synced data, the CLI prints a warning.

## Routing

In Hosted mode, `query` reads the hosted record on [gscdump.com](http://gscdump.com) and never calls Google. JSON output has `meta.source: "hosted"`.
If the hosted record cannot serve the dates yet, `query` stops and exits 1. It says why and names the next step:

::pre{tabindex="0"}
```txt
Error: The Site's record is not readable yet.
Sync finished at 2026-10-01 03:16 UTC. gscdump prepares the record for reads after Sync. Try again in a few minutes.
```
::

`--live` needs Local mode. `--sql` and `--schema` read the local Store in either mode.

In Local mode, login is optional. Log in with your own Google credentials, or sync a local Store.
`query`, `analyze`, and `report` pick one source for each run:

- If the Store covers every date the run needs, the run reads the Store. This is also true while a sync runs.
- If the Store has no data and Google is connected, a live-capable run asks Search Console.
stderr names the live fallback. JSON output has `meta.source: "live"`. Stored-only Analyzers and Reports still need a Store.
- If the Store has no data and Google is not connected, the run stops and names `gscdump init`.
- If some dates are missing, the run stops and prints a `gscdump sync` command. Use `--live` only when that Analyzer or Report supports live rows.
- If a sync for the Site is running and the dates are not covered yet, the run stops with `Sync running: 41 of 90 days done.`
A sync without a heartbeat in the last 2 minutes counts as stopped.

One run never mixes Store rows and live rows. Live results hold the top rows of each request, and synced data keeps more of the long tail.
`query --sql` reads the Store only.
With JSON output, every failure prints `{ "error": { "code", "message", "nextCommand" } }` on stdout and exits 1.
JSON output means `--format json`, `--json`, or the default format of `query`.
A stop has its own code. Examples: `USAGE` for an invalid option value such as `--limit 0`, and the Hosted mode codes `NO_SITES`, `SITE_NOT_FOUND`, `RECORD_NOT_READY`, and `RANGE_NOT_SYNCED`.
Any other failure has the code `FAILED`, such as a network or API error, or `UNEXPECTED`, a defect in the CLI.

::pre{tabindex="0"}
```json
{
  "error": {
    "code": "USAGE",
    "message": "Invalid --limit. Use a positive safe integer, such as --limit 1000.",
    "nextCommand": null
  }
}
```
::

The Store’s quota ledger tracks Search Analytics, URL Inspection, and Indexing API publish calls.
Each API has separate limits. Other applications can also spend your Google quota.
When a limit is reached, the command stops and prints a retry time.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
