WP Manifestindependent plugin directory
manifest / integrations / typesense-search

Typesense Search

Typesense Search Plugin for WordPress and Municipio

★ 0stars
244composer installs
1.9.2latest · 7d ago

Install

Download source zip No release zip yet

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/considbrs-webdev/typesense-search/archive/refs/heads/main.zip

On Packagist as considbrs-webdev/typesense-search:

composer require considbrs-webdev/typesense-search
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

A WordPress plugin that integrates Typesense as the search back-end for WordPress sites running the Municipio theme. It keeps a Typesense collection in sync with your WordPress content in real-time and exposes a configurable front-end search UI.

  • Author: Consid Borås AB
  • License: MIT
  • Requires: WordPress 5.6+, PHP 8.1+

Table of contents

  1. What the plugin does
  2. Requirements
  3. Installation
  4. Settings
  5. Per-post controls
  6. WP-CLI commands
  7. How indexing works
  8. Extensibility
  9. WordPress hooks and filters reference

1. What the plugin does

  • Connects to a self-hosted or cloud Typesense instance.
  • Automatically indexes WordPress posts (any post type), pages, and PDF files into a single Typesense collection whenever content is published, updated, unpublished, trashed, or deleted.
  • Provides a configurable search page UI (Instantsearch-based) that supports faceting, pagination, hit highlighting, and result truncation.
  • Provides a quick-search overlay that attaches to configurable CSS selectors on the front-end.
  • Can manage pinned search results for specific search phrases using Typesense curation sets (Typesense 30+).
  • Can record privacy-conscious local search statistics for both the full search page and quick search, with dashboard widgets and a searchable log under Tools → Search log.
  • Exposes WP-CLI commands for bulk indexing, dry-run previews, and index maintenance.
  • Is designed to be extended: add fields to existing documents via WordPress filters, write custom indexing strategies for new content types, or index content from external sources (APIs, feeds, third-party systems) alongside WordPress content.

2. Requirements

Requirement Notes
WordPress 5.6+ (wp_after_insert_post hook)
PHP 8.1+ (union types, readonly, named arguments)
Typesense server Any self-hosted instance or Typesense Cloud. Pinned results require Typesense 30+ curation sets
pdftotext binary Optional — required only for PDF indexing
WP-CLI Optional — required only for CLI commands

3. Installation

  1. Clone or copy the plugin into wp-content/plugins/typesense-search/.
  2. Run composer install inside the plugin directory to install the PHP client.
  3. Run npm ci && npm run build to compile front-end assets.
  4. Activate the plugin from Plugins in the WordPress admin.
  5. Navigate to Settings → Typesense Search and fill in the Connection tab (see §4).

4. Settings

The settings page is at Settings → Typesense Search and is split into seven tabs.


4.1 PHP constants

All five connection settings can be overridden by defining PHP constants before WordPress loads the plugin. The recommended place is a dedicated config file included from wp-config.php (e.g. wp-content/config/typesense.php). This is the standard pattern in Municipio/Helsingborg-stad setups.

When a constant is defined:

  • Its value is used instead of whatever is stored in the WordPress database.
  • The corresponding field in Settings → Connection is rendered read-only so it cannot accidentally be overwritten from the UI.
  • Any form submission that would change the value is silently a no-op.

Supported constants

Constant WordPress option Description
TYPESENSE_HOST typesense_search_remote Full URL to the Typesense server
TYPESENSE_FRONTEND_HOST typesense_search_frontend_host Optional public host sent to the browser
TYPESENSE_COLLECTION typesense_search_index_name Name of the Typesense collection
TYPESENSE_ADMIN_KEY typesense_search_admin_key Full-access Admin API key (server-side only)
TYPESENSE_SEARCH_KEY typesense_search_search_key Search-only key passed to front-end JavaScript

Setup

Create a config file (e.g. wp-content/config/typesense.php) and include it from wp-config.php:

<?php
// wp-content/config/typesense.php
define('TYPESENSE_HOST',       'https://search.example.com');
define('TYPESENSE_COLLECTION', 'my-wordpress-site');
define('TYPESENSE_ADMIN_KEY',  'your-admin-key');
define('TYPESENSE_SEARCH_KEY', 'your-search-only-key');
// define('TYPESENSE_FRONTEND_HOST', 'https://public.example.com'); // optional

Blank values — a constant set to an empty string ('') is treated as "not set" and the database option is used instead.


4.2 Connection tab

These settings tell the plugin how to reach your Typesense instance.

Setting Option key Description
Remote URL typesense_search_remote Base URL of your Typesense server, e.g. https://search.example.com
Index (collection) name typesense_search_index_name The Typesense collection to read from and write to
Admin API key typesense_search_admin_key Full-access key — used server-side for indexing and collection management
Search API key typesense_search_search_key Read-only key — passed to the front-end JavaScript
Frontend host typesense_search_frontend_host Optional override of the host sent to the browser (useful behind reverse proxies)

The admin key is kept server-side. The search key is the only credential exposed to the browser.

4.3 Settings tab

Setting Option key Description
Post types typesense_search_post_types Which public post types to index. Stored as an array of post-type slugs
Index Modularity content typesense_index_modularity_content Whether to also index content from Modularity modules
Index PDF files typesense_search_index_pdf Enable PDF indexing via pdftotext. Requires the binary to be installed
Results per page typesense_search_hits_per_page Number of search hits shown per page on the full search results page (default: 10)
Sort control style typesense_search_sort_display Show sort options as radio buttons or a dropdown

4.4 Advanced settings tab

This tab collects the detailed search controls: facets, search-field weights, highlight context, truncation, debounce behavior, and search statistics.

Search field weights

Use the Search field weights card to control how strongly each indexed field contributes to result relevance. Each field has an independent scale from 1 (lowest) to 5 (highest):

Admin label Typesense field
Title title
Excerpt excerpt
Content content
Content type name type_name
Extra search terms extra_terms

The plugin passes the configured values to both full and quick searches as Typesense's query_by_weights parameter. Typesense reads those values in the same order as query_by: title,excerpt,content,extra_terms,type_name.

Facets

Configure which fields can be used as facets in the search UI.

Setting Option key Description
Facets typesense_search_facets Array of facet definitions. Each entry has: field (Typesense field name), label (UI label), placeholder, display_as (dropdown or button_group)

Search statistics

Search statistics are optional and stored in a local WordPress table; no Typesense server analytics are required. A completed query is recorded at most once per normalised term and anonymous browser session. The same mechanism is used by the full search page and quick search.

Setting Option key Description
Enable search logging typesense_search_logging_enabled Enables local search-statistics collection.
Dashboard widgets typesense_search_logging_dashboard_widgets Adds Latest searches, Failed searches, and Popular searches widgets to the WordPress dashboard.
Require statistics consent typesense_search_logging_require_consent Requires explicit client-side consent before the plugin creates session storage or records terms. Disabled by default.
Search registration delay typesense_search_logging_delay_seconds Seconds a completed query must remain unchanged before it is recorded (default: 1). Independent of search debounce.
Minimum search-term characters typesense_search_logging_minimum_characters Shorter terms are searched but not logged (default: 3).
Statistics retention typesense_search_statistics_retention_days Days to retain terms and anonymous session hashes (default: 90).

When consent is required, call the following from the consent platform's initial-state callback and whenever that state changes. Existing trackers use the event to start or stop logging; sending false also clears the plugin's session storage.

function setTypesenseSearchStatisticsConsent(granted) {
  window.typesenseSearchStatisticsConsent = granted === true;
  window.dispatchEvent(new CustomEvent('typesense-search:statistics-consent', {
    detail: { granted: granted === true }
  }));
}

Expired statistics are removed by a daily WP-Cron event. Ensure WP-Cron runs reliably, or schedule wp typesense prune-search-statistics from server cron.

The Statistics tab shows a search-statistics overview and can clear the stored data. Tools → Search log provides paginated event rows, filters for results and context, sortable date/hit columns, bulk deletion, and a grouped term view with unique-session totals.

Pinned results

When the connected Typesense server supports curation sets (Typesense 30+), the Advanced settings tab shows an Enable pinned results toggle. This feature is off by default. After it is enabled and the settings are saved, the plugin adds a separate Settings → Pinned results admin page for managing the rules.

4.5 Pinned results page

