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

nano-string-utils

v0.28.1

Published

Modern string utilities with zero dependencies. Tree-shakeable (<1KB each), TypeScript-first, type-safe. Validation, XSS prevention, case conversion, fuzzy matching & more.

Readme

Abstract illustration: a tangle of chaotic grey threads enters a small precision mechanism and leaves as evenly spaced blue and green ribbons

nano-string-utils is a modern string utility library for JavaScript and TypeScript: case conversion (camelCase, kebabCase, snake_case, PascalCase), slugify, truncate, email and URL validation, HTML escaping and XSS sanitization, fuzzy matching, Levenshtein distance, pluralization, Unicode and emoji-safe helpers, and more. It has no runtime dependencies, ships ESM and CommonJS, and every function is independently importable — so a lodash or underscore.string replacement costs you bytes, not kilobytes.

Works in Node.js, Deno, Bun, browsers, and edge runtimes.

Interactive playground and API docs → · Migrating from lodash →

Why nano-string-utils

  • Nothing you didn't ask for. Zero runtime dependencies means zero transitive supply-chain surface. The whole library is 9.6 KB brotlied; a typical import is a few hundred bytes.
  • Real tree-shaking. Every function lives in its own module and is marked side-effect free, so bundlers drop what you don't reference.
  • Types that do work. Branded types make a validated Email or URL unforgeable, and template literal types compute exact return values at compile time.
  • Null-safe by default. Almost every function takes null or undefined and passes it straight through instead of throwing. See Null safety for the four that don't.
  • Unicode-aware where it counts. graphemes, codePoints, reverse, and toASCII handle emoji, combining marks, and surrogate pairs correctly.
  • Batteries for real apps. XSS sanitization, PII redaction, fuzzy search scoring, and sentence splitting are in the box, not in three more dependencies.
  • 1,686 tests across 69 files, run in CI on Node 20, 22, and 24.

Diagram: importing slugify, camelCase and isEmail ships 595 bytes gzipped, while the other 58 functions in the library contribute zero bytes because they are never referenced

Install

npm install nano-string-utils
pnpm add nano-string-utils
yarn add nano-string-utils
bun add nano-string-utils
# Deno, from JSR
deno add jsr:@zheruel/nano-string-utils
<!-- ES modules -->
<script type="module">
  import {
    slugify,
    camelCase,
  } from "https://unpkg.com/nano-string-utils/dist/index.js";

  slugify("Hello World"); // 'hello-world'
</script>

<!-- Or a global build -->
<script src="https://unpkg.com/nano-string-utils/dist/index.iife.js"></script>
<script>
  nanoStringUtils.slugify("Hello World!"); // 'hello-world'
</script>

Quick start

import {
  slugify,
  camelCase,
  truncate,
  isEmail,
  fuzzyMatch,
  sanitize,
} from "nano-string-utils";

// Reshape
slugify("Hello World!"); // 'hello-world'
camelCase("hello-world"); // 'helloWorld'
truncate("Long text here", 10); // 'Long te...'

// Validate
isEmail("[email protected]"); // true
isEmail("invalid.email"); // false

// Search
fuzzyMatch("gto", "goToLine"); // { matched: true, score: 0.546 }
fuzzyMatch("abc", "xyz"); // null

// Make safe
sanitize("<script>alert('xss')</script>Hello"); // 'Hello'
sanitize("<b>Bold</b> text", { allowedTags: ["b"] }); // '<b>Bold</b> text'

// Null-safe, always
slugify(null); // null — no throw

The 61 functions

Every function links to a live, editable example in the playground. Sizes are gzipped, measured per function with the rest of the library tree-shaken away.

Case conversion

| Function | What it does | Gzipped | | --- | --- | --- | | camelCase | Convert to camelCase | 235 B | | pascalCase | Convert to PascalCase | 222 B | | kebabCase | Convert to kebab-case | 201 B | | snakeCase | Convert to snake_case | 200 B | | constantCase | Convert to CONSTANT_CASE | 228 B | | dotCase | Convert to dot.case | 207 B | | pathCase | Convert to path/case | 207 B | | sentenceCase | Convert to Sentence case | 414 B | | titleCase | Convert to Title Case, keeping small words lowercase | 561 B |

String manipulation

| Function | What it does | Gzipped | | --- | --- | --- | | slugify | Turn any string into a URL-safe slug | 138 B | | truncate | Cut a string to a max length and append a suffix | 276 B | | excerpt | Truncate at a word boundary instead of mid-word | 261 B | | capitalize | Uppercase the first character | 99 B | | reverse | Reverse a string, surrogate-pair safe | 229 B | | pad | Pad both ends to a target length | 214 B | | padStart | Pad the start to a target length | 176 B | | padEnd | Pad the end to a target length | 180 B | | trim | Trim arbitrary characters from both ends | 179 B | | trimStart | Trim arbitrary characters from the start | 165 B | | trimEnd | Trim arbitrary characters from the end | 170 B | | mask | Mask characters, e.g. for card or phone numbers | 229 B | | wrap | Word-wrap text to a given column width | 329 B | | randomString | Generate a random string of a given length | 227 B | | wordCount | Count words | 123 B | | countSubstrings | Count non-overlapping occurrences of a substring | 134 B |

