Query syntax
A query is matched against the raw source of each page: its response headers and HTML, exactly as the server sent them. Every example below is a link that runs it.
Words and phrases
Letter case never matters. Everything else does: a term matches only if it appears as written, punctuation and spacing included, and it matches whole words.
- jquery
- Pages containing the word jquery. It does not match jqueryui, which is a different word.
- "wp-content/themes/astra"
- The exact phrase. Quotes are needed when a phrase has spaces or starts with a minus.
- wp-content/themes/astra
- The same search: a single term with punctuation is matched as written, too.
- "<!-- Google Tag Manager -->"
- HTML comments, tags and attributes are all part of the source.
A word inside a phrase has to line up with word edges as well. "themes/astra" finds /themes/astra/ but not /themes/astral/. To match part of a word, use a regular expression.
Combining terms
- jquery "wp-content"
- Both must appear. Terms separated by spaces are all required.
- "amcharts.js" -bootstrap
- A minus in front of a term excludes pages that contain it.
- slick | swiper
- Either one. A vertical bar joins alternatives.
- jquery (slick | swiper)
- Brackets group. jQuery, plus at least one of the two.
- "gtag config"~5
- Proximity: the words in this order, at most five words apart.
A bar binds more tightly than a space, so jquery slick | swiper means jquery and either slick or swiper. A query made only of exclusions is refused: it would match almost every page.
Regular expressions
- regex:"UA-\d{4,10}-\d{1,4}"
- Universal Analytics IDs such as UA-1234567-1.
- regex:"ca-pub-\d{16}"
- AdSense publisher IDs.
- gtag regex:"G-[A-Z0-9]{10}"
- The same pattern, only on pages that also contain gtag.
Patterns use RE2 syntax and ignore case. There are no lookarounds or backreferences. A regular expression is checked against page source directly, which is slow across the whole index; pair it with a word, as in the last example, and only the pages containing that word are checked.
Extracting values
extract: does not change which pages match. It reads values out of each one and shows them with the result, in the Sites view and in CSV exports: a list of sites becomes a list of IDs, plugins or contacts.
- "addthis_widget.js" extract:"pubid=(ra-[a-z0-9]+)"
- The AddThis publisher ID of every site using it. The capture group is the value.
- "/wp-content/plugins/" extract:"/wp-content/plugins/([a-z0-9_-]+)/"
- Which WordPress plugins each site loads.
- generator:wordpress site:de extract:emails
- Email addresses on German WordPress homepages.
- "href=\"tel:" site:it extract:phones
- Phone numbers from tel: links.
- "cdn.shopify.com" extract:socials
- Social profiles Shopify stores link to.
- "googletagmanager.com" extract:ids
- Analytics, ad and verification IDs: each one a lead to the other sites that share it.
The presets are emails, phones, socials and ids; anything else is a regular expression, RE2 syntax, case ignored. Values come from the page that matched. For every page of one site, open its site profile.
Filters
Filters narrow the whole query, wherever they appear in it. Add as many as you like.
- jquery site:de
- Sites under a top-level domain.
- jquery site:shopify.com
- One domain and its subdomains. Several site: filters widen the search to any of them.
- jquery rank:1000
- Only sites ranked in the top 1,000.
- jquery depth:1
- Homepages and pages one link away. depth:0, the default, is homepages only.
- jquery depth:all
- Every crawled page, however deep.
- server:nginx
- The Server header contains nginx.
- generator:wordpress
- The meta generator tag contains WordPress.
- script:cdn.shopify.com
- The page loads a script from that host or one of its subdomains.
- jquery ip:104.21.*
- The site resolved to that IP address. A trailing * matches a prefix.
- jquery url:/blog/
- The page URL contains /blog/.
A filter on its own is a complete query: server:nginx lists every nginx site. Put quotes around a value with spaces, as in generator:"Hugo 0.1".
A minus excludes a filter, and a bar between filters makes them alternatives:
- generator:wordpress -site:com
- WordPress sites outside .com.
- jquery -rank:1000
- Sites outside the top 1,000, unranked ones included.
- server:nginx | server:openresty
- Served by either one.
A bar cannot make a filter an alternative to a word: jquery | site:de means jquery on .de sites, and the results say so.
Response headers
Each page is stored with the status line and headers in front of the HTML, one header per line. A phrase that spans a header name and its value finds servers by what they announce.
- "Server: Apache/2.2"
- Servers still running Apache 2.2.
- "X-Powered-By: PHP/5"
- Sites announcing PHP 5.
- "Strict-Transport-Security"
- Sites that send HSTS.
Limits
- A query can be up to 1,000 characters and 32 terms.
- A search that takes longer than 20 seconds is stopped. Add a more specific word to make it faster.
- The count of matches is always shown in full. How many of them you can page through depends on your plan.
API
Pro and Business plans include an API with the same search as the site. Create a token on your account page and send it as a bearer token.
curl -G https://webgrep.78.47.41.66.sslip.io/api/v1/search \ -H "Authorization: Bearer wg_…" \ --data-urlencode 'q="wp-content/themes/astra" site:de' \ --data-urlencode 'page=1'
The response lists the page of results and what is left of today's quota:
{
"query": "\"wp-content/themes/astra\" site:de",
"total": 1842,
"domains": 1790,
"page": 1,
"per_page": 20,
"reachable_pages": 93,
"results": [
{
"url": "https://example.de/",
"host": "example.de",
"domain": "example.de",
"title": "Example",
"rank": 48211,
"depth": 0,
"ip": "93.184.215.14",
"server": "nginx",
"generator": "WordPress 6.6.2",
"size": 84113,
"fetched_at": "2026-09-28 14:03:11",
"snippet": "…<link rel=\"stylesheet\" href=\"/wp-content/themes/astra/…",
"snippet_clipped_start": true
}
],
"warnings": [],
"quota_remaining": 998
}401means the token is missing or unknown,403that the plan has no API access,422that the query was empty or invalid (the message says why),429that today's requests are used up, and503that the search took too long. A refused query is not counted.warningslists anything about the query worth knowing: a part that was left out, or a term that makes it slow.- Requests are counted per day, and the counter resets 24 hours after the first request.
More endpoints
GET /api/v1/site/{domain}: a site profile, with its technologies, tracking IDs, contacts and links.GET /api/v1/technologies: the technology catalog, each with its query and how many sites use it. Free of quota.GET /api/v1/account: the plan and what is left of today. Free of quota.
AI assistants (MCP)
The same token connects Claude, ChatGPT, Cursor or any other client of the Model Context Protocol. The assistant gets tools to search page source, look up sites and read the technology catalog, and writes the queries itself: ask it which German shops use Klarna and Hotjar, or who else uses a site's AdSense ID. Each search or site lookup spends one API request.
Claude Code:
claude mcp add --transport http webgrep https://webgrep.78.47.41.66.sslip.io/api/mcp \ --header "Authorization: Bearer wg_…"
Cursor, Claude Desktop and others, in their MCP settings:
{
"mcpServers": {
"webgrep": {
"url": "https://webgrep.78.47.41.66.sslip.io/api/mcp",
"headers": { "Authorization": "Bearer wg_…" }
}
}
}