Pinned results let editors promote selected posts for specific search phrases without changing the post title, content, excerpt, or extra search terms. Each rule contains:

  • A search phrase.
  • A match type: Exact for the phrase itself, or Contains for any query containing the phrase.
  • An enabled/disabled state.
  • One or more posts, ordered by the position they should occupy in the search results.

Rules are stored locally in WordPress until an administrator syncs them to Typesense. Syncing writes the rules to a Typesense curation set and attaches that set to the configured collection. The manager is only available when both conditions are met:

  • Enable pinned results is turned on in Advanced settings.
  • The connected Typesense server supports curation sets, which requires Typesense 30 or later.

4.6 Quick search tab

Quick search is a lightweight search overlay that attaches to any element on the page.

Setting Option key Description
Enable quick search typesense_quick_search_enabled Toggle the feature on or off
CSS selectors typesense_quick_search_selectors One or more CSS selectors the overlay binds to. Each entry has selector, sibling (bool — place the widget next to the element rather than inside it), and mobile_behavior (regular or overlay; the latter opens an accessible dialog on screens up to 767px wide)
Results per page typesense_quick_search_hits_per_page Number of results shown in the overlay (default: 5)

4.7 Statistics tab

Shows a live overview of the Typesense collection, including document count and index size, and provides search-statistics summaries when logging is enabled. The collection overview uses the admin API key through an AJAX proxy.

4.8 Logging tab

Shows the latest indexing run and document-level issues captured while indexing. The log can be cleared from this tab.

4.9 Status tab

Checks whether the current configuration is valid and the collection exists. Can create the collection if it is missing.


5. Per-post controls

Every indexed post type records meta fields, managed via a meta box visible in the post editor.

Meta key Constant Effect
_typesense_exclude MetaBox::META_EXCLUDE Set to '1' to prevent this post from being indexed (or to remove it from the index if already present)
_typesense_exclude_as_section MetaBox::META_EXCLUDE_AS_SECTION Pages only. Set to '1' to prevent a top-level page from being used as the top_most_parent section for itself, descendant pages, and attached PDFs
_typesense_extra_terms MetaBox::META_EXTRA_TERMS Free-text field included in the indexed document, allowing keywords that don't appear in the post body to influence search ranking

The _typesense_exclude flag is honoured by all built-in strategies. Custom strategies should check it in their shouldIndex() implementation if the same per-post control is desired.

The _typesense_exclude_as_section flag keeps the page indexed, but clears its top_most_parent value when the page is the top-level page in its tree. When the setting is changed, the plugin re-indexes published descendant pages and their attached PDFs so their section facets update immediately.


6. WP-CLI commands

The plugin registers a typesense command when WP-CLI is loaded. All subcommands run after WordPress is fully loaded (--when after_wp_load).


wp typesense index

Bulk-indexes all published posts for the post types enabled in settings.

# Index everything enabled in settings
wp typesense index

# Preview without writing anything
wp typesense index --dry-run

# Index specific post types
wp typesense index --post-type=post,page

# Control memory usage on large sites
wp typesense index --batch-size=50 --yes

# Include PDF attachments from the media library
wp typesense index --include-pdf

# Also run all external strategies after indexing posts
wp typesense index --include-external --yes

# Index only PDF attachments
wp typesense index --only-pdf --yes

# Index only external strategies, or one named strategy
wp typesense index --only-external --yes
wp typesense index --only-external=pitea-eservice --yes

# Slow down the progress bar for visual debugging
wp typesense index --dry-run --sleep=200
Flag Description
--post-type=<types> Comma-separated post-type slugs. Defaults to all types enabled in settings
--batch-size=<n> Posts per database query. Defaults to all posts in one query
--dry-run Resolve strategies and check shouldIndex() but do not write to Typesense
--include-pdf Also index PDF attachments via pdftotext (requires the binary to be installed)
--include-external After indexing posts (and PDFs), also run all registered external strategies
--only-pdf Index only PDF attachments. Cannot be combined with --post-type or --only-external
--only-external[=<identifier>] Index only external strategies; optionally target one identifier. Cannot be combined with --post-type or --only-pdf
--yes Skip the confirmation prompt
--sleep=<ms> Sleep after each post in milliseconds (useful for development)

wp typesense rebuild