Validation

| Function | What it does | Gzipped | | --- | --- | --- | | isEmail | Validate an email address, with opt-in international support | 222 B | | isUrl | Validate a URL | 168 B | | isUUID | Validate a UUID | 123 B | | isHexColor | Validate a hex colour such as #ff5733 | 138 B | | isNumeric | Check the string represents a number | 122 B | | isInteger | Check the string represents an integer | 115 B | | isAlphanumeric | Check the string is letters and digits only | 123 B | | isASCII | Check the string is pure ASCII | 145 B | | isBlank | Check the string is empty or only whitespace | 65 B | | detectScript | Detect the dominant writing system, e.g. Latin or Cyrillic | 540 B | | classifyText | Classify text as prose, code, an identifier and more | 897 B |

Security and escaping

| Function | What it does | Gzipped | | --- | --- | --- | | sanitize | Strip dangerous HTML to prevent XSS | 811 B | | stripHtml | Remove every HTML tag | 85 B | | escapeHtml | Escape &, <, >, " and ' | 136 B | | unescapeHtml | Decode HTML entities back to characters | 155 B | | redact | Redact emails, phone numbers, cards and other PII | 728 B | | templateSafe | Interpolate a template with automatic HTML escaping | 501 B |

Search, diff and matching

| Function | What it does | Gzipped | | --- | --- | --- | | fuzzyMatch | Fuzzy match with a relevance score, for command palettes | 595 B | | levenshtein | Edit distance between two strings | 413 B | | levenshteinNormalized | Edit distance normalised to a 0–1 similarity | 472 B | | diff | Character-level diff between two strings | 265 B | | highlight | Wrap search terms in markup for result highlighting | 461 B | | template | Interpolate {{placeholders}} from an object | 302 B | | hashString | Fast non-cryptographic string hash | 155 B | | memoize | Memoise a string function with an LRU-bounded cache | 334 B |

Unicode and international

| Function | What it does | Gzipped | | --- | --- | --- | | graphemes | Split into user-perceived characters, emoji-aware | 171 B | | codePoints | Split into Unicode code points | 131 B | | deburr | Strip accents and diacritics, café becomes cafe | 273 B | | toASCII | Transliterate to an ASCII-safe representation | 1.2 KB | | normalizeWhitespace | Collapse exotic Unicode spaces into plain spaces | 268 B | | removeNonPrintable | Strip control and non-printable characters | 304 B |

Natural language

| Function | What it does | Gzipped | | --- | --- | --- | | pluralize | Pluralise an English word, with irregulars | 459 B | | singularize | Singularise an English word | 562 B | | humanizeList | Join a list as a, b, and c | 251 B | | smartSplit | Split into sentences without breaking on Dr. or 3.5 | 586 B | | extractEntities | Pull emails, URLs, mentions, hashtags and dates out of text | 572 B |

Size and speed

Chart comparing gzipped bundle cost per function on a log scale: nano-string-utils and es-toolkit sit between 99 and 331 bytes while lodash ranges from 1.7 KB to 3.4 KB

The whole library is 9.6 KB brotlied with every function included. In practice you import a handful and ship a few hundred bytes.

Against lodash and es-toolkit

Numbers below are gzipped bytes, generated by npm run bench:data.

| Function | nano | es-toolkit | lodash | | ------------ | ----- | ---------- | ------ | | capitalize | 99 B | 107 B | 1.7 KB | | kebabCase | 201 B | 197 B | 2.8 KB | | snakeCase | 200 B | 197 B | 2.8 KB | | pascalCase | 222 B | 231 B | — | | camelCase | 235 B | 273 B | 3.5 KB | | deburr | 273 B | 332 B | 1.8 KB | | truncate | 276 B | — | 2.9 KB | | template | 302 B | — | 5.6 KB |

Against lodash, nano is 7–19× smaller on every function both libraries ship. Against es-toolkit, sizes are within a few bytes either way — the difference there is scope, not weight: validation, sanitization, PII redaction, fuzzy matching, and Unicode helpers have no es-toolkit equivalent.

Throughput

Operations per second, higher is better. Across the 16 benchmarked functions nano leads on 8, lodash on 4, and es-toolkit on 4.

| Function | nano | lodash | es-toolkit | | ------------ | ------ | ------ | ---------- | | capitalize | 21.2M | 16.2M | 21.2M | | snakeCase | 4.2M | 3.1M | 4.1M | | camelCase | 3.3M | 2.2M | 3.3M | | template | 996K | 53K | — | | padStart | 7.9M | 18.8M | — | | deburr | 2.3M | 2.9M | 2.3M |

Case conversion is on par with es-toolkit and roughly 1.4× faster than lodash; template is about 19× faster than lodash's. lodash is genuinely faster at padding and at truncating long strings. Run npm run bench to reproduce any of this on your own machine.

