Casing and Slugs
These methods turn one naming style into another: a C# property name into a JSON key, a database column into a column header, a product title into a URL. Every case conversion uses the same word splitter, so they all agree on where one word ends and the next begins.
At a glance
| Method | What it does |
|---|---|
SplitWords | Splits PascalCase, camelCase, snake_case, kebab-case or plain text into words. |
ToCamelCase | "order line item" → "orderLineItem" |
ToPascalCase | "order_line_item" → "OrderLineItem" |
ToKebabCase | "OrderLineItem" → "order-line-item" |
ToSnakeCase | "OrderLineItem" → "order_line_item" |
ToConstantCase | "maxRetryCount" → "MAX_RETRY_COUNT" |
ToTrainCase | "contentType" → "Content-Type" |
ToSentenceCase | "ThisIsSampleSentence" → "This is sample sentence" |
ToTitleCase | "user_first_name" → "User First Name" |
FirstCharToUpper | Upper-cases only the first character. |
ToSlug | Builds a lower-case, hyphenated, URL-friendly slug. |
RemoveDiacritics | Removes accents and other combining marks. |
All of these are extension methods on string in the System namespace, so they need no extra using.
Splitting words
SplitWords
SplitWords breaks text into its words and drops the separators. It is the base of every case conversion on this
page. It is also useful on its own, for example to search inside identifiers or to count the words in a column name.
string[] fromMixed = "getHTTPResponse_v2".SplitWords();
// → ["get", "HTTP", "Response", "v2"]
string[] fromAcronym = "HTTPResponse".SplitWords();
// → ["HTTP", "Response"]
string[] fromMixedSeparators = "my_var name".SplitWords();
// → ["my", "var", "name"]
string[] afterDigit = "v2Beta".SplitWords();
// → ["v2", "Beta"]
string[] noisy = "--a..b__".SplitWords();
// → ["a", "b"]
A new word starts at each of these points:
| Rule | Example | Words |
|---|---|---|
Any character that is not a letter or a digit (space, _, -, ., ...) is a separator. A run of separators counts as one. | "already-kebab" | ["already", "kebab"] |
| A lower-case letter followed by an upper-case letter. | "orderTotal" | ["order", "Total"] |
| The end of an acronym: the last upper-case letter of a run, when a lower-case letter follows it. | "HTTPResponse" | ["HTTP", "Response"] |
| An upper-case letter after a digit. | "v2Beta" | ["v2", "Beta"] |
These do not start a new word:
| Case | Example | Words |
|---|---|---|
| A digit after a letter. | "Order2024Total" | ["Order2024", "Total"] |
| A lower-case letter after a digit. | "a1b" | ["a1b"] |
| An all-capitals word with nothing after it. | "ABC" | ["ABC"] |
SplitWords throws ArgumentNullException (parameter str) for a null receiver. An empty or white-space string
returns an empty array.
"Letter", "digit", "upper-case" and "lower-case" follow char.IsLetterOrDigit, char.IsUpper and char.IsLower, so
the rules work for any script, not only ASCII. Scripts without case, such as Arabic, are split only at separators.
Case conversions
Each conversion splits the text with SplitWords, changes the case of each word, and joins the words
with the style's separator. Words that are all capitals are normalized too, so an acronym becomes Html, not HTML,
in PascalCase, camelCase, Train-Case and Title Case.
One input, every style
The table uses the input "invoiceHTMLReport_v2", which SplitWords reads as ["invoice", "HTML", "Report", "v2"].
| Method | Separator | Word casing | Result |
|---|---|---|---|
ToCamelCase() | none | first word lower, the rest capitalized | "invoiceHtmlReportV2" |
ToPascalCase() | none | every word capitalized | "InvoiceHtmlReportV2" |
ToKebabCase() | - | every word lower | "invoice-html-report-v2" |
ToSnakeCase() | _ | every word lower | "invoice_html_report_v2" |
ToConstantCase() | _ | every word upper | "INVOICE_HTML_REPORT_V2" |
ToTrainCase() | - | every word capitalized | "Invoice-Html-Report-V2" |
ToSentenceCase() | space | first word capitalized, the rest lower | "Invoice html report v2" |
ToTitleCase() | space | every word capitalized | "Invoice Html Report V2" |
"Capitalized" means the first character is upper-case and the rest of the word is lower-case.
Unlike SplitWords, the case conversions accept null. A null, empty or white-space-only string is returned
unchanged: ((string?)null).ToKebabCase() returns null, and " ".ToKebabCase() returns " ". The return
type carries [NotNullIfNotNull], so the compiler knows that a non-null input gives a non-null result.
ToCamelCase
Use camelCase for JSON property names, JavaScript identifiers and query-string keys.
string fromAcronym = "HTTPResponse".ToCamelCase(); // → "httpResponse"
string fromWords = "HTTP response code".ToCamelCase(); // → "httpResponseCode"
string fromSnake = "my_var name".ToCamelCase(); // → "myVarName"
string fromKebab = "already-kebab".ToCamelCase(); // → "alreadyKebab"
string withDigit = "v2Beta".ToCamelCase(); // → "v2Beta"
ToCamelCase(bool useCurrentCulture, bool handleAbbreviations) is marked [Obsolete]. The handleAbbreviations
flag is ignored, because acronyms are always handled now. Call ToCamelCase(useCurrentCulture) instead. See
Upgrading from 1.x.
ToPascalCase
Use PascalCase for C# type and member names, for example when you generate code from a schema.
string fromAcronym = "HTTPResponse".ToPascalCase(); // → "HttpResponse"
string fromMixed = "http_response code".ToPascalCase(); // → "HttpResponseCode"
string fromSnake = "my_var name".ToPascalCase(); // → "MyVarName"
string withDigit = "v2Beta".ToPascalCase(); // → "V2Beta"
ToKebabCase
Use kebab-case for CSS classes, URL path segments, command-line options and file names.
string fromPascal = "HTMLParser".ToKebabCase(); // → "html-parser"
string fromCamel = "getHTTPResponse".ToKebabCase(); // → "get-http-response"
string fromSnake = "my_var name".ToKebabCase(); // → "my-var-name"
string withDigit = "v2Beta".ToKebabCase(); // → "v2-beta"
ToSnakeCase
Use snake_case for database columns and for APIs that follow Python or Ruby naming.
string column = "UnitPrice".ToSnakeCase(); // → "unit_price"
string fromPascal = "ThisIsSampleSentence".ToSnakeCase(); // → "this_is_sample_sentence"
string fromAcronym = "HTTPResponse".ToSnakeCase(); // → "http_response"
string withDigit = "v2Beta".ToSnakeCase(); // → "v2_beta"
ToConstantCase
CONSTANT_CASE (also called screaming snake case) suits environment variable names and constants in other languages.
string setting = "maxRetryCount".ToConstantCase(); // → "MAX_RETRY_COUNT"
string fromAcronym = "HTTPResponse".ToConstantCase(); // → "HTTP_RESPONSE"
string fromKebab = "already-kebab".ToConstantCase(); // → "ALREADY_KEBAB"
ToTrainCase
Train-Case is the style of HTTP header names: capitalized words joined by -.
string header = "contentType".ToTrainCase(); // → "Content-Type"
string fromAcronym = "HTTPResponse".ToTrainCase(); // → "Http-Response"
string fromSnake = "my_var name".ToTrainCase(); // → "My-Var-Name"
ToSentenceCase
Sentence case turns an identifier into readable text: the first word is capitalized and every other word is lower-case. It fits validation messages and log text built from property names.
string label = "ThisIsSampleSentence".ToSentenceCase(); // → "This is sample sentence"
string fromAcronym = "HTTPResponse".ToSentenceCase(); // → "Http response"
string fromKebab = "already-kebab".ToSentenceCase(); // → "Already kebab"
string withDigit = "v2Beta".ToSentenceCase(); // → "V2 beta"
ToTitleCase
Title Case capitalizes every word and joins the words with spaces. Use it for column headers and form labels.
string header = "user_first_name".ToTitleCase(); // → "User First Name"
string fromAcronym = "HTTPResponse".ToTitleCase(); // → "Http Response"
string fromKebab = "already-kebab".ToTitleCase(); // → "Already Kebab"
string withDigit = "v2Beta".ToTitleCase(); // → "V2 Beta"
This is not TextInfo.ToTitleCase. It splits identifiers first and does not keep acronyms in capitals
("HTTPResponse" becomes "Http Response"). It has no English rules for small words such as "of" or "the": every
word is capitalized.
Culture
Every conversion above has an optional useCurrentCulture parameter (default false). By default, upper- and
lower-casing use the invariant culture, so the result is the same on every machine. Pass true to use
CultureInfo.CurrentCulture instead. The difference matters in cultures with special casing rules, such as Turkish,
where upper-case I lowers to a dotless ı.
// With CultureInfo.CurrentCulture set to "tr-TR":
string invariant = "ID".ToCamelCase(); // → "id"
string turkish = "ID".ToCamelCase(useCurrentCulture: true); // → "ıd"
Keep the default for anything a program reads back: JSON keys, column names, header names, configuration keys. Use the current culture only for text that a person reads.
Capitalizing
FirstCharToUpper
FirstCharToUpper upper-cases the first character and leaves the rest of the string exactly as it is. Unlike the
case conversions, it does not split words or lower-case anything.
string property = "orderTotal".FirstCharToUpper(); // → "OrderTotal"
string greeting = "hello".FirstCharToUpper(); // → "Hello"
string unchangedRest = "hELLO".FirstCharToUpper(); // → "HELLO"
string digitFirst = "1abc".FirstCharToUpper(); // → "1abc"
string empty = "".FirstCharToUpper(); // → ""
FirstCharToUpper always uses the invariant culture and has no culture parameter. It throws ArgumentNullException
(parameter input) for a null receiver. An empty string is returned as is.
Slugs and diacritics
ToSlug
ToSlug(int? maxLength = null) builds a URL-friendly slug. It removes accents, lower-cases the text with the
invariant culture, and joins each run of letters and digits with a single -. Leading and trailing separators are
dropped. Use it for blog post URLs, product handles and file names made from titles.
string post = "Héllo, Wörld! 2024".ToSlug(); // → "hello-world-2024"
string padded = " --Hello--World-- ".ToSlug(); // → "hello-world"
string tech = "C# & .NET".ToSlug(); // → "c-net"
string report = "Q3 Sales Report: Café & Crème!".ToSlug(); // → "q3-sales-report-cafe-creme"
string noLetters = "!!!".ToSlug(); // → ""
Letters from any script are kept, so non-Latin titles still give readable slugs:
string arabic = "مرحبا بالعالم".ToSlug(); // → "مرحبا-بالعالم"
Pass maxLength to cap the length, for example to fit a database column. A cut never leaves a trailing -, so the
result can be shorter than maxLength.
string cutAtWord = "hello world".ToSlug(6); // → "hello"
string cutInWord = "hello world".ToSlug(7); // → "hello-w"
string shortEnough = "hello world".ToSlug(50); // → "hello-world"
string handle = "Q3 Sales Report: Café & Crème!".ToSlug(16); // → "q3-sales-report"
The cut counts characters and can split a word ("hello-w" above). The result is empty when the text has no letters
or digits, so check for "" before you use a slug as a key.
ToSlug throws ArgumentNullException (parameter str) for a null receiver, and ArgumentOutOfRangeException
(parameter maxLength) when maxLength is zero or negative. A null maxLength means no limit. Accent removal
depends on Unicode normalization: see the globalization-invariant mode warning under
RemoveDiacritics.
RemoveDiacritics
RemoveDiacritics removes accents and other combining marks and keeps the base letters. It also removes Arabic
diacritics (harakat). Use it to build search keys, or to compare names typed with and without accents.
string dessert = "Crème brûlée".RemoveDiacritics(); // → "Creme brulee"
string city = "São Paulo".RemoveDiacritics(); // → "Sao Paulo"
string greeting = "Héllo Wörld".RemoveDiacritics(); // → "Hello World"
string arabic = "مَرْحَبًا".RemoveDiacritics(); // → "مرحبا"
string plain = "plain".RemoveDiacritics(); // → "plain"
The method decomposes the text (Unicode normalization form D), drops every non-spacing mark, and recomposes the rest (form C). Case and punctuation do not change.
Only letters built from a base letter plus a mark lose anything. Letters that are separate characters in Unicode,
such as ß, ø or æ, are kept as they are.
The decomposition needs the platform's Unicode data. In an app that runs with InvariantGlobalization enabled, .NET
does not decompose precomposed letters, so "Crème brûlée".RemoveDiacritics() returns "Crème brûlée" unchanged
and ToSlug keeps the accents ("crème-brûlée"). Only marks that are already separate characters, such as Arabic
harakat or an e followed by the combining accent U+0301, are still removed.
RemoveDiacritics throws ArgumentNullException (parameter str) for a null receiver. An empty string returns
"".
See also
- String predicates: checks such as empty, numeric or matching, which return
falsefornull. - String manipulation: trimming, truncating, replacing and other edits.
- Text and humanize: plurals and other text formatted for people.
- Conventions: the null, culture and exception rules shared by the whole library.