Drops the Typesense collection, recreates it from the plugin schema, then optionally re-indexes all content in one operation. Use this whenever the Typesense collection schema needs to change (e.g. after modifying the Municipio/TypesenseSearch/Collection/getSchema filter).

# Full rebuild: drop schema, recreate, re-index everything
wp typesense rebuild

# Preview what would happen without making any changes
wp typesense rebuild --dry-run

# Reset schema only — re-index manually later with wp typesense index
wp typesense rebuild --skip-index --yes

# Rebuild and re-index only pages
wp typesense rebuild --post-type=page --yes

# Full rebuild including PDF attachments
wp typesense rebuild --include-pdf --yes

# Full rebuild including external strategies
wp typesense rebuild --include-external --yes
Flag Description
--post-type=<types> Comma-separated post-type slugs to re-index. Defaults to all types enabled in settings
--batch-size=<n> Posts per database query during re-indexing. Defaults to all posts in one query
--skip-index Drop and recreate the schema only; do not re-index any posts
--dry-run Report what would happen without writing anything to Typesense
--include-pdf Also index PDF attachments after the schema is recreated
--include-external Also run all registered external strategies after re-indexing posts
--yes Skip the confirmation prompt
--sleep=<ms> Sleep after each post in milliseconds during re-indexing

wp typesense clear

Removes indexed documents from the Typesense collection. Deletes are executed as a single bulk request per post type, so the operation is fast even for large collections.

# Clear all post types enabled in settings
wp typesense clear

# Preview without deleting anything
wp typesense clear --dry-run

# Remove only pages
wp typesense clear --post-type=page

# Remove every document from the collection regardless of settings
wp typesense clear --post-type=all --yes

# Clear posts and PDF documents (together)
wp typesense clear --include-pdf --yes

# Clear posts and external strategy documents (together)
wp typesense clear --include-external --yes

# Clear ONLY PDF attachments — no post types, no external strategies
wp typesense clear --only-pdf --yes

# Clear ONLY external strategy documents (all registered strategies)
wp typesense clear --only-external --yes

# Clear ONLY a single external strategy's documents
wp typesense clear --only-external=pitea-eservice --yes
Flag Description
--post-type=<types> Comma-separated post-type slugs to clear. Defaults to all types enabled in settings. Pass all to remove every document in the collection
--dry-run Count matching documents and print a summary without deleting anything
--include-pdf Also clear PDF attachment documents (type=attachment) alongside post types
--include-external Also clear all documents belonging to registered external strategies alongside post types (ignored when --post-type=all)
--only-pdf Clear only PDF attachment documents; skip the post-type loop entirely. Cannot be combined with --post-type or --only-external
--only-external[=<identifier>] Clear only external strategy documents. Without a value, all strategies are targeted. With a value (e.g. --only-external=pitea-eservice), only that strategy's documents are removed. Cannot be combined with --post-type or --only-pdf
--yes Skip the confirmation prompt
--sleep=<ms> Sleep between post-type operations in milliseconds

wp typesense prune-search-statistics

Removes local search-statistics rows older than the configured retention period. This is the same cleanup performed by the daily WP-Cron event.

# Use the retention period from Advanced settings
wp typesense prune-search-statistics

# Remove statistics older than 30 days
wp typesense prune-search-statistics --days=30
Flag Description
--days=<days> Override the retention period configured in Advanced settings.

wp typesense populate-search-log

Adds sample local search-log entries for development and testing. Each entry uses a unique session; by default, 70% reuse a small term pool so grouped and popular-search views have meaningful counts.

# Add 100 sample entries (70% repeated terms)
wp typesense populate-search-log

# Add 100 entries, 80% of which reuse the sample terms
wp typesense populate-search-log --count=100 --repeat-percent=80
Flag Description
--count=<number> Number of sample entries to add (default: 100; maximum: 10,000).
--repeat-percent=<percentage> Percentage of entries that reuse a sample term (default: 70; clamped to 0–100).

wp typesense list-external

Lists all external indexing strategies registered by third-party plugins via the Municipio/TypesenseSearch/RegisterStrategies action. Use the printed identifiers with sync-external or clear --only-external.

# List all registered external strategies
wp typesense list-external

This command takes no flags.


wp typesense sync-external

Fetches and upserts documents from all registered external indexing strategies (or a single named one). External strategies are registered by third-party plugins via the Municipio/TypesenseSearch/RegisterStrategies action and have no WordPress lifecycle hooks — syncing must be triggered explicitly here or via WP-Cron.

# Sync all registered external strategies
wp typesense sync-external

# Sync only one strategy by its identifier
wp typesense sync-external pitea-eservice

# Preview registered strategies without fetching or writing anything
wp typesense sync-external --dry-run
Argument / Flag Description
[<identifier>] Optional strategy identifier (e.g. pitea-eservice). Omit to sync all registered external strategies
--dry-run List registered strategies without fetching or upserting anything
--yes Skip the confirmation prompt

7. How indexing works

This section describes the entire indexing pipeline from a post save through to a Typesense document upsert. Understanding it is essential before writing custom strategies or enrichers.

7.1 Architecture overview

WordPress lifecycle event
        │
        ▼
  IndexingHooks          ← listens to wp_after_insert_post, trashed_post,
        │                   before_delete_post
        ▼
  IndexingRegistry       ← holds all registered strategies; routes each post
        │                   to the correct one via supports()
        ▼
  IndexingStrategyInterface
        ├─ shouldIndex()  ← eligibility check (post status, settings, meta flags)
        ├─ buildDocument()← assembles IndexableDocument from the post
        └─ index() / deindex() ← upsert or delete in Typesense
                │
        ┌───────┴────────┐
        ▼                ▼
TypesenseClientService   SettingsRepository
(cached client)          (typed option reads)

7.2 Services layer

Three shared services are built once by App and injected into every component that needs them.

Class Namespace Responsibility
SettingsRepository TypesenseSearch\Services Typed, default-aware getters for every WordPress option used by the plugin. Replaces scattered get_option() calls throughout the codebase.
TypesenseClientService TypesenseSearch\Services Lazily builds and caches the \Typesense\Client for the lifetime of the request. Consumers call getClient() — credentials are only read once even if dozens of strategies or hooks call it.
ErrorLogLogger TypesenseSearch\Logger Default implementation of LoggerInterface that writes to PHP's error_log(). Debug messages are suppressed unless WP_DEBUG is enabled. Swap it for any other implementation by passing a different LoggerInterface to strategies.

Replacing the logger — if you want to route plugin log messages to a custom destination (e.g. Sentry, a file, or a test spy), implement LoggerInterface and pass your implementation when registering strategies:

add_action(
    'Municipio/TypesenseSearch/RegisterStrategies',
    function (
        \TypesenseSearch\Indexing\IndexingRegistry $registry,
        \TypesenseSearch\Services\TypesenseClientService $clientService,
        \TypesenseSearch\Services\SettingsRepository $settings,
        \TypesenseSearch\Logger\LoggerInterface $logger
    ): void {
        $registry->register(new MyCustomStrategy($clientService, $settings, new MySentryLogger()));
    },
    10, 4
);

7.3 IndexingHooks

IndexingHooks wires WordPress actions to the registry during bootstrap and also exposes its own actions for external code.

Hooks the plugin listens to

WordPress hook When it fires What the plugin does
wp_after_insert_post (priority 20) After a post and all its meta are fully saved If post_status === 'publish': call shouldIndex() → index() (or deindex() if excluded). If transitioning away from publish: call deindex().
trashed_post Post moved to the Trash deindex()
before_delete_post Post permanently deleted deindex()

Priority 20 on wp_after_insert_post is intentional — it ensures all meta boxes have written their values before shouldIndex() reads them.

Hooks the plugin exposes for external use

External plugins and themes can trigger indexing operations without depending on any internal class:

Action hook Parameter What it does
typesense_search/index_post int $post_id Resolves the strategy for the given post and runs the same shouldIndex() → index() / deindex() logic as wp_after_insert_post. The post must be published — no-op otherwise.
typesense_search/deindex_post int $post_id Removes the document for the given post ID from the index. Safe to call even if the document does not exist.
// Re-index a post after your plugin changes data that affects the index
do_action('typesense_search/index_post', $post_id);

// Explicitly remove a post from the index
do_action('typesense_search/deindex_post', $post_id);

PDF attachments have their own lifecycle hooks (add_attachment, edit_attachment, delete_attachment) registered by PdfIndexingStrategy::registerHooks().

