TypesenseSearch for Magento 2
A drop-in Typesense integration that replaces Magento's default search with a blazing-fast, typo-tolerant, faceted search engine — with real-time autocomplete, AI query rewriting, and a fully configurable storefront results page.
Requirements
| Dependency | Version | Notes |
|---|---|---|
| Magento Open Source / Adobe Commerce | 2.4.x | Tested on 2.4.6 – 2.4.9 |
| PHP | 8.1 – 8.3 | Required by Magento framework |
| Typesense Server | 0.25+ | Self-hosted or Typesense Cloud |
| typesense/typesense-php | ^4.8 | Installed via Composer |
| php-http/curl-client | ^2.3 | HTTP transport for the PHP client |
Installation
Install via Composer after configuring your credentials. See your dashboard for your license key.
composer config repositories.ayasoftware composer https://www.ayasoftware.com/repo
auth.json (one-time)composer config --global http-basic.www.ayasoftware.com your@email.com YOUR_LICENSE_KEY
Your license key is available in the Ayasoftware dashboard.
composer require ayasoftware/typesensesearch
bin/magento module:enable Ayasoftware_TypesenseSearch
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f en_US
bin/magento cache:flush
php container. Run them from /workspace/magento.Typesense Server
The module requires a running Typesense server. It can be self-hosted or provisioned via Typesense Cloud.
Self-hosted (recommended for development)
Download the binary and start the server with your data directory and API key:
/path/to/typesense-server \
--data-dir=/var/typesense-data \
--api-key=YOUR_API_KEY \
--listen-port=8108 \
--enable-cors
Nginx proxy (recommended for Codespaces / cloud)
When the Typesense port is not directly reachable from the browser, add a proxy block to your Nginx config so the storefront can reach Typesense through the existing :8000 port:
nginx.conflocation /typesense/ {
proxy_pass http://php:8108/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
Then set the Public Path field to /typesense in the Magento admin config (see below). The storefront JS will automatically route all Typesense requests through the Nginx proxy.
Magento Configuration
Navigate to Stores → Configuration → Ayasoftware → Typesense Search.
Credentials
| Field | Config Path | Description |
|---|---|---|
| Enable Frontend | typesense_general/credentials/enable_frontend |
Globally enables/disables the extension |
| Search API Key | typesense_general/credentials/search_only_api_key |
Read-only key exposed to the browser |
| Admin API Key | typesense_general/credentials/admin_api_key |
Full-access key used by PHP indexers (never sent to browser) |
| Node (internal) | typesense_general/credentials/node |
Hostname/IP used by PHP (e.g. localhost) |
| Public Node | typesense_general/credentials/public_node |
Hostname used by the browser (e.g. your Codespace domain) |
| Public Path | typesense_general/credentials/public_path |
Proxy path prefix, e.g. /typesense. When set, browser requests route through Nginx. |
| Port | typesense_general/credentials/port |
Internal port (default 8108) |
| Public Port | typesense_general/credentials/public_port |
Browser-facing port (e.g. 443 behind Nginx) |
| Protocol | typesense_general/credentials/protocol |
http for internal, https for public |
| Index Prefix | typesense_general/credentials/index_prefix |
Prefix for Typesense collection names, e.g. magento_ |
Search Result Page
| Field | Config Path | Default |
|---|---|---|
| Enable Result Page | typesense_search_result/instant_search_result/enable_result_page |
1 |
| Products Per Page | typesense_search_result/instant_search_result/page_per_product |
12 |
| Enable Price Slider | typesense_search_result/instant_search_result/enable_price_slider |
1 |
| Enable Add to Cart | typesense_search_result/instant_search_result/enable_addtocart |
0 |
Search Filters
Filters are configured under Stores → Configuration → Ayasoftware → Typesense Search → Search Result → Search Filters.
Each filter row has three columns:
- Attribute — the Typesense field name (must be marked facet: true in the schema)
- Label — display name shown in the filter sidebar
- Option —
Searchable,Not Searchable, orFilter Only
Default filter set (v1.2.1)
| Attribute | Label | Mode |
|---|---|---|
category | Category | Filter Only |
color | Color | Filter Only |
size | Size | Filter Only |
material | Material | Filter Only |
gender | Gender | Filter Only |
price | Price | Price Slider |
facet: true in the Typesense schema can be used as filters. Run the product indexer to rebuild the schema after adding new attributes.Price Slider
The price slider renders a jQuery UI range input in the filter sidebar. Enable it at Search Result → Enable Price Slider and add price to Search Filters.
Behaviour
- The slider's min / max bounds are fixed to the unfiltered price range of the current keyword. Applying a price filter does not shrink the bounds — you can always widen the range back.
- The search fires only when the handle is released (not on every pixel drag), keeping the UI responsive.
- The price range input is editable — type any value (e.g.
100 - 500or$100 – $500) and press Enter to apply. - Removing the price filter chip resets the handles to the full absolute range.
Customising slider CSS
typesense.css/* Slider track */
#price-range.ui-slider {
background: #dde8f5;
height: 6px;
margin: 38px 10px 10px;
}
/* Drag handles */
#price-range.ui-slider .ui-slider-handle {
background: #1979C3;
height: 22px;
width: 22px;
}
/* Tooltip bubble */
.ts-price-tooltip {
background: #1979C3;
color: #fff;
padding: 2px 6px;
border-radius: 4px;
}
Items Per Page
Two config values control the per-page selector in the search results toolbar:
| Config Path | Value | Effect |
|---|---|---|
typesense_search_result/instant_search_result/page_per_product |
12 |
Default number of products fetched per page |
catalog/frontend/grid_per_page_values |
12,24,36 |
Options shown in the "Show N per page" dropdown |
Set via CLI:
bin/magento config:set typesense_search_result/instant_search_result/page_per_product 12
bin/magento config:set catalog/frontend/grid_per_page_values 12,24,36
bin/magento cache:clean config
Personalization
When enabled, search results and category pages boost products based on each shopper's individual behaviour — clicks, add-to-cart events, and purchases. A visitor with no recorded history sees the standard ranking unchanged.
How it works
At page render time the extension queries the ayasoftware_search_events table for the current visitor, computes a weighted score per product, and injects a Typesense _eval() expression into the sort_by parameter. Products with higher scores float toward the top of results for that specific visitor.
- Logged-in customers — history is scoped by
customer_idand persists across sessions and devices. - Guests — history is scoped by the session cookie (
typesense_search_session) for the duration of the browser session. - Boost scores are cached in Redis for 15 minutes per visitor to avoid repeated DB queries on every page load.
- A manual sort selection by the shopper (price, newest, etc.) always overrides personalisation.
Configuration
Navigate to Stores → Configuration → Ayasoftware → Typesense Search → Personalization.
| Field | Config Path | Default | Description |
|---|---|---|---|
| Enable Personalization | typesense_personalization/settings/enabled |
No | Master toggle. Off by default. |
| History Window (days) | typesense_personalization/settings/history_days |
30 |
How many days of past events to include in the score calculation. |
| Max Products to Boost | typesense_personalization/settings/max_boosts |
20 |
Cap on the number of products included in the _eval() formula. Keep ≤ 20 to avoid bloating search requests. |
| Click Weight | typesense_personalization/settings/click_weight |
1 |
Score per product click event. |
| Add to Cart Weight | typesense_personalization/settings/add_to_cart_weight |
3 |
Score per add-to-cart event. |
| Purchase Weight | typesense_personalization/settings/order_weight |
5 |
Score per order event. A single purchase outweighs five clicks. |
| Enable A/B Test | typesense_personalization/settings/ab_test_enabled |
No | Split visitors into control (standard ranking) and treatment (personalised ranking) groups. Requires Personalization to be enabled. |
Database migration
Version 1.3.0 adds a customer_id column to ayasoftware_search_events. Run the following after upgrading:
bin/magento setup:upgrade
bin/magento cache:flush
Performance under load
Two changes in v1.4.0 address concurrency pressure at scale:
-
Cache stampede prevention (ProfileBuilder) — The 15-minute TTL now has jitter (base 900 s + a random 0–120 s), so users who start sessions in the same window don't expire in lockstep and trigger the expensive
GROUP BYrebuild all at once. On a cache miss, a lock ensures only one request per user rebuilds the profile — concurrent requests for the same key return empty immediately (personalization is a boost, not a hard requirement) rather than dog-piling the database. Late arrivals pick up the warm cache on the next request. -
Async analytics writes — The bigger pressure point wasn't Redis at all — it was the synchronous MySQL
INSERTon every click and search event, which hits connection limits well before the cache layer becomes a concern. That path is now asynchronous: the Track controller publishes the event to a message queue and the request returns as soon as it's enqueued, while a background consumer handles the actual insert. It reuses the existing queue infrastructure, so there are no new dependencies.
Net effect: read pressure is spread across time and deduplicated per user, and write pressure is decoupled from the request lifecycle entirely.
bin/magento queue:consumers:start typesense.analytics.event or add it to your supervisor / cron configuration.Running Indexers
The extension ships four indexers. Run them in this order for a full initial index:
# Prerequisite — rebuild Magento's category product index first
bin/magento indexer:reindex catalog_category_product catalog_product_category
# Typesense indexers
bin/magento indexer:reindex typesense_products typesense_categories typsense_pages suggestions
INNER JOIN on catalog_category_product_index_store{N}.
Products not present in that index will be silently excluded from Typesense.
Always rebuild catalog_category_product before re-indexing products.
Indexer reference
| Indexer ID | What it indexes |
|---|---|
typesense_products | Catalog products — name, attributes, price, images, stock |
typesense_categories | Category tree |
typsense_pages | CMS pages |
suggestions | Popular search suggestions |
Partial re-index (Update on Schedule)
Set the indexers to Update by Schedule in the Magento admin and ensure the cron is running. Product saves, stock changes, and category updates will queue incremental re-index jobs automatically.
Auto-Start on Container Restart
For devcontainer environments, add the provided startup script to postStartCommand so Typesense starts automatically on every container boot.
1. Create the startup script
Save as .devcontainer/bin/start-typesense.sh:
start-typesense.sh#!/usr/bin/env bash
set -u
# Re-exec detached so postStartCommand doesn't kill us when it returns
if [ -z "${_TS_DETACHED:-}" ]; then
_TS_DETACHED=1 setsid -f "$0" "$@" </dev/null >>/workspace/typesense-data/typesense.log 2>&1
exit 0
fi
BINARY=/workspace/typesense-bin/typesense-server
DATA_DIR=/workspace/typesense-data
API_KEY=${TYPESENSE_API_KEY:-localdev_key_123}
# Kill stale instance and clear lock files
for p in /proc/[0-9]*; do
cmdline=$(tr '\0' ' ' < "$p/cmdline" 2>/dev/null) || continue
case "$cmdline" in *typesense-server*) kill "${p#/proc/}" 2>/dev/null ;; esac
done
sleep 1
rm -f "$DATA_DIR/db/LOCK" "$DATA_DIR/meta/LOCK"
exec "$BINARY" \
--data-dir="$DATA_DIR" \
--api-key="$API_KEY" \
--listen-port=8108 \
--enable-cors
2. Register in devcontainer.json
devcontainer.json"postStartCommand": {
"forward-ports": "/bin/bash .devcontainer/bin/forward-ports.sh",
"typesense": "/bin/bash .devcontainer/bin/start-typesense.sh"
}
LOCK files automatically, preventing the segfault that occurs when a previous instance was killed abruptly.Troubleshooting
"Configuration issues try again" on the storefront
The Typesense client failed to connect. Check in order:
- Typesense server is running:
curl http://localhost:8108/healthshould return{"ok":true} - Nginx proxy is responding:
curl http://nginx:8000/typesense/health - Search API key and node/port are correctly saved in Magento config
- Run
bin/magento cache:flushafter any config change
Products missing from search results
- Confirm the product is enabled with visibility set to Catalog, Search
- Confirm the product is assigned to a website
- Rebuild
catalog_category_productfirst, then re-runtypesense_products - Check the Typesense collection:
curl -H "X-TYPESENSE-API-KEY: <key>" http://localhost:8108/collections/magento_default-products/documents/search?q=<sku>&query_by=sku
Filters not showing on results page
- Verify Search Filters is not empty in admin config
- Verify each filter attribute is listed as
facet: truein the Typesense collection schema - Flush
full_pageandblock_htmlcaches after any config change
Typesense crashes with segfault on restart
Stale lock files from a previous killed process. Remove them:
rm -f /workspace/typesense-data/db/LOCK /workspace/typesense-data/meta/LOCK
Then restart the server. The auto-start script (v1.2.1+) handles this automatically.
Checking the Typesense index
# List collections and document counts
curl -s -H "X-TYPESENSE-API-KEY: <key>" http://localhost:8108/collections \
| python3 -c "import sys,json; [print(c['name'], c['num_documents']) for c in json.load(sys.stdin)]"
Changelog
-
Feat
A/B Testing for Personalisation — Split visitors deterministically into control (standard ranking) and treatment (personalised ranking) groups. Assignment is a hash of the visitor identity so it is sticky across requests with no extra state. Configure via Stores → Configuration → Ayasoftware → Typesense Search → Personalization: toggle, experiment name (changing it reshuffles assignments), and traffic split %.
-
Feat
A/B Results in Analytics Dashboard — When both variants have recorded data a side-by-side comparison panel appears automatically, showing CTR, add-to-cart rate, conversion rate, and revenue per variant with a Δ% column and winner highlighting.
-
Impr
ab_variantcolumn added toayasoftware_search_events(nullable — null means experiment was off) andayasoftware_search_analytics(not null, defaultnone). Runbin/magento setup:upgradeafter updating. - Impr Analytics aggregation now groups by variant, so control and treatment metrics accumulate in separate rows without affecting existing non-experiment data.
-
Impr
Cache stampede prevention — Personalization profile TTL now includes a random 0–120 s jitter so sessions that start together don't expire together. A per-user lock on cache miss ensures only one request rebuilds the profile; concurrent requests degrade gracefully to unranked results rather than piling onto the database.
-
Impr
Async analytics writes — The Track controller now publishes events to the
typesense.analytics.eventmessage queue instead of inserting directly into MySQL. The web request returns immediately; a background consumer handles the insert. Requiresbin/magento queue:consumers:start typesense.analytics.event.
-
Feat
Personalization Signals — Search results and category pages now boost products based on each shopper's click, add-to-cart, and purchase history. Works for both logged-in customers and guests. Scores are cached in Redis; degrades gracefully for first-time visitors.
-
Feat
New Personalization admin config group — enable/disable toggle, history window, max products to boost, and per-event-type weight controls (click / add to cart / purchase).
-
ImprAnalytics tracking now records
customer_idfor logged-in shoppers, enabling history that persists across sessions and devices. -
ImprAdded
customer_idcolumn (with index) toayasoftware_search_events. Runbin/magento setup:upgradeafter updating.
- FixSearch results page not showing any filters
- FixPrice attribute not appearing in the filter sidebar
- FixPrice slider max value permanently reduced after filtering
- FixPrice slider not resetting when the price filter chip is removed
- FixEnter key not applying the typed price range
- FeatAuto-start Typesense on devcontainer boot via
postStartCommand - FeatPrice handles show a live tooltip with the current value while dragging
- FeatPrice range input is editable — type a range and press Enter to apply
- ImprPrice slider fires search on handle release only, not on every drag pixel
- ImprSearch results default to 12 items per page with a 12/24/36 selector
- ImprFilters pre-configured out of the box: Category, Color, Size, Material, Gender, Price
New Features
-
Feat
AI Search Intent — Transform natural language searches into intelligent filters, categories, price ranges, brands, and attributes automatically.
-
Feat
Search Analytics Dashboard — New Magento Admin dashboard with store and 7 / 30 / 90-day filters. Tracks search impressions, product clicks, add-to-cart events, orders, conversion rates, and revenue attribution.
-
Feat
Actionable Recommendations — Surfaces zero-result searches, low click-through queries, and frequently searched terms that generate no orders.
-
Feat
Daily Analytics Aggregation — Efficient cron-based aggregation for fast reporting across large datasets.
-
Feat
Multilingual AI Filter Parsing — AI-powered natural-language filter parsing supporting multilingual queries.
-
Feat
AI Provider Selection — Choose between Claude, OpenAI, and Google Gemini. Provider-specific API key and model configuration shared by query rewriting and AI filters.
Improvements
- ImprImproved search result product rendering and interaction handling
- ImprExpanded product attribute configuration options
- ImprUpdated module and Composer package version to 1.2.0
New Feature — AI-Powered Query Rewriting
-
Feat
Claude AI query rewriting — Search queries on the result page are automatically cleaned and expanded before being sent to Typesense, improving result quality for conversational, abbreviated, or misspelled queries.
-
Feat
Result page banner — When a query is rewritten, the page shows "Showing results for: [rewritten query]" with an option to search the original term instead.
-
Impr
Redis caching — Rewritten queries are cached for 24 hours. Repeated queries cost nothing. Falls back to the original query silently if the API is unavailable or times out.
Examples
| Original query | Rewritten to |
|---|---|
"cheap red running shoes" | Removes filler, focuses on product terms |
"tee shirt xl" | "t-shirt xl" — expands abbreviations |
"do you have argus tank" | "argus tank" — strips conversational phrasing |
| Common misspellings | Fixed automatically |
Only fires on the search result page (not autocomplete) to avoid latency impact. Uses Claude Haiku with 60 output tokens max.
Estimated cost: $2–$20/month for most stores depending on search volume and cache hit rate.