Simple, expected, and deterministic best-match sorting of an array in JavaScript
npx @tessl/cli install tessl/npm-match-sorter@8.1.0match-sorter provides simple, expected, and deterministic best-match sorting of arrays in JavaScript. It implements an intelligent ranking algorithm that sorts items based on multiple match criteria including exact matches, prefix matches, substring matches, acronym matches, and fuzzy matching.
npm install match-sorter// Main function and utilities
import { matchSorter, rankings, defaultBaseSortFn } from "match-sorter";
// Import with types (TypeScript)
import {
matchSorter,
rankings,
defaultBaseSortFn,
type MatchSorterOptions,
type KeyAttributesOptions,
type KeyOption,
type KeyAttributes,
type RankingInfo,
type ValueGetterKey
} from "match-sorter";For CommonJS:
const { matchSorter, rankings, defaultBaseSortFn } = require("match-sorter");import { matchSorter } from "match-sorter";
// Simple string array matching
const items = ["apple", "banana", "grape", "orange"];
const results = matchSorter(items, "ap");
// Returns: ["apple"] (starts with "ap")
// Object matching with keys
const users = [
{ name: "Sarah Connor", email: "sarah@example.com" },
{ name: "John Connor", email: "john@example.com" },
{ name: "Kyle Reese", email: "kyle@example.com" }
];
const matches = matchSorter(users, "connor", { keys: ["name"] });
// Returns: [{ name: "Sarah Connor", ... }, { name: "John Connor", ... }]match-sorter uses a hierarchical ranking system that evaluates matches based on quality:
The primary function for filtering and sorting arrays based on string matching.
/**
* Takes an array of items and a value and returns a new array with the items that match the given value
* @param items - the items to sort
* @param value - the value to use for ranking
* @param options - configuration options for the sorter
* @returns the new filtered and sorted array
*/
function matchSorter<ItemType = string>(
items: ReadonlyArray<ItemType>,
value: string,
options?: MatchSorterOptions<ItemType>
): Array<ItemType>;
/** Rankings constants are also available as a property on the matchSorter function */
matchSorter.rankings: typeof rankings;Usage Examples:
// Simple string matching
const fruits = ["apple", "apricot", "banana"];
matchSorter(fruits, "ap");
// Returns: ["apple", "apricot"] (both start with "ap")
// Case sensitivity and ranking
const items = ["Apple", "apple", "apply"];
matchSorter(items, "apple");
// Returns: ["apple", "Apple", "apply"] (exact match first, then case-insensitive, then starts-with)
// No matches below threshold
matchSorter(["hello", "world"], "xyz");
// Returns: [] (no items match "xyz")
// Using rankings via matchSorter property
const exactOnly = matchSorter(items, "search", {
threshold: matchSorter.rankings.EQUAL
});Predefined ranking values for different match quality levels.
const rankings: {
readonly CASE_SENSITIVE_EQUAL: 7;
readonly EQUAL: 6;
readonly STARTS_WITH: 5;
readonly WORD_STARTS_WITH: 4;
readonly CONTAINS: 3;
readonly ACRONYM: 2;
readonly MATCHES: 1;
readonly NO_MATCH: 0;
};
type Ranking = typeof rankings[keyof typeof rankings];Ranking Quality Levels:
CASE_SENSITIVE_EQUAL (7): Exact match with same caseEQUAL (6): Exact match ignoring caseSTARTS_WITH (5): String starts with search termWORD_STARTS_WITH (4): A word in the string starts with search termCONTAINS (3): String contains search termACRONYM (2): String's acronym matches search term (first letters of words and hyphen-separated parts)MATCHES (1): Fuzzy match based on character proximityNO_MATCH (0): No match foundUsage Examples:
import { matchSorter, rankings } from "match-sorter";
// Set minimum threshold to only return exact matches
const exactMatches = matchSorter(items, "search", {
threshold: rankings.EQUAL
});
// Custom threshold per key
const users = [{ name: "John", bio: "Developer" }];
const results = matchSorter(users, "dev", {
keys: [
{ key: "name", threshold: rankings.STARTS_WITH },
{ key: "bio", threshold: rankings.CONTAINS }
]
});
// Demonstrate ranking order
const cities = ["New York", "newark", "NEWARK", "Denver"];
matchSorter(cities, "newark");
// Returns: ["NEWARK", "newark", "New York"]
// (case-sensitive first, then case-insensitive, then starts-with)
// Acronym matching example
const phrases = ["New York City", "National Youth Corp", "Not Your Cat"];
matchSorter(phrases, "nyc");
// Returns: ["New York City", "National Youth Corp", "Not Your Cat"]
// (all match the acronym "nyc")Default sorting function used for tie-breaking when items have equal rankings.
/**
* Default base sorting function for tie-breaking
* Uses locale-aware string comparison on rankedValue
* @param a - first ranked item to compare
* @param b - second ranked item to compare
* @returns comparison result (-1, 0, 1)
*/
const defaultBaseSortFn: BaseSorter<unknown> = (a, b) =>
String(a.rankedValue).localeCompare(String(b.rankedValue));The MatchSorterOptions interface provides comprehensive configuration for customizing match behavior. See the Types section for complete interface definitions.
Usage Examples:
// Object key matching
const books = [
{ title: "JavaScript Guide", author: "John Doe" },
{ title: "TypeScript Handbook", author: "Jane Smith" }
];
// Search multiple keys
matchSorter(books, "script", { keys: ["title", "author"] });
// Matches both books on title
// Nested property matching
const users = [
{ profile: { name: "Alice", bio: "Developer" } },
{ profile: { name: "Bob", bio: "Designer" } }
];
matchSorter(users, "alice", { keys: ["profile.name"] });
// Custom value getter
matchSorter(users, "dev", {
keys: [(user) => [user.profile.name, user.profile.bio].join(" ")]
});
// Key-specific configuration
matchSorter(books, "doe", {
keys: [
{ key: "title", threshold: rankings.STARTS_WITH },
{ key: "author", threshold: rankings.CONTAINS, maxRanking: rankings.EQUAL }
]
});
// Preserve diacritics
const names = ["José", "Jose", "María"];
matchSorter(names, "jose", { keepDiacritics: true });
// Returns: ["Jose"] (exact match only)
matchSorter(names, "jose", { keepDiacritics: false });
// Returns: ["Jose", "José"] (diacritics ignored)
// Custom sorter for reverse order
matchSorter(items, "search", {
sorter: (rankedItems) => rankedItems.sort((a, b) => b.rank - a.rank)
});
// Custom base sort for tie-breaking
matchSorter(items, "search", {
baseSort: (a, b) => a.index - b.index // Preserve original order
});Support for complex nested property access patterns.
Nested Object Properties:
const data = [
{ user: { profile: { name: "Alice" } } },
{ user: { profile: { name: "Bob" } } }
];
matchSorter(data, "alice", { keys: ["user.profile.name"] });Array Index Access:
const data = [
{ tags: ["javascript", "web"] },
{ tags: ["python", "data"] }
];
matchSorter(data, "web", { keys: ["tags.1"] }); // Search second tagWildcard Array Access:
const data = [
{ tags: ["red", "blue", "green"] },
{ tags: ["yellow", "purple"] }
];
matchSorter(data, "blue", { keys: ["tags.*"] }); // Search all array elementsAll types exported by match-sorter for TypeScript usage:
interface MatchSorterOptions<ItemType = unknown> {
/** Array of keys to search when items are objects */
keys?: ReadonlyArray<KeyOption<ItemType>>;
/** Minimum ranking threshold for inclusion in results */
threshold?: Ranking;
/** Custom base sort function for tie-breaking */
baseSort?: BaseSorter<ItemType>;
/** Whether to preserve diacritics in string comparison */
keepDiacritics?: boolean;
/** Custom sorter function to replace default sorting logic */
sorter?: Sorter<ItemType>;
}
interface KeyAttributesOptions<ItemType> {
/** Property key or custom getter function */
key?: string | ValueGetterKey<ItemType>;
/** Minimum ranking threshold for this key */
threshold?: Ranking;
/** Maximum ranking this key can achieve */
maxRanking?: Ranking;
/** Minimum ranking this key can achieve */
minRanking?: Ranking;
}
type KeyOption<ItemType> =
| KeyAttributesOptions<ItemType>
| ValueGetterKey<ItemType>
| string;
interface KeyAttributes {
/** Minimum ranking threshold for this key */
threshold?: Ranking;
/** Maximum ranking this key can achieve */
maxRanking: Ranking;
/** Minimum ranking this key can achieve */
minRanking: Ranking;
}
interface RankingInfo {
/** The string value that was ranked */
rankedValue: string;
/** The ranking score achieved */
rank: Ranking;
/** Index of the key that produced this ranking */
keyIndex: number;
/** Threshold setting for the matched key */
keyThreshold: Ranking | undefined;
}
interface ValueGetterKey<ItemType> {
/** Custom function to extract searchable values from items */
(item: ItemType): string | Array<string>;
}
interface BaseSorter<ItemType> {
/** Custom base sorting function for tie-breaking */
(a: RankedItem<ItemType>, b: RankedItem<ItemType>): number;
}
interface Sorter<ItemType> {
/** Custom sorter function that replaces default sorting logic */
(matchItems: Array<RankedItem<ItemType>>): Array<RankedItem<ItemType>>;
}
interface RankedItem<ItemType> extends RankingInfo {
/** The original item from the input array */
item: ItemType;
/** Original index of the item in the input array */
index: number;
}