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

@p-vbordei/slug

v0.2.1

Published

URL-safe slugifier with transliteration for Romanian, German, French, Polish, Czech, Turkish, Russian, Greek and more. Zero dependencies.

Readme

slug

ci

npm downloads bundle

URL-safe slugifier with explicit transliteration for the languages most likely to mangle naive normalization — Romanian, German, French, Polish, Czech, Turkish, Russian/Ukrainian, Greek, plus common symbols. Zero dependencies.

import { slugify } from "@p-vbordei/slug";

slugify("Mălai cu brânză și țuică");         // "malai-cu-branza-si-tuica"
slugify("Schöne Grüße aus München");         // "schoene-gruesse-aus-muenchen"
slugify("Привет мир");                       // "privet-mir"
slugify("crème brûlée");                     // "creme-brulee"
slugify("100% pure");                        // "100-percent-pure"

slugify("Hello World", { separator: "_" });          // "hello_world"
slugify("very long title", { maxLength: 10 });       // "very-long"

Install

npm install @p-vbordei/slug

Works with Node 20+, browsers, Bun, Deno. ESM + CJS.

Why

Naive text.normalize("NFD").replace(/diacritics/, "") does the wrong thing for several common languages:

  • German ß → just disappears (should be ss)
  • German äa (should be ae in well-formed slugs)
  • Polish ł → unchanged (NFD doesn't decompose it)
  • Cyrillic alphabet → unchanged

slug ships an explicit transliteration table for 200+ characters across 9 language groups, then falls back to NFD-strip for everything else. The result: human-readable slugs even for non-ASCII input.

Recipes

Database-unique slug

import { slugify } from "@p-vbordei/slug";

async function uniqueSlug(title: string): Promise<string> {
  const base = slugify(title, { maxLength: 80 });
  let candidate = base;
  let n = 1;
  while (await db.exists("posts", { slug: candidate })) {
    candidate = `${base}-${++n}`;
  }
  return candidate;
}

Filename-safe slug

import { slugify } from "@p-vbordei/slug";

const filename = slugify(userTitle, {
  strict: true,         // ASCII-only
  maxLength: 100,
});
fs.writeFileSync(`./uploads/${filename}.txt`, content);

Preserve case for branding

import { slugify } from "@p-vbordei/slug";

slugify("Project Sandcastle", { lower: false });  // "Project-Sandcastle"

Add custom replacements

import { slugify } from "@p-vbordei/slug";

slugify("C# is awesome", {
  replacements: { "#": "sharp" },
});  // "c-sharp-is-awesome"

URL path component

import { slugify } from "@p-vbordei/slug";

const url = `/blog/${slugify(post.title)}-${post.id}`;

API

slugify(input, opts?): string

| Option | Type | Default | Meaning | |---|---|---|---| | separator | string | "-" | Joiner between words | | lower | boolean | true | Lowercase the result | | strict | boolean | false | Drop anything not ASCII alphanumeric (after transliteration) | | trim | boolean | true | Strip leading/trailing separators | | maxLength | number | — | Clip to length; doesn't leave trailing separator | | replacements | Record<string, string> | — | Custom maps, applied before built-ins |

Languages with explicit transliteration

| Language | Examples | |---|---| | Romanian | ă→a, â→a, î→i, ș→s, ț→t | | German | ä→ae, ö→oe, ü→ue, ß→ss | | French / Spanish / Portuguese | ç→c, ñ→n, œ→oe, æ→ae | | Polish | ą→a, ć→c, ę→e, ł→l, ń→n, ś→s, ź→z, ż→z | | Czech / Slovak | č→c, ď→d, ě→e, ň→n, ř→r, š→s, ť→t, ů→u, ý→y, ž→z | | Turkish | ı→i, İ→I, ğ→g | | Nordic | å→a, ø→o | | Cyrillic (Russian / Ukrainian) | а→a, ж→zh, ц→ts, ш→sh, щ→shch, ю→yu, я→ya | | Greek | α→a, β→v, γ→g, δ→d, ε→e, θ→th, χ→ch, ψ→ps |

Symbols: &and, %percent, +plus, @at.

Everything else gets NFD-decomposed and stripped of combining marks (catches accents not in the explicit table).

Caveats

  • English-locale word substitutions for symbols. Override via replacements for other languages.
  • No ñny (Spanish) — uses ñn to match prevailing convention; override if you need it.
  • No Chinese/Japanese/Korean transliteration — too ambiguous to ship a default. Use a CJK-romanization library and pass results through slugify.

License

Apache-2.0 © Vlad Bordei