• Сообщество
  • Блог
Документация
Плагины и интеграцииВсе расширения для Medusa от сообществаСтартерыЗапускайте проекты быстрее с готовыми решениями
ЭкспертыПодберите специалиста для разработки и развития вашего проекта на MedusaКейсыПосмотрите примеры Medusa в продакшене и успешные внедрения
Меч Moscow
Комплексная e-commerce платформа на Medusa для московского fashion-бренда

Меч Moscow · Fashion

Gorgo снижает затраты на адаптацию Medusa к локальным рынкам.

Мы разрабатываем плагины интеграции, осуществляем поддержку и развиваем сообщество разработчиков на Medusa в Telegram.

  • Ресурсы Medusa
  • Плагины и интеграции
  • Стартеры
  • Эксперты
  • Кейсы
  • Medusa Чат в Telegram
  • Medusa Новости в Telegram
  • Документация Gorgo
  • Связаться с нами
  • TelegramGitHub
Плагины
A

Algolia plus

Reusable Medusa v2 module that syncs product data to Algolia and powers storefront search through the Medusa backend

npm install medusa-plugin-algolia-plus
Категория
Поиск
Создано
Dtoyoda10
Версия
1.0.0
Последнее обновление
2 дня назад
Ежемесячные загрузки
Загрузка данных
Звезды на Github
0
npmNPMGitHubGithub

medusa-plugin-algolia-plus

A production-grade, reusable Medusa v2 module that synchronizes product catalog data to Algolia and powers storefront search through the Medusa backend. It is client-agnostic, configuration-driven, and safe to deploy across multiple storefront projects without modification.

  • Backend: zero application code — register the plugin and set three environment variables. Products auto-sync to Algolia; an admin reindex page and a store search route are exposed.
  • Storefront: one drop-in (shipped with the package) points InstantSearch at the Medusa search route. The Algolia key never reaches the browser.
Package
Algolia client v5 (the only place that imports it)
Medusapinned to 2.13.5 (peer dependency)
StatusStandalone repo · 79 unit tests · typecheck + plugin build green · installable locally via yalc

Table of contents

  1. How it works
  2. Design principles & ADRs
  3. Installation
  4. Configuration
  5. The Algolia record
  6. Index settings
  7. Customizing the mapping
  8. Sync behavior
  9. Coverage & limitations
  10. API surface
  11. Storefront integration
  12. Admin UI
  13. Production hardening
  14. Operations runbook
  15. Troubleshooting
  16. Module API reference
  17. Testing
  18. Package structure
  19. Medusa version compatibility
  20. Roadmap / deferred

How it works

1Medusa (source of truth)
2 │
3 ├─ product / variant / category / collection / order events
4 │ │
5 │ ▼
6 │ subscribers (thin, never throw)
7 │ │ resolve affected product ids
8 │ ▼
9 │ syncProductsWorkflow ──► AlgoliaModuleService ──► Algolia index
10 │ deleteProductsFromAlgoliaWorkflow (the only algoliasearch caller)
11 │
12 └─ admin: POST /admin/algolia/sync ──► algolia.sync ──► paginated full reindex
13
14Storefront ──► POST /store/products/search ──► AlgoliaModuleService.search ──► Algolia
15 (InstantSearch via the shipped searchClient; key stays on the backend)

The Algolia index is a derived view: every record is reproducible from Medusa data alone, so the index can be wiped and rebuilt at any time with no loss of business state.


Design principles & ADRs

  • The Algolia index is a derived view. Medusa is the source of truth.
  • One service owns all Algolia traffic. is the only file that imports . Subscribers, workflows, and routes call into it — never construct Algolia requests directly.
  • Subscribers stay thin; workflows do the work. Every meaningful action is a workflow with compensation; subscribers resolve ids and delegate.
  • Stable object IDs. — repeated syncs upsert, never duplicate. This is the foundational idempotency contract.
  • Configuration is environment-driven. No hardcoded values that block reuse.
  • Storefront search goes through Medusa (ADR-004) — the Algolia write key stays on the backend; search behavior is centralized for every storefront.
