Tune what the search box returns
Exclude pages from the index, weight document priority, and scope the indexed HTML region without replacing the search backend.
When the search index is already live but results contain nav or footer noise, a page appears that should be hidden, or relative document weight needs adjusting, the options below tune the index without touching the search client. For how the index is built, sharded, and queried, see How the search index is built and queried.
Before you begin
- A working Pennington site that serves
/search/en/index.json(or the default locale code) — the search index entrypoint - Pages using
DocSiteFrontMatteror anotherIFrontMatterimplementation (which carries theSearchdefault member) - The default locale code (from
LocalizationOptions) — it is the suffix in the index filename
examples/DocSiteKitchenSinkExample ships with the DocSite-pinned #main-content selector and a Content/main/hidden.md fixture demonstrating search: false.
Options
Exclude a markdown page with search: false
Add search: false to the page's front matter. The index builder skips the page entirely while it continues to render at its URL and appear in the sidebar.
---
title: Internal draft
search: false
---
---
title: Not in search
description: This page is intentionally excluded from the search index.
sectionLabel: authoring
order: 220
search: false
uid: kitchen-sink.main.hidden
---
This page carries `search: false` in its front matter. It still renders
at its URL and still appears in the sidebar, but the search index
JSON does **not** contain it. Open `/search-index-en.json` to verify
— this title and body are absent from the `documents` array.
Pair this with `llms: false` on a separate page to carve the opposite
hole in `/llms.txt`.
Exclude a Razor @page with a metadata sidecar
Razor components do not carry YAML front matter, so RazorPageContentService loads a sibling Foo.razor.metadata.yml file. Place the sidecar next to the component; search: false there has the same effect as in a markdown page's front matter.
title: Internal Tools
search: false
Set the default document priority
SearchIndexOptions.DefaultPriority (default 5) is the baseline weight every document starts from — p is a linear multiplier on a result's score, so a document with twice the priority needs only half the term relevance to tie. Raise it for sites whose content should outrank neighbors; lower it for auxiliary content. AreaPriorities and PrefixPriorities (below) override it per content area and per URL prefix; see Pennington.Search.SearchIndexOptions for the shipped defaults.
Under AddDocSite this property is reachable via the ConfigurePennington escape hatch (ConfigurePennington = penn => penn.SearchIndex.DefaultPriority = …), so this adjustment does not require dropping down to bare AddPennington.
Drop a URL territory below prose with a prefix priority
To rank a whole URL subtree below comparable prose — generated API reference is the usual case, where dozens of type pages repeat a term and bury the article that explains it — register the prefix in SearchIndexOptions.PrefixPriorities. The longest matching prefix wins, and its value replaces the area priority rather than stacking, so the subtree drops to an absolute low p even inside a boosted area.
services.AddDocSite(() => new DocSiteOptions
{
SiteTitle = "My Docs",
SiteDescription = "Project documentation",
ConfigurePennington = penn =>
penn.SearchIndex.PrefixPriorities["/imagesharp/api/"] = 3,
});
AddApiReference registers each reference tree's route prefix here automatically at priority 3; set ApiReferenceRegistrationOptions.SearchPriority to choose a different value (AddApiReference(o => o.SearchPriority = 2)).
Override the content selector on DocSite
The selector scopes which HTML element's text becomes the search body — and the same element drives llms.txt sidecars and the build-time link audit, so chrome is stripped once. DocSiteOptions.ContentSelector defaults to #main-content to match the stock MainLayout.razor; set it after replacing the layout or to widen the indexed region. See What the DocSite and BlogSite templates wire for you for the cases that require dropping to bare AddPennington.
services.AddDocSite(() => new DocSiteOptions
{
SiteTitle = "My Docs",
SiteDescription = "Project documentation",
ContentSelector = "article.prose",
});
Add query synonyms
To make a term also match alternates, set SearchIndexOptions.Synonyms. Keys and values are stemmed at build time and shipped in the entrypoint, so authors write natural words; the client expands query terms as it searches.
services.AddDocSite(() => new DocSiteOptions
{
SiteTitle = "My Docs",
SiteDescription = "Project documentation",
ConfigurePennington = penn =>
penn.SearchIndex.Synonyms = new Dictionary<string, string[]>
{
["config"] = ["configuration", "settings"],
},
});
Choose which facets the client can filter by
SearchIndexOptions.Facets selects the dimensions surfaced as filter chips: content area (the first URL segment after any locale prefix), section, and tags. Only area is on by default — it stays a short, stable list that reads well as chips. Section and tag vocabularies grow large enough to bury the filter bar, so opt into them when the extra filtering is worth the chips.
services.AddDocSite(() => new DocSiteOptions
{
SiteTitle = "My Docs",
SiteDescription = "Project documentation",
ConfigurePennington = penn =>
penn.SearchIndex.Facets = SearchFacetField.Area | SearchFacetField.Section | SearchFacetField.Tags,
});
Result
The build emits the index under /search/{locale}/: an index.json entrypoint with the document table, facet labels, and ranking stats, plus t-*.json term shards and f-*.json per-page fragments. A document-table row in the entrypoint, after the knobs above are applied:
{
"u": "/how-to/configuration/search/",
"t": "Tune what the search box returns",
"l": 142,
"p": 10,
"f": { "section": [0], "tag": [2, 5], "area": [1] }
}
The page body lives in its fragment (f-{docId}.json), fetched only when the page appears in results. Pages with search: false are absent from the table; each row's p is DefaultPriority, overridden by any matching AreaPriorities or PrefixPriorities entry.
Verify
- Run
dotnet runand fetch/search/{locale}/index.json. The excluded page is absent from thedocstable - Add a second locale and observe one index tree per locale (
/search/en/index.json,/search/fr/index.json). Registered-but-empty locales return a valid entrypoint with an emptydocsarray - Fetch the matching
/search/{locale}/f-{docId}.jsonand confirm itsbodycontains only the scoped element's text (no header / sidebar / footer noise) - After raising
DefaultPriority(or registering anAreaPriorities/PrefixPrioritiesentry), fetchindex.jsonand confirm the affected rows carry the new value in theirpfield - After adding a synonym, fetch
index.jsonand confirm the stemmed synonym map carries the entry; in the modal, query the key term and confirm pages that mention only the alternate now appear - After enabling the section and tag facets, fetch
index.jsonand confirm rows carrysectionandtagids in theirfobject; in the modal, confirm the matching filter chips appear above the results
Related
- How-to: Add the search modal to a non-DocSite site — surface this index in a search UI on a bare host
- Reference: Front matter key reference
- Reference:
SearchIndexOptions— the knobs this how-to touches; see alsoHighlightingOptions,LlmsTxtOptions, andOutputOptions - Background: How the search index is built and queried
- Background: What the DocSite and BlogSite templates wire for you
- How-to: Make the site discoverable to LLM crawlers