Full interactive benchmarks →

Type safety

Diagram showing two TypeScript panels: branded types reject a raw string where an Email is required until isValidEmail narrows it, and template literal types resolve camelCase of hello-world to the literal type helloWorld

Branded types

A string that has been validated is a different type from one that hasn't, so you can't lose the validation on the way through your code.

import { toEmail, isValidEmail, assertEmail } from "nano-string-utils";
import type { Email } from "nano-string-utils";

declare function sendWelcome(to: Email): void;

const input: string = form.get("email");

sendWelcome(input); // ✗ Type error: string is not assignable to Email

// Narrow it — type guard
if (isValidEmail(input)) {
  sendWelcome(input); // ✓ input is Email here
}

// Or convert — returns Email | null
const email = toEmail(input);
if (email) sendWelcome(email);

// Or assert — throws BrandedTypeError on invalid input
assertEmail(input);
sendWelcome(input); // ✓

Email, URL, Slug, SafeHTML, HexColor, NumericString, AlphanumericString, UUID, and IntegerString each come with a toX converter, an isValidX guard, an assertX assertion, and an unsafeX escape hatch for values you have already validated elsewhere.

Template literal types

When the compiler knows the input string, it knows the exact output string.

const a = camelCase("hello-world");
//    ^? const a: "helloWorld"

const b = kebabCase("backgroundColor");
//    ^? const b: "background-color"

const c = constantCase("user id");
//    ^? const c: "USER_ID"

Pass a runtime string and you get string back — no cost, no ceremony.

Null safety

Almost every function tolerates null and undefined rather than throwing, so you can drop them into pipelines handling untrusted input. The input type flows through: string in, string out; null in, null out.

slugify(null); // null
camelCase(undefined); // undefined
truncate(null, 10); // null
isEmail(null); // false
wordCount(undefined); // 0

Four functions are the exception and will throw on a nullish argument — guard them yourself:

| Function | Throws on | | ----------------------- | --------------------- | | levenshtein | either argument | | levenshteinNormalized | either argument | | redact | the input string | | humanizeList | the input array |

CLI

npx nano-string-utils slugify "Hello World"   # hello-world

Install it globally for everyday use, or reach for npx, deno run, or bunx on demand.

npm install -g nano-string-utils

nano-string camelCase "hello-world"          # helloWorld
nano-string truncate "Long text here" --length 10
nano-string template "Hello {{name}}" --data '{"name":"World"}'
nano-string isEmail "[email protected]"       # true
nano-string levenshtein "kitten" "sitting"   # 3

# Reads stdin, so it pipes
echo "Hello World" | nano-string slugify     # hello-world
cat post.md | nano-string excerpt --length 160

nano-string --help lists every command; nano-string <function> --help explains one.

Runtime compatibility

| Runtime | Supported | Install | CLI | | ------------ | --------- | ------------------------------------- | --- | | Node.js ≥ 18 | ✅ | npm install nano-string-utils | ✅ | | Deno | ✅ | deno add jsr:@zheruel/nano-string-utils | ✅ | | Bun | ✅ | bun add nano-string-utils | ✅ | | Browsers | ✅ | bundler or CDN | — | | Edge / workers | ✅ | bundler | — |

Only standard JavaScript APIs are used, so behaviour is identical everywhere. Both ESM and CommonJS builds ship in the package, with full type definitions for each.

Compared with the alternatives

| Library | Full size | Dependencies | Tree-shakeable | TypeScript | Scope | | ------------------- | --------- | ------------ | --------------------- | ---------- | ---------------------------------- | | nano-string-utils | 9.6 KB | 0 | ✅ | ✅ strict | Strings only, 61 functions | | lodash | ~70 KB | 0 | ⚠️ needs lodash-es | ✅ via DT | General purpose | | es-toolkit | ~30 KB | 0 | ✅ | ✅ | General purpose | | underscore.string | ~20 KB | 0 | ❌ | ❌ | Strings only | | voca | ~30 KB | 0 | ❌ | ✅ via DT | Strings only |

If you already use es-toolkit for arrays and objects, nano-string-utils composes with it — the two overlap on seven functions and disagree on almost nothing.

Step-by-step lodash migration guide →

Documentation

Development

git clone https://github.com/Zheruel/nano-string-utils.git
cd nano-string-utils
npm install

npm test              # watch mode
npm run test:coverage # once, with coverage
npm run typecheck
npm run build
npm run size          # enforce the bundle budget
npm run bench         # performance benchmarks

Adding a function means a module in src/, a test file in tests/, an export from src/index.ts, and an entry in docs-src/src/metadata.ts. CLAUDE.md has the full checklist.

Contributing

Issues and pull requests are welcome. For anything substantial, open an issue first so we can agree on the shape of it.

  1. Fork and branch (git checkout -b feature/thing)
  2. Add tests — every function has a matching file in tests/
  3. Run npm run typecheck && npm run test:coverage && npm run build && npm run size
  4. Open a pull request

License

MIT © Zheruel