sindresorhus/slugify
Slugify a string
Slowing — last commit 8mo ago
worst-casePermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
last commit was 8mo ago; Scorecard "Branch-Protection" is 0/10…
- ✓Last commit 8mo ago
- ✓5 active contributors
- ✓MIT licensed
- ✓CI configured
- ✓Tests present
- ⚠Slowing — last commit 8mo ago
- ⚠Small team — 5 top contributors
- ⚠Single-maintainer risk — top contributor 86% of commits
- ⚠Scorecard: marked unmaintained (0/10)
- ⚠Scorecard: default branch unprotected (0/10)
What would change the verdict?
- →Deploy as-is WAIT → GO if: 1 commit in the last 180 days; bring "Branch-Protection" to ≥3/10 (see scorecard report)
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests + OpenSSF Scorecard
Embed this verdict
[](https://repopilot.app/r/sindresorhus/slugify)Paste into your README — the badge live-updates from the latest cached analysis.
Onboarding doc
Onboarding: sindresorhus/slugify
Generated by RepoPilot · 2026-05-05 · Source
Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/sindresorhus/slugify shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
Verdict
WAIT — Slowing — last commit 8mo ago
- Last commit 8mo ago
- 5 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Slowing — last commit 8mo ago
- ⚠ Small team — 5 top contributors
- ⚠ Single-maintainer risk — top contributor 86% of commits
- ⚠ Scorecard: marked unmaintained (0/10)
- ⚠ Scorecard: default branch unprotected (0/10)
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests + OpenSSF Scorecard</sub>
Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live sindresorhus/slugify
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/sindresorhus/slugify.
What it runs against: a local clone of sindresorhus/slugify — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in sindresorhus/slugify | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 266 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of sindresorhus/slugify. If you don't
# have one yet, run these first:
#
# git clone https://github.com/sindresorhus/slugify.git
# cd slugify
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of sindresorhus/slugify and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "sindresorhus/slugify(\\.git)?\\b" \\
&& ok "origin remote is sindresorhus/slugify" \\
|| miss "origin remote is not sindresorhus/slugify (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "index.js" \\
&& ok "index.js" \\
|| miss "missing critical file: index.js"
test -f "index.d.ts" \\
&& ok "index.d.ts" \\
|| miss "missing critical file: index.d.ts"
test -f "overridable-replacements.js" \\
&& ok "overridable-replacements.js" \\
|| miss "missing critical file: overridable-replacements.js"
test -f "package.json" \\
&& ok "package.json" \\
|| miss "missing critical file: package.json"
test -f "test.js" \\
&& ok "test.js" \\
|| miss "missing critical file: test.js"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 266 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~236d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/sindresorhus/slugify"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
TL;DR
@sindresorhus/slugify converts user-facing strings into URL-safe, filename-safe slugs by handling transliteration (e.g., 'Déjà Vu!' → 'deja-vu'), camelCase deconstruction, special character removal, and multi-language support via the @sindresorhus/transliterate dependency. It's a production utility for generating clean identifiers from arbitrary text across 100+ languages including German umlauts, Russian Cyrillic, Vietnamese, and Arabic. Single-entry-point library: index.js exports the main slugify() function, index.d.ts provides TypeScript types, and overridable-replacements.js defines the default character mappings (& → 'and', 🦄 → 'unicorn', ♥ → 'love'). No monorepo—straightforward npm package structure with test.js at root for integration tests.
Who it's for
Full-stack developers, Next.js/Node.js backend engineers, and CMS/content platform builders who need to auto-generate URL slugs, file basenames, and database IDs from user-submitted titles, tags, or filenames without writing character-handling boilerplate.
Maturity & risk
Mature and stable: version 3.0.0 indicates a released, versioned package with TypeScript definitions (index.d.ts), comprehensive test coverage (test.js), and active CI/CD via GitHub Actions (.github/workflows/main.yml). Single-maintainer (Sindre Sorhus) with strong community track record, though no recent commit data visible, suggesting stable rather than actively evolving.
Very low risk: only 2 production dependencies (@sindresorhus/transliterate and escape-string-regexp), both are well-maintained Sindre Sorhus modules. Node.js ≥20 requirement is modern but reasonable. Single-maintainer model typical for Sorhus, mitigated by high test coverage and explicit published package on npm. No indication of breaking changes in current release.
Active areas of work
Not visible from file list—no recent commit hashes, PR metadata, or issue tracker visible. Repository appears stable/maintained-in-steady-state rather than actively feature-developing, typical of mature utility libraries.
Get running
git clone https://github.com/sindresorhus/slugify.git
cd slugify
npm install
npm test
Daily commands:
npm test
``` runs xo (linter) and ava (test runner) as defined in package.json scripts. For development, use `node -e "import s from './index.js'; console.log(s('test'))"` to test locally.
## Map of the codebase
- `index.js` — Main entry point implementing the core slugify algorithm; handles string transformation, transliteration, separator logic, and all configuration options.
- `index.d.ts` — TypeScript type definitions that document the function signature and options interface; essential for IDE support and type safety.
- `overridable-replacements.js` — Exports the custom character replacement map used during slugification; contributors may need to extend language support or handle special cases here.
- `package.json` — Defines runtime requirements (Node ≥20), module type (ESM), and export mappings; critical for understanding compatibility and build configuration.
- `test.js` — Comprehensive test suite covering core functionality, edge cases, and language support; reference for expected behavior and integration patterns.
## Components & responsibilities
- **slugify() function** _(JavaScript, Unicode API, RegExp)_ — Orchestrates the full transformation pipeline: normalization, replacement, cleaning, case conversion, and joining with separator
- **Failure mode**: Returns unexpected output if input contains unsupported characters or if replacements are incomplete; gracefully falls back to removal of unmappable characters
- **overridable-replacements map** _(JavaScript Map)_ — Provides symbol-to-word mappings (e.g., ♥ → love) that are applied before generic character filtering
- **Failure mode**: Missing entries cause symbols to be stripped rather than transliterated; user can extend by importing and modifying the map
- **Type definitions (index.d.ts)** _(TypeScript)_ — Documents the public API contract; enables static type checking for TypeScript and JSDoc-aware environments
- **Failure mode**: Outdated or incorrect types lead to IDE false positives/negatives and confusion for contributors
## Data flow
- `User input string` → `Unicode normalization` — Input string is normalized to NFKD form to decompose accents and composed characters
- `Normalized string` → `Character replacement map` — Each character is checked against the overridable replacements and substituted if found
- `Replaced string` → `Character filtering & splitting` — Non-word characters are removed or used as boundaries; string is split on whitespace and separators
- `Filtered tokens` → `Case conversion` — Each token is lowercased (if lowercase option is true)
- `Case-converted tokens` → `Join operation` — Tokens are joined using the specified separator (default '-')
- `Joined slug` → `User output` — Final slug string returned to caller
## How to make changes
### Add support for a new character replacement
1. Open overridable-replacements.js and add a new key-value pair to the Map, where the key is the character/symbol and the value is its replacement string (`overridable-replacements.js`)
2. Add test cases in test.js to verify the new character is correctly transformed (`test.js`)
### Add a new option to the slugify function
1. Update the options parameter type definition in index.d.ts with the new property (`index.d.ts`)
2. Implement the option logic in the main slugify function in index.js (`index.js`)
3. Add test cases in test.js to cover the new option with various inputs (`test.js`)
### Test language transliteration support
1. Add test case in test.js with sample strings from the target language and expected slug output (`test.js`)
2. Run npm test to verify the language is correctly handled by built-in Unicode normalization and replacements (`package.json`)
## Why these technologies
- **ES Modules (ESM)** — Modern JavaScript standard with better tree-shaking and cleaner syntax; aligns with current Node.js conventions (requires Node ≥20).
- **Unicode NFKD normalization** — Handles accents, diacritics, and composed characters from diverse languages; built into JavaScript runtime (no external dependency).
- **TypeScript definitions (index.d.ts)** — Provides IDE autocomplete, type safety, and self-documentation without requiring TypeScript as a build dependency.
## Trade-offs already made
- **Single-file main implementation (index.js) with embedded logic rather than modular helpers**
- Why: Keeps the package small and simple; slugification is a focused, single-purpose task.
- Consequence: Less reusable internal functions, but acceptable given the library's scope and minimal complexity.
- **Rely on Unicode normalization + character replacement map rather than full transliteration library**
- Why: Reduces dependencies and bundle size; Unicode normalization handles most cases; custom replacements handle special symbols.
- Consequence: May not handle all edge cases in uncommon languages compared to dedicated transliteration libraries, but covers the documented supported languages.
- **Require Node ≥20**
- Why: Simplifies code (assumes modern JavaScript features); reduces maintenance burden for old runtime versions.
- Consequence: Users on older Node versions must upgrade or use an earlier version of the package.
## Non-goals (don't propose these)
- Provide full transliteration for all world languages beyond the documented major ones
- Offer URL encoding/decoding (slugify only produces slug-safe strings, does not handle percent-encoding)
- Support custom transliteration rules beyond the overridable replacements map
- Handle emoji or image-based content
- Maintain backward compatibility below Node 20 (explicit engine boundary)
## Code metrics
- **Avg cyclomatic complexity**: ~2.5 — Straightforward string transformation pipeline with linear iteration and simple conditional logic; no nested loops or complex algorithms.
- **Largest file**: `index.js` (65 lines)
- **Estimated quality issues**: ~0 — XO linter enforces consistent style; TypeScript definitions are current; test coverage is comprehensive; no obvious violations detected.
## Anti-patterns to avoid
- **Hard-coded character replacements mixed with filtering logic** _(Low)_ — `index.js`: Character replacements and filtering rules are intertwined; extending the replacement map requires understanding the main function's internals.
- **Limited error handling for invalid options** _(Low)_ — `index.js`: Options are consumed without type validation; passing incorrect types (e.g., non-string separator) may produce unexpected output rather than throwing clear errors.
## Performance hotspots
- `index.js - String iteration and replacement loop` _(Algorithmic)_ — Character-by-character replacement from the map is O(n*m) where n is string length and m is the map size; for very long strings or large replacement maps, this could be slow.
- `overridable-replacements.js - Map lookup` _(Caching)_ — Current implementation iterates and replaces; no caching of compiled regex; each call rebuilds the replacement logic.
## Traps & gotchas
No environment variables, config files, or external services required—pure function. One subtle gotcha: the customReplacements option completely overrides default mappings for a given key (e.g., if you pass [['&', 'ampersand']], it replaces the default '& → and'), so order matters if you want to chain replacements. Leading/trailing spaces in customReplacements values are intentional—'@' → ' at ' (with spaces) triggers dash separation, but '@' → 'at' (no spaces) does not.
## Architecture
```mermaid
sequenceDiagram
participant Caller as User Code
participant Slugify as slugify()
participant Replacements as overridable-replacements
participant Unicode as Unicode API
Caller->>Slugify: slugify('Café & Dogs', {separator: '-', lowercase: true})
Slugify->>Unicode: normalize(string, 'NFKD')
Note right of Unicode: Decompose accents
Unicode-->>Slugify: 'Cafe & Dogs'
Slugify->>Replacements: Get custom replacements map
Replacements-->>Slugify: {♥: 'love', '&': 'and', ...}
par Apply replacements
Slugify->>Slugify: Replace '&' with 'and'
and
Slugify->>Slugify: Process custom symbols
end
Slugify->>Slugify: Remove non-word chars, split on spaces/separators
Slugify->>Slugify: Apply lowercase option
Slugify->>Slugify: Join with separator '-'
Slugify-->>Caller: 'cafe-and-dogs'
Concepts to learn
- Transliteration — Core capability of this library—mapping non-Latin scripts (Cyrillic, Arabic, CJK) to their Latin equivalents; required knowledge to understand why 'я люблю единорогов' becomes 'ya-lyublyu-edinorogov'.
- Unicode Normalization (NFD/NFC) — The @sindresorhus/transliterate dependency uses Unicode decomposition to separate base characters from combining diacritics, allowing 'é' (single code point) and 'é' (e + acute accent) to be handled consistently.
- CamelCase Deconstruction / Kebab-Case Conversion — slugify's decamelize option (default true) splits 'fooBar' into 'foo bar' before joining with separators—a non-trivial regex pattern recognizing uppercase letter sequences and word boundaries.
- Regular Expression Anchoring & Boundary Matching — The preserveLeadingUnderscore and preserveTrailingDash options require careful regex handling to detect start/end positions before/after string transformations, without consuming those characters.
- String Immutability & Functional Composition — slugify chains multiple string transformations (replacement → transliteration → case change → separator insertion) in sequence—understanding immutable composition patterns is key to adding new transformation steps.
- Character Escape Sequences & Special Character Handling — Custom replacements must handle emoji (like 🦄), symbols (♥), and regex metacharacters safely; the escape-string-regexp dependency prevents accidental regex injection.
- TypeScript Declaration Merging & Overload Signatures — index.d.ts defines optional options parameter with union type for separator string or custom replacement arrays—TypeScript overloads ensure IDE autocomplete and type safety for different call patterns.
Related repos
sindresorhus/transliterate— Direct dependency and core capability provider—handles multi-language character-to-ASCII mapping; understanding this is essential for debugging transliteration issues in slugify.sindresorhus/decamelize— Separate, focused utility for converting camelCase to separate words; slugify integrates this logic internally but developers may want to use decamelize standalone.vercel/slugify— Alternative npm package providing similar functionality; useful for comparing API design and feature trade-offs if extending or forking.sindresorhus/awesome— Curated list of awesome Node.js/JavaScript projects by the same author—helpful for discovering complementary utilities in the Sindre Sorhus ecosystem.sindresorhus/escape-string-regexp— Direct dependency used internally for safe regex handling; understanding its role prevents accidental regex injection in custom replacements.
PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive test coverage for overridable-replacements.js
The overridable-replacements.js file is included in the published package (per package.json files array) but there's no visible test coverage for its API, edge cases, or integration with the main slugify function. This is a critical file for users who want to customize replacements, yet test.js likely lacks dedicated tests for this feature.
- [ ] Review test.js to identify missing tests for overridable-replacements.js functionality
- [ ] Add test cases for: custom replacement objects, merging with defaults, edge cases (empty replacements, regex conflicts, unicode handling)
- [ ] Add test cases for integration: verify custom replacements work correctly with separator and other options
- [ ] Run 'npm test' to ensure all new tests pass xo linting and ava execution
Add test coverage for the incomplete 'lower' option documented in README
The README.md shows the 'lower' option section header but the documentation appears cut off (ends at 'lower'). This suggests either the feature is incomplete or the docs are truncated. test.js likely lacks tests validating the 'lower' option behavior across different input cases.
- [ ] Review README.md and index.d.ts to confirm the 'lower' option exists and its expected behavior
- [ ] Add test cases in test.js for: lower=true (default behavior), lower=false (preserve case), mixed case inputs with lower option
- [ ] Verify the option works correctly with transliteration and special characters
- [ ] Run 'npm test' to validate the implementation
Add TypeScript definition tests (type-checking tests)
While index.d.ts exists for type definitions, there are no visible type-checking tests. Modern packages should include runtime validation of TypeScript types using tools like tsd or type-level tests to ensure the exported types match actual behavior and catch breaking changes.
- [ ] Add tsd as a dev dependency: 'npm install --save-dev tsd'
- [ ] Create test-d/ directory with TypeScript test files for index.d.ts
- [ ] Add tests for: basic slugify call, options object variants (with/without options), type inference, rejected invalid options
- [ ] Add 'type-check' script to package.json scripts and integrate into 'test' script
- [ ] Run 'npm test' to ensure type checking passes
Good first issues
- Add test coverage for edge case: multiple consecutive separators (e.g., slugify('foo---bar') with separator='-' should collapse to 'foo-bar'): test.js: Test file exists but edge case of repeated separators doesn't appear to be explicitly covered; add ava test cases.
- Document the interaction between customReplacements and decamelize option in README: readme.md: README shows customReplacements examples but doesn't clarify if decamelize runs before or after custom replacements—add clarifying example and note in API section.
- Add TypeScript-specific test file to verify index.d.ts types with real imports: test.d.ts (new file): Currently only test.js (runtime) exists; add a separate .d.ts test using tsd or similar to validate TypeScript type accuracy and catch regressions in future updates.
Top contributors
- @sindresorhus — 49 commits
- @aegatlin — 3 commits
- @TiagoDanin — 2 commits
- @BendingBender — 2 commits
- @hanneskuettner — 1 commits
Recent commits
7c318bd— 3.0.0 (sindresorhus)dc4b445— Addtransliterateoption (sindresorhus)b084182— Addlocaleoption (sindresorhus)f4595fe— Require Node.js 20 (sindresorhus)d572cba— 2.2.1 (sindresorhus)c9cb96d— Improve compatibility with partial strings (#73) (hanneskuettner)753c86a— 2.2.0 (sindresorhus)f235b34— AddpreserveCharactersoption (#70) (aegatlin)48a9112— Remove contractions and possessives (#71) (aegatlin)fe4d089— 2.1.1 (sindresorhus)
Security observations
The @sindresorhus/slugify package demonstrates a strong security posture. It is a focused utility library with minimal dependencies (2 direct dependencies), no hardcoded secrets, and no apparent injection vulnerabilities based on the file structure. The package uses ES modules and has a clear security policy in place (.github/security.md). Main concerns are minor: dependency version constraints could be more restrictive, and one linting rule is disabled. No critical or high-severity vulnerabilities were identified in the static analysis.
- Low · Dependency on @sindresorhus/transliterate —
package.json - dependencies. The package depends on @sindresorhus/transliterate@^2.0.0 with a caret version constraint. This allows minor and patch updates automatically, which could introduce unexpected behavior changes or security issues in transitive dependencies. Fix: Consider using a more restrictive version constraint (e.g., ~2.0.0 or exact version) if stability is critical, or regularly audit dependencies using 'npm audit' and 'npm outdated'. - Low · Permissive XO linting rule override —
package.json - xo configuration. The project disables '@typescript-eslint/member-ordering' rule in XO configuration. While this is a style rule rather than a security rule, disabling linter rules can mask code quality issues that may have security implications. Fix: Review the rationale for disabling this rule. Re-enable it if possible or document why it's necessary to improve code maintainability.
LLM-derived; treat as a starting point, not a security audit.
Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.