mrkeyoor.com_
Tue 22 Sept 22:36 UTC
npmDataupdated 22 Sept 2026

lunr review

Lunr 2.3.9 builds a full-text index over JavaScript objects and returns ranked document references for matching terms. It supports field restrictions, field and term boosts, wildcards, fuzzy edit distance, and required or prohibited terms. The index stays in memory and can be serialized, which makes Lunr useful for offline documentation and other static collections. It does not store the original records or update a finished index in place. There is no newer current-version behavior to report: npm 2.3.9 was published in August 2020, and the repository has not been pushed since July 2024.

Verdict

Lunr still fits prebuilt offline search over a modest, mostly static corpus. New work that needs updates, TypeScript packaging, or product-search features should start elsewhere.

We installed it

Lab card: what happened when we installed lunrScreenshot of lunr documentation
Install✓ · 0.7s1 package on disk · 2 MB
ImportESM import works · require() works · CommonJS package
Browser8.6 KBgzipped (30.1 KB minified), bundled with esbuild
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does lunr install cleanly?

Yes. In a fresh container with an empty cache, npm install lunr finished in 0.7s, leaving 1 package and 2 MB on disk. npm audit reported no known vulnerabilities.

How much does lunr add to a browser bundle?

8.6 KB gzipped (30.1 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does lunr work with both ESM and CommonJS?

Yes. Both import 'lunr' and require('lunr') worked in Node 22 in our run. The package is published as CommonJS.

Does lunr include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

lunr or minisearch: which should you use?

minisearch: Use it for client-side search that needs document additions, removals, autocomplete, and fuzzy matching. Lunr still fits prebuilt offline search over a modest, mostly static corpus.

When should you not use lunr?

Documents change while the application is running. A built Lunr index has no normal add or remove workflow, so updates require a rebuild.

API stability5/5The builder callback, ref and field declarations, search grammar, programmatic query API, result objects, pipeline functions, and serialized index format have remained unchanged for years. Existing integrations are unlikely to face surprise releases because npm has stayed at 2.3.9 since August 2020. That is operational stability, though it comes from a frozen release line rather than an active compatibility program.
Docs4/5lunrjs.com documents indexing, searching, scoring, query syntax, customization, language plugins, and prebuilt indexes. Important traps are stated plainly: bare terms use OR, unknown fields cause errors, leading wildcards cost more, and wildcard terms skip stemming. The examples use older JavaScript conventions and do not solve current TypeScript or package-export questions, which leaves modern build setup to community knowledge.
Maintenance2/5npm does not mark the package deprecated, and GitHub still lists the repository as active. Version 2.3.9 was published in August 2020, however, and the last recorded repository push was July 31, 2024. GitHub now shows 9,200 stars and 130 open issues and pull requests. Users can reasonably trust the mature code they test today; quick fixes, new module packaging, and active browser-platform work are unlikely.
Ecosystem4/5npm counted 7,425,565 downloads in the latest completed week, and the repository has 9,200 stars. Lunr appears in static-site search implementations, supports custom pipeline functions, and has language plugins beyond core English. Its published package has no dependencies, but also no bundled types or export map. Teams on strict TypeScript and ESM stacks may need community declarations and interop configuration.

Use it if

  • A static documentation or help corpus needs search that continues working without a server or network connection.
  • You can build the index during deployment, ship it as an asset, and retain a separate map of source records.
  • BM25 ranking, field boosts, fuzzy terms, and a compact query language cover the search experience you need.
  • The collection is small enough for its serialized index and lookup structures to live in each user's memory.
Skip it if

Setup reality

We installed lunr 2.3.9 in a clean Node 22 Bookworm container. npm completed in 0.7 seconds, left one package on disk, and used 2 MB. Lunr has no direct or peer dependencies and is 1192 KB unpacked. npm audit found no known vulnerabilities. It is CommonJS with no exports map; require and ESM import both worked in our test. No TypeScript declarations were present. Our browser bundle was 30.1 KB minified and 8.6 KB gzipped.

Define a unique ref field and every searchable field before adding documents. Lunr keeps references, scores, and match metadata, while your application keeps titles, URLs, excerpts, and permissions. A typo in a fielded query can throw because the named field must exist. Bare terms are optional and combine like OR; prefixes such as plus and minus change term presence. Wildcards bypass stemming, and a leading wildcard can be expensive.

Building on page load blocks the main thread as the corpus grows. The documented alternative is to build under Node, serialize the index with JSON.stringify, compress it for transport, and load it with lunr.Index.load. This removes browser build work but still costs download, JSON parse time, and resident memory. Rebuild whenever documents, fields, boosts, or pipeline functions change.

English stemming and stop words ship in core. Other languages use plugins such as lunr-languages, loaded in the required order before the index is built. A serialized index depends on registered pipeline functions being available when it is loaded. Treat user text as data through the programmatic query API if punctuation should not invoke Lunr's query grammar.

Patterns

Index a document array build-index

const lunr = require('lunr')

const documents = [
  { id: '1', title: 'Moonlight', body: 'Light reflected by the moon' },
  { id: '2', title: 'Sunrise', body: 'The sun above the horizon' },
]
const index = lunr(function () {
  this.ref('id')
  this.field('title')
  this.field('body')
  documents.forEach((doc) => this.add(doc))
})

The ref value must be unique. Register every searchable field before adding documents.

Join references to source records resolve-results

const records = new Map(documents.map((doc) => [doc.id, doc]))
const matches = index.search('moon').map(({ ref, score, matchData }) => ({
  record: records.get(ref),
  score,
  matchData,
}))

Lunr results do not contain the original object. Keep records in a separate map, store, or database.

Weight title matches higher boost-title-field

const index = lunr(function () {
  this.ref('id')
  this.field('title', { boost: 8 })
  this.field('body')
  documents.forEach((doc) => this.add(doc))
})

An index-time field boost affects every search. Use query boosts when importance changes by screen or request.

Restrict a term to the title search-field

const results = index.search('title:moon')

An unknown field name raises a query error. Do not insert arbitrary user-selected field names without validation.

Match a term prefix search-prefix

const results = index.search('astro*')

Wildcard terms are not stemmed. Leading wildcards such as *astro are slower because more vocabulary must be inspected.

Allow one edit search-fuzzy

const results = index.search('javascript~1')

Higher edit distances widen matches and cost more. Short queries can become noisy even at a distance of one.

Require two terms and reject one require-terms

const results = index.search('+javascript +testing -browser')

Several bare terms use OR. Prefix each required term with plus when every term must occur.

Raise one query term's score boost-term

const results = index.search('javascript^10 testing')

The boost changes ranking. It does not make the other bare term mandatory.

Avoid parsing user punctuation query-programmatically

const results = index.query((query) => {
  query.term(userTerm, {
    fields: ['title'],
    presence: lunr.Query.presence.REQUIRED,
    boost: 5,
  })
  query.term('legacy', { presence: lunr.Query.presence.PROHIBITED })
})

The query object gives code direct control over fields and presence without treating user punctuation as syntax.

Write an index during the build serialize-index

const fs = require('node:fs')
fs.writeFileSync(
  'public/search-index.json',
  JSON.stringify(index),
)

Recreate this artifact after any document, field, boost, or pipeline change. Keep source records separately.

Load a prebuilt browser index load-index

import lunr from 'lunr'

const data = await fetch('/search-index.json').then((response) => response.json())
const index = lunr.Index.load(data)
const results = index.search('offline')

Prebuilding removes indexing work from the page, but download, parse, and in-memory costs remain.

Install the French pipeline index-french

const lunr = require('lunr')
require('lunr-languages/lunr.stemmer.support')(lunr)
require('lunr-languages/lunr.fr')(lunr)

const index = lunr(function () {
  this.use(lunr.fr)
  this.ref('id')
  this.field('text')
  documents.forEach((doc) => this.add(doc))
})

Install lunr-languages separately and load stemmer support before the language plugin.

Alternatives

PackageRegistryPick it when
minisearchnpmUse it for client-side search that needs document additions, removals, autocomplete, and fuzzy matching.
flexsearchnpmUse it when browser indexing and lookup speed matter more than Lunr's small, familiar API.
fuse.jsnpmUse it for fuzzy matching over a modest array without maintaining a full-text index.

More data guides

numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.