ADRDecision
001Workflow + step + compensation for all sync
002Product id as Algolia
003Subscribers delegate, never compute
004Storefront searches through Medusa, not Algolia directly
005Dependent changes resolved via Query graph
006Unpublished == deleted in the index

Installation

is a self-contained package. It is built with (output in ) and must be built before a Medusa backend can load it. and ship as runtime dependencies, so a consumer installs only the one package.

From a registry

1npm install medusa-plugin-algolia-plus
2# or: pnpm add medusa-plugin-algolia-plus / yarn add medusa-plugin-algolia-plus

Local development with yalc

To develop the plugin against a Medusa backend on the same machine without publishing to a registry, use yalc. It packs the built package into a local store and copies it into the backend — closer to a real install than (no peer-dependency hoisting surprises, and the backend resolves exactly as it would from npm).

In this plugin repo — build and publish to the local yalc store:

1pnpm install
2pnpm yalc:publish # runs `medusa plugin:build`, then `yalc publish`

In your Medusa backend — link it, install, and register the plugin:

1yalc add medusa-plugin-algolia-plus # writes "medusa-plugin-algolia-plus": "file:.yalc/medusa-plugin-algolia-plus"
2pnpm install # or npm/yarn — installs the linked package

Then register it in (see Configuration) and restart the backend.

Iterating — after each change to the plugin, rebuild and propagate to every linked backend in one step:

pnpm yalc:push # rebuilds, then `yalc push` updates all consumers

Unlink with (or ) in the backend, then reinstall the registry version.

Only compiled output is shipped: is , so packs plus and this README — never the raw . Always build before publishing; the / scripts do this for you.


Configuration

Environment variables

VariableRequiredDescription
✅ yesAlgolia Application ID
✅ yesAlgolia write/admin API key (backend-only)
✅ yesProduct index name (e.g. )
noReindex page size, 1–1000 (default )
noBase URL used to build each record's

⚠️ Use a write key, not a Search-Only key. Indexing and settings calls require , , and ACLs. A Search-Only key fails with "Not enough rights to add an object".

If the three required vars are not all present, the module is not registered (search silently disabled). If it is registered with invalid options, it throws at Medusa startup — — never silently at the first sync (the fail-fast contract).

Register the plugin

:

1plugins: [
2 ...(ALGOLIA_APP_ID && ALGOLIA_API_KEY && ALGOLIA_PRODUCT_INDEX_NAME
3 ? [
4 {
5 resolve: "medusa-plugin-algolia-plus",
6 options: {
7 appId: ALGOLIA_APP_ID,
8 apiKey: ALGOLIA_API_KEY, // write key — stays on the backend
9 productIndexName: ALGOLIA_PRODUCT_INDEX_NAME,
10 // optional, see the table below:
11 // batchSize: 50,
12 // storefrontUrl: "https://shop.example.com",
13 // currencyCodes: ["eur", "usd"],
14 // metadataFields: { material: "material", season: "season" },
15 // searchableAttributes: [...], attributesForFaceting: [...],
16 // customRanking: [...], maxRetries: 3, retryBaseDelayMs: 200,
17 // requestTimeoutMs: 30000,
18 },
19 },
20 ]
21 : []),
22];

Module options

OptionTypeDefaultPurpose
—Algolia Application ID (required)
—Algolia write key (required)
—Product index name (required)
(1–1000)Full-reindex / fan-out page size
(URL)—Builds each record's
allRestrict indexed currencies (lowercased)
defaultReplace the record shape entirely
—Shallow static fields on every record
—Metafield mapping:
defaultApplied at reindex time
defaultApplied at reindex time
defaultApplied at reindex time
(1k–120k)Per-request HTTP timeout
(0–10)Retries on transient Algolia errors
(0–60k)Backoff base delay

The Algolia record

The default mapper produces one record per product ():

