camelCase vs snake_case vs kebab-case: A Developer's Guide


Last reviewed: October 1, 2026
Short answer: camelCase, snake_case and kebab-case are three ways to join several words into one name without spaces. camelCase capitalizes each word after the first (userProfileId), snake_case joins lowercase words with underscores (user_profile_id) and kebab-case joins them with hyphens (user-profile-id). None is better in general. Use the one your language or platform expects: camelCase for JavaScript, Java and C# locals, snake_case for Python, Ruby and Rust functions and for most SQL schemas, kebab-case for CSS, HTML attributes and URLs.
The rule that matters most is consistency inside each layer, plus a deliberate, automated mapping where layers meet.
This guide defines each style, shows what the official style guides of the major ecosystems actually say, and covers the awkward parts: acronyms, digits, case-insensitive systems and converting names between layers. When you need to rename a batch of identifiers or turn a heading into a slug, a free browser-based case converter saves retyping; the rest of this article explains which target style to choose and what the conversion does to your words.
In this guide
- What case styles are and why consistency matters
- The styles defined
- What each language's style guide recommends
- CSS, HTML, URLs, SQL, JSON and environment variables
- Where kebab-case cannot be used
- Acronyms, numbers and underscores
- Converting between styles
- Mapping names between layers
- Case sensitivity pitfalls
- Enforcing conventions and renaming safely
- Common mistakes
- Cheat sheet
- FAQ
- Sources
What case styles are and why consistency matters
A case style is a rule for writing a multi-word name as a single token: how words are separated and which letters are capitalized. Identifiers cannot contain spaces, so every codebase needs some such rule.
Consistency matters for three practical reasons. First, readers use the shape of a name as a signal: OrderItem looks like a type, orderItem like a variable and ORDER_ITEM like a constant. Second, predictable names are searchable. Third, some systems give case real meaning: Effective Go states that a name is visible outside its package only if its first character is upper case.
The styles defined
Every style answers two questions: what goes between words (nothing, underscore, hyphen, dot) and which letters are upper case.
| Style | Example | Also called | Typical use |
|---|---|---|---|
| camelCase | userProfileId | lowerCamelCase, mixedCase | Variables and functions in JavaScript, Java, C# parameters; JSON keys |
| PascalCase | UserProfileId | UpperCamelCase, CapWords, StudlyCaps | Classes and types almost everywhere; public members in C#; exported names in Go |
| snake_case | user_profile_id | lower_case_with_underscores | Python, Ruby and Rust functions and variables; SQL tables and columns |
| SCREAMING_SNAKE_CASE | USER_PROFILE_ID | CONSTANT_CASE, UPPER_SNAKE_CASE | Constants; environment variables |
| kebab-case | user-profile-id | hyphenated, dash-case | CSS properties and classes, HTML attributes, URL slugs |
| Train-Case | User-Profile-Id | Capitalized kebab-case | The customary spelling of HTTP header names such as Content-Type |
| dot.case | user.profile.id | Namespaced keys in configuration files and package names |
MDN's glossary notes that in camel case the very first letter may be lower case (lower camel case) or upper case (upper camel case, also known as Pascal case).
Two related styles apply to text, not identifiers: sentence case capitalizes only the first word and proper nouns, and title case capitalizes the main words of a heading.
What each language's style guide recommends
Most ecosystems agree on PascalCase for types and disagree on everything else. The table summarizes the guide for each language; details follow.
| Language (guide) | Variables, functions | Classes, types | Constants | Notes |
|---|---|---|---|---|
| Python (PEP 8) | snake_case | CapWords | UPPER_CASE_WITH_UNDERSCORES | Modules: short, all lowercase |
| JavaScript (MDN, Google) | camelCase | PascalCase | CONSTANT_CASE | File names lowercase with _ or - (Google) |
| TypeScript (Google) | lowerCamelCase | UpperCamelCase | CONSTANT_CASE | No leading or trailing underscores |
| Java (Google) | lowerCamelCase | UpperCamelCase | UPPER_SNAKE_CASE | Packages all lowercase, no underscores |
| C# (.NET guidelines) | camelCase for parameters | PascalCase | PascalCase | Public methods and properties are PascalCase too |
| Go (Effective Go) | mixedCaps | MixedCaps | MixedCaps | Upper-case first letter means exported |
| Rust (API Guidelines) | snake_case | UpperCamelCase | SCREAMING_SNAKE_CASE | Modules and macros snake_case |
| Ruby (community guide) | snake_case | CamelCase | SCREAMING_SNAKE_CASE | Files and directories snake_case |
| PHP (PSR-1, PSR-12) | camelCase methods | StudlyCaps | UPPER_CASE with underscores | No rule for property names |
Points the table cannot show:
- Python. PEP 8 says function and variable names should be lowercase with words separated by underscores, classes use CapWords, and modules get short, all-lowercase names.
- JavaScript and TypeScript. MDN's code style guide and the Google JavaScript Style Guide agree on camelCase for variables and functions and PascalCase for classes. The Google TypeScript guide adds UpperCamelCase for interfaces, types and enums and says identifiers must not use an underscore as a prefix or suffix.
- Java. The Google Java Style Guide concatenates lowercase words in package names, as in
com.example.deepspace. - C#. The .NET conventions use PascalCasing for all public member, type and namespace names and camelCasing only for parameters, and say not to use underscores to separate words. A C# method is
GetUserName(), notgetUserName(). - Go. Effective Go says to use
MixedCapsormixedCapsrather than underscores. Package names are lower case, single-word names.Owneris exported;owneris not. - Rust. The API Guidelines follow RFC 430: UpperCamelCase for types, traits and enum variants, snake_case for functions, methods, modules and macros.
- Ruby. There is no single official guide; the table reflects the widely used community Ruby Style Guide.
- PHP. PSR-1 intentionally avoids any recommendation on property names, asking only for consistency. PSR-12 builds on PSR-1 and says names must not be prefixed with a single underscore to indicate protected or private visibility.
CSS, HTML, URLs, SQL, JSON and environment variables
Outside programming languages the conventions are driven by syntax rules and case sensitivity: kebab-case on the web platform, snake_case in SQL, upper snake case for environment variables.
CSS classes and custom properties
CSS property names such as background-color and font-family are kebab-case, so class names and custom properties usually follow suit: .nav-item, --main-color. Custom properties start with two dashes and, per MDN, are case sensitive: --my-color and --My-color are different properties.
HTML attributes and data-* attributes
HTML attributes such as aria-label are kebab-case. The HTML Standard allows attribute names to be written in any mix of upper and lower case, but lowercase is the norm. Custom data-* attributes have a documented bridge to JavaScript: the dataset property removes the data- prefix and converts the rest from kebab-case to camelCase.
<div id="user" data-user-id="42" data-date-of-birth="1990-01-31"></div>
const el = document.querySelector("#user");
el.dataset.userId; // "42"
el.dataset.dateOfBirth; // "1990-01-31"
el.dataset.lastLogin = "today"; // creates data-last-login="today"
MDN recommends that data attribute names contain no capital letters, and warns that a hyphen followed by a non-letter, as in data-test-1, does not map cleanly to dataset.
URLs and slugs
Use lowercase kebab-case for URL paths. Google Search Central recommends hyphens instead of underscores to separate words in URLs because it helps users and search engines identify the concepts in the URL. It also notes that Google treats /APPLE and /apple as distinct URLs, so pick one case, and lowercase is the practical choice.
SQL identifiers
Use snake_case for tables and columns. The reason is technical, not aesthetic: unquoted SQL identifiers are case-insensitive, so camelCase is lost unless you quote every reference (details in the case sensitivity section). The PostgreSQL manual describes the common convention of writing key words in upper case and names in lower case, as in UPDATE my_table SET a = 5;.
Environment variables
Use SCREAMING_SNAKE_CASE, as in DATABASE_URL. POSIX says the environment variable names used by its standard utilities consist solely of uppercase letters, digits and the underscore, and do not begin with a digit. It reserves names containing lowercase letters for applications, so lowercase names are legal, but the upper-case convention is what tools and people expect.
JSON field names
There is no single standard, so choose one style per API and document it. The JSON:API recommendations say member names should be camel-cased. Google's API design guidance (AIP-140) defines protobuf fields in lower_snake_case and maps them to an appropriate convention in JSON. Many public APIs use snake_case keys instead. Any of these is fine; mixing them in one API is not.
File names
Follow the ecosystem: the Google JavaScript guide requires lowercase file names that may include underscores or dashes, and the Ruby community guide uses snake_case files. Avoid file names that differ only by case.
Where kebab-case cannot be used
Kebab-case cannot be an identifier in most programming languages because the hyphen is the minus operator. MDN's glossary makes the same point: hyphens represent subtraction, which makes kebab casing unsuitable for variable names.
const user-name = "Ada"; // SyntaxError
total = user-count; // parsed as: user minus count
// Kebab-case keys are still usable as strings:
const headers = { "content-type": "application/json" };
headers["content-type"]; // works
headers.content-type; // parsed as: headers.content minus type
That is why kebab-case lives where names are not expression syntax: CSS, HTML attributes, URLs and file names. When such a name crosses into code it is converted, as dataset does, or quoted as a string key.
Acronyms, numbers and underscores
Acronyms are where style guides genuinely disagree, so follow the guide for your language rather than a universal rule.
| Guide | Rule | Example |
|---|---|---|
| Google Java, JavaScript, TypeScript | Treat acronyms as ordinary words | newCustomerId, XmlHttpRequest, loadHttpUrl |
| Rust API Guidelines | Acronyms count as one word in UpperCamelCase | Uuid, not UUID |
| .NET | Two-letter acronyms stay upper case; longer ones are capitalized like words | IOStream, HtmlTag; Id, not ID |
| Python PEP 8 | Capitalize all letters of an acronym in CapWords | HTTPServerError |
| Ruby community guide | Keep acronyms upper case in class names | XMLParser |
So is it userID or userId? Under the Google guides and .NET it is userId. Platform names do not always follow either school: MDN points out that XMLHttpRequest mixes both approaches, upper-casing XML but not Http. The treat-as-words rule has a practical advantage: names convert cleanly. HttpServer splits into two words unambiguously, while HTTPServer needs a smarter splitter and HTTPSURL cannot be split by a machine at all.
Numbers. Identifiers cannot start with a digit in most languages, and digits create ambiguous word boundaries: is ipv6Address "ipv6 address" or "ipv 6 address"? The Google Java guide, for example, gives turnOn2sv for "Turn on 2SV". Expect converters to disagree on names like sha256Hash.
Leading underscores and dunders. In Python, underscores carry meaning that sits on top of the case style. PEP 8 and the language reference describe _name as a weak internal-use marker that from module import * skips, name_ as a way to avoid clashing with a keyword, __name inside a class as triggering name mangling, and __name__ ("dunder" names such as __init__) as reserved for the language. Other guides differ: Google's TypeScript guide forbids underscore prefixes and suffixes, and PSR-12 says an underscore prefix has no meaning. A case converter should preserve leading underscores rather than treat them as separators.
Converting between styles
Every conversion is the same two steps: split the name into words, then rejoin the words in the target style. Rejoining is trivial; splitting is where the edge cases are.
The algorithm
- Split on explicit separators: underscores, hyphens, dots and spaces.
- Split on case boundaries: between a lowercase letter or digit and an uppercase letter (
user|Id), and between a run of capitals and a capital that starts a lowercase word (XML|Http). - Lowercase all words.
- Rejoin: capitalize as the target style requires and insert its separator.
function splitWords(name) {
return name
.replace(/([a-z0-9])([A-Z])/g, "$1 $2") // fooBar -> foo Bar
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") // XMLHttp -> XML Http
.split(/[\s_.-]+/)
.filter(Boolean)
.map(w => w.toLowerCase());
}
const cap = w => w[0].toUpperCase() + w.slice(1);
const toSnake = s => splitWords(s).join("_");
const toKebab = s => splitWords(s).join("-");
const toConst = s => splitWords(s).join("_").toUpperCase();
const toPascal = s => splitWords(s).map(cap).join("");
const toCamel = s => splitWords(s)
.map((w, i) => (i === 0 ? w : cap(w))).join("");
Worked examples
| Input | Words | snake_case | camelCase | kebab-case |
|---|---|---|---|---|
userProfileId | user, profile, id | user_profile_id | userProfileId | user-profile-id |
background-color | background, color | background_color | backgroundColor | background-color |
MAX_RETRY_COUNT | max, retry, count | max_retry_count | maxRetryCount | max-retry-count |
XMLHttpRequest | xml, http, request | xml_http_request | xmlHttpRequest | xml-http-request |
HTTPServer | http, server | http_server | httpServer | http-server |
userID | user, id | user_id | userId | user-id |
Tricky inputs
- Round trips are lossy.
userIDbecomesuser_idand comes back asuserId. Acronym capitalization is not stored in snake_case, so it cannot be restored. - Brand-style capitals. The splitter above turns
iOSinto "i, os" (i_os) andIPv6Addressinto "i, pv6, address". Names like these need an exception list or manual review. - Digits. With the rules above
parseHTML5Docsplits as "parse, html5, doc". Other tools put digits in their own word (html_5). Check what your tool does before a bulk rename. - Leading and trailing underscores.
_privateand__init__lose their underscores in a naive split. Strip and re-attach them, or skip such names.
Mapping names between layers
A typical web application uses three or four styles at once, and that is normal. Each layer follows its own convention and a serializer converts at the boundary.
| Layer | Style | Example |
|---|---|---|
| Database column | snake_case | created_at |
| JSON API field | camelCase (or snake_case, by API policy) | createdAt |
| Frontend JavaScript | camelCase | order.createdAt |
| URL path | kebab-case | /order-items/42 |
| Environment variable | SCREAMING_SNAKE_CASE | ORDER_API_URL |
Most serialization libraries and object-relational mappers handle this in one of two ways: a global naming policy that converts every field with the split-and-rejoin algorithm, or a per-field override where you state the external name explicitly. Practical advice:
- Convert in exactly one place per boundary, not ad hoc in each handler.
- Use explicit per-field names for anything with acronyms or digits, since automatic conversion is lossy there.
- Convert keys, never values. A blanket key transformer applied to user-supplied maps (for example a dictionary keyed by product codes) will corrupt data.
Case sensitivity pitfalls
Names that differ only by case are distinct in some systems and identical in others, and bugs appear where the two kinds meet.
File systems
The MySQL manual summarizes the usual situation: file names are not case-sensitive on Windows, are case-sensitive on most Unix systems, and on macOS the default file system is not case-sensitive. An import of ./UserService that resolves to userService.js on a developer laptop can therefore fail on a Linux build server. Keep file names in one lowercase style and make import paths match exactly.
SQL: PostgreSQL vs MySQL
In PostgreSQL, key words and unquoted identifiers are case-insensitive, and unquoted names are folded to lower case. Quoting an identifier makes it case-sensitive. The manual's example: FOO, foo and "foo" are the same identifier, but "Foo" and "FOO" are different from those and from each other. It also notes that the SQL standard folds unquoted names to upper case instead, and advises to always quote a particular name or never quote it.
CREATE TABLE orders ("createdAt" timestamp);
SELECT createdAt FROM orders; -- error: column "createdat" does not exist
SELECT "createdAt" FROM orders; -- works, but must be quoted forever
In MySQL, column, index and stored routine names are not case-sensitive on any platform, but database and table names correspond to directories and files, so their case sensitivity depends on the operating system and the lower_case_table_names setting. The manual recommends always creating and referring to databases and tables using lowercase names for portability. Both databases point to the same conclusion: lowercase snake_case identifiers avoid the whole problem.
HTTP headers and URLs
RFC 9110 states that HTTP field names are case-insensitive, so Content-Type, content-type and CONTENT-TYPE are the same header. Never compare header names with a case-sensitive string match. For URIs, the same RFC says the scheme and host are case-insensitive while all other components are compared case-sensitively, which is why /Docs and /docs can be different pages.
Enforcing conventions and renaming safely
Conventions checked by a linter in continuous integration hold; conventions that are only written down drift.
- ESLint
camelcaseflags underscores inside identifiers, while allowing leading and trailing underscores and all-caps constants. Options such aspropertiesandignoreDestructuringhelp when you consume snake_case API data. - typescript-eslint
naming-conventionlets you assign formats (camelCase, PascalCase, snake_case, UPPER_CASE and strict variants) to selectors such as variables, classes or interfaces. - Pylint
invalid-name(C0103) reports Python names that do not match the expected style for their kind. - Stylelint
selector-class-patternchecks CSS class names against a regular expression you supply, which is how you enforce kebab-case classes.
When you rename existing code to fix a convention:
- Use your editor's or language server's rename refactoring rather than search and replace, so only real references change.
- Search for the old name as a string too: templates, configuration, SQL and documentation are not covered by code-aware renames.
- Treat anything that crosses a boundary (JSON fields, database columns, environment variables, URLs, public functions) as a breaking change. Add the new name, support both for a while, then remove the old one.
- For a file rename that only changes case, check the result on a case-sensitive system before merging.
Common mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
| Carrying one language's style into another (camelCase functions in Python, snake_case methods in Java) | Code clashes with the standard library and every dependency | Follow the target language's guide |
| camelCase column names in PostgreSQL | Folded to lower case unless quoted everywhere | snake_case columns, map in the data layer |
| Underscores in URL slugs | Goes against Google's recommendation to separate words with hyphens | Lowercase kebab-case slugs |
Mixed userID, userId and user_id in one API | Clients must special-case every field | One policy per API, enforced by a serializer |
| Comparing HTTP header names case-sensitively | Header names are case-insensitive | Normalize to lowercase before comparing |
Cheat sheet
| You are naming a... | Use | Example |
|---|---|---|
| JavaScript or TypeScript variable or function | camelCase | fetchUserOrders |
| Class or type (most languages) | PascalCase | OrderRepository |
| Python, Ruby or Rust function or variable | snake_case | fetch_user_orders |
| Constant (Python, Java, JavaScript, Rust) | SCREAMING_SNAKE_CASE | MAX_RETRY_COUNT |
| Environment variable | SCREAMING_SNAKE_CASE | DATABASE_URL |
| SQL table or column | snake_case | order_items.created_at |
| CSS class or custom property | kebab-case | .order-item, --brand-color |
| HTML data attribute | kebab-case (camelCase in dataset) | data-order-id |
| URL slug | lowercase kebab-case | /blog/case-styles-guide |
FAQ
What is the difference between camelCase and PascalCase?
Only the first letter. camelCase starts lower case (orderItem); PascalCase starts upper case (OrderItem). PascalCase is also called UpperCamelCase, and most languages reserve it for classes and types.
Should I use camelCase or snake_case in Python?
snake_case for functions, methods and variables, CapWords for classes and upper case with underscores for constants, as PEP 8 specifies.
Should JSON keys be camelCase or snake_case?
JSON itself does not care. The JSON:API recommendations suggest camelCase, which also matches JavaScript clients; plenty of APIs use snake_case. Pick one for the whole API and convert at the serializer.
Why can't I use kebab-case for variable names?
Because the hyphen is the subtraction operator in most languages, so user-name is parsed as user minus name. Kebab-case works only where names are not part of expression syntax, such as CSS, HTML attributes and URLs.
Is it userId or userID?
It depends on the guide. Google's Java, JavaScript and TypeScript guides and the .NET guidelines give userId. Whichever you choose, apply it everywhere; the inconsistency costs more than either option.
Are hyphens or underscores better in URLs?
Hyphens. Google Search Central recommends hyphens rather than underscores to separate words in URLs.
How do I convert camelCase to snake_case?
Insert a separator at each boundary between a lowercase letter or digit and an uppercase letter, handle runs of capitals so that XMLHttp becomes "XML Http", then lowercase and join with underscores. userProfileId becomes user_profile_id.
Why does my camelCase column not work in PostgreSQL?
PostgreSQL folds unquoted identifiers to lower case, so createdAt is looked up as createdat. You must either quote the name in every query or, better, name the column created_at.
Conclusion
Case styles are conventions with consequences: visibility in Go, identifier folding in SQL, distinct URLs, broken imports on case-sensitive file systems. Follow the official guide for each language, use kebab-case for the web platform and URLs, snake_case for SQL and upper snake case for environment variables, and convert at layer boundaries in one automated place. For one-off renames a case converter tool does the mechanical part; it sits alongside other free developer tools.
Sources referenced in this guide
- PEP 8: Style Guide for Python Code
- Python Language Reference: Lexical analysis (reserved classes of identifiers)
- Google JavaScript Style Guide
- Google TypeScript Style Guide
- Google Java Style Guide
- MDN Web Docs: Guidelines for writing JavaScript code examples
- MDN Web Docs Glossary: Camel case
- MDN Web Docs Glossary: Snake case
- MDN Web Docs Glossary: Kebab case
- MDN Web Docs: HTMLElement.dataset
- MDN Web Docs: data-* global attributes
- MDN Web Docs: Custom properties (--*)
- MDN Web Docs: XMLHttpRequest
- WHATWG HTML Standard: The HTML syntax
- Microsoft Learn: .NET Capitalization Conventions
- Effective Go
- Rust API Guidelines: Naming
- The Ruby Style Guide (community)
- PHP-FIG PSR-1: Basic Coding Standard
- PHP-FIG PSR-12: Extended Coding Style
- PostgreSQL Documentation: Lexical Structure
- MySQL 8.4 Reference Manual: Identifier Case Sensitivity
- Google Search Central: URL structure best practices
- The Open Group Base Specifications (POSIX): Environment Variables
- JSON:API Recommendations
- Google AIP-140: Field names
- IETF RFC 9110: HTTP Semantics
- ESLint: camelcase rule
- typescript-eslint: naming-convention rule
- Pylint: invalid-name (C0103)
- Stylelint: selector-class-pattern


