# strapi-plugin-elasticsearch **Repository Path**: emonupg/strapi-plugin-elasticsearch ## Basic Information - **Project Name**: strapi-plugin-elasticsearch - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-12 - **Last Updated**: 2026-03-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Strapi plugin strapi-plugin-elasticsearch A plugin to enable integrating Elasticsearch with Strapi CMS. ## Installation via npm: ``` npm i @geeky-biz/strapi-plugin-elasticsearch ``` via yarn: ``` yarn add @geeky-biz/strapi-plugin-elasticsearch ``` ## Supported Strapi version The latest version of this plugin works for Strapi v5. For Strapi v4, please install the version 0.0.8 of this plugin. ## Supported ElasticSearch version The plugin is expected to work with ElasticSearch v8. It may or may not function as expected with ElasticSearch v7 or v9. Please share your findings if you have tried the plugin with these ElasticSearch versions. ## Plugin Configuration Within your Strapi project's `config/plugin.js`, enable the plugin and provide the configuration details: ``` module.exports = { // ... 'elasticsearch': { enabled: true, config: { indexingCronSchedule: "", searchConnector: { host: "", username: "", password: "", certificate: "" }, indexingPerformance: { batchSize: 100, relationFetchConcurrency: 6, bulkRetries: 2 }, indexAliasName: "" } }, // ... } ``` Example plugin configuration (with adequate `.env` variables set up): ``` module.exports = { // ... 'elasticsearch': { enabled: true, config: { indexingCronSchedule: "00 23 * * *", //run daily at 11:00 PM searchConnector: { host: process.env.ELASTIC_HOST, username: process.env.ELASTIC_USERNAME, password: process.env.ELASTIC_PASSWORD, certificate: path.join(__dirname, process.env.ELASTIC_CERT_NAME) }, indexingPerformance: { batchSize: Number(process.env.ELASTIC_BATCH_SIZE || 100), relationFetchConcurrency: Number(process.env.ELASTIC_RELATION_FETCH_CONCURRENCY || 6), bulkRetries: Number(process.env.ELASTIC_BULK_RETRIES || 2) }, indexAliasName: process.env.ELASTIC_ALIAS_NAME } }, // ... } ``` ### Performance tuning for large datasets Use `indexingPerformance` to increase throughput while controlling memory usage: - `batchSize`: number of records fetched per DB page and sent in one bulk request. - `relationFetchConcurrency`: max concurrent relation fetches when relation indexing is enabled. - `bulkRetries`: retry count for transient ES bulk failures. Recommended starting point for large datasets on single-node setups: ```js indexingPerformance: { batchSize: 200, relationFetchConcurrency: 8, bulkRetries: 3, } ``` Increase gradually and monitor Strapi memory + Elasticsearch disk/CPU. ## Ensuring connection to Elasticsearch When connected to Elasticsearch, the `Connected` field within the `Setup Information` screen shall display `true`. ![image](https://github.com/geeky-biz/strapi-plugin-elasticsearch/assets/17068206/a0fe0d6c-95e9-4c3c-95e1-46209db113c7) ## Configuring collections & attributes to be indexed The `Configure Collections` view displays the collections and the fields setup to be indexed. ![image](https://github.com/geeky-biz/strapi-plugin-elasticsearch/assets/17068206/13ad3a24-02a6-4c86-8da2-e015ba9c18ea) From this view, individual collection can be selected to modify configuration: ![image](https://github.com/geeky-biz/strapi-plugin-elasticsearch/assets/17068206/bdc1d674-a74f-4534-9b48-ad0e0eebaeea) ## Configuring indexing for dynamic zone or component attributes To enable indexing content for attributes of type `component` or `dynamiczone`, additional information needs to be provided via JSON in the following format: ``` { "subfields": [ { "component": "", "field": "" }, {...}, {...} ] } ``` ### Example 1: If we have an attribute called `seo_details` of type `component` like the following within our collection `schema.json`: ``` "seo_details": { "type": "component", "repeatable": false, "component": "metainfo.seo" }, ``` And, if we seek to index the contents of the `meta_description` field belonging to the component `seo`, our `subfields` configuration should be: ``` { "subfields": [ { "component": "metainfo.seo", "field": "meta_description" } ] } ``` ![image](https://github.com/geeky-biz/strapi-plugin-elasticsearch/assets/17068206/df1f7dba-2aa1-410e-a567-1de73156a020) ### Example 2: If we have an attribute called `sections` of type `dynamiczone` within our collection `schema.json`: ``` "sections": { "type": "dynamiczone", "components": [ "content.footer", "content.paragraph", "content.separator", "content.heading" ] }, ... ``` And, if we seek to index the contents of the fields `title` for `content.heading` and `details` as well as `subtext` for `content.paragraph`, our `subfields` configuration should be: ``` { "subfields": [ { "component": "content.paragraph", "field": "details" }, { "component": "content.paragraph", "field": "subtext" }, { "component": "content.heading", "field": "title" } ] } ``` The subfields JSON also supports multiple level of nesting: ``` { "subfields": [ { "component": "content.footer", "field": "footer_link", "subfields": [ { "component": "content.link", "field": "display_text" } ] } ] } ``` Note: Indexing of `relations` attributes is supported when the relation field is enabled in collection configuration. During rebuild, relation data is loaded per entry to avoid oversized relation queries. ## Indexing of metadata fields By default, the plugin excludes indexing the following metadata fields: ``` ['createdAt', 'createdBy', 'publishedAt', 'publishedBy', 'updatedAt', 'updatedBy'] ``` This means, these metadata fields do not appear in the `Configure Collections` view to set them up for indexing. To allow indexing these fields, `allowIndexingMetadataFields : true` needs to be specified in the plugin configuration. ``` module.exports = { // ... 'elasticsearch': { enabled: true, config: { // ... allowIndexingMetadataFields: true, } }, // ... } ``` With the above plugin configuration, the metadata fields become available to be selected for indexing. ## Transforming data before indexing To modify the data stored within Strapi before it is submitted by the plugin for indexing, transformer functions can be setup. 1. Define all the transformer functions within `src/extensions/elasticsearch/strapi-server.ts` as exhibited in the following example (it is vital for the transformer function to return the transformed data): ```JavaScript export default (plugin) => { plugin.config.transformers = { capitalize: (value: string) => { return value.charAt(0).toUpperCase() + value.slice(1); }, renderMarkdown: (value: string) => { return markdownToTxt(value); }, roundFloatValue: (value: number) => { return Number(value.toFixed(2)); } }; return plugin; } ``` 2. Once defined, a drop-down to select the transformer function shall be available with the list of function names (as in the screenshot below). Select the transformer function for specific fields. ![image](https://github.com/user-attachments/assets/fa21c228-1d2f-48cc-b6d3-c921bf6a0b5e) 3. With the above setup in place: - Your transformer function will execute every time before submitting the data for indexing. - The data returned from the transformer function will be submitted for indexing. ## Transforming the full document before indexing If you need to add new Elasticsearch fields derived from one or more source fields, you can define document-level transformers. 1. Register document-level transformers in `src/extensions/elasticsearch/strapi-server.ts`: ```JavaScript export default (plugin) => { plugin.config.documentTransformers = { valueDateToRange: ({ document }) => { const raw = document.value_date; if (!raw || typeof raw !== 'string') { return document; } const normalized = raw.replace(/\s+/g, ''); const singleMatch = normalized.match(/^(\d{4})$/); const slashMatch = normalized.match(/^(\d{4})\/(\d{4})$/); const rangeMatch = normalized.match(/^(\d{4})-(\d{4})$/); if (singleMatch) { const year = Number(singleMatch[1]); return { ...document, value_date_start: year, value_date_end: year }; } if (slashMatch || rangeMatch) { const match = slashMatch || rangeMatch; const start = Number(match[1]); const end = Number(match[2]); if (Number.isFinite(start) && Number.isFinite(end)) { return { ...document, value_date_start: start, value_date_end: end }; } } return document; }, }; return plugin; } ``` 2. In `Configure Collection` -> `Collection Settings`, select a `Document transformer`. 3. The selected function runs once after field extraction and can modify the complete document before it is written to Elasticsearch. ## Exporting and Importing indexing configuration To enable backing up the indexing configuration or transferring it between various environments, these can be Exported / Imported from the `Configure Collections` view. ![image](https://github.com/geeky-biz/strapi-plugin-elasticsearch/assets/17068206/6e099392-499e-4101-8f51-85b7eff8aa38) ## Scheduling Indexing Once the collection attributes are configured for indexing, any changes to the respective collections & attributes is marked for indexing. The cron job (configured via `indexingCronSchedule`) makes actual indexing requests to the connected Elasticsearch instance. - `Trigger Indexing` triggers the cron job immediately to perform the pending indexing tasks without waiting for the next scheduled run. - `Rebuild Indexing` completely rebuilds the index. It may be used if the Elasticsearch appears to be out of sync from the data within Strapi. ![image](https://github.com/geeky-biz/strapi-plugin-elasticsearch/assets/17068206/71df02a9-8513-4a91-8e23-2b5f34495c20) Whenever a collection is configured for indexing, it may already have data that needs to be indexed. To facilitate indexing of the past data, a collection can be scheduled for indexing in the next cron run from the `Configure Collections` view: ![image](https://github.com/geeky-biz/strapi-plugin-elasticsearch/assets/17068206/7f37453a-dc87-406a-8de0-0391018b7fb5) ### Per-Collection Rebuild The plugin supports rebuilding indices on a per-collection basis. Each collection is indexed into its own separate Elasticsearch index with automatic versioning and alias management. **Key Features:** - **Separate Indices**: Each content type gets its own index (e.g., `strapi-api-article_v000001`) - **Per-Collection Rebuild**: Rebuild button available on each collection's configuration page - **Validation**: Automatic validation after rebuild with document count and sample checks - **Alias Management**: Each collection has its own alias for zero-downtime updates - **Global Search**: A global search alias aggregates all collection indices for cross-collection search **To rebuild a specific collection:** 1. Navigate to the collection's configuration page 2. Click "Rebuild This Collection" button 3. Confirm the rebuild 4. View validation results showing document count and sample checks **Index Naming Convention:** - Collection Index: `strapi-{collection-short-name}_v{version}` (e.g., `strapi-api-article_v000001`) - Collection Alias: `strapi-alias-{collection-short-name}` (e.g., `strapi-alias-api-article`) - Global Search Alias: `strapi-search-all` ### Triggering Indexing Externally To trigger indexing from an external event: - From `Users & Permissions Plugin` -> `Roles` -> Select a Role -> `ElasticSearch`, select `triggerIndexing` - Now, for that role, invoking the route `/api/elasticsearch/trigger-indexing/` shall trigger indexing to index all the data that is pending to be indexed (same behavior as that when clicking the `Trigger Indexing` button from the Strapi Admin UI Elasticsearch Plugin screen). ## Searching You may directly use the Elasticsearch search API or you may use the Search API exposed by the plugin. The plugin provides two search endpoints: ### Global Search (All Collections) Search across all indexed collections using `/api/elasticsearch/search`: ``` /api/elasticsearch/search?query=query%5Bbool%5D%5Bshould%5D%5B0%5D%5Bmatch%5D%5Bcity%5D=atlanta&page=1&pageSize=10 ``` This searches the global alias that includes all collection indices. ### Content-Type Specific Search Search within a specific collection using `/api/elasticsearch/search/:collectionname`: ``` /api/elasticsearch/search/api::article.article?query=query%5Bmatch%5D%5Btitle%5D=news&page=2&pageSize=10 ``` This searches only the specified collection's index, providing faster and more targeted results. The plugin search API is a wrapper around the Elasticsearch search API that passes the query parameter to Elasticsearch and returns a normalized response. Pagination inputs: - `page`: page number, starts from `1` - `pageSize`: optional, defaults to `10` - Backward compatible: `from` + `size` still work if `page` is not provided - `track_total_hits`: optional. Defaults to `true` for accurate total count beyond 10k. When `page` is provided, the plugin computes `from = (page - 1) * pageSize` internally. Response format: ```json { "data": [{ "...": "..." }], "meta": { "pagination": { "page": 1, "pageSize": 10, "pageCount": 3, "total": 24 }, "took": 7 } } ``` Example query format: ``` query[bool][should][0][match][city]=atlanta ``` Note: To use the `search` API, you must provide access via `Settings` -> `Users & Permissions Plugin` -> `Roles` -> (Select adequate role) -> `Elasticsearch` -> `search`. ### Extending Search API The recommended way to enhance the Search API is to write your own route and controller. Below is an example of how this can be achieved (for custom behavior beyond the built-in `data` + `meta.pagination` response): - Within your setup, create `src/extensions/elasticsearch/strapi-server.js` with the following contents: ``` const { Client } = require('@elastic/elasticsearch') const qs = require('qs'); let client = null; module.exports = (plugin) => { client = new Client({ node: plugin.config.searchConnector.host, auth: { username: plugin.config.searchConnector.username, password: plugin.config.searchConnector.password }, tls: { ca: plugin.config.searchConnector.certificate, rejectUnauthorized: false } }); plugin.controllers['performSearch'].enhancedSearch = async (ctx) => { try { const params = qs.parse(ctx.request.query) const query = params.search; const pagesize = params.pagesize; const from = params.from; const result= await client.search({ index: plugin.config.indexAliasName, query: { "bool" : { "should" : [ { "match": { "content": "dummy"} } ] } }, size: pagesize, from: from }); return result; } catch(err) { console.log('Search : elasticClient.enhancedSearch : Error encountered while making a search request to ElasticSearch.') throw err; } } plugin.routes['search'].routes.push({ method: 'GET', path: '/enhanced-search', handler: 'performSearch.enhancedSearch', }); return plugin; }; ``` - This will create a new route `/api/elasticsearch/enhanced-search` being served by the function defined above. - You can add / modify the routes and controllers as necessary. ## Bugs For any bugs, please create an issue [here](https://github.com/geeky-biz/strapi-plugin-elasticsearch/issues). ## About - This plugin is created by [Punit Sethi](https://punits.dev). - I'm an independent developer working on Strapi migrations, customizations, configuration projects (see [here](https://punits.dev/strapi-customizations/)). - For any Strapi implementation requirement, write to me at `punit@tezify.com`.