1{
2 "objectID": "prod_123",
3 "id": "prod_123",
4 "title": "Cool Shirt",
5 "subtitle": null,
6 "description": "A very cool shirt",
7 "handle": "cool-shirt",
8 "thumbnail": "https://cdn/thumb.jpg",
9 "images": [{ "id": "img_1", "url": "https://cdn/1.jpg" }],
10 "categories": [{ "id": "cat_1", "name": "Shirts", "handle": "shirts" }],
11 "collection": { "id": "col_1", "title": "Spring", "handle": "spring" },
12 "tags": ["summer"],
13 "type": "apparel",
14 "variants": [
15 { "id": "var_1", "sku": "SHIRT-S", "title": "S", "prices": { "eur": 1999, "usd": 2499 } }
16 ],
17 "price_min": { "eur": 1999, "usd": 2499 }, // per currency, across variants
18 "price_max": { "eur": 2999, "usd": 3499 },
19 "availability": true, // derived from stock + flags
20 "url": "https://shop.example.com/products/cool-shirt",
21 "created_at": "…",
22 "updated_at": "…"
23}

Prices are keyed by lowercase currency code, in minor units (as Medusa stores them). is if any variant is purchasable: not inventory-managed, allows backorder, or has across its inventory levels.


Index settings

Sensible defaults are applied automatically at the start of every full reindex, so search, faceting, filtering, and ranking work out of the box — no manual Algolia dashboard setup:

SettingDefault
, , , , ,
, , ,
(in-stock first)

Override any of them via the matching module option. For numeric price filtering, add per-currency price facets to , e.g. / .


Customizing the mapping

Three escape hatches, smallest to largest:

1. — set static fields on every record:

fieldOverrides: { brand: "Acme" } // objectID is never overridable

2. — metafield-driven mapping; project keys onto record fields:

1metadataFields: { material: "material", season: "season_facet" }
2// product.metadata.material → record.material, etc.

3. — replace the record shape entirely (pure function):

1import type { ProductMapper } from "medusa-plugin-algolia-plus";
2
3const productMapper: ProductMapper = (product, ctx) => ({
4 objectID: product.id, // must equal the product id
5 name: product.title,
6 // …your shape…
7});

Sync behavior

Full reindex

emits and returns immediately; the subscriber walks the entire catalog page by page () into and applies the configured index settings first. Triggered from Settings → Algolia → Reindex all products or the route directly. Idempotent — running it twice yields no duplicates.

Event-driven sync

Thin, never-throwing subscribers keep the index current automatically:

You do in MedusaEventIn Algolia
Create / edit a product / record upserted (or removed if unpublished)
Delete a productrecord removed
Add / edit a variant, change price / parent product reindexed
Rename a category / collection / products in it reindexed
Place / cancel / receive-return an order / / order's products' refreshed

A product is in search only while (ADR-006): unpublish removes it, republish re-adds it under the same id.

Availability model

Medusa 2.13.5 emits no event for stock changes, so availability is tracked through order events — placing an order reserves inventory ( drops); cancel/return releases it. The mapper computes stock-accurate availability from .


Coverage & limitations

Two changes can't be caught in real time in 2.13.5 (both repaired by a manual full reindex — the documented disaster-recovery primitive):

  • A direct admin stock-level edit (not via an order) emits no event.
  • Deleting a category, collection, or single variant severs the product links before the event is delivered, so affected products can't be resolved from it. (Whole-product deletes are handled; a variant removed while editing a product is covered by the accompanying .)

Idempotency guarantees (by construction, because is stable): duplicate event delivery, out-of-order delivery (last-writer-wins), and a manual reindex racing an event sync all converge to the correct state.


API surface

Admin

RouteMethodPurpose
Trigger a full reindex (emits , returns )
Connection health, index name, and current options
Re-sync a single product synchronously

Store

— validated by a Zod middleware, calls , and returns the unmodified Algolia (, , , , , …).

