TL;DR
change-case is the most comprehensive string transformation library — one package that handles every case format (camelCase, PascalCase, kebab-case, snake_case, SCREAMING_SNAKE_CASE, etc.). camelcase is the single-purpose option — just converts strings to camelCase, ~300B, tree-shakable, and Sindre Sorhus quality. slugify converts strings to URL-safe slugs — handles Unicode, transliteration (converting accented chars to ASCII), and special characters. For all-in-one case conversions: change-case. For just camelCase: camelcase. For URL slugs: slugify.
Key Takeaways
- change-case: ~15M weekly downloads — all case formats in one package, tree-shakable
- camelcase: ~70M weekly downloads — single-purpose, most popular, Sindre Sorhus
- slugify: ~5M weekly downloads — URL slugs, Unicode transliteration, SEO-safe
- Node.js
String.prototype.toUpperCase/toLowerCaseare built-in — use them for simple cases - change-case is modular —
import { camelCase } from "change-case"only pulls what you need - For API/database field name conversion (snake_case → camelCase), change-case is the standard
Download Trends
| Package | Weekly Downloads | Bundle Size | All Cases | Unicode | URL Slugs |
|---|---|---|---|---|---|
change-case | ~15M | ~30KB total | ✅ | ✅ | ❌ |
camelcase | ~70M | ~300B | ❌ camelCase only | ✅ | ❌ |
slugify | ~5M | ~10KB | ❌ | ✅ | ✅ |
change-case
change-case — comprehensive case conversion:
All case formats
import {
camelCase,
pascalCase,
snakeCase,
kebabCase,
constantCase,
dotCase,
pathCase,
sentenceCase,
capitalCase,
noCase,
trainCase,
} from "change-case"
const input = "hello world foo bar"
camelCase(input) // "helloWorldFooBar"
pascalCase(input) // "HelloWorldFooBar"
snakeCase(input) // "hello_world_foo_bar"
kebabCase(input) // "hello-world-foo-bar"
constantCase(input) // "HELLO_WORLD_FOO_BAR" (SCREAMING_SNAKE_CASE)
dotCase(input) // "hello.world.foo.bar"
pathCase(input) // "hello/world/foo/bar"
sentenceCase(input) // "Hello world foo bar"
capitalCase(input) // "Hello World Foo Bar"
noCase(input) // "hello world foo bar" (normalized)
trainCase(input) // "Hello-World-Foo-Bar" (HTTP header style)
Handles mixed inputs
import { camelCase, snakeCase, kebabCase } from "change-case"
// From snake_case:
camelCase("user_first_name") // "userFirstName"
camelCase("api_response_data") // "apiResponseData"
// From camelCase:
snakeCase("userFirstName") // "user_first_name"
kebabCase("packageHealthScore") // "package-health-score"
// From PascalCase:
snakeCase("PackageHealthScore") // "package_health_score"
kebabCase("PackageHealthScore") // "package-health-score"
// From SCREAMING_SNAKE_CASE:
camelCase("HTTP_STATUS_CODE") // "httpStatusCode"
camelCase("API_ENDPOINT_URL") // "apiEndpointUrl"
// From mixed:
camelCase(" hello world ") // "helloWorld" (trims + handles spaces)
camelCase("hello-world_foo.bar") // "helloWorldFooBar" (mixed separators)
Practical use cases
import { camelCase, snakeCase, pascalCase } from "change-case"
// API response normalization — convert snake_case → camelCase:
function normalizeApiResponse<T extends Record<string, unknown>>(data: T) {
return Object.fromEntries(
Object.entries(data).map(([key, value]) => [camelCase(key), value])
)
}
const apiResponse = {
user_id: "123",
first_name: "Alice",
package_health_score: 95,
is_premium_user: true,
}
normalizeApiResponse(apiResponse)
// { userId: "123", firstName: "Alice", packageHealthScore: 95, isPremiumUser: true }
// Database query — convert camelCase → snake_case for PostgreSQL:
function toDbColumns(obj: Record<string, unknown>) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [snakeCase(key), value])
)
}
// Generate component names from file names:
const fileName = "package-health-card"
pascalCase(fileName) // "PackageHealthCard" — React component name
// Environment variable names:
constantCase("databaseConnectionString") // "DATABASE_CONNECTION_STRING"
camelcase
camelcase — single-purpose, Sindre Sorhus quality:
Usage
import camelCase from "camelcase"
// Basic:
camelCase("foo bar") // "fooBar"
camelCase("foo-bar") // "fooBar"
camelCase("foo_bar") // "fooBar"
camelCase("Foo Bar") // "fooBar"
camelCase("--foo-bar--") // "fooBar"
camelCase("__foo_bar__") // "fooBar"
camelCase("FOO_BAR") // "fooBar"
// PascalCase option:
camelCase("foo bar", { pascalCase: true }) // "FooBar"
// Preserve consecutive uppercase (for acronyms):
camelCase("myURL") // "myUrl" (default)
camelCase("myURL", { preserveConsecutiveUppercase: true }) // "myURL"
// Multiple words:
camelCase(["foo", "bar"]) // "fooBar"
camelCase(["--foo-bar", "--foo-baz"]) // "fooBarFooBaz"
When camelcase beats change-case
// camelcase is 70M downloads/week vs change-case's 15M
// because it's focused, small, and trusted
// Bundle comparison:
// import camelCase from "camelcase" → 300B
// import { camelCase } from "change-case" → ~5KB (even with tree-shaking)
// For packages or libraries that only need camelCase conversion,
// camelcase is the right choice to minimize bundle size
slugify
slugify — URL-safe string transformation:
Basic usage
import slugify from "slugify"
// Basic slug:
slugify("React vs Vue: A Comprehensive Comparison (2026)")
// "React-vs-Vue-A-Comprehensive-Comparison-2026"
// Lowercase (recommended for URLs):
slugify("React vs Vue: A Comprehensive Comparison (2026)", {
lower: true,
})
// "react-vs-vue-a-comprehensive-comparison-2026"
// With strict mode (only alphanumeric + separator):
slugify("Hello World! @2026 #npm", { strict: true, lower: true })
// "hello-world-2026-npm"
Unicode transliteration
import slugify from "slugify"
// Converts accented characters to ASCII equivalents:
slugify("Ångström units & schülerschaft", { lower: true })
// "angstrom-units-and-schuleschaft"
slugify("Über alles", { lower: true })
// "uber-alles"
slugify("José García", { lower: true })
// "jose-garcia"
// Chinese/Japanese/Korean — requires locale configuration:
slugify("中文", { locale: "zh", lower: true })
// "zhong-wen"
// Custom character map:
slugify.extend({ "♥": "love", "©": "copyright" })
slugify("I ♥ npm packages © 2026")
// "I-love-npm-packages-copyright-2026"
Blog URL slug generation
import slugify from "slugify"
// Generate SEO-friendly URL slugs:
function generateSlug(title: string, date: string): string {
const slug = slugify(title, {
lower: true,
strict: true, // Remove special characters
trim: true,
})
// Optional: append date for uniqueness
// return `${date}-${slug}`
return slug
}
generateSlug("React vs Vue: Which is Better in 2026?", "2026-03-09")
// "react-vs-vue-which-is-better-in-2026"
generateSlug("Top 10 npm Packages You're Not Using (But Should)", "2026-03-09")
// "top-10-npm-packages-youre-not-using-but-should"
// For Next.js static generation:
export async function generateStaticParams() {
const posts = await getAllPosts()
return posts.map((post) => ({
slug: generateSlug(post.title, post.date),
}))
}
Feature Comparison
| Feature | change-case | camelcase | slugify |
|---|---|---|---|
| camelCase | ✅ | ✅ | ❌ |
| PascalCase | ✅ | ✅ (option) | ❌ |
| snake_case | ✅ | ❌ | ❌ |
| kebab-case | ✅ | ❌ | ❌ |
| SCREAMING_SNAKE | ✅ | ❌ | ❌ |
| URL slugs | ❌ | ❌ | ✅ |
| Unicode transliteration | ✅ | ❌ | ✅ |
| Special char removal | ✅ | ✅ | ✅ |
| Bundle size | ~5KB/fn | ~300B | ~10KB |
| TypeScript | ✅ | ✅ | ✅ |
| Tree-shakable | ✅ | N/A | N/A |
When to Use Each
Choose change-case if:
- You need multiple case formats (not just camelCase)
- Normalizing API responses (snake_case → camelCase)
- Generating database column names, environment variables, component names
- Build tools or code generation where multiple cases are needed
Choose camelcase if:
- You only need camelCase conversion — no other formats
- Bundle size is critical — 300B vs 5KB makes a difference in edge environments
- You want the Sindre Sorhus-quality
preserveConsecutiveUppercaseoption
Choose slugify if:
- Generating URL-safe slugs for blog posts, product pages, or any URL segment
- You need Unicode transliteration (accented chars → ASCII)
- SEO-friendly URL generation in Next.js/Astro/Nuxt content pipelines
Use built-ins for simple cases:
// Don't install a library for simple lowercase/uppercase:
"Hello World".toLowerCase() // "hello world" — no library needed
"hello world".toUpperCase() // "HELLO WORLD"
// For title case without a library:
const titleCase = (s: string) =>
s.replace(/\b\w/g, (c) => c.toUpperCase())
// "hello world" → "Hello World"
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on change-case v5.x, camelcase v8.x, and slugify v1.x.
Unicode and Internationalization Edge Cases
The Unicode handling differences between these libraries matter significantly when your application deals with non-ASCII content. change-case handles Unicode word boundaries correctly, recognizing that héllo and wörld are separate words and producing héllo-wörld for kebab-case or héllo_wörld for snake_case. It preserves the accented characters rather than transliterating them, which is the right behavior for case conversion (you're changing the casing convention, not the characters themselves).
slugify takes the opposite approach by design: its core value is transliteration — converting accented and Unicode characters to their ASCII equivalents for URL-safe output. José García becomes jose-garcia rather than josé-garcía. This is almost always what you want for URL slugs since some environments have inconsistent handling of Unicode in URLs, and ASCII-only slugs work everywhere without percent-encoding. The transliteration table covers Latin extended characters (á, é, ü, ø, etc.) comprehensively, and the locale option handles locale-specific transliterations (German ü → ue, Swedish å → a, etc.).
camelcase's Unicode support is more limited — it handles common Latin extended characters but isn't designed for CJK (Chinese, Japanese, Korean) or right-to-left scripts. For pure ASCII input (which covers most programming use cases like converting API field names), this is irrelevant. For user-facing string conversion where input might come from international users, test your specific character set against camelcase's behavior.
One practical gotcha with slugify: the library's default behavior without the strict: true option preserves some characters that can cause unexpected URL behavior. Always use { lower: true, strict: true } as your baseline configuration for URL slug generation. The strict option removes all characters except alphanumerics and the separator, which is what most URL routing systems expect.
Using These Libraries in Build Pipelines and Code Generation
Beyond runtime string conversion, these libraries see heavy use in build-time code generation scenarios where the performance characteristics are different (called once at build time, not per request) but the correctness requirements are higher (a bug produces incorrect generated code, not just a bad runtime value).
change-case is the standard choice for code generation tasks. Code generators that produce TypeScript/JavaScript output need to produce valid identifiers, and change-case handles the word-boundary detection that makes this reliable. Generating React component names from file paths? pascalCase(fileName). Generating TypeScript enum member names from database column names? constantCase(columnName). Generating environment variable names from configuration keys? constantCase(configKey). Having all of these in one consistent package means generated code uses uniform conventions throughout.
When building an OpenAPI code generator, schema property names, parameter names, and model names often arrive in snake_case from the API spec but need to be rendered as camelCase TypeScript identifiers and as PascalCase class names. change-case's camelCase and pascalCase handle the same input consistently, and the noCase function is useful as an intermediate normalization step when input strings have inconsistent existing casing that you want to strip before applying a target format.
slugify is commonly embedded in CMS and documentation frameworks for URL slug generation. The Astro, Next.js, and Nuxt content pipelines all either use slugify directly or implement equivalent functionality. If you're building a content pipeline and need consistent slugs from post titles — especially titles with ampersands, quotes, colons, or non-ASCII characters — slugify's comprehensive character map (including the extend() API for custom character substitutions) covers edge cases that naive title.toLowerCase().replace(/\s+/g, '-') patterns miss.
Compare string utility and JavaScript packages on PkgPulse →
Handling API Response Normalization at Scale
One of the highest-value uses of change-case in real codebases is automating the transformation of API responses that arrive in snake_case (Python/PostgreSQL convention) into camelCase TypeScript objects. While a simple recursive key-transform function handles the common case, production systems encounter complications that naive implementations miss.
The first complication is nested objects and arrays. A flat normalizeKeys function that maps Object.entries only transforms the top level; deeply nested objects retain their snake_case keys. A production-grade normalizer needs to recurse into objects and map over arrays. change-case's camelCase function handles mixed separators (underscores, hyphens, spaces) and consecutive uppercase sequences correctly, which matters for keys like http_status_code (should become httpStatusCode, not httPStatusCode).
The second complication is performance. For APIs returning large arrays — a list endpoint returning 1000 user objects, each with 20 fields — a pure JavaScript recursive normalizer calls camelCase 20,000 times per response. change-case's functions are fast enough for this in practice, but if you're profiling and finding key normalization in hot paths, consider normalizing at the schema validation layer using Zod's .transform() or a custom tRPC middleware that normalizes once on the server rather than in every client.
The third complication is round-tripping: normalizing snake_case responses to camelCase for the client, and converting camelCase request bodies back to snake_case for the database. change-case's snakeCase handles this inverse direction. A clean pattern is wrapping both directions in typed utility functions and applying them at the boundary layer (your API client or tRPC router) rather than at every call site.
When to Use Each
Use change-case if:
- You need multiple case transformations in a single project (camel, snake, kebab, pascal, etc.)
- You want a modular, tree-shakeable package where you only import the functions you need
- You are building a code generator, scaffolding tool, or API wrapper that converts identifier formats
- You want TypeScript types included without installing additional type packages
Use camelcase if:
- You only need camelCase conversion and want the smallest possible dependency
- You are building a Sindre Sorhus-ecosystem tool and want consistent package styles
- You need locale-aware handling or special character support in the conversion
Use slugify if:
- You are generating URL slugs from user-provided strings (blog titles, product names)
- You need transliteration (converting "café" to "cafe", "über" to "uber") for SEO-safe URLs
- You want locale-specific transliteration rules
The three packages solve adjacent but distinct problems. change-case and camelcase are for identifier/code formatting; slugify is for URL-safe string generation. In most projects, you end up using one of the first two plus slugify, so they are rarely direct competitors.
A practical note: change-case handles locale-aware transformations poorly for non-ASCII characters. For identifiers containing accented characters, Unicode letters, or CJK characters, test your case conversion output carefully and consider adding custom locale handling.
See also: AVA vs Jest and dinero.js vs currency.js vs Intl.NumberFormat, Scalar vs Redoc vs Swagger UI (2026).