npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

strapi-plugin-fuzzy-search

v4.1.0

Published

Register a weighted fuzzy search endpoint for Strapi Headless CMS you can add your content types to in no time.

Readme

Strapi-plugin-fuzzy-search

All Contributors

Github CI Npm release Npm monthly downloads License Coverage

Register a weighted fuzzy search endpoint for Strapi Headless CMS you can add your content types to in no time.

Uses fuzzysort under the hood: Simple, quick and easy. No need to worry about setting up an instance for a complex search engine.

Table of Contents

Roadmap 🏗️

  • ✅ Return indices/highlights of matches (via includeMatches config)
  • Improve response performance
  • Include option to hide unpublished content by default
  • Allow single types as content types
  • Pass configuration as query params/args
    • Configure fuzzysort through params/args per content type

Requirements

Strapi Version >= v5.1.1

For compatibility with older Strapi versions please use older Versions of this package (<= 1.11.2 and <= 2.0.3)

Installation

Enable the fuzzy-search plugin in the ./config/plugins.js of your Strapi project.

Make sure to set the appropriate permissions for the search route in the Permissions tab of the Users & Permission Plugin for the role to be able to access the search route.

Options/Config

Mandatory settings are marked with *.

General Options

The plugin requires several configurations to be set in the .config/plugins.js file of your Strapi project to work.

| Key | Type | Notes                                               | | -------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | contentTypes* | Array of Objects | List the content types you want to register for fuzzysort. Each object requires the uid: string and modelName: string to be set for a content type | | includeMatches | boolean | Global setting to include search metadata (score and match indexes) in the response. Can be overridden per content type. Default: false. | | transliterate | boolean | If this is set to true the search will additionally run against transliterated versions of the content for the keys specified in the keys array for a given content type. E.g. 你好 will match for ni hao. Note that activating this feature for a content type comes at a performance cost and may increase the response time. |

IMPORTANT: Please note that as of now, only collectionTypes are supported as contentTypes.

Fuzzysort Options

The fuzzysortOptions allow for some finetuning of fuzzysorts searching algorithm to your needs.

| Key | Type | Notes | | -------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | characterLimit | int (positive) | Limits the length of characters the algorithm is searching through for any string of the content type | | threshold | float (positive) | Sets the threshold for the score of the entries that will be returned. The lower, the "fuzzier" the results. | | limit | int (positive) | Limits the amount of entries returned from the search | | keys* | array of objects | Lists the fields of the models the algorithm should search (name: string) and a factor to weight them by weight: float. The higher the weight, the higher a match for a given field will be evaluated for a content type. |

Full Example config

module.exports = ({ env }) => ({
  // ...

  "fuzzy-search": {
    enabled: true,
    config: {
      // Global setting (optional, default: false)
      includeMatches: true,
      contentTypes: [
        {
          uid: "api::author.author",
          modelName: "author",
          transliterate: true,
          // Override global includeMatches for this content type (optional)
          includeMatches: false,
          fuzzysortOptions: {
            characterLimit: 300,
            threshold: 0.6,
            limit: 10,
            keys: [
              {
                name: "name",
                weight: 0.1,
              },
              {
                name: "description",
                weight: -0.1,
              },
            ],
          },
        },
        {
          uid: "api::book.book",
          modelName: "book",
          fuzzysortOptions: {
            characterLimit: 500,
            keys: [
              {
                name: "title",
                weight: 0.2,
              },
              {
                name: "description",
                weight: -0.2,
              },
            ],
          },
        },
      ],
    },
  },

  // ...
});

A note on performance:

A high characterLimit, limit, a low threshold (the lower the value the more matches) as well as setting transliterate: true all hamper the performance of the search algorithm. We recommend that you start out with a characterLimit: 500, threshold: 0.5, limit: 15 and work your way from there. The characterLimit especially can be quite delicate, so make sure to test every scenario when dialing in it's value.

Usage

Basic Search

Hitting the /api/fuzzy-search/search?query=<your-query-string> will return an array of matched entries for each content type registered in the config. If no match could be found an empty array will be returned. The endpoint accepts an optional locale=<your-locale> query as well.

Alternatively (and if the graphql plugin is installed), a search query is registered that accepts query: String! and locale: String (optional) as arguments. If set, the locale parameter will be used as a default for all queried content types. If you'd rather filter the content types by local on an individual level, pass the locale to the content type as a filter parameter.

IMPORTANT: Please note that in order to query for the locale of the content types via the locale parameter, localization must be enabled for the content types.

Example Requests

REST

await fetch(`${API_URL}/api/fuzzy-search/search?query=deresh&locale=en`);
// GET /api/fuzzy-search/search?query=deresh&locale=en

GraphQl