1curl -X POST http://localhost:9000/store/products/search \
2 -H "content-type: application/json" \
3 -H "x-publishable-api-key: pk_..." \
4 -d '{"query":"shirt","hitsPerPage":12,"facetFilters":[["categories.handle:shirts"]]}'

Accepted body fields: (default ), , (1–200), , , , , ; additional InstantSearch params pass through. Invalid bodies fail with a structured at the middleware before the handler runs.


Storefront integration

The package ships a reusable, dependency-free InstantSearch so the storefront integration is a one-line swap (no Algolia key in the browser):

1// storefront: src/lib/search-client.ts
2import { createAlgoliaSearchClient } from "medusa-plugin-algolia-plus/storefront/create-search-client"
3
4export const searchClient = createAlgoliaSearchClient({
5 backendUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL!,
6 publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY!,
7})
8export const SEARCH_INDEX_NAME = process.env.NEXT_PUBLIC_INDEX_NAME || "products"
1<InstantSearch searchClient={searchClient} indexName={SEARCH_INDEX_NAME}>
2 <SearchBox />
3 <RefinementList attribute="categories.handle" />
4 <Hits />
5 <Pagination />
6</InstantSearch>

It forwards ///// // and wraps the route's response as , so the full InstantSearch widget ecosystem (search, autocomplete, faceting, pagination) works unchanged. Pass a custom for SSR or extra headers.

If you prefer not to add the package as a storefront dependency, inline the same ~15-line client — it just POSTs to .


Admin UI

Settings → Algolia renders:

  • Connection health (green/red) with the Algolia error if unreachable.
  • Index name, Batch size, Storefront URL.
  • Reindex all products button (background full reindex).
  • Re-sync a single product input (per-product retry).

Production hardening

  • Retry / backoff — every Algolia call retries transient failures (network, HTTP 5xx, 429) with bounded exponential backoff + jitter (, default 3); other 4xx fail immediately. Sits above the Algolia client's own host-failover retry.
  • Structured logging — every operation emits one parseable line through the Medusa logger (no ):
    [algolia] plugin=algolia trace=ab12cd34 op=full_reindex entity=product index=products count=540 duration_ms=12030 status=success
    Fields: , , , , , , (////), and on failure + . Retries log .
  • Never-throw subscribers — a failed sync is caught and logged; it never propagates into Medusa's request/event lifecycle (a failed sync can't 500 a product save).
  • Batch safety — is chunked to Algolia's 1000/call cap; / auto-chunk; large fan-outs are processed in chunks; an out-of-range from HTTP input is rejected.
  • Compensation — / snapshot prior index state and restore it if the surrounding workflow fails.

Operations runbook

Initial setup (pre-flight)

  • / (write) / set
  • Plugin built (, or for a local link) and registered in
  • Backend starts with no error
  • Settings → Algolia shows Connected
  • "Reindex all products" populates the index (); twice → no duplicates
  • (Storefront) wired; CORS + publishable key allow the store route

When to reindex

After installing/reconfiguring, after changing index-setting options, after bulk stock edits, or after category/collection deletions (see limitations).

Monitoring

Watch for lines (they carry the product id, error, and ) and bursts (transient Algolia issues). The full reindex is the catch-all repair.


Troubleshooting

SymptomCause / fix
Backend can't resolve after run the package manager install () in the backend after , and confirm the plugin was built ( builds first) — only is published
at startupa required env var is missing/empty — intended fail-fast
/ you used a Search-Only key — switch to a write/admin key
Connection Unreachable, "Index products does not exist"the index is created on the first successful write — fix the key, then reindex
(storefront)storefront still points at MeiliSearch — swap its (see storefront integration)
No "Algolia" in Settings / routes 404plugin not built, or env not set, so it isn't registered
Store search returns Algolia module not registered (missing env)
Code changes don't take effectrebuild + restart (; with a yalc link, propagates to the backend), or use watch mode
(Next 15)storefront tech debt (/ are async) — unrelated to Algolia

Module API reference

Public exports from :

Module & service — , , . Service methods:

MethodDescription
Resolve an index type to its configured name
Upsert records (objectID upsert; auto-chunked)
Fetch records by id (chunked snapshot)
Delete records by id
Search; returns the Algolia
Apply configured-or-default index settings
Connectivity probe (never throws)
Map products with the configured mapper/context

Options — , , , , , and the constants.

Mapping — , , .

Workflows / steps — , , , .

Resolvers — , , , , .

Events — namespace (all subscribed event-name constants).

Storefront — (subpath: ).


Testing

1pnpm typecheck # tsc --noEmit (module + admin)
2pnpm test # 79 unit tests across 9 suites
3pnpm build # medusa plugin:build → .medusa/server

Unit suites cover options validation, the mapper (incl. availability, currency, metafields), resolvers (mocked Query), retry policy, index settings, the search route handler, the storefront client, and service construction/configurable mapping.

Test the live module directly with (resolves the service from the container — no HTTP, no events):

1// <your-backend>/src/scripts/test-algolia.ts
2import { syncProductsWorkflow } from "medusa-plugin-algolia-plus"
3export default async function ({ container }) {
4 const algolia: any = container.resolve("algolia")
5 console.log(await algolia.healthCheck())
6 await algolia.indexData([{ objectID: "test_1", id: "test_1", title: "Hi" }])
7 console.log((await algolia.search("Hi")).hits)
8 await algolia.deleteFromIndex(["test_1"])
9 const { result } = await syncProductsWorkflow(container).run({ input: {} })
10 console.log(result)
11}
cd <your-backend> && npx medusa exec ./src/scripts/test-algolia.ts

Package structure

1src/
2├─ index.ts # public barrel export
3├─ modules/algolia/
4│ ├─ index.ts # Module(ALGOLIA_MODULE, { service })
5│ ├─ service.ts # AlgoliaModuleService (only algoliasearch importer)
6│ ├─ options.ts # Zod options, defaults, index-settings resolver
7│ ├─ types.ts # record & mapper types
8│ ├─ events.ts # version-pinned event names
9│ ├─ logger.ts # structured logging
10│ ├─ retry.ts # bounded backoff + jitter
11│ ├─ mappers/product.ts # default mapper + PRODUCT_SYNC_FIELDS + availability
12│ └─ resolvers/ # dependent-change → product-id resolvers
13├─ workflows/ # sync + delete workflows and steps (with compensation)
14├─ subscribers/ # product / variant / category / collection / order / reindex
15├─ api/
16│ ├─ admin/algolia/ # sync (GET/POST) + products/[id] (POST)
17│ ├─ store/products/search/ # search route + Zod validators
18│ └─ middlewares.ts # store search body validation
19├─ admin/routes/algolia/page.tsx # Settings → Algolia UI
20└─ storefront/create-search-client.ts # shipped InstantSearch client

Medusa version compatibility

Pinned to Medusa 2.13.5. Event names and Query-graph paths are version-sensitive and centralized:

  • Subscribed events live in — change them there if a future Medusa version renames events.
  • The module uses workflow events (, …), not module-prefixed events (), to avoid double-processing.
  • The fetched product fields are in .

The module declares no database models, so there is no migration to run.


Roadmap / deferred

  • Persistent failed-sync log + a per-product retry queue (today: stateless per-product re-sync + the full reindex as the repair primitive).
  • A scheduled (cron) full reindex for automatic staleness repair.
  • Additional index types (categories, collections) — and are already open for extension.

Еще в этой категории

Посмотреть все
Поиск
MeiliSearch logo

MeiliSearch

От Rokmohar

Open-source поисковый движок для вашей витрины

Загрузка данных
GitHubnpm
Поиск
MeiliSearch logo

MeiliSearch

От Vymalo

Подключите быстрый поиск с MeiliSearch

Загрузка данных
GitHubnpm
Поиск
Relewise logo

Relewise

От Relewise

Прокачайте поиск по товарам с Relewise

Загрузка данных
npm