Builds a JSON search index at build time and ships the Orama client-side search runtime. Every page gets the search script injected, and the search UI is available via Ctrl+K (or Cmd+K on macOS). Enabled by default.
How it works
During the build, the plugin extracts searchable content from every non-draft page and writes a JSON index file per language. At runtime, the embedded Orama client loads the index and handles queries entirely client-side, with no server required.
Search index structure
Each page produces one primary document plus one document per heading. This enables both page-level and section-level search results.
Page document fields
| Field | Source |
|---|---|
title |
Page title |
url |
Page URL |
description |
Page description |
content |
HTML-stripped page content, truncated to max_content_length |
section |
Collection name |
tags |
Page tags |
version |
Page version ID (if versioned) |
breadcrumb |
Collection > Section > Subsection path |
Heading documents
Each heading on the page generates an additional document with the heading text as title and the anchor URL (page-url#heading-id) as url. Heading documents include the breadcrumb extended with the page title (e.g., "Docs > Guides > Writing Content").
Output files
| File | Description |
|---|---|
search-index.en.json |
Search index for English (or the default language) |
search-index.fr.json |
Search index for French (one file per language) |
assets/vendor/orama/orama.esm.js |
Orama search engine (ES module) |
assets/js/static-search.js |
Search initialization and UI script |
Configuration
sarde.yaml
plugins: config: search: max_content_length: 5000 exclude: - /admin/* - /drafts/*| Option | Type | Default | Description |
|---|---|---|---|
max_content_length |
Number | 5000 |
Maximum characters of page content stored in the index. Higher values increase index size but improve result relevance for long pages. |
exclude |
List | [] |
URL glob patterns for pages to exclude from the index. |
The global search config section controls whether search is enabled site-wide:
search: enabled: true provider: "orama"| Option | Type | Default | Description |
|---|---|---|---|
search.enabled |
Boolean | true |
Enable or disable search site-wide. |
search.provider |
String | "orama" |
Search provider. Currently only "orama" is supported. |
Multi-language support
On multi-language sites, the plugin groups pages by their Lang field and writes a separate index file for each language (search-index.en.json, search-index.fr.json, etc.). Pages without a language tag fall into the en index by default. The search UI loads the index matching the current page's language.
Version-scoped search
When a collection uses versioning, each page carries a version field in its search document. The search UI filters results to the currently viewed version, so readers searching within v2 documentation do not see results from v1.
Content extraction
Page content is extracted by stripping all HTML tags from the rendered output and collapsing whitespace. The raw HTML is first truncated to 3x max_content_length bytes (to limit processing), then tag-stripped, then truncated to the final max_content_length. Truncation is rune-safe (it never splits a multi-byte UTF-8 character).
Incremental rebuild caching
The plugin caches extracted search documents per page across incremental rebuilds. When a page's content digest has not changed, its cached documents are reused without re-extraction. On a full build, the cache is repopulated from scratch (because breadcrumb inputs from _index.md section titles may have changed even if page content did not).
Disabling search
Remove search from the enabled list, or set the global config:
search: enabled: falseEdge cases
- Draft pages are excluded from the index.
- Duplicate URLs (permalink collisions) are deduplicated: only the first page encountered keeps its entry.
- Heading documents within a page are also deduplicated by their anchor URL.
- The Orama runtime and search UI script are only copied on full builds. Incremental rebuilds skip the asset copy since the files are identical.
- Pages excluded via
excludepatterns are matched against their permalink using glob semantics.