Sitelinks Search Box Schema Framework
Use conditional-logic schema generation to keep Sitelinks Search Box markup aligned with real page state and avoid stale or invalid static templates.
- Google no longer shows the sitelinks search box: the farewell was announced Oct 21, 2024 and the box stopped appearing in Search on Nov 21, 2024. The nositelinkssearchbox rule was archived on Nov 29, 2024.
- Leftover WebSite plus SearchAction markup causes no errors, and SearchAction keeps its schema.org meaning, describing your site search for AI assistants and other consumers.
- Build WebSite markup for site names instead: name and url on the home page, plus alternateName. Add SearchAction only when the site has a working search URL.
When To Use It
Google Search Central RetiredDocumentation changelogThe documentation page was removed on Nov 29, 2024. The live use of WebSite markup is now site name structured data on the home page, which the framework below builds, with an optional SearchAction kept for other consumers.
Key Implementation Documentation Highlights
- Google lists no required properties for the sitelinks search box, because the feature is retired.
- Site name markup, the replacement use of WebSite, requires name (the site name: unique, accurate, concise, commonly recognized, without suffixes such as Inc) and url (the canonical home page of the domain or subdomain).
- Site name recommends alternateName, listed in order of preference, for example a recognized acronym or shorter name.
- Put the WebSite markup on the home page at the domain or subdomain root; subdirectory home pages are not supported. Use the same name in og:site_name, the title, headings and Organization markup.
- Optional potentialAction: a SearchAction whose target urlTemplate contains
{search_term_string}and whose query-input isrequired name=search_term_string. It earns no Google feature.
Rules the framework enforces before it emits markup:
- url must be the root of the domain or subdomain; any path, query, or fragment suppresses the markup.
- The home page must be indexable and self-canonical.
- A missing name, or one ending in a legal suffix such as Inc or LLC, suppresses the markup.
- SearchAction is added only when site search exists and the URL template is absolute and contains {search_term_string}.
Template Approach
const pageData = {
siteName: "Example Outfitters",
homeUrl: "https://www.example.com/",
searchTemplate: "https://www.example.com/search?q={search_term_string}"
};
const staticTemplate = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": pageData.siteName,
"url": pageData.homeUrl,
"potentialAction": {
"@type": "SearchAction",
"target": { "@type": "EntryPoint", "urlTemplate": pageData.searchTemplate },
"query-input": "required name=search_term_string"
}
};
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Example Outfitters",
"url": "https://www.example.com/",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://www.example.com/search?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
}
Conditional-logic Framework
function isAbsoluteUrl(value) {
if (typeof value !== "string") return false;
try {
const url = new URL(value);
return url.protocol === "https:" || url.protocol === "http:";
} catch {
return false;
}
}
function isRootUrl(value) {
// site name markup works at domain or subdomain level only, never a subdirectory
if (!isAbsoluteUrl(value)) return false;
const url = new URL(value);
return url.pathname === "/" && !url.search && !url.hash;
}
function compact(value) {
if (Array.isArray(value)) {
const items = value.map(compact).filter((item) => item !== undefined);
return items.length ? items : undefined;
}
if (value && typeof value === "object") {
const out = {};
for (const [key, item] of Object.entries(value)) {
const kept = compact(item);
if (kept !== undefined) out[key] = kept;
}
return Object.keys(out).length ? out : undefined;
}
if (value === null || value === undefined || value === "") return undefined;
return value;
}
const LEGAL_SUFFIX = /,?\s+(inc|llc|ltd|gmbh|corp)\.?$/i;
function buildSearchAction(template) {
// optional and earns no Google feature since Nov 2024; keep only if real site search exists
if (!isAbsoluteUrl(template) || !template.includes("{search_term_string}")) return null;
return {
"@type": "SearchAction",
"target": { "@type": "EntryPoint", "urlTemplate": template },
"query-input": "required name=search_term_string"
};
}
function buildWebSiteSchema(source) {
// home page gates: indexable, self-canonical, root of the domain or subdomain
if (source.indexable === false) return null;
if (!isRootUrl(source.url)) return null;
if (source.canonicalUrl && source.canonicalUrl !== source.url) return null;
// site name: required, concise, no legal suffix, same as og:site_name and the title
const name = (source.siteName || "").trim();
if (!name || LEGAL_SUFFIX.test(name)) return null;
const schema = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": name,
"alternateName": (source.alternateNames || []).filter((n) => n && n !== name), // order of preference
"url": source.url,
"potentialAction": source.hasSiteSearch ? buildSearchAction(source.searchUrlTemplate) : null
};
return compact(schema);
}
// Usage:
// buildWebSiteSchema({
// url: "https://www.example.com/",
// canonicalUrl: "https://www.example.com/",
// indexable: true,
// siteName: "Example Outfitters",
// alternateNames: ["EO", "Example Outfitters Store"],
// hasSiteSearch: true,
// searchUrlTemplate: "https://www.example.com/search?q={search_term_string}"
// });
import re
from urllib.parse import urlparse
def is_absolute_url(value):
if not isinstance(value, str):
return False
parsed = urlparse(value)
return parsed.scheme in ("http", "https") and bool(parsed.netloc)
def is_root_url(value):
# site name markup works at domain or subdomain level only, never a subdirectory
if not is_absolute_url(value):
return False
parsed = urlparse(value)
return parsed.path == "/" and not parsed.query and not parsed.fragment
def compact(value):
if isinstance(value, list):
items = [kept for kept in (compact(item) for item in value) if kept is not None]
return items or None
if isinstance(value, dict):
out = {}
for key, item in value.items():
kept = compact(item)
if kept is not None:
out[key] = kept
return out or None
if value is None or value == "":
return None
return value
LEGAL_SUFFIX = re.compile(r",?\s+(inc|llc|ltd|gmbh|corp)\.?$", re.I)
def build_search_action(template):
# optional and earns no Google feature since Nov 2024; keep only if real site search exists
if not is_absolute_url(template) or "{search_term_string}" not in template:
return None
return {
"@type": "SearchAction",
"target": {"@type": "EntryPoint", "urlTemplate": template},
"query-input": "required name=search_term_string",
}
def build_website_schema(source):
# home page gates: indexable, self-canonical, root of the domain or subdomain
if source.get("indexable") is False:
return None
if not is_root_url(source.get("url")):
return None
if source.get("canonicalUrl") and source["canonicalUrl"] != source["url"]:
return None
# site name: required, concise, no legal suffix, same as og:site_name and the title
name = (source.get("siteName") or "").strip()
if not name or LEGAL_SUFFIX.search(name):
return None
schema = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": name,
"alternateName": [n for n in source.get("alternateNames") or [] if n and n != name], # order of preference
"url": source["url"],
"potentialAction": build_search_action(source.get("searchUrlTemplate")) if source.get("hasSiteSearch") else None,
}
return compact(schema)
# Usage:
# build_website_schema({
# "url": "https://www.example.com/",
# "canonicalUrl": "https://www.example.com/",
# "indexable": True,
# "siteName": "Example Outfitters",
# "alternateNames": ["EO", "Example Outfitters Store"],
# "hasSiteSearch": True,
# "searchUrlTemplate": "https://www.example.com/search?q={search_term_string}",
# })
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Example Outfitters",
"alternateName": [
"EO",
"Example Outfitters Store"
],
"url": "https://www.example.com/",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://www.example.com/search?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
}
The Output As An Entity Graph
The conditional-logic output above, parsed in your browser and drawn as the graph a crawler builds from it. Every nested object is a node and the property that holds it is the edge. Tap a node to inspect its properties, or drag to rearrange.
Why Conditional Logic Is Better Than Static Templates
- Moves WebSite markup to its live purpose, site names, without breaking existing SearchAction consumers.
- Enforces the root-URL rule in code, so the block never ships on subdirectory home pages where site names are not supported.
- Reads the site name from the same setting that fills og:site_name and the title, keeping every name source consistent.
- Drops SearchAction automatically when site search is removed, instead of advertising a dead endpoint.
Property Reference
Google retired the Sitelinks Search Box rich result, so its documentation no longer lists required or recommended properties. The framework above is still valid schema.org markup and still describes the page to search engines and AI assistants.
Schema.org types used