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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@muhgholy/search-query-parser

v3.0.0

Published

A portable, framework-agnostic search query parser with Gmail-like syntax support

Readme

Search Query Parser

npm version CI License: MIT

A portable, framework-agnostic search query parser with Gmail-like syntax support. Zero dependencies, TypeScript-first, and optimized for performance.

Features

  • 🚀 Zero dependencies - Lightweight and fast
  • 📝 Gmail-like syntax - Familiar search operators
  • 🔧 Framework-agnostic - Works anywhere (Node.js, browser, edge)
  • 📦 TypeScript-first - Full type safety
  • Optimized - Returns simple array for easy iteration
  • 🎯 Extensible - Custom operators support

Installation

npm install @muhgholy/search-query-parser
yarn add @muhgholy/search-query-parser
pnpm add @muhgholy/search-query-parser

Usage

import { parse } from "@muhgholy/search-query-parser";

const terms = parse('"Promo Code" -spam from:newsletter after:-7d');

// Returns: TParsedTerm[]
// [
//   { type: 'phrase', value: 'Promo Code', negated: false },
//   { type: 'text', value: 'spam', negated: true },
//   { type: 'from', value: 'newsletter', negated: false },
//   { type: 'after', value: '-7d', negated: false, date: Date }
// ]

// Simple iteration
for (const term of terms) {
	switch (term.type) {
		case "text":
		case "phrase":
			// Handle text search
			break;
		case "from":
			// Handle from filter
			break;
		case "after":
			if (term.date) {
				// Use resolved date
			}
			break;
		case "or":
			// Handle OR logic
			// term.terms contains the operands
			break;
		case "group":
			// Handle group
			// term.terms contains the inner terms
			break;
	}
}

Supported Syntax

Text Search

| Syntax | Description | Example | | ----------- | ------------------ | --------------------- | | word | Plain text search | hello | | "phrase" | Exact phrase match | "hello world" | | -word | Exclude term | -spam | | -"phrase" | Exclude phrase | -"unsubscribe here" | | ( ... ) | Grouping | (term1 term2) | | OR | Logical OR | term1 OR term2 |

Lists & Arrays

Comma-separated values are automatically treated as OR conditions.

| Syntax | Description | Example | | ---------------- | --------------------- | ---------------------- | | key:val1,val2 | Value 1 OR Value 2 | to:john,jane | | key:"a","b" | Quoted list | to:"John Doe","Jane" | | -key:val1,val2 | NOT val1 AND NOT val2 | -from:spam,marketing |

Operators

| Operator | Aliases | Description | Example | | ----------- | ------------------ | ----------------- | ------------------------- | | from: | f:, sender: | From address/name | from:[email protected] | | to: | t:, recipient: | To address/name | to:jane | | subject: | subj:, s: | Subject line | subject:meeting | | body: | content:, b: | Body content | body:invoice | | has: | - | Has property | has:attachment | | is: | - | Status filter | is:unread | | in: | folder:, box: | Folder/mailbox | in:inbox | | label: | tag:, l: | Label/tag | label:important | | header-k: | hk: | Header key | header-k:X-Custom | | header-v: | hv: | Header value | header-v:"custom value" | | date: | d: | Date/Range | date:2024-01-01 | | before: | b4:, older: | Before date | before:2024-12-31 | | after: | af:, newer: | After date | after:2024-01-01 | | size: | larger:, smaller: | Size filter | size:>1mb |

Date Filters

| Syntax | Description | Example | | ------------------- | ---------------------- | ---------------------------- | | date:YYYY-MM-DD | Specific date | date:2024-01-01 | | date:Start-End | Date range | date:2024-01-01-2024-12-31 | | after:YYYY-MM-DD | After date (absolute) | after:2024-01-01 | | before:YYYY-MM-DD | Before date (absolute) | before:2024-12-31 | | after:-Nd | After N days ago | after:-7d | | after:-Nh | After N hours ago | after:-24h | | after:-Nw | After N weeks ago | after:-2w | | after:-Nm | After N months ago | after:-1m | | after:-Ny | After N years ago | after:-1y | | after:"natural" | Natural language | after:"last week" |

Supported natural dates: today, yesterday, tomorrow, last week, last month, last year, this week, this month, this year

Size Filters

| Syntax | Description | Example | | --------- | -------------------- | ------------- | | size:>N | Larger than N bytes | size:>1mb | | size:<N | Smaller than N bytes | size:<100kb | | size:N | Equal to N bytes | size:500 |

Supported units: b, kb, mb, gb

API Reference

