Service Hooks
Customize search engine service configuration and settings.
Provider-Specific Hooks
Many hooks in this section are provider-specific (e.g., celersearch_service_meilisearch_*). Similar hooks will be available for other providers as they're added.
Service Settings
celersearch_service_settings
Modify settings for all services.
add_filter('celersearch_service_settings', function($settings, $service) {
// Add global settings
$settings['maxTotalHits'] = 10000;
return $settings;
}, 10, 2);
celersearch_service_{provider}_settings
Modify settings for a specific provider.
add_filter('celersearch_service_meilisearch_settings', function($settings, $config, $service) {
// MeiliSearch-specific settings
$settings['displayedAttributes'] = ['title', 'content', 'url'];
return $settings;
}, 10, 3);
celersearch_required_filterable_attributes
Filter the attributes that are always kept filterable when settings are pushed to the engine, regardless of how an admin curated the index's filterable list. The plugin's own query paths filter on these (e.g. the status = publish storefront filter) — if such an attribute stopped being filterable, Meilisearch would reject the filter and silently break search.
add_filter('celersearch_required_filterable_attributes', function($required, $index) {
// My integration always filters on visibility
$required[] = 'visibility';
return $required;
}, 10, 2);
| Parameter | Type | Description |
|---|---|---|
$required | string | Attribute names merged into the engine's filterableAttributes |
$index | BaseIndex | The index whose settings are being built |
Defaults: ['status'] for all indices. The Products index adds price, in_stock, ratings.average_rating, and taxonomies (required by the shop browse/filter feature).
MeiliSearch Configuration
celersearch_service_meilisearch_stop_words
Customize stop words (words ignored in search).
add_filter('celersearch_service_meilisearch_stop_words', function($words, $service) {
// Add custom stop words
$words = array_merge($words, [
'lorem', 'ipsum', 'dolor',
'click', 'here', 'read', 'more',
]);
// Add language-specific stop words
$words = array_merge($words, [
// German
'der', 'die', 'das', 'und',
// French
'le', 'la', 'les', 'et',
]);
return array_unique($words);
}, 10, 2);
celersearch_service_meilisearch_synonyms
Define search synonyms.
add_filter('celersearch_service_meilisearch_synonyms', function($synonyms, $service) {
return array_merge($synonyms, [
// Product synonyms
'laptop' => ['notebook', 'computer', 'pc'],
'phone' => ['mobile', 'smartphone', 'cell'],
// Brand synonyms
'iphone' => ['apple phone', 'ios phone'],
// Size synonyms
'small' => ['s', 'sm', 'petit'],
'medium' => ['m', 'med'],
'large' => ['l', 'lg', 'big'],
'xl' => ['extra large', 'xlarge'],
]);
}, 10, 2);
celersearch_service_meilisearch_ranking_rules
Customize result ranking.
add_filter('celersearch_service_meilisearch_ranking_rules', function($rules, $service) {
// Default MeiliSearch rules
return [
'words', // Number of matched words
'typo', // Typo tolerance
'proximity', // Word proximity
'attribute', // Attribute ranking order
'sort', // Custom sort rules
'exactness', // Exact matches first
// Add custom ranking
'is_featured:desc', // Featured items first
'popularity:desc', // Popular items higher
];
}, 10, 2);
celersearch_service_meilisearch_custom_ranking
Filter the numeric tie-breakers (attribute:asc|desc) from the Custom Ranking index setting before they're appended to rankingRules, after the textual rules above. See Custom Ranking.
add_filter('celersearch_service_meilisearch_custom_ranking', function($rules, $service) {
$rules[] = 'total_sales:desc'; // always tie-break by sales
return array_values(array_unique($rules));
}, 10, 2);
celersearch_service_meilisearch_typo_tolerance
Configure typo tolerance.
add_filter('celersearch_service_meilisearch_typo_tolerance', function($config, $service) {
return [
'enabled' => true,
'minWordSizeForTypos' => [
'oneTypo' => 4, // Allow 1 typo for 4+ char words
'twoTypos' => 8, // Allow 2 typos for 8+ char words
],
'disableOnWords' => [
// Exact match required for these
'sku', 'isbn', 'upc',
],
'disableOnAttributes' => [
// No typo tolerance on these fields
'sku', 'product_code',
],
];
}, 10, 2);
celersearch_service_meilisearch_faceting
Configure faceted search.
add_filter('celersearch_service_meilisearch_faceting', function($config, $service) {
return [
'maxValuesPerFacet' => 1000, // Max facet values
'sortFacetValuesBy' => [
'category' => 'alpha', // Sort alphabetically
'price' => 'count', // Sort by occurrence
],
];
}, 10, 2);
celersearch_service_meilisearch_pagination
Configure pagination limits.
add_filter('celersearch_service_meilisearch_pagination', function($config) {
return [
'maxTotalHits' => 10000, // Max searchable results
];
});
celersearch_service_meilisearch_proximity_precision
Configure proximity precision.
add_filter('celersearch_service_meilisearch_proximity_precision', function($precision, $service) {
// 'byWord' or 'byAttribute'
return 'byWord';
}, 10, 2);
Token Configuration
celersearch_service_meilisearch_separator_tokens
Define characters that separate words.
add_filter('celersearch_service_meilisearch_separator_tokens', function($tokens, $service) {
return array_merge($tokens, [
'/', '\\', '|', '@',
]);
}, 10, 2);
celersearch_service_meilisearch_non_separator_tokens
Define characters that don't separate words.
add_filter('celersearch_service_meilisearch_non_separator_tokens', function($tokens, $service) {
// Keep these as part of words
return ['#', '@', '+', '-'];
}, 10, 2);
celersearch_service_meilisearch_dictionary
Add custom dictionary entries.
add_filter('celersearch_service_meilisearch_dictionary', function($dictionary, $service) {
// Keep compound words together
return array_merge($dictionary, [
'new york',
'los angeles',
'san francisco',
'iphone pro',
'macbook air',
]);
}, 10, 2);
AI / Embedders
Hooks for AI semantic and hybrid search. See the AI Semantic Search guide for setup and concepts.
celersearch_embedder_sources
Register or modify embedder source definitions. Each source declares its admin form fields — type, default, sanitizer, secret flag — and how each field maps onto Meilisearch's embedder payload keys (engine_key). The plugin registers openAi by default; third parties can register additional sources (e.g. rest, ollama, userProvided). Fields render generically in the admin UI, and fields flagged secret get value masking automatically. Storage is per-source (embedder.configs[<slug>]), so adding a source is data, not a migration.
add_filter('celersearch_embedder_sources', function($sources) {
$sources[] = [
'slug' => 'ollama',
'label' => 'Ollama',
'fields' => [
[
'name' => 'model',
'label' => fn() => __('Model', 'my-plugin'),
'type' => 'text',
'default' => 'nomic-embed-text',
'engine_key' => 'model',
'sanitize' => fn($value) => sanitize_text_field((string) $value),
],
[
'name' => 'url',
'label' => fn() => __('URL', 'my-plugin'),
'type' => 'text',
'default' => 'http://localhost:11434/api/embed',
'engine_key' => 'url',
'sanitize' => fn($value) => esc_url_raw((string) $value),
],
[
'name' => 'document_template',
'label' => fn() => __('Document template', 'my-plugin'),
'type' => 'textarea',
// Context-less fallback; with an index in scope the default is
// the index type's own template. Guard fields with {% if %} —
// an unknown field fails the document's whole batch.
'default' => "{% if doc.title %}{{ doc.title }}\n{% endif %}{% if doc.content %}{{ doc.content }}{% endif %}",
'engine_key' => 'documentTemplate',
'sanitize' => fn($value) => sanitize_textarea_field((string) $value),
],
],
];
return $sources;
});
| Parameter | Type | Description |
|---|---|---|
$sources | array | Source definitions. Each: slug, label, fields — and each field: name, label (callable returning the translated string), type, default, engine_key, sanitize (callable), optional secret (bool) and options |
celersearch_embedder_document_template
Filter the default document template an index pre-fills (and falls back to when the stored template is empty). Each index type ships a default built from its own document fields — posts emit title/excerpt/content while products emit post_title/short_description/content — so use this to adjust the default for a specific index or a custom index type. Keep every field inside {% if doc.field %}…{% endif %} guards: rendering is strict and an unknown field fails the document's whole batch.
add_filter('celersearch_embedder_document_template', function($template, $index) {
if ($index->get_slug() === 'woocommerce-products') {
$template .= "\n{% if doc.sku %}SKU {{ doc.sku }}{% endif %}";
}
return $template;
}, 10, 2);
| Parameter | Type | Description |
|---|---|---|
$template | string | Liquid template rendered per document to produce the embedded text |
$index | BaseIndex | The index the template applies to |
celersearch_service_meilisearch_embedders
Filter the embedders payload pushed to Meilisearch as part of index settings. The plugin maps the stored embedder config to a single embedder named default; external integrations can inject or override embedders here. Because this rides push_settings(), embedders added here are re-applied on every full rebuild (index delete/recreate) — they inherit the plugin's rebuild persistence.
add_filter('celersearch_service_meilisearch_embedders', function($embedders, $index, $service) {
// Add a second, self-hosted embedder. Use the index's own template so the
// fields always match its documents (or write your own guarded template).
$embedders['ollama'] = [
'source' => 'ollama',
'model' => 'nomic-embed-text',
'url' => 'http://localhost:11434/api/embed',
'documentTemplate' => $index->get_embedder_document_template(),
];
return $embedders;
}, 10, 3);
| Parameter | Type | Description |
|---|---|---|
$embedders | array | Embedder name => Meilisearch embedder payload. Contains default when an embedder is configured and enabled |
$index | BaseIndex | The index whose settings are being built |
$service | MeiliSearch | The service instance |
When the filtered array is empty, the embedders key is omitted from the settings payload entirely — non-destructive to embedders managed outside the plugin.
celersearch_hybrid_search_params
Override the hybrid (semantic + keyword) search parameters per query. This runs centrally in the MeiliSearch service, so every call path — main query interception, autocomplete REST, views, WooCommerce shop filters — is covered.
add_filter('celersearch_hybrid_search_params', function($hybrid, $index, $params) {
// Lean more semantic on the products index
if ($index->get_slug() === 'products') {
return ['embedder' => 'default', 'semanticRatio' => 0.8];
}
// Return null (or an empty array) to force pure keyword search
return $hybrid;
}, 10, 3);
| Parameter | Type | Description |
|---|---|---|
$hybrid | array|null | Hybrid parameters (embedder, semanticRatio), or null when no embedder is enabled |
$index | BaseIndex | The index being searched |
$params | array | The search parameters for this request (before hybrid injection) |
The default is built from the index's embedder settings: ['embedder' => 'default', 'semanticRatio' => <ratio>] when an embedder is enabled with a semantic ratio above 0 (the ratio is clamped to 0–1), otherwise null. Callers that set hybrid in the search params themselves win — the filter is skipped in that case.
Provider Registration
celersearch_supported_providers
Register custom search providers.
add_filter('celersearch_supported_providers', function($providers) {
$providers[] = [
'name' => 'Typesense',
'slug' => 'typesense',
'model' => \MyPlugin\Providers\TypesenseProvider::class,
'fields' => [
[
'label' => __('API URL', 'my-plugin'),
'name' => 'url',
'type' => 'url',
'rules' => 'required',
],
[
'label' => __('API Key', 'my-plugin'),
'name' => 'api_key',
'type' => 'password',
'rules' => 'required',
],
[
'label' => __('Protocol', 'my-plugin'),
'name' => 'protocol',
'type' => 'dropdown',
'options' => [
['label' => 'HTTPS', 'value' => 'https'],
['label' => 'HTTP', 'value' => 'http'],
],
],
],
];
return $providers;
});
Complete Example
Optimize MeiliSearch for e-commerce:
<?php
// Stop words for cleaner search
add_filter('celersearch_service_meilisearch_stop_words', function($words) {
return array_merge($words, [
// Common filler words
'the', 'a', 'an', 'and', 'or', 'but',
// E-commerce noise
'buy', 'shop', 'sale', 'discount', 'cheap',
'free', 'shipping', 'delivery',
]);
});
// Product synonyms
add_filter('celersearch_service_meilisearch_synonyms', function($synonyms) {
return [
// Size synonyms
'xs' => ['extra small'],
's' => ['small'],
'm' => ['medium'],
'l' => ['large'],
'xl' => ['extra large'],
'xxl' => ['2xl', 'double xl'],
// Color synonyms
'grey' => ['gray'],
'colour' => ['color'],
// Category synonyms
'tee' => ['t-shirt', 'tshirt'],
'pants' => ['trousers', 'jeans'],
'shoes' => ['footwear', 'sneakers'],
];
});
// Ranking for e-commerce
add_filter('celersearch_service_meilisearch_ranking_rules', function($rules) {
return [
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
// E-commerce specific
'in_stock:desc', // In-stock first
'is_featured:desc', // Featured products
'sales_count:desc', // Bestsellers higher
];
});
// Typo tolerance
add_filter('celersearch_service_meilisearch_typo_tolerance', function($config) {
return [
'enabled' => true,
'minWordSizeForTypos' => [
'oneTypo' => 4,
'twoTypos' => 8,
],
'disableOnAttributes' => [
'sku',
'product_id',
'barcode',
],
];
});
// Faceting for filters
add_filter('celersearch_service_meilisearch_faceting', function($config) {
return [
'maxValuesPerFacet' => 500,
'sortFacetValuesBy' => [
'brand' => 'alpha',
'category' => 'alpha',
'size' => 'alpha',
'color' => 'alpha',
'price' => 'count',
],
];
});