Converting Between camelCase, snake_case, and kebab-case: Rules and Gotchas (2026)
You're migrating an API from snake_case (Python backend) to camelCase (JavaScript frontend). You write a quick converter. It works for user_name β userName. Then it mangles get_HTTP_response into getHttpResponse or getHTTPResponseβand you're not sure which is right.
Case conversion looks trivial until you hit acronyms, numbers, and edge cases. A naive converter breaks on real-world identifiers.
This guide covers the exact conversion rules between the major naming conventions, the acronym problem, and copy-paste-ready code that handles the gotchas.
The Five Naming Conventions
| Convention | Example | Used In |
|---|---|---|
| camelCase | firstName |
JavaScript, Java variables |
| PascalCase | FirstName |
Classes, React components, C# |
| snake_case | first_name |
Python, Ruby, SQL, databases |
| kebab-case | first-name |
URLs, CSS, HTML attributes |
| SCREAMING_SNAKE | FIRST_NAME |
Constants, env variables |
Where Each Matters
- JavaScript objects/JSON: camelCase (
userId,createdAt) - Python/database columns: snake_case (
user_id,created_at) - CSS classes/URLs: kebab-case (
main-nav,/blog-post) - Constants: SCREAMING_SNAKE (
MAX_RETRIES,API_KEY) - Class names: PascalCase (
UserProfile,HttpClient)
Conversion Rules
The Universal Approach: Split, Then Join
All case conversion follows two steps:
- Split the identifier into words
- Join the words in the target format
The hard part is step 1 (splitting correctly).
Splitting Rules by Source Format
| Source | Split On |
|---|---|
| snake_case | underscores _ |
| kebab-case | hyphens - |
| camelCase / PascalCase | before each uppercase letter |
| SCREAMING_SNAKE | underscores, then lowercase |
Joining Rules by Target Format
| Target | Join Method |
|---|---|
| camelCase | first word lowercase, rest capitalized, no separator |
| PascalCase | all words capitalized, no separator |
| snake_case | all lowercase, joined with _ |
| kebab-case | all lowercase, joined with - |
| SCREAMING_SNAKE | all uppercase, joined with _ |
The Acronym Problem
Why Naive Conversion Breaks
Consider converting getHTTPResponse to snake_case.
Naive rule ("insert _ before each uppercase"):
get_H_T_T_P_Response β wrong! acronym shattered
What you actually want:
get_http_response
The Ambiguity
There's no universal "correct" answer for acronyms because the source loses information:
| snake_case | Could become camelCase |
|---|---|
get_http_response |
getHttpResponse OR getHTTPResponse |
parse_url |
parseUrl OR parseURL |
user_id |
userId OR userID |
Both are used in practice. Google's JavaScript style guide says treat acronyms as words (getHttpResponse). Others preserve them (getHTTPResponse).
Recommended: Treat Acronyms as Words
The most consistent, least buggy approach (Google style):
HTTPβHttpgetHTTPResponseβget_http_responseβgetHttpResponseuserIDβuser_idβuserId
This makes conversion reversible and predictable.
Copy-Paste Converters (JavaScript)
Split any identifier into words
function splitWords(str) {
return str
// handle camelCase/PascalCase boundaries
.replace(/([a-z])([A-Z])/g, '$1 $2')
// handle acronym followed by word: HTTPResponse -> HTTP Response
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
// handle letter-number boundaries
.replace(/([a-zA-Z])([0-9])/g, '$1 $2')
// replace separators with spaces
.replace(/[_-]+/g, ' ')
.trim()
.toLowerCase()
.split(/\s+/);
}
// splitWords("getHTTPResponse") -> ["get", "http", "response"]
// splitWords("user_id") -> ["user", "id"]
// splitWords("main-nav-bar") -> ["main", "nav", "bar"]
Convert to each format
function toCamelCase(str) {
const words = splitWords(str);
return words[0] + words.slice(1)
.map(w => w[0].toUpperCase() + w.slice(1))
.join('');
}
function toPascalCase(str) {
return splitWords(str)
.map(w => w[0].toUpperCase() + w.slice(1))
.join('');
}
function toSnakeCase(str) {
return splitWords(str).join('_');
}
function toKebabCase(str) {
return splitWords(str).join('-');
}
function toScreamingSnake(str) {
return splitWords(str).join('_').toUpperCase();
}
// toCamelCase("get_http_response") -> "getHttpResponse"
// toSnakeCase("getHTTPResponse") -> "get_http_response"
// toKebabCase("MainNavBar") -> "main-nav-bar"
Python Equivalents
import re
def split_words(s):
s = re.sub(r'([a-z])([A-Z])', r'\1 \2', s)
s = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1 \2', s)
s = re.sub(r'([a-zA-Z])([0-9])', r'\1 \2', s)
s = re.sub(r'[_-]+', ' ', s)
return s.strip().lower().split()
def to_camel(s):
words = split_words(s)
return words[0] + ''.join(w.capitalize() for w in words[1:])
def to_snake(s):
return '_'.join(split_words(s))
def to_kebab(s):
return '-'.join(split_words(s))
# to_camel("get_http_response") -> "getHttpResponse"
# to_snake("getHTTPResponse") -> "get_http_response"
Edge Cases That Break Converters
1. Numbers in identifiers
address2 β address_2 or address2?
oauth2Token β oauth_2_token or oauth2_token?
Recommendation: Keep numbers attached to preceding word unless clearly separate: oauth2_token.
2. Consecutive separators
user__name β user_name (collapse doubles)
--main--nav-- β main-nav (trim and collapse)
Fix: Use [_-]+ (one-or-more) in split regex, and .trim().
3. Leading/trailing separators
_private β private? or _private (preserve)?
Note: In Python, leading underscore is semantic (private). Don't strip it if it carries meaning.
4. All-caps acronym alone
URL β url (snake) β url (camel)?
API β api
Handled correctly by treating acronyms as words.
5. Mixed with special characters
first.name β depends: split on . too?
user@domain β usually leave @ alone
Decide explicitly what your separators are.
Conversion Matrix (Quick Reference)
Starting from user_profile_id:
| Target | Result |
|---|---|
| camelCase | userProfileId |
| PascalCase | UserProfileId |
| snake_case | user_profile_id |
| kebab-case | user-profile-id |
| SCREAMING_SNAKE | USER_PROFILE_ID |
FAQ
Q: Is it userId or userID?
A: Both are used. Google's style guide recommends userId (acronyms as words) for consistency and reversible conversion. Pick one and stay consistent.
Q: How do I convert API JSON (snake_case) to JavaScript (camelCase) automatically?
A: Libraries like humps (JS) or camelize do this recursively for objects. Or write a recursive key-mapper using the converters above.
Q: Why does my converter turn HTTPResponse into h_t_t_p_response?
A: Your regex inserts a boundary before every uppercase letter. Add the acronym rule: ([A-Z]+)([A-Z][a-z]) to keep consecutive capitals together.
Q: Should CSS use kebab-case or camelCase?
A: CSS properties and classes use kebab-case (background-color, .main-nav). In JavaScript accessing CSS, they become camelCase (element.style.backgroundColor).
Q: What about database columns?
A: Most SQL databases are case-insensitive and conventionally use snake_case (created_at, user_id) to avoid quoting.
Conclusion
Case conversion is straightforward once you handle the gotchas:
- Split then join β the universal two-step approach
- Handle acronyms β treat
HTTPas one word, not four letters - Watch numbers β decide if
address2splits or not - Collapse separators β use
[_-]+and.trim() - Stay consistent β pick
userIdoruserIDand never mix
Use the copy-paste converters above; they handle camelCase, snake_case, kebab-case, PascalCase, and SCREAMING_SNAKE with correct acronym and number handling.