query {
  search(query: "deresh", locale: "en") {
    authors {
      data {
        attributes {
          name
        }
      }
    }
    books {
      data {
        attributes {
          title
          description
        }
      }
    }
  }
}

Example Responses

IMPORTANT: Please note that as of now published as well as unpublished entries will be returned by default. Modify this behavior by passing a filter to the query.

REST

{
  "authors": [
    {
      "id": 1,
      "name": "Любко Дереш",
      "description": "As an author, Lyubko has had somewhat of a cult-like following among the younger generation in Ukraine since the appearance of his novel Cult at age eighteen, which was followed almost immediately by the publication of another novel, written in early high school.",
      "createdAt": "2022-05-05T13:08:19.312Z",
      "updatedAt": "2022-05-05T13:34:46.488Z",
      "publishedAt": "2022-05-05T13:22:17.310Z"
    }
  ],
  "books": [
    {
      "id": 1,
      "title": "Jacob's Head",
      "description": "Jacob’s Head by Lyubko Deresh is scheduled to be adapted into a movie in Ukraine in the near future.",
      "createdAt": "2022-05-05T13:08:43.816Z",
      "updatedAt": "2022-05-05T13:24:07.107Z",
      "publishedAt": "2022-05-05T13:22:23.764Z"
    }
  ]
}

GraphQl

{
  "data": {
    "search": {
      "authors": {
        "data": [
          {
            "attributes": {
              "name": "Любко Дереш"
            }
          }
        ]
      },
      "books": {
        "data": [
          {
            "attributes": {
              "title": "Jacob's Head",
              "description": "Jacob’s Head by Lyubko Deresh is scheduled to be adapted into a movie in Ukraine in the near future."
            }
          }
        ]
      }
    }
  }
}

Search Metadata

When includeMatches is enabled (either globally or per content type), each result will include a searchMeta object containing:

  • score: The weighted overall match score (higher is better)
  • matches: Per-field match information with score and indexes for each searchable field

The indexes array contains the character positions that matched in the original string, following fuzzysort's native format. This allows you to implement custom highlighting on the client side.

Example Response with Search Metadata

REST:

{
  "authors": [
    {
      "id": 1,
      "name": "Любко Дереш",
      "description": "As an author, Lyubko has had somewhat of a cult-like following...",
      "searchMeta": {
        "score": 0.95,
        "matches": {
          "name": {
            "score": 0.95,
            "indexes": [8, 9, 10, 11, 12]
          },
          "description": {
            "score": null,
            "indexes": null
          }
        }
      }
    }
  ]
}

GraphQL:

query {
  search(query: "deresh") {
    authors {
      data {
        attributes {
          name
        }
        searchMeta {
          score
          matches
        }
      }
    }
  }
}

Note: If a field doesn't match the query, its score and indexes will be null.

Pagination

REST

The endpoint accepts query parameters in line with Strapis pagination by page parameters. The difference being that the pagination is scoped for the content types individually.

Important: Please note that if pagination parameters are passed for a content type, it's data will now be available under the key data, whereas the pagination meta data can be found under the key meta.

| Parameter | Type | Description | Default | | ------------------------------------ | ------- | ------------------------------------------------------------------------- | ------- | | pagination[myContentType][page] | Integer | Page number | 1 | | pagination[myContentType][pageSize] | Integer | Page size | 25 | | pagination[myContentType][withCount] | Boolean | Adds the total numbers of entries and the number of pages to the response | True |

Please note that [myContentType] needs to be in the plural form. For example, for the content type "author," the correct parameter is "authors".

Request:

await fetch(`${API_URL}/api/fuzzy-search/search?query=deresh&pagination[authors][pageSize]=2&pagination[authors][page]=3`);
// GET /api/fuzzy-search/search?query=deresh&pagination[authors][pageSize]=2&pagination[authors][page]=3

Response:

{
  "authors": [
    // ...
  ],
  "books": {
    "data": [
      // ...
    ],
    "meta": {
      "pagination": {
        "page": 3,
        "pageSize": 2,
        "total": 6,
        "pageCount": 3
      }
    }
  }
}

GraphQL

The endpoint accepts pagination arguments in line with Strapis pagination by page and pagination by offset parameters.

| Parameter | Description | Default | | -------------------- | ---------------------------- | ------- | | pagination[page] | Page number | 1 | | pagination[pageSize] | Page size | 10 | | pagination[start] | Start value | 0 | | pagination[limit] | Number of entities to return | 10 |

IMPORTANT: Please note that in line with Strapis defaults, pagination methods can not be mixed. Always use either page with pageSize or start with limit.

Request:

