Reading Time: 12 minutes
Google Custom Search died on January 1, 2027. Here’s the playbook.
As of this writing in August 2026, the API is still technically up. The shutdown notice landed in January 2026 in a quiet Google Developers blog post and a batch of console email notifications. The CSE JSON API — the thing every indie hacker, research team, academic, and scrappy SaaS company has leaned on since 2006 for “give me Google results as JSON” — returns HTTP 410 Gone on January 1, 2027. You have about nine months.
Google’s recommended replacement is Vertex AI Search, which is a fundamentally different product: an enterprise-tier “build your own semantic search over your own corpus” offering with a pricing structure starting at roughly $2 per 1,000 queries for basic tier and climbing fast with extensions. It is not a CSE replacement. It does not return the public web search results that CSE returned. Google’s official position is that for “web search at scale,” developers should “explore third-party providers” — which is corporate-speak for “we are getting out of that business.”
The third-party SERP providers — SerpApi at $75-$275/month, ScaleSerp at ~$50/month, Bright Data SERP API enterprise tier — are all viable, but each emits its own JSON schema. Migrating from CSE to SerpApi is a rewrite of your parsing layer. Migrating to ScaleSerp is a different rewrite. Migrating to Bright Data is a third rewrite.
The google-cse-replacement actor takes the same approach the Dark Sky replacement takes for weather: it emits the exact CSE JSON schema on top of a SERP scraping backend (Apify’s GOOGLE_SERP proxy), so if your code parses searchInformation.totalResults, items[].title, items[].link, and items[].snippet, you can change two lines and keep shipping.
Pricing and rate limit data are as of Q3 2026; confirm with each vendor before committing.
Google CSE JSON API was the closest thing the web had to an officially-blessed “search the internet” API for years. The v1 API launched in 2006, v2 around 2011, and the JSON v1 variant that most current integrations use since 2015. It served three distinct audiences:
Each audience has different needs from a replacement, and each breaks in a different way when CSE goes down.
For reference, a CSE response looks like this:
{
"kind": "customsearch#search",
"url": {
"type": "application/json",
"template": "https://www.googleapis.com/customsearch/v1?q={searchTerms}&..."
},
"queries": {
"request": [{
"title": "Google Custom Search - climate change",
"totalResults": "124000000",
"searchTerms": "climate change",
"count": 10,
"startIndex": 1,
"inputEncoding": "utf8",
"outputEncoding": "utf8",
"safe": "off",
"cx": "YOUR_CSE_ID"
}],
"nextPage": [{ "startIndex": 11, "count": 10 }]
},
"context": { "title": "My CSE" },
"searchInformation": {
"searchTime": 0.42,
"formattedSearchTime": "0.42",
"totalResults": "124000000",
"formattedTotalResults": "124,000,000"
},
"items": [
{
"kind": "customsearch#result",
"title": "Climate change - Wikipedia",
"htmlTitle": "Climate change - Wikipedia",
"link": "https://en.wikipedia.org/wiki/Climate_change",
"displayLink": "en.wikipedia.org",
"snippet": "Climate change includes both human-induced...",
"htmlSnippet": "Climate change includes both...",
"formattedUrl": "https://en.wikipedia.org/wiki/Climate_change",
"htmlFormattedUrl": "https://en.wikipedia.org/wiki/Climate_change",
"pagemap": { ... }
}
]
}
That’s a lot of structure. The replacement returns every one of those fields.
| Feature | Google CSE (RIP Jan 2027) | Vertex AI Search | SerpApi | ScaleSerp | Bright Data SERP | google-cse-replacement |
|---|---|---|---|---|---|---|
| Returns public web Google results | yes | no (your corpus only) | yes | yes | yes | yes |
| JSON schema matches CSE | yes | no | no | no | no | yes |
Site-scoped search (siteSearch=) | yes | yes (on your corpus) | yes | yes | yes | yes |
q query string | yes | yes | yes | yes | yes | yes |
Pagination (start=, num=) | up to 100 | n/a | up to 100 | up to 100 | up to 300+ | up to 100 |
lr, cr language/country filters | yes | partial | yes | yes | yes | yes |
| Image search | yes | no | yes | yes | yes | yes |
sort parameter | yes | n/a | limited | limited | limited | partial |
| Ad block detection | included | n/a | yes (paid tier) | yes | yes | yes |
| Related searches | via UI only | n/a | yes | yes | yes | yes |
| Knowledge graph / panel | no | n/a | yes | yes | yes | yes |
| Enterprise SLA | yes | yes | yes (paid) | no | yes | limited |
Pricing comparison for a mid-size app doing 100,000 searches per month:
| Provider | Pricing model | Monthly cost at 100k | Notes |
|---|---|---|---|
| Google CSE | $5 per 1,000 queries (above 100/day free) | $500 | Gone as of Jan 2027. |
| Vertex AI Search | ~$2 per 1,000 for basic; up to $7+ with extensions | $200-$700+ | Not a CSE replacement; requires your own corpus. |
| SerpApi | Starter $75/mo (5k), Developer $275/mo (25k), Production $500/mo (100k) | $500 | Includes knowledge panels, related searches. |
| ScaleSerp | $0.00075-$0.001 per query tier | $75-$100 | Cheap but slimmer feature set. |
| Bright Data SERP | ~$5 per 1,000 (volume discounts above 1M/mo) | $500 | Enterprise focus. |
| google-cse-replacement | $0.005/query PPE | $500 | CSE-compatible schema; bring your own rate plan. |
At 100k/month the replacement lands in the same price range as CSE was. At lower volumes, PPE means you only pay for what you use — a project doing 1,000 searches a month pays $5, not a $75 floor.
The existing CSE quota structure is worth understanding because the migration strategy depends on it:
When CSE shuts down, the “multiple engine IDs for higher throughput” trick is what will hurt the most people. Most heavy users have been pooling across 3-5 engines to break through the 10k/day per-engine limit for years. That capacity vanishes at once.
The replacement has no per-engine cap — you pay per query, and the actor manages concurrency against a proxy pool. Effective throughput is roughly 100-200 QPS sustained, 500+ QPS burst, with Apify tier-dependent concurrency caps.
[your app]
|
| (same HTTP call shape as CSE)
v
[google-cse-replacement actor]
|
+-> input validator
| - q, cx, key (ignored), num, start, lr, cr, safe, siteSearch, searchType
|
+-> Apify GOOGLE_SERP proxy
| - rotates IPs and sessions
| - handles Google's rate-limit signals
| - returns raw SERP HTML
|
+-> SERP parser
| - extracts organic results
| - pulls metadata (totalResults, searchTime)
| - extracts pagemap where possible
|
+-> CSE schema serializer
| - emits response in exact CSE v1 JSON shape
|
v
[CSE-shaped JSON response]
The GOOGLE_SERP proxy is Apify’s dedicated SERP-scraping infrastructure. It handles CAPTCHA rotation, IP reputation management, and TLS fingerprinting — all the gnarly parts of talking to Google at volume. Each call through the proxy incurs a flat cost that the $0.005/query PPE absorbs.
Existing CSE code looks like this:
import requests
resp = requests.get("https://www.googleapis.com/customsearch/v1", params={
"key": GOOGLE_API_KEY,
"cx": CSE_ENGINE_ID,
"q": "climate change",
"num": 10,
})
data = resp.json()
for item in data["items"]:
print(item["title"], item["link"])
The migrated version:
from apify_client import ApifyClient
client = ApifyClient("APIFY_TOKEN")
run = client.actor("nexgendata/google-cse-replacement").call(run_input={
"q": "climate change",
"num": 10,
})
data = client.dataset(run["defaultDatasetId"]).iterate_items().__next__()
for item in data["items"]:
print(item["title"], item["link"])
Fields parsed downstream (items[].title, items[].link, items[].snippet, items[].displayLink, searchInformation.totalResults) are all populated identically.
A common pattern: a RAG pipeline that used CSE to find relevant URLs for an LLM to summarize.
from apify_client import ApifyClient
client = ApifyClient("APIFY_TOKEN")
def google_search_for_rag(query, n=5):
run = client.actor("nexgendata/google-cse-replacement").call(run_input={
"q": query,
"num": n,
"lr": "lang_en",
})
data = client.dataset(run["defaultDatasetId"]).iterate_items().__next__()
return [
{
"url": item["link"],
"title": item["title"],
"snippet": item["snippet"],
}
for item in data.get("items", [])
]
results = google_search_for_rag("latest research on GLP-1 cardiovascular outcomes", n=5)
for r in results:
print(r["url"])
The rest of the RAG pipeline (fetch page, extract text, embed, inject into LLM prompt) is unchanged.
curl -X POST "https://api.apify.com/v2/acts/nexgendata~google-cse-replacement/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"q": "site:arxiv.org transformer architecture 2025",
"num": 10
}'
The site: operator works the same as it does in Google’s UI. The actor forwards the query verbatim; Google handles operator parsing.
If you used CSE as the search box on your marketing site (the “site search” use case), you were probably using CSE’s siteSearch parameter or a pre-configured engine ID. Both migrate straightforwardly:
const { ApifyClient } = require('apify-client');
const apify = new ApifyClient({ token: process.env.APIFY_TOKEN });
async function siteSearch(query) {
const run = await apify.actor('nexgendata/google-cse-replacement').call({
q: query,
siteSearch: 'yourdomain.com',
siteSearchFilter: 'i',
num: 10,
});
const { items } = await apify.dataset(run.defaultDatasetId).listItems();
return items[0]?.items || [];
}
(async () => {
const results = await siteSearch('pricing tiers');
console.log(results.map(r => r.title));
})();
Behind the scenes, the actor appends site:yourdomain.com to the query and forwards it through the SERP proxy. Same results you’d have gotten from CSE with cx scoped to your domain.
Research workflows need to paginate past page 1.
from apify_client import ApifyClient
client = ApifyClient("APIFY_TOKEN")
def deep_search(query, max_results=50):
collected = []
for start in range(1, max_results + 1, 10):
run = client.actor("nexgendata/google-cse-replacement").call(run_input={
"q": query,
"num": 10,
"start": start,
})
data = client.dataset(run["defaultDatasetId"]).iterate_items().__next__()
collected.extend(data.get("items", []))
if not data.get("queries", {}).get("nextPage"):
break
return collected
urls = [item["link"] for item in deep_search("open-source RAG frameworks 2026", max_results=50)]
Caveat: like CSE, the replacement caps at 100 results per query (Google’s own limit — beyond start=100, Google returns nothing). For “I need 1,000 results for this query,” the actor is not the right tool; you’d want Bright Data’s SERP tier that includes deeper pagination via non-standard pathways.
curl -X POST "https://api.apify.com/v2/acts/nexgendata~google-cse-replacement/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"q": "bald eagle",
"searchType": "image",
"num": 10
}'
Image search returns items[] with a link pointing at the image URL, plus image.contextLink, image.thumbnailLink, and image.byteSize where available — matching CSE’s image response schema.
A legal research team at a boutique IP firm built a citation-verification bot in 2019. The bot took a brief, extracted every citation, and ran CSE to confirm each citation’s URL was still live and returning relevant content. About 12,000 CSE queries per month, averaging $60/month on Google’s paid tier.
The CSE shutdown notice panicked them in January. Their paralegal spent two weeks evaluating SerpApi and ScaleSerp — both of which would have worked but required rewriting the citation-matching logic that parsed CSE’s specific pagemap field structure.
The migration with the CSE replacement took their engineer half an afternoon:
googleapis.com/customsearch/v1 to the Apify actor run endpoint.At 12k queries/month × $0.005 = $60/month, they pay the same as they were paying Google, without rewriting anything, without changing their paralegal training materials, and without onboarding onto a new vendor’s quirks.
For teams currently running production CSE, here’s the checklist:
items[].title and items[].link, almost any SERP provider works. If it’s searchInformation.totalResults or queries.request[].totalResults or the nested pagemap — the replacement (or SerpApi’s CSE-compat mode, if you want commercial SLA) is your shortest path.The actor’s effective rate is governed by:
For very high-volume use cases (>1M queries/month), contact Apify for volume pricing. The underlying proxy cost has tiered discounts.
htmlTitle and htmlSnippet fields include highlighting of query terms. The actor re-synthesizes these by bolding exact query matches in the title and snippet; Google’s highlighter occasionally bolds stems or inflections the simple approach misses. A htmlTitle.includes('') sanity check in your code will still pass, but byte-identical matches will not.pagemap coverage. Google’s pagemap surfaces structured data (Product schemas, Recipe schemas, Organization schemas) from the page. The actor extracts schema.org JSON-LD from each result page opportunistically but cannot guarantee the same coverage as Google’s in-house extractor. For heavy pagemap consumers, test your specific queries before cutover.gl=us and cr=countryUS mostly work but the proxy pool is predominantly US-based IPs. For gl=jp or gl=de at high fidelity, request residential proxy via input (small surcharge).safe=off, safe=active both supported. safe=medium is ignored (Google deprecated that value years ago even before the CSE announcement).site:, inurl:, intitle:, filetype:, -, "") all pass through. Advanced operators like AROUND(n) and -inurl: work but are increasingly flaky on Google’s own SERP, not an actor issue.As of August 2026, yes. The January 2026 announcement was unambiguous: “The Custom Search JSON API will be discontinued on January 1, 2027.” No extension has been announced. Plan for it.
The CSE UI widget (the search bar you embed with
We cover AI agents, automation, and the tools that make them work. Our mission is to make AI accessible to everyone.