parse(input: string, options?: TParserOptions): TParseResult

Parse a search query string into an array of terms.

const terms = parse('"hello world" from:john -spam', {
	operatorsAllowed: ["from", "to"], // Only allow specific operators
	// OR
	operatorsDisallowed: ["size"], // Block specific operators
	// Custom operators
	operators: [{ name: "priority", aliases: ["p"], type: "string", allowNegation: true }],
});

tokenize(input: string): TToken[]

Low-level tokenizer for custom parsing needs.

const tokens = tokenize('from:john "hello world"');

parseDate(value: string): { date: Date } | null

Parse date strings (absolute, relative, natural).

parseDate("-7d"); // { date: Date (7 days ago) }
parseDate("2024-01-01"); // { date: Date }
parseDate("last week"); // { date: Date }

escapeRegex(str: string): string

Escape special regex characters.

escapeRegex("hello.*world"); // 'hello\\.\\*world'

validate(input: string): { valid: boolean; errors: string[] }

Validate search query syntax.

validate('"unclosed quote'); // { valid: false, errors: ['Unmatched quote: "'] }

hasTerms(input: string): boolean

Check if search string has any terms.

hasTerms(""); // false
hasTerms("hello"); // true

summarize(input: string): string[]

Get human-readable summary of search query.

summarize('"Promo" from:newsletter after:-7d');
// ['Exact: "Promo"', 'From: newsletter', 'After: 12/6/2024']

Types

type TDefaultTermType =
	| "text" // Plain text
	| "phrase" // Exact phrase
	| "from" // From filter
	| "to" // To filter
	| "subject" // Subject filter
	| "body" // Body filter
	| "header-k" // Header key
	| "header-v" // Header value
	| "has" // Has property
	| "is" // Status filter
	| "in" // Folder filter
	| "before" // Before date
	| "after" // After date
	| "label" // Label filter
	| "size" // Size filter
	| "or" // Logical OR
	| "group"; // Parenthesized group

type TTermType = TDefaultTermType;

type TParsedTerm<T extends string = TTermType> = {
	type: T;
	value: string;
	negated: boolean;
	date?: Date; // Resolved date (for date types)
	dateRange?: {
		// Resolved date range
		start: Date;
		end: Date;
	};
	size?: {
		// Parsed size (for size type)
		op: "gt" | "lt" | "eq";
		bytes: number;
	};
	terms?: TParsedTerm<T>[]; // For 'or' and 'group' types
};

type TParseResult<T extends string = TTermType> = TParsedTerm<T>[];

type TOperatorDef<T extends string = TTermType> = {
	name: string; // Operator name (becomes term type)
	aliases: string[]; // Alternative names
	type: "string" | "date" | "size"; // Value parsing type
	allowNegation: boolean; // Whether negation is allowed
};

type TParserOptions<T extends string = TTermType> = {
	operators?: TOperatorDef<T>[];
	caseSensitive?: boolean;
	operatorsAllowed?: string[];
	operatorsDisallowed?: string[];
};

Examples

MongoDB Integration

import { parse, escapeRegex } from "@muhgholy/search-query-parser";

function buildMongoQuery(searchQuery: string) {
	const terms = parse(searchQuery);
	const conditions = [];

	for (const term of terms) {
		const regex = { $regex: escapeRegex(term.value), $options: "i" };

		switch (term.type) {
			case "text":
			case "phrase":
				conditions.push({
					$or: [{ title: term.negated ? { $not: regex } : regex }, { content: term.negated ? { $not: regex } : regex }],
				});
				break;
			case "from":
				conditions.push({ "from.email": regex });
				break;
			case "after":
				if (term.date) {
					conditions.push({ createdAt: { $gte: term.date } });
				}
				break;
		}
	}

	return conditions.length ? { $and: conditions } : {};
}

SQL Integration

import { parse, escapeRegex } from "@muhgholy/search-query-parser";

function buildSQLWhere(searchQuery: string) {
	const terms = parse(searchQuery);
	const clauses = [];
	const params = [];

	for (const term of terms) {
		switch (term.type) {
			case "text":
				clauses.push(term.negated ? "(title NOT LIKE ? AND content NOT LIKE ?)" : "(title LIKE ? OR content LIKE ?)");
				params.push(`%${term.value}%`, `%${term.value}%`);
				break;
			case "after":
				if (term.date) {
					clauses.push("created_at >= ?");
					params.push(term.date.toISOString());
				}
				break;
		}
	}

	return { where: clauses.join(" AND "), params };
}

License

MIT © Muhammad Gholy