Meilisearch plugin for Medusa 2
This plugin integrates MeiliSearch with your Medusa e-commerce store and adds support for internationalization (i18n) of your product catalog.
Run the following command to install the plugin with npm:
npm install --save @rokmohar/medusa-plugin-meilisearch
Or with yarn:
yarn add @rokmohar/medusa-plugin-meilisearch
This step is required only if you are upgrading from previous version to v1.0.
This plugin is only for MedusaJS v2.4.0 or newer.
If you are using MedusaJS v2.3.1 or older, please use the older version of this plugin.
Add the plugin to your file:
import { loadEnv, defineConfig } from '@medusajs/framework/utils'import { MeilisearchPluginOptions } from '@rokmohar/medusa-plugin-meilisearch'loadEnv(process.env.NODE_ENV || 'development', process.cwd())module.exports = defineConfig({// ... other configplugins: [// ... other plugins{resolve: '@rokmohar/medusa-plugin-meilisearch',options: {config: {host: process.env.MEILISEARCH_HOST ?? '',apiKey: process.env.MEILISEARCH_API_KEY ?? '',},settings: {// The key is used as the index name in Meilisearchproducts: {// Required: Index typetype: 'products',// Optional: Whether the index is enabled. When disabled:// - Index won't be created or updated// - Documents won't be added or removed// - Index won't be included in searches// - All operations will be silently skippedenabled: true,// Optional: Specify which fields to include in the index// If not specified, all fields will be includedfields: ['id', 'title', 'description', 'handle', 'variant_sku', 'thumbnail'],indexSettings: {searchableAttributes: ['title', 'description', 'variant_sku'],displayedAttributes: ['id', 'handle', 'title', 'description', 'variant_sku', 'thumbnail'],filterableAttributes: ['id', 'handle'],},primaryKey: 'id',// Create your own transformer/*transformer: (product) => ({id: product.id,// other attributes...}),*/},categories: {// Required: Index typetype: 'categories',// Optional: Whether the index is enabledenabled: true,// Optional: Specify which fields to include in the index// If not specified, all fields will be includedfields: ['id', 'name', 'description', 'handle', 'is_active', 'parent_id'],indexSettings: {searchableAttributes: ['name', 'description'],displayedAttributes: ['id', 'name', 'description', 'handle', 'is_active', 'parent_id'],filterableAttributes: ['id', 'handle', 'is_active', 'parent_id'],},primaryKey: 'id',// Create your own transformer/*transformer: (category) => ({id: category.id,name: category.name,// other attributes...}),*/},},i18n: {// Choose one of the following strategies:// 1. Separate index per language// strategy: 'separate-index',// languages: ['en', 'fr', 'de'],// defaultLanguage: 'en',// 2. Language-specific fields with suffixstrategy: 'field-suffix',languages: ['en', 'fr', 'de'],defaultLanguage: 'en',translatableFields: ['title', 'description'],},} satisfies MeilisearchPluginOptions,},],})
Important: Product events and background tasks will not work if your Medusa instance is running in mode, because the server instance does not process subscribers or background jobs.
Depending on your setup:
Monolithic architecture (only one backend instance):
Ensure you do not set the or environment variable. By default, Medusa will use mode, which supports both background processing and serving HTTP requests from the same instance.
Split architecture (separate server and worker instances):
Follow the official Medusa documentation on worker mode.
In this case, you must add this plugin in the worker instance, as the server instance does not handle event subscribers or background tasks.
The plugin supports two main strategies for handling translations, with flexible configuration options for each.
{i18n: {// Choose strategy: 'separate-index' or 'field-suffix'strategy: 'field-suffix',// List of supported languageslanguages: ['en', 'fr', 'de'],// Default language to fall back todefaultLanguage: 'en',// Optional: List of translatable fieldstranslatableFields: ['title', 'description', 'handle']}}
You can provide detailed configuration for each translatable field:
{i18n: {strategy: 'field-suffix',languages: ['en', 'fr', 'de'],defaultLanguage: 'en',translatableFields: [// Simple field name'title',// Field with different target name{source: 'description',target: 'content' // Will be indexed as content_en, content_fr, etc.},// Field with transformation{source: 'handle',transform: (value) => value.toLowerCase().replace(/\s+/g, '-')}]}}
The plugin provides a flexible way to transform your products with custom translations. Instead of relying on specific storage formats, you can provide translations directly to the transformer:
import { transformProduct } from '@rokmohar/medusa-plugin-meilisearch'const getProductTranslations = async (productId: string) => {// Example: fetch from your translation service/databasereturn {title: [{ language_code: 'en', value: 'Blue T-Shirt' },{ language_code: 'fr', value: 'T-Shirt Bleu' },],description: [{ language_code: 'en', value: 'A comfortable blue t-shirt' },{ language_code: 'fr', value: 'Un t-shirt bleu confortable' },],}}// Example usage in your custom transformerconst customTransformer = async (product, options) => {const translations = await getProductTranslations(product.id)return transformProduct(product, {...options,translations,})}
For production environments, you'll often want to integrate with external translation management platforms. Here's an example of integrating with Tolgee, a popular translations management platform:
import { default as axios } from 'axios'import { logger } from '@medusajs/framework'import { TranslationMap } from '@rokmohar/medusa-plugin-meilisearch'type TranslationsHttpResponse = {[lang: string]: {[id: string]: Record<string, string>}}const options = {apiKey: process.env.TOLGEE_API_KEY ?? '',baseURL: process.env.TOLGEE_API_URL ?? '',projectId: process.env.TOLGEE_PROJECT_ID ?? '',}const httpClient = axios.create({baseURL: `${options.baseURL}/v2/projects/${options.projectId}`,headers: {Accept: 'application/json','X-API-Key': options.apiKey,},maxBodyLength: Infinity,})export const getTranslations = async (id: string, langs: string[]) => {const translations: TranslationMap = {}try {const response = await httpClient.get<TranslationsHttpResponse>(`/translations/${langs.join(',')}?ns=${id}`)Object.entries(response.data).forEach(([language_code, { [id]: values }]) => {Object.entries(values).forEach(([key, value]) => {if (!(key in translations)) {translations[key] = []}translations[key].push({ language_code, value })})})} catch (e) {logger.error(e)}return translations}
Usage in your transformer configuration:
{settings: {products: {type: 'products',// ... other configtransformer: async (product, defaultTransformer, options) => {const translations = await getTranslations(product.id, ['sl', 'en'])return defaultTransformer(product, {...options,translations,includeAllTranslations: true})},}}}
This integration fetches translations from Tolgee's API and transforms them into the format expected by this plugin. For complete storefront translation management, consider using the medusa-plugin-tolgee by SteelRazor47, which provides comprehensive translation management features including admin widgets.
This strategy creates a separate MeiliSearch index for each language. For example, if your base index is named "products", it will create:
Benefits:
This strategy adds language suffixes to translatable fields in the same index. For example:
Benefits:
If no translatable fields are specified and using the field-suffix strategy, the plugin will automatically detect string fields as translatable. You can override this by explicitly specifying the fields:
{i18n: {strategy: 'field-suffix',languages: ['en', 'fr'],defaultLanguage: 'en',// Only these fields will be translatabletranslatableFields: ['title', 'description']}}
GET /store/meilisearch/products-hits
Query Parameters:
GET /store/meilisearch/products
Query Parameters:
This plugin provides full support for MedusaJS v2 categories, including:
{settings: {categories: {type: 'categories',enabled: true,fields: ['id', 'name', 'description', 'handle', 'is_active', 'parent_id'],indexSettings: {searchableAttributes: ['name', 'description'],displayedAttributes: ['id', 'name', 'description', 'handle', 'is_active', 'parent_id'],filterableAttributes: ['id', 'handle', 'is_active', 'parent_id'],},primaryKey: 'id',},},i18n: {strategy: 'field-suffix',languages: ['en', 'fr', 'de'],defaultLanguage: 'en',translatableFields: ['name', 'description'], // Category-specific translatable fields},}
GET /store/meilisearch/categories-hits
Query Parameters:
GET /store/meilisearch/categories
Query Parameters:
This plugin supports AI-powered semantic search using vector embeddings. See docs/semantic-search.md for detailed configuration and usage instructions.
Add the environment variables to your and file:
# ... others varsMEILISEARCH_HOST=MEILISEARCH_API_KEY=
If you want to use with the from this README, use the following values:
# ... others varsMEILISEARCH_HOST=http://127.0.0.1:7700MEILISEARCH_API_KEY=ms
You can add the following configuration for Meilisearch to your :
services:# ... other servicesmeilisearch:image: getmeili/meilisearch:latestports:- '7700:7700'volumes:- ~/data.ms:/data.msenvironment:- MEILI_MASTER_KEY=mshealthcheck:test: ['CMD', 'curl', '-f', 'http://localhost:7700']interval: 10stimeout: 5sretries: 5
You can find instructions on how to add search to a Medusa NextJS starter inside the nextjs folder.
Feel free to open issues and pull requests!