7.4 IndexingRegistry

The registry is the central routing table. It holds two separate sets of strategies:

  • WordPress strategies (IndexingStrategyInterface) — event-driven; one handles each post saved by WordPress.
  • External strategies (ExternalIndexingStrategyInterface) — pull-driven; triggered explicitly by cron, CLI, or any other mechanism. See §7.6 and §8.3.

Built-in registration order (order matters — first match wins for WordPress strategies):

  1. PdfIndexingStrategy — matches PDF attachments
  2. PostIndexingStrategy — matches everything else that is not an attachment

7.5 IndexingStrategyInterface

Every WordPress indexing strategy implements this contract:

Method Responsibility
getIdentifier(): string Unique slug (e.g. 'post', 'pdf'). Used for registry lookups and log messages
supports(\WP_Post $post): bool Is this strategy the right type for this post? (e.g. "is it a PDF?")
shouldIndex(\WP_Post $post): bool Does this specific post qualify for indexing right now? (status, settings, meta flags)
buildDocument(\WP_Post $post): IndexableDocument\|false Build the document to upsert. Return false to abort
index(\WP_Post $post): bool Upsert the document into Typesense
deindex(int $postId): bool Delete the document from Typesense
registerHooks(): void Wire up any additional WordPress hooks this strategy needs

AbstractIndexingStrategy provides default index() and deindex() implementations (upsert and delete via the Typesense PHP client) so concrete strategies only need to implement supports(), shouldIndex(), buildDocument(), and optionally registerHooks().

7.6 IndexableDocument

IndexableDocument is an immutable value object returned by buildDocument(). It guarantees that every document sent to Typesense has at minimum a non-empty id and title field (both required by Typesense).

$doc = new IndexableDocument([
    'id'    => (string) $post->ID,
    'title' => $post->post_title,
    'url'   => get_permalink($post),
    // ...
]);

// Non-destructive update (returns a new instance)
$doc = $doc->with('author', get_the_author());

// Pass to Typesense
$doc->toArray();

7.7 Built-in strategies

PostIndexingStrategy ('post')

Handles all non-attachment post types. A post is indexed when:

  1. Its post type is enabled in Settings → Typesense Search → Settings.
  2. post_status === 'publish'.
  3. _typesense_exclude is not set to '1'.

The result at step 3 is filterable via PostIndexingStrategy::FILTER_SHOULD_INDEX (Municipio/TypesenseSearch/Indexer/shouldIndex).

Document fields built by DocumentBuilder::build() (see §7.7):

Field Source
id $post->ID (string)
title post_title
content the_content filter output, HTML-stripped
excerpt get_the_excerpt(), processed by ExcerptHelper
url get_permalink()
type post_type
type_name Post-type label
date post_date_gmt as Unix timestamp
post_date_formatted Formatted using the site's date format
thumbnail Medium-size featured image URL
extra_terms _typesense_extra_terms meta value

PdfIndexingStrategy ('pdf')

Handles attachment posts with post_mime_type === 'application/pdf'. A PDF is indexed when:

  1. Settings → Index PDF files is enabled.
  2. The pdftotext binary is available on the server.
  3. _typesense_exclude is not set to '1'.

Text is extracted via pdftotext and capped at DEFAULT_MAX_CONTENT_LENGTH (50 000 characters), overridable via PdfIndexingStrategy::FILTER_MAX_CONTENT_LENGTH.

Additional PDF document field:

Field Source
`top_

This README is longer than the copy stored here. Read the rest on GitHub →

Releases

15 releases.

Tag
Published
1.9.2 latest
Oct 15, 2026 7d ago
Oct 5, 2026 17d ago
Oct 3, 2026 19d ago
Sep 28, 2026 24d ago
Sep 24, 2026 28d ago
Sep 17, 2026 1mo ago
Sep 13, 2026 1mo ago
Aug 16, 2026 2mo ago
Aug 9, 2026 2mo ago
Jul 1, 2026 3mo ago
Jun 29, 2026 3mo ago
Jun 26, 2026 3mo ago
Jun 24, 2026 4mo ago
Jun 23, 2026 4mo ago
Jun 23, 2026 4mo ago

These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.