search(query: "deresh") {
  books(pagination: { page: 3, pageSize: 2 }) {
    data {
      attributes {
        title
      }
    }
    meta {
      pagination {
        page
        pageSize
        pageCount
        total
      }
    }
  }
}

Response:

{
  "data": {
    "search": {
      "books": {
        "data": [
          // ...
        ],
        "meta": {
          "pagination": {
            "page": 3,
            "pageSize": 2,
            "pageCount": 3,
            "total": 6
          }
        }
      }
    }
  }
}

Population

REST

The endpoint accepts query parameters in line with Strapis populate parameters. The difference being that the population is scoped for the content types individually.

| Parameter | Type | Description | | ------------------------------------ | ------- | ------------------------------------------------------------------------- | | population[myContentType] | String/PopulationParams | Either pass a string for the relation to populate directly, or build more complex population queries in line with the official documentation on population like in the example below. |

Request:

await fetch(`${API_URL}/api/fuzzy-search/search?query=deresh&populate[books][author][populate][0]=city`);
// GET /api/fuzzy-search/search?query=deresh&populate[books][author][populate][0]=city

Response:

{
  "authors": [
    // ...
  ],
  "books": [
    {
      "id": 1,
      "title": "A good book",
      "description": "Written by Lyubko Deresh",
      "createdAt": "2022-09-21T16:16:07.365Z",
      "updatedAt": "2023-07-24T12:00:51.624Z",
      "publishedAt": "2022-09-21T16:16:09.973Z",
      "locale": "en",
      "author": {
        "id": 2,
        "name": "Любко Дереш",
        "description": "A poet, novelist, essayist, and translator.",
        "createdAt": "2022-09-24T12:39:34.669Z",
        "updatedAt": "2023-11-10T12:28:22.828Z",
        "publishedAt": "2022-09-24T12:39:53.945Z",
        "locale": "en",
        "city": {
          "id": 1,
          "name": "Pustomyty",
          "createdAt": "2023-11-10T12:26:29.981Z",
          "updatedAt": "2023-11-10T12:26:32.254Z",
          "publishedAt": "2023-11-10T12:26:32.252Z"
        }
      }
    }
  ]
}

GraphQL

See Strapis official GraphQL documentation for queries.

Filters

REST

The endpoint accepts query parameters in line with Strapis filter parameters parameters. The difference being that the filters are scoped for the content types individually.

Request:

await fetch(`${API_URL}/api/fuzzy-search/search?query=deresh&filters[books][publishedAt][$notNull]=true`);
// GET /api/fuzzy-search/search?query=deresh&filters[books][publishedAt][$notNull]=true

Response:

{
  "authors": [
    // ...
  ],
  "books": {
    "data": [
      {
        "id": 3,
        "title": "A good book",
        "description": "Written by Lyubko Deresh",
        "createdAt": "2023-02-27T08:11:11.771Z",
        "updatedAt": "2023-02-27T08:11:12.208Z",
        "publishedAt": "2023-02-27T08:11:12.207Z",
        "locale": "en"
      }
    ]
  }
}

GraphQL

The endpoint accepts filter arguments in line with Strapis filters parameter .

Request:

search(query: "deresh") {
  books(filters: { publishedAt: { notNull: true } }) {
    data {
      attributes {
        title
      }
    }
  }
}

Response:

{
  "data": {
    "search": {
      "books": {
        "data": [
          {
            "attributes": {
              "title": "A good book"
            }
          }
        ]
      }
    }
  }
}

Filter by Content Type (REST)

The REST-endpoint accepts an optional parameter to select only some content types the fuzzy search should run for.

| Parameter | Type | Description | | --------------------- | ------ | --------------------------------------------------------------- | | filters[contentTypes] | string | Comma seperated list of the content types to run the search for |

Request:

await fetch(`${API_URL}/api/fuzzy-search/search?query=deresh&filters[contentTypes]=books,authors`);
// GET /api/fuzzy-search/search?query=deresh&filters[contentTypes]=books

Response:

{
  "authors": [
    // ...
  ],
  "books": [
    // ...
  ]
}

Why use fuzzysort and not something like Fuse.js?

While Fuse.js proofs to be an amazing library, it can yield unexpected results and what is to be perceived as "false positives" (by a human) when searching through longer strings. Fuzzysort aims to solve this problem by introducing the evaluation and scoring of exact matches. Since we had issues with Fuse.js and it's underlying algorithm, we opted for fuzzysearch to do the heavy lifting instead.

Contributing

Feel free to contribute, PRs are always welcome!

Please see the CONTRIBUTING.md for reference.

Found a bug?

If you found a bug or have any questions please submit an issue. If you think you found a way how to fix it, please feel free to create a pull request!

Contributors ✨

Thanks goes to these wonderful people (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome!