Skip to main content

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​

MethodWhat it does
SplitWordsSplits 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"
FirstCharToUpperUpper-cases only the first character.
ToSlugBuilds a lower-case, hyphenated, URL-friendly slug.
RemoveDiacriticsRemoves 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.

Splitting identifiers
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:

RuleExampleWords
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:

CaseExampleWords
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"]
note

SplitWords throws ArgumentNullException (parameter str) for a null receiver. An empty or white-space string returns an empty array.

info

"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"].

MethodSeparatorWord casingResult
ToCamelCase()nonefirst word lower, the rest capitalized"invoiceHtmlReportV2"
ToPascalCase()noneevery 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()spacefirst word capitalized, the rest lower"Invoice html report v2"
ToTitleCase()spaceevery word capitalized"Invoice Html Report V2"

"Capitalized" means the first character is upper-case and the rest of the word is lower-case.

Null and white space

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.

camelCase
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"
Obsolete overload

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.

PascalCase
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.

kebab-case
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.

snake_case
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.

CONSTANT_CASE
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 -.

Train-Case
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.

Sentence case
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.

Title Case
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"
tip

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 ı.

Invariant versus current culture
// With CultureInfo.CurrentCulture set to "tr-TR":
string invariant = "ID".ToCamelCase(); // → "id"
string turkish = "ID".ToCamelCase(useCurrentCulture: true); // → "ıd"
warning

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.

Upper-case the first character
string property = "orderTotal".FirstCharToUpper(); // → "OrderTotal"
string greeting = "hello".FirstCharToUpper(); // → "Hello"
string unchangedRest = "hELLO".FirstCharToUpper(); // → "HELLO"
string digitFirst = "1abc".FirstCharToUpper(); // → "1abc"
string empty = "".FirstCharToUpper(); // → ""
note

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.

URL slugs
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:

Non-Latin text
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.

Limiting the length
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"
warning

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.

note

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.

Removing 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.

info

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.

Globalization-invariant mode

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.

note

RemoveDiacritics throws ArgumentNullException (parameter str) for a null receiver. An empty string returns "".

See also​