Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/nikoicarl/unified-search/archive/refs/heads/main.zipUnified Search (WordPress)
One search across several sites, backed by one shared Solr collection. Install the plugin on each WordPress site with the same Solr settings and a different site key; every site indexes its own pages and searches all of them. Other systems (Drupal, static sites…) can join by writing documents of the same shape into the collection.
Requirements
- WordPress 6.0+ and PHP 8.1+
- A Solr 9 server you run or rent (any host that lets you upload a configset). The plugin doesn't search the WordPress database: without Solr it does nothing. One collection serves every site that searches together.
Install
- Download
unified-search.zipfrom the latest release (not GitHub's "Download ZIP", which installs under the wrong folder name). - Plugins → Add New → Upload Plugin, choose the zip, then Activate.
- Create the Solr collection and fill in the settings: see Setup.
Later versions show up as normal plugin updates.
What it does
- Search UI: the
[unified_search]shortcode, or (setting) takes over the normal WordPress?s=search. "This site / All sites" switch, Site and Type filters, highlighted snippets, paging. - Indexing this site (
wp unified-search push): fetches each rendered page, so content from page builders and custom fields (not inpost_content) is found too, and sends it to Solr. Optional real-time updates on publish, unpublish and delete. - Keywords and synonyms: a synonym list shared by every site, and per-site keywords that point searches at specific pages.
Searches go from the WordPress server to Solr. The browser never sees the Solr address or password.
Setup
- Create a Solr collection from the bundled configset (see solr/README.md). One collection serves every site.
- Install and activate the plugin.
- Unified Search → Settings (its own item in the admin sidebar): enter
the Solr URL, collection and credentials, and a short site key unique
to this site (e.g.
main,blog). Optionally list site labels (key|Label, one per line) so results show friendly site names. Save, then Test connection. - Unified Search → Index: click Re-index everything the first time, then
Index new & changed afterwards (it skips pages not edited since they
were last indexed). Pages are fetched 6 at a time in batches of 20, with
a progress bar; keep the tab open. If the tab closes or Solr goes away,
Resume continues from where it stopped. (WP-CLI
wp unified-search push/push --changeddoes the same from a terminal.) Every whole-site run also removes documents for pages deleted, unpublished or set to noindex since they were indexed, and pages that now redirect elsewhere or return 404. - Turn on Update index on publish so edits keep the index current without re-running the full index.
- Put
[unified_search]on a page, or turn on Replace WordPress search. Deactivate any other search plugin that also takes over?s=.
Solr bandwidth
Hosted Solr plans often bill the bytes the index sends back, so searches ask
for as little as possible: only the fields the results page shows (fl), one
page of rows, fq filters, gzip, and no echoed request header
(omitHeader). Identical searches within 5 minutes are answered from a
WordPress transient (filter unified_search_results_cache_ttl, 0 turns it
off); any index or synonym change starts a fresh cache. Expired cached
results are removed hourly, and a type filter that isn't a real result type
is never cached. The zero-results health check runs at most once every 5
minutes, and result pages send X-Robots-Tag: noindex, follow so crawlers
don't turn into queries. When Solr can't be reached (down, timing out, over
quota, wrong password), searches show "temporarily unavailable" for the next
minute without asking it again, so visitors don't each wait out the timeout;
Test connection clears that as soon as it succeeds.
Keywords and synonyms (Unified Search → Keywords)
- Synonyms — shared by every site. Stored in Solr as a managed synonym
list (
unified_synonymsin the bundled configset), so all sites' searches use one list and every site's screen edits that same list.picture => photo, galleryis one-way;timetable, scheduleis two-way. No re-index needed after edits. If someone changed the list after you opened the screen, Save stops instead of undoing their change, and keeps your edits in the box. Accented letters are kept ("école"). - Page keywords — per site. "bba" → Business: attached to every page whose title contains the name, and an exact match ranks it first on every site. Import a CSV/XLSX/PDF sheet (Page | Keywords | Alternates) or add them one at a time. After edits, run Index new & changed.
The schema needs a text field type with a managed synonym filter and the
keywords / keywords_exact fields; the bundled configset has both. If your
schema names them differently, set Text field type and Synonym list
in Settings. After a Solr restart the synonym list can come up
uninitialised; the plugin detects that and reloads the collection itself.
Search boxes (Unified Search → Settings → Search boxes)
- Header search icon (on by default): on desktop the magnifier expands
the input next to it (Esc / click outside closes, empty searches aren't
sent); at 1024px and below it opens the search page. Applies to every
element matching the selector (default
.brxe-search, the Bricks Search element; set your theme's search form selector). - Search page at
/search/(configurable): an empty search page with the cursor in the box. Only served when nothing else answers that address. [unified_search_box]: a standalone box that opens the results page. Options:placeholder,scope="all|site",button.
Both lead to the same results either way: through WordPress's ?s= while
Replace WordPress search is on, otherwise on the search page itself
(/search/?uns_q=…).
Styling
The results page uses uns- class names and CSS variables. Match your theme
by overriding the variables, e.g. .uns { --uns-primary: #b00020; } (see
assets/unified-search.css). Fonts are inherited from the theme.
WP-CLI
wp unified-search status # connection + document counts per site
wp unified-search preview <post-id> # show the document for one post, send nothing
wp unified-search push [--changed] [--post_type=a,b] [--limit=n] [--batch=20] [--dry-run]
wp unified-search clear --yes # delete this site's documents
Production
Keep credentials out of the database with constants in wp-config.php:
define( 'UNIFIED_SEARCH_SOLR_URL', 'https://solr.example.com/solr' );
define( 'UNIFIED_SEARCH_COLLECTION', 'unified_search' );
define( 'UNIFIED_SEARCH_SOLR_USER', '…' );
define( 'UNIFIED_SEARCH_SOLR_PASS', '…' );
define( 'UNIFIED_SEARCH_SITE_KEY', 'main' );
define( 'UNIFIED_SEARCH_INDEX_HOST', 'www.example.com' ); // the live site's address
// Only if your schema doesn't use the bundled configset's names:
define( 'UNIFIED_SEARCH_FIELD_TYPE', 'text_unified' );
define( 'UNIFIED_SEARCH_SYNONYMS', 'unified_synonyms' );
Documents carry the links of the environment that sends them, so only the
indexing host may write to a Solr outside the machine: staging and local
copies get "Not sending documents…" instead of putting their own links into
the shared index. Searching works everywhere. A Solr on the same machine or
local network (localhost, *.local, *.ddev.site, a bare service name)
always accepts documents. To test indexing on a staging copy, set the
indexing host to that copy for the test, then clear its documents
(Remove this site from the index) before going back.
Updates
WordPress offers new GitHub releases of the plugin as normal plugin updates
(Plugins screen, Dashboard → Updates), using the bundled
Plugin Update Checker
(v5.7, vendor/plugin-update-checker). It installs the release's
unified-search.zip. No setup is needed. Visitors' page loads never check
(only admin, cron and WP-CLI).
Optional, in wp-config.php:
// A private fork, or to lift GitHub's 60 anonymous requests an hour per server:
// a fine-grained token for that repository only, Contents: Read-only.
define( 'UNIFIED_SEARCH_GITHUB_TOKEN', 'github_pat_…' );
// Updates from a fork instead (or false to turn update checks off):
define( 'UNIFIED_SEARCH_UPDATE_REPO', 'https://github.com/you/unified-search/' );
Releasing a version
- Change the code here, then raise the version in both places in
unified-search.php: theVersion:header andUNIFIED_SEARCH_VERSION. - Add a line to the changelog below, commit, then build the zip:
bin/build-zip.sh(writesdist/unified-search.zip). - Tag and push:
git tag v0.3.0 && git push origin main --tags. - On GitHub, create a Release from that tag and attach
dist/unified-search.zip. The newest Release is what sites are offered.
Installing it on a site
- Plugins → Update now (or
wp plugin update unified-search). - Hosts with read-only code on Test/Live (Pantheon, WP Engine…): update on the environment that can change code, commit, then deploy as usual.
- WordPress caches update checks for up to 12 hours; "Check for updates" under the plugin's row on the Plugins screen checks now.
Document shape
| Field | Example |
|---|---|
id |
blog:post:415 ({site}:{kind}:{id}) |
site |
blog |
url, title, summary, body |
page text |
type |
Post (post type singular label) |
date |
2026-09-14T16:11:00Z (publish date for posts, last modified for other types) |
image |
og:image or featured image (optional) |
keywords, keywords_exact |
page keywords (optional) |
Filters
unified_search_post_types— post types to indexunified_search_is_indexable—( bool, WP_Post )unified_search_document—( array $doc, WP_Post ), change before sendingunified_search_query_params—( array $params, array $state ), Solr query paramsunified_search_fetch_concurrency— pages fetched at once while indexing (default 6)unified_search_job_batch_size— pages fetched per admin-button request (default 20)unified_search_type_color—( string $color, string $type ), badge colourunified_search_keyword_map—( array $map ), the page keyword mapunified_search_results_cache_ttl— seconds identical searches are cached (default 300,0off)unified_search_kept_kinds—( string[] $kinds ), document kinds (the middle part of{site}:{kind}:{id}) that whole-site runs never prune; for add-on contentunified_search_push_extra—( array{pushed:int, errors:string[]} $result ), runs at the end of every whole-site index: send add-on documents that aren't posts and add your counts
Actions
unified_search_keywords_changed—( array $new_map, array $old_map ), after the page keyword map changes
Add-on API
Unified_Search_Settings::client()— a Solr client for this site's settings (update(),select(),ids(),get_synonyms(),set_synonyms())Unified_Search_Keywords::merge_synonyms( $rules )— add synonym rules to the shared listUnified_Search_Keywords::lookup_phrase( $query )— page names a keyword points to
Changelog
- 0.3.0 — Renamed to Unified Search (slug
unified-search) and made site-neutral: generic defaults, a bundled Solr configset (solr/configset), configurable field type and synonym list names, and hooks for add-on content. Updates no longer need a GitHub token, and install the release zip. Site-specific integrations (snippet switch-over, minutes and calendar lists, Program Keywords compatibility) moved out of the plugin. - 0.2.1 — Test release for the GitHub update path; no code changes.
- 0.2.0 — First release from its own repository: GitHub updates, indexing host guard, Solr outage cut-off, safer synonym writes, and the audit fixes.
License
GPL-2.0-or-later. See LICENSE.