ekaii fork: instant Jellyfin search via Meilisearch pre-bind middleware (base arnesacnussem 1.11.1.15)
  • C# 68.9%
  • HTML 31.1%
Find a file
exo 572bd5f32e search: cap results at 50/request to bound worst-case latency
Common single words (love/the/star) match ~800 items; materializing that many DTOs
(~9ms each for audio) took 5-10s. The search page only shows a scroll-row per section,
so the top-50 by relevance per request is plenty. Cap => common terms drop from 5-10s to
50-620ms; gibberish stays ~5-100ms (0 hits -> empty-fast); richness preserved (Artists/
Persons/Items are separate requests, each capped, so every section stays populated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-26 09:39:07 +02:00
install-plugin ekaii fork: instant search via pre-bind middleware 2026-08-26 02:01:35 +02:00
src search: cap results at 50/request to bound worst-case latency 2026-08-26 09:39:07 +02:00
.gitignore ekaii fork: instant search via pre-bind middleware 2026-08-26 02:01:35 +02:00
Jellyfin.Plugin.Meilisearch.sln ekaii fork: instant search via pre-bind middleware 2026-08-26 02:01:35 +02:00
LICENSE ekaii fork: instant search via pre-bind middleware 2026-08-26 02:01:35 +02:00
README.md search: also rewrite /Artists & /Persons, empty-fast on 0 hits, passthrough live-TV 2026-08-26 09:28:01 +02:00

jellyfin-plugin-meilisearch (ekaii fork)

Fork of arnesacnussem/jellyfin-plugin-meilisearch @ tag 1.11.1.15 (Jellyfin 10.11.x, net9.0). Upstream MIT LICENSE preserved.

What this fork changes

The upstream plugin decorated IItemRepository and rewrote searchTerm searches to an id list, but the request still went through Jellyfin's recursive, per-user Folder.GetItems path (~0.5s per query on large libraries; Meilisearch itself answers in ~2ms). On the cine.ekaii.fr library (~230k items) a web search fans out ~8 of those in parallel, so search felt like 1-2s.

This fork replaces the repository decorator with a tiny ASP.NET middleware (SearchRewriteMiddleware, wired via an IStartupFilter) that runs before MVC binding: for GET .../Items?searchTerm=… it asks Meilisearch for the ordered id set and rewrites the request to GET .../Items?ids=… (dropping searchTerm/recursive/includeItemTypes/sort so the native ids path runs — ~10-50ms, and it preserves the supplied id order, i.e. Meilisearch relevance). Typo tolerance and graceful fallback to native search (Meili down / no hits) are kept. Measured on prod: movie/series/episode/person searches ~15-45ms (was ~500ms); music ~200-250ms (inherent audio DTO cost; was ~850ms).

Same plugin GUID as upstream (974395db-…) so it upgrades in place.


Update (2026-08-26): covers the whole search page

The Jellyfin web search page does NOT use /Search/Hints; it fans out /Items?searchTerm= (a few, by type), /Artists?searchTerm= and /Persons?searchTerm= in parallel. The middleware now also rewrites /Artists and /Persons (path → /Items, since those controllers ignore ids=), returns an empty result fast (non-existent id) when Meili has no match instead of falling back to the slow native scan, and leaves live-TV/EPG-only queries to native (Meili doesn't index EPG). Verified in a real browser against the live library: every section (movies/episodes/people/songs) renders correctly and server-side latency dropped from ~0.3-1.2s per request to ~8-130ms.


Meilisearch Plugin for Jellyfin

A plugin for Jellyfin that improves search by utilizing Meilisearch as a search engine. Search logic is offloaded to a Meilisearch instance, and the response from Jellyfin is modified

Improved:

Note

As long as your client uses /Items endpoint for search, it should be supported seamlessly I guess

Inspired by JellySearch.


Setup instructions

  1. Setup a Meilisearch instance (maybe a hosted one in the cloud will also work, but I don't recommend)

    • Docker is recommended. Example docker-compose.yml:
      services:
         meilisearch:
           container_name: meilisearch
           image: getmeili/meilisearch:v1.34 # older versions may have compatibility issues
           restart: unless-stopped
      
           environment:
             MEILI_ENV: production
             MEILI_NO_ANALYTICS: "true"
             MEILI_MASTER_KEY: super-secret-key
      
           volumes:
             # meilisearch's data
             - ./data:/meili_data
      
           ports:
             - 7700:7700
      
  2. Install the Meilisearch plugin

    • In Jellyfin:
      1. Add the plugin Repository:
        https://raw.githubusercontent.com/arnesacnussem/jellyfin-plugin-meilisearch/refs/heads/master/manifest.json
        
      2. Install the Meilisearch plugin
      3. Restart Jellyfin Server
  3. Configure the Meilisearch plugin

    • In Meilisearch plugin's page:
      1. Meilisearch URL: URL to your Meilisearch instance, as seen by Jellyfin (example: http://meilisearch:7700)
      2. Meilisearch Api Key: API key to access your Meilisearch instance (if required) (example: super-secret-key)
      3. Click Save
      4. The plugin's page should show a healthy status
        • Example:
          {
              "meilisearch": "Server: available",
              "meilisearchOk": true,
              "averageSearchTime": "0ms",
              "indexStatus": {
                "Database": "Data Source=/config/data/jellyfin.db;Cache=Default;Default Timeout=30;Pooling=True",
                "Items": "20569",
                "LastIndexed": "1/28/2026 4:10:01 PM"
              }
            }
          

Note

You can also set the environment variables in Jellyfin, to configure the plugin without editing the Jellyfin UI: MEILI_URL and MEILI_MASTER_KEY

Note

If you want share one Meilisearch instance across multiple Jellyfin instance, you can fill the Meilisearch Index Name, if leaving empty, it will use the server name.

  1. Test Meilisearch plugin search

    1. Try Jellyfin search
    2. Issues? Check Jellyfin's logs and Meilisearch's logs

Index will update on following events:

  • Server start
  • Configuration change
  • Library scan complete
  • Update index task being triggered

How it works

The core feature, which is to mutate the search request, is done by injecting an ActionFilter. So it may only support a few versions of Jellyfin. At the moment I'm using Jellyfin 10.11.0, but it should work on other versions as long as the required parameter name of /Items endpoint doesn't change.


I've seen JellySearch, which is a wonderful project, but I don't really like setting up a reverse proxy or any of that hassle.

So I am writing this, but it still requires a Meilisearch instance.

At this moment, only the /Items endpoint is affected by this plugin, but it still improves a lot on my 200k items library.