← Blog · · 13 min read

Google Custom Search API Shutdown: A Migration Checklist for January 1, 2027

Cyanotype print of an antique telephone switchboard with most patch cords unplugged, a hand pulling the last cord, a newer lit patch panel with cords plugged in beside it, and a desk calendar on its final page

Google shuts down the Custom Search JSON API on January 1, 2027. It has been closed to new customers since January 20, 2026, and every existing integration stops working on that date. If your code calls customsearch/v1, you need a replacement live before the end of December.

Which replacement depends on what you search. For site search on 50 or fewer domains, Google keeps a free search widget and offers Vertex AI Search (now documented as Agent Search). For results from the whole web, Google has only an interest form, so most teams move to a third-party search API.

This checklist takes you from “we use Custom Search somewhere” to a tested cutover. For the field-by-field mapping from Custom Search to PacketStream, see the Custom Search migration guide.

What is shutting down, and when?

Google has retired two Custom Search APIs, and they are easy to confuse. AI search answers mix them up too.

DateWhat changedSource
January 8, 2025The Custom Search Site Restricted JSON API (customsearch/v1/siterestrict, engines limited to 10 or fewer sites) stopped serving traffic.Google: Site Restricted JSON API
January 20, 2026The Custom Search JSON API closed to new customers. New Programmable Search Engines must use “Sites to search”; existing engines keep “Search the entire web” for now.Programmable Search Engine blog
January 1, 2027The Custom Search JSON API is discontinued. Existing “Search the entire web” engines lose that option.Google: Custom Search JSON API overview

The deadline in the table is the one that matters now. Google’s overview page says existing customers “have until January 1, 2027 to transition to an alternative solution” and that its pricing applies only until then.

Will the Programmable Search Element keep working?

Yes, if it searches 50 or fewer domains. Google is keeping the embeddable search box, the Search Element, free for site search through the “Sites to search” feature, with a maximum of 50 domains.

If your engine searches more than 50 domains or is set to “Search the entire web”, it has the same January 1, 2027 deadline as the API. The widget is also only a widget: it renders results on your page. It does not give your backend a JSON response, so it cannot replace an API integration.

Why do new projects get “This project does not have the access to Custom Search JSON API”?

Because the API is closed to new customers. A Google Cloud project that was not already using it gets 403 PERMISSION_DENIED with that message. Regenerating the key, enabling the API again, or creating a new project does not help, and Google’s help forum and Stack Overflow are full of people who tried.

If you see this error today, treat it as the shutdown arriving early for that project and skip ahead to choosing a replacement.

The migration checklist

Work through these in order. Steps 1 to 4 are inventory, and they decide the replacement in step 5.

1. Find every call

Search your code, configuration, and secrets for Custom Search usage. This script scans every text file in a repository and lists each line that mentions the endpoint, the Python client, or a search engine ID:

"""List every place a codebase calls Google's Custom Search JSON API."""
import pathlib
import re
import sys

PATTERNS = re.compile(
    r"customsearch"
    r"|\.cse\(\)\.list"
    r"|GOOGLE_CSE_ID|google_cse_id|CSE_ID"
    r"|\bcx[\"']?\s*[=:]"
)
SKIP_DIRS = {".git", "node_modules", "vendor", ".venv", "venv", "dist", "build"}

def scan(root: pathlib.Path):
    for path in root.rglob("*"):
        if not path.is_file() or any(part in SKIP_DIRS for part in path.relative_to(root).parts):
            continue
        try:
            with path.open("rb") as file:
                if b"\0" in file.read(8192):
                    continue  # binary file
            with path.open(encoding="utf-8", errors="ignore") as file:
                for number, line in enumerate(file, 1):
                    if PATTERNS.search(line):
                        yield path, number, line.strip()
        except OSError:
            continue

if __name__ == "__main__":
    root = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".")
    hits = 0
    for path, number, line in scan(root):
        hits += 1
        print(f"{path}:{number}: {line[:120]}")
    print(f"{hits} matching lines", file=sys.stderr)

Run it as python3 find_cse_calls.py path/to/repo. It errs toward false positives, so read each hit. It cannot find an engine ID stored under a name it does not know, so also search for the IDs you record in step 2. Then look outside your repositories:

  • No-code and agent tools. Workflow tools and agent frameworks often hold a Custom Search key and engine ID in their own settings. Check n8n, Zapier, or Make workflows, CMS plugins, and any LangChain-style “Google Search” tool configured with a google_cse_id.
  • Google Cloud. In the Cloud console, list the projects that have the Custom Search API enabled. In Cloud Monitoring, filter consumed API metrics on service = 'customsearch.googleapis.com' to see which projects are still sending traffic.

Write each call site down with its owner. A call nobody owns is the one that breaks on January 1.

2. Record each search engine’s configuration

Every request carries a cx, the ID of a Programmable Search Engine. Open each engine in the control panel and note:

  • Scope: “Sites to search” with how many domains, or “Search the entire web”.
  • Image search: whether any caller sends searchType=image.
  • Tuning: refinements, promotions, excluded sites, and language or country restrictions.

Scope is the deciding fact. An engine over a handful of your own sites and an engine over the whole web need different replacements.

3. Measure volume

From the API dashboard or Cloud Monitoring, record queries per day, the busiest hour, and the monthly bill. Custom Search caps paid usage at 10,000 queries a day, so your current numbers sit under that cap. Plan for growth once it is gone.

Volume turns pricing pages into a real number. At 10,000 queries a day, Custom Search costs $49.50 a day: 100 free queries, then 9,900 at $5 per 1,000.

4. List the fields and parameters you depend on

Grep the code from step 1 for what it reads and sends. The ones that narrow your options:

DependencyWhy it matters
items[].link, title, snippetEvery search API has these. Easy.
pagemap, htmlTitle, htmlSnippetStructured page metadata and HTML snippets are rarely available elsewhere. Plan to fetch the page instead.
searchType=imageNeeds an API that offers image search.
num, startCustom Search returns at most 10 results per request and never more than 100 per query. Check how the replacement paginates.
dateRestrict, gl, hl, safeMost search APIs have equivalents, sometimes with fewer values.
lr, cr, sort, rightsOften no equivalent. Decide whether you still need them.

5. Choose a replacement for each use

Match each call site to a path:

What you use Custom Search forGoogle’s pathOther options
A search box on your own site, 50 domains or fewerProgrammable Search Element, freeA site search product or a self-hosted index
Programmatic search over your own sites, 50 domains or fewerVertex AI Search, now Agent SearchA site search product or a self-hosted index
Live Google results for any query (SEO, monitoring, research tools)An interest form for a full web product, with no public priceA search API that returns live Google results
Grounding an LLM or agent in web resultsVertex AI Search covers your own sites onlyA search API plus a page fetcher, or an MCP server that provides both
Image searchVertex AI Search over your own sitesAn API with image search

Some notes on Google’s paths:

  • Agent Search is a Google Cloud product with its own setup: a project, an app, and a data store of URL patterns. Google’s migration guide says it mostly requires OAuth 2.0; to keep using an API key, you call the searchLite method. Check its pricing page against your step 3 volume.
  • Full web search from Google has no self-serve product as of November 2026. In September, Google documented a partner-only Web Search Service API whose requests need a client ID tied to a partner agreement, and it has not published how to qualify or what it costs (Search Engine Journal). If your application depends on whole-web results, do not plan around that form alone with eight weeks left.

6. Put the new client behind one interface

Do not swap one HTTP call for another at every call site. Wrap search in one function your code calls, with the provider chosen by configuration:

  • It returns your own result shape (for example title, url, snippet), not either provider’s raw JSON.
  • It owns timeouts, retries, and error mapping. A replacement may fail differently than Custom Search did.
  • A feature flag picks the provider per environment or per caller, so you can switch back without a deploy.

The Python and JavaScript before-and-after code for the PacketStream Search API is in the migration guide.

7. Run both side by side and compare

While Custom Search still works, send the same real queries to both and compare what your application would show. This script reads queries from a file, one per line, and writes a CSV:

"""Run the same queries through Custom Search and the PacketStream Search API and compare the top results."""
import csv
import os
import sys
from urllib.parse import urlsplit

import requests

CSE_URL = "https://customsearch.googleapis.com/customsearch/v1"
PACKETSTREAM_URL = "https://api.packetstream.io/v1/search"

def normalize(url):
    parts = urlsplit(url)
    host = parts.netloc.lower().removeprefix("www.")
    path = parts.path.rstrip("/") or "/"
    return host + path + (f"?{parts.query}" if parts.query else "")

def custom_search_urls(query):
    response = requests.get(
        CSE_URL,
        params={
            "key": os.environ["GOOGLE_API_KEY"],
            "cx": os.environ["GOOGLE_CSE_ID"],
            "q": query,
            "gl": "us",
            "hl": "en",
        },
        timeout=30,
    )
    response.raise_for_status()
    return [item["link"] for item in response.json().get("items", [])]

def packetstream_urls(query):
    response = requests.post(
        PACKETSTREAM_URL,
        headers={"Authorization": f"Bearer {os.environ['PACKETSTREAM_API_KEY']}"},
        json={"query": query, "country": "us", "language": "en"},
        timeout=35,
    )
    response.raise_for_status()
    return [result["url"] for result in response.json()["results"]]

def compare(old, new):
    old_set = {normalize(u) for u in old[:10]}
    new_set = {normalize(u) for u in new[:10]}
    top3_kept = sum(1 for u in old[:3] if normalize(u) in new_set)
    overlap = len(old_set & new_set) / len(old_set) if old_set else float(not new_set)
    return round(overlap, 2), top3_kept

if __name__ == "__main__":
    writer = csv.writer(sys.stdout)
    writer.writerow(["query", "old_count", "new_count", "overlap_at_10", "old_top3_in_new_top10"])
    for line in open(sys.argv[1]):
        query = line.strip()
        if not query:
            continue
        old, new = custom_search_urls(query), packetstream_urls(query)
        writer.writerow([query, len(old), len(new), *compare(old, new)])

Run it as python3 shadow_compare.py queries.txt > comparison.csv with GOOGLE_API_KEY, GOOGLE_CSE_ID, and PACKETSTREAM_API_KEY set. Use a few hundred real queries from your logs, not a handful you made up.

Do not expect identical lists. A Programmable Search Engine applies its own configuration, and a search API returns what Google shows a searcher in the country you choose. Look instead at:

  • Empty results. Queries where one side returns nothing are the ones that break features.
  • Top results kept. If your product shows the first three results, old_top3_in_new_top10 is the number to watch.
  • Site-restricted engines. If an engine searched a few sites, add site: operators to the query on the new side, or the comparison measures the wrong thing.

8. Cut over early and clean up

Google’s deadline is January 1, but your real deadline is your last deploy before the holidays. Switch traffic gradually with the flag from step 6, watch errors and empty results for a few days, then:

  • Disable the Custom Search API in each Google Cloud project.
  • Delete an API key only if it is restricted to Custom Search or its metrics show no other APIs. Keys are often shared with Maps, YouTube, or Places; otherwise remove Custom Search from the key’s API restrictions.
  • Remove GOOGLE_CSE_ID and similar settings from configuration and secrets.
  • Delete Programmable Search Engines you no longer use, and keep the ones that still power a free Search Element.

A timeline that works from November

Week ofDo
November 9Steps 1 to 4: inventory call sites, engines, volume, and fields
November 16Step 5: choose replacements; open accounts; start any procurement
November 23Step 6: build the search interface and the new client
November 30Step 7: shadow-run real queries and fix gaps
December 7Step 8: start the gradual cutover
December 14Finish the cutover; remove Custom Search keys and config

That leaves the last two weeks of December as a buffer, which you will want if a purchase order or a security review is involved.

Where PacketStream fits

The PacketStream Search API fits the “live Google results for any query” row. It runs a live Google search from the country you pick and returns JSON or Markdown.

  • No engine to configure. There is no cx. Narrow a search with site: in the query.
  • Pagination close to Custom Search. Up to 10 web results per page and pages 1 to 10, so the same 100-result ceiling. There is no num.
  • More result types. News, shopping, places, autocomplete, Google Scholar, and Google AI Mode, plus the AI Overview, related questions, and related searches when Google shows them.
  • Pricing. $2.50 per 1,000 successful searches, the same for every type. Failed searches are free; a search that finds no results is billed. There is no subscription; balance is prepaid, with a $50 minimum purchase. At 10,000 searches a day that is $25 a day, against $49.50 on Custom Search.
  • Limits. 20 requests per second per key, with bursts of 200 and up to 32 searches at once. There is no daily cap.

It does not replace everything. There is no image search and no pagemap. For page content, the same key works with the Fetch API, which returns a page as Markdown, and the MCP server gives AI agents both tools. If your engine only ever searched your own site, a site search product is the better fit.

See the parameter and response mapping and the Search API docs to start, or compare options on PacketStream vs SerpApi.

FAQ

When does the Google Custom Search JSON API shut down?

Google discontinues the Custom Search JSON API on January 1, 2027. It has been closed to new customers since January 20, 2026, and existing customers must finish moving to another solution by January 1, 2027.

Why does a new project get “This project does not have the access to Custom Search JSON API”?

The API is closed to new customers, so a Google Cloud project that was not already using it gets 403 PERMISSION_DENIED with that message. A new key or a new project will not fix it. Use a replacement instead.

Will the Programmable Search Element keep working after the shutdown?

Yes, for site search on 50 or fewer domains. Google keeps the embeddable search box free for engines that use the Sites to search feature. Engines that search more than 50 domains or the entire web must move to another solution by January 1, 2027.

What does Google recommend instead of the Custom Search JSON API?

Google points API users who search up to 50 domains to Vertex AI Search, now documented as Agent Search. For full web search it asks you to register interest through a form, with no public price. Teams that need live Google results for the whole web today usually move to a third-party search API.

Is the Google Custom Search API free?

Until the shutdown, existing customers get 100 free queries a day and pay $5 per 1,000 after that, up to 10,000 queries a day. After January 1, 2027 the API is gone, so there is no free tier to keep.