github-slugger vs slug vs slugify vs url-slug
Generating URL-Safe Slugs in JavaScript Applications
github-sluggerslugslugifyurl-slugSimilar Packages:

Generating URL-Safe Slugs in JavaScript Applications

github-slugger, slug, slugify, and url-slug are npm packages designed to convert human-readable text into URL-safe, lowercase identifiers (slugs). These slugs are commonly used for generating anchor links, clean URLs, or file names from titles or user input. Each package offers different levels of configurability, character handling, and compatibility with specific use cases like GitHub-style heading IDs.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
github-slugger040115.9 kB1-ISC
slug040033.2 kB66 months agoMIT
slugify01,73216.1 kB4219 days agoMIT
url-slug09126.4 kB2a month agoMIT

Generating URL-Safe Slugs: github-slugger vs slug vs slugify vs url-slug

When building web applications, you often need to turn titles, names, or user input into clean, URL-friendly strings — known as "slugs." These are used for permalinks, anchor IDs, file names, or route parameters. The four packages under review — github-slugger, slug, slugify, and url-slug — all aim to solve this problem, but they differ significantly in scope, behavior, and suitability for real-world scenarios.

⚠️ Deprecation Notice: url-slug Is Not Safe for New Projects

First, a critical note: url-slug is officially deprecated. According to its npm page, the package is no longer maintained and should not be used in new codebases. While it once provided a chainable API for slug creation, it lacks updates, security patches, and modern Unicode support. We’ll include it in comparisons for completeness, but do not choose it — opt for slug or slugify instead.

// ❌ Avoid: url-slug (deprecated)
// const slug = require('url-slug');
// const result = slug('Hello World').create(); // Not recommended

🔤 Core Behavior: How Each Package Handles Input

Let’s compare how each library transforms the same inputs, especially edge cases involving special characters, duplicates, and non-Latin scripts.

Basic ASCII Input

All packages handle simple English text similarly:

// github-slugger
const GithubSlugger = require('github-slugger');
const gh = new GithubSlugger();
console.log(gh.slug('Hello World')); // 'hello-world'

// slug
const slug = require('slug');
console.log(slug('Hello World')); // 'hello-world'

// slugify
const slugify = require('slugify');
console.log(slugify('Hello World')); // 'hello-world'

// url-slug (deprecated — shown for reference only)
// console.log(slug('Hello World').create()); // 'hello-world'

Non-Latin Characters (e.g., German, Russian, Arabic)

This is where differences emerge. Only slug and slugify offer transliteration.

// github-slugger → strips non-ASCII
const gh = new GithubSlugger();
console.log(gh.slug('München')); // 'm-nchen' (loses ü)

// slug → transliterates using charmap
const slug = require('slug');
slug.extend({ 'ü': 'ue' }); // optional customization
console.log(slug('München')); // 'muenchen'

// slugify → built-in transliteration for common chars
const slugify = require('slugify');
console.log(slugify('München')); // 'munchen' (ü → u by default)

// url-slug → limited support, inconsistent
// console.log(slug('München').create()); // likely 'm-nchen' or similar

For Cyrillic:

// github-slugger
console.log(gh.slug('Привет')); // '' (empty — all chars stripped)

// slug
console.log(slug('Привет')); // 'privet' (with proper charmap)

// slugify
console.log(slugify('Привет')); // 'privet' (built-in support)

💡 Note: slug requires explicit charmaps for full i18n coverage, while slugify includes common transliterations by default.

🧩 Uniqueness Handling: Critical for Anchor Links

If you’re generating HTML IDs (e.g., for table-of-contents navigation), duplicate slugs break linking. Only github-slugger tracks state to ensure uniqueness:

// github-slugger
const gh = new GithubSlugger();
console.log(gh.slug('Section'));   // 'section'
console.log(gh.slug('Section'));   // 'section-1'
console.log(gh.slug('Section'));   // 'section-2'

// slug
const slug = require('slug');
console.log(slug('Section')); // 'section'
console.log(slug('Section')); // 'section' (no uniqueness)

// slugify
const slugify = require('slugify');
console.log(slugify('Section')); // 'section'
console.log(slugify('Section')); // 'section' (no uniqueness)

This makes github-slugger the only correct choice for Markdown processors or static site generators that must match GitHub’s behavior exactly.

⚙️ Configuration and Customization

Custom Separators

All active packages allow changing the separator (default: -):

// github-slugger → no separator option; always uses '-'

// slug
console.log(slug('Hello World', { replacement: '_' })); // 'hello_world'

// slugify
console.log(slugify('Hello World', { replacement: '_' })); // 'hello_world'

Lowercasing and Trimming

slug and slugify let you disable lowercasing:

// slug
console.log(slug('Hello World', { lower: false })); // 'Hello-World'

// slugify
console.log(slugify('Hello World', { lower: false })); // 'Hello-World'

// github-slugger → always lowercases

Extending Character Maps

Only slug allows deep customization of transliteration:

const slug = require('slug');
slug.charmap['α'] = 'a';
slug.charmap['β'] = 'b';
console.log(slug('αβγ')); // 'abg' (if γ is supported or stripped)

slugify does not expose its internal charmap for modification.

🧪 Real-World Use Cases

Case 1: Building a Markdown Editor That Matches GitHub

You’re creating a live preview pane that must generate the same anchor IDs as GitHub for synced scrolling or TOC navigation.

Use github-slugger — it’s the only one that replicates GitHub’s algorithm and handles duplicates correctly.

const GithubSlugger = require('github-slugger');
const slugger = new GithubSlugger();

const headings = ['Introduction', 'API', 'API', 'Conclusion'];
const ids = headings.map(h => slugger.slug(h));
// ['introduction', 'api', 'api-1', 'conclusion']

Case 2: Multilingual Blog with User-Generated Titles

Your CMS accepts titles in Spanish, Japanese, Arabic, etc., and you need readable slugs for SEO-friendly URLs.

Use slug — its extensible charmaps and locale support give you full control over transliteration quality.

const slug = require('slug');
// Configure once at app startup
slug.defaults.modes.pretty = {
  replacement: '-',
  symbols: true,
  remove: /[*+~.()"'!:@]/g,
  lower: true,
  charmap: slug.charmap,
  multicharmap: slug.multicharmap
};

console.log(slug('¡Hola, mundo! 👋')); // 'hola-mundo'

Case 3: Simple Admin Dashboard with English-Only Content

You just need to turn form inputs like "Product Name" into clean route params like /products/product-name.

Use slugify — zero dependencies, small footprint, and works out of the box.

const slugify = require('slugify');
app.get('/products/:slug', (req, res) => {
  const product = db.findBySlug(req.params.slug);
});

// When saving
const slug = slugify(userInput, { lower: true });

📊 Summary Table

Featuregithub-sluggerslugslugifyurl-slug
GitHub-compatible✅ Yes❌ No❌ No❌ No
Uniqueness tracking✅ Yes (stateful)❌ No❌ No❌ No
Transliteration❌ ASCII-only✅ Full (customizable)✅ Common scripts❌ Limited
Custom separators❌ Always -✅ Yes✅ Yes✅ Yes (deprecated)
Disable lowercasing❌ Always lowercase✅ Yes✅ Yes✅ Yes (deprecated)
Zero dependencies✅ Yes❌ No (unidecode)✅ Yes✅ Yes (deprecated)
Actively maintained✅ Yes✅ Yes✅ YesDeprecated

💡 Final Guidance

  • Need GitHub parity?github-slugger is your only option.
  • Building a global app with rich language support?slug gives you the control you need.
  • Just want something simple and reliable for English content?slugify is lightweight and effective.
  • Considering url-slug? → Don’t. It’s deprecated — choose one of the above instead.

In practice, most frontend teams will reach for slugify for general-purpose slugging, while documentation tools and Markdown processors lean on github-slugger. Reserve slug for when you truly need its advanced i18n capabilities.

How to Choose: github-slugger vs slug vs slugify vs url-slug

  • github-slugger:

    Choose github-slugger if you need to replicate GitHub's exact slug generation behavior for Markdown headings — for example, when building a documentation site or editor that must match GitHub's anchor links. It maintains an internal state to ensure uniqueness within a document, which is essential for avoiding duplicate IDs in HTML. However, it doesn't support custom character replacements or transliteration beyond ASCII, so avoid it if you need internationalization or fine-grained control over output.

  • slug:

    Choose slug if you require robust internationalization support, including transliteration of non-Latin characters (e.g., Cyrillic, Greek, Arabic) into ASCII equivalents, and want extensive configuration options like custom charmaps, modes, and locale-specific rules. It’s ideal for multilingual applications where input may come from diverse languages and you need predictable, readable slugs. Its API is flexible but slightly more verbose than simpler alternatives.

  • slugify:

    Choose slugify if you want a lightweight, zero-dependency solution that handles basic ASCII and common Unicode characters with sensible defaults out of the box. It supports simple options like custom separators and lowercasing, and includes built-in transliteration for many Latin-based scripts. It’s a good middle ground for most web apps that don’t need GitHub compatibility or advanced i18n but still want clean, readable slugs without heavy configuration.

  • url-slug:

    Avoid url-slug in new projects. The package is deprecated (as noted on its npm page) and hasn’t been maintained. While it offered basic slug generation with a fluent API, it lacks modern features, security updates, and active support. Migrate to slug or slugify instead for better reliability and functionality.

README for github-slugger

github-slugger

npm Build

Generate a slug just like GitHub does for markdown headings. It also ensures slugs are unique in the same way GitHub does it. The overall goal of this package is to emulate the way GitHub handles generating markdown heading anchors as close as possible.

This project is not a markdown or HTML parser: passing alpha *bravo* charlie or alpha <em>bravo</em> charlie doesn’t work. Instead pass the plain text value of the heading: alpha bravo charlie.

Install

npm install github-slugger

Usage

import GithubSlugger from 'github-slugger'

const slugger = new GithubSlugger()

slugger.slug('foo')
// returns 'foo'

slugger.slug('foo')
// returns 'foo-1'

slugger.slug('bar')
// returns 'bar'

slugger.slug('foo')
// returns 'foo-2'

slugger.slug('Привет non-latin 你好')
// returns 'привет-non-latin-你好'

slugger.slug('😄 emoji')
// returns '-emoji'

slugger.reset()

slugger.slug('foo')
// returns 'foo'

Check test/fixtures.json for more examples.

If you need, you can also use the underlying implementation which does not keep track of the previously slugged strings (not recommended):

import GithubSlugger, {slug} from 'github-slugger'

slug('foo bar baz')
// returns 'foo-bar-baz'

slug('foo bar baz')
// returns the same slug 'foo-bar-baz' because it does not keep track

Contributing

Contributions welcome! Please read the contributing guidelines first.

License

ISC