Normalizing Cases

Converting text to a consistent case so equal values compare as equal.

Case normalization converts text to one consistent case, such as lowercase emails, uppercase state codes, or title case names, so values that mean the same thing compare as equal. Case folding is the stricter version used for comparison, which also maps characters such as the German ß to ss.

Why it matters when importing data

To a computer, Ana@Example.com and ana@example.com are different strings, so mixed case creates duplicate contacts, failed lookups, and unique checks that pass when they should not. Names typed in all caps or all lowercase also look careless when they appear in your product. Normalizing on import fixes both.

Normalizing Cases in JavaScript

Pick the case by what the field is for: lowercase for emails and usernames, uppercase for codes, title case for names you display. This title case function capitalizes after spaces and hyphens, and after the O' and D' prefixes so o'neil becomes O'Neil while macy's stays Macy's. The u flag makes it work for accented letters:

// Title case that capitalizes after spaces and hyphens, and after the O' and D'
// prefixes, so "o'neil" becomes "O'Neil" but "macy's" stays "Macy's".
const toTitleCase = (value) =>
  value
    .trim()
    .toLowerCase()
    .replace(
      /(^|[\s-])(\p{L})/gu,
      (match, separator, letter) => separator + letter.toUpperCase()
    )
    .replace(
      /(^|[\s-])([OD])'(\p{L})/gu,
      (match, separator, prefix, letter) =>
        separator + prefix + "'" + letter.toUpperCase()
    );

const rows = [
  { name: "mary-jane SMITH", email: "Mary.Smith@Example.COM", state: "ca" },
  { name: "o'neil, PAT", email: "PAT@example.com ", state: "Ny" },
  { name: "DAVID mcdonald", email: "d.mcdonald@example.com", state: "tx" },
  { name: "MACY'S buyer team", email: "Buyers@Macys.com", state: "OH" },
];

const results = rows.map((row) => ({
  name: toTitleCase(row.name),
  email: row.email.trim().toLowerCase(),
  state: row.state.trim().toUpperCase(),
}));
console.table(results);

Input

nameemailstate
mary-jane SMITHMary.Smith@Example.COMca
o'neil, PATPAT@example.com Ny
DAVID mcdonaldd.mcdonald@example.comtx
MACY'S buyer teamBuyers@Macys.comOH

Output

NameEmailState
Mary-Jane Smithmary.smith@example.comCA
O'Neil, Patpat@example.comNY
David Mcdonaldd.mcdonald@example.comTX
Macy's Buyer Teambuyers@macys.comOH

Edge cases to handle

  • Names that break title case. McDonald, van der Berg, and DeShawn do not follow the rule, as the last row shows. Only title case names when the source is all caps or all lowercase, and let users correct exceptions.
  • Compare with folding, store with care. For matching, a.localeCompare(b, undefined, { sensitivity: "base" }) === 0 ignores case and accents. Store the user's version unless the field is an identifier such as an email.
  • Locale. Turkish has a dotted and dotless i, so "I".toLowerCase() is wrong for Turkish text. Use toLocaleLowerCase("tr") when the locale is known.
  • Trim first. A trailing space survives case conversion and still breaks equality. Trim before you normalize.

How Dromo handles normalizing cases

Dromo's unique_case_insensitive validator flags duplicates that differ only by case, so Ana@Example.com and ana@example.com are caught even before you normalize them. To change the stored value, a column hook can lowercase, uppercase, or title case an entire column before the review step, and the user can still edit individual cells.

Frequently asked questions

What is case normalization?

Case normalization converts text to a single consistent case, such as all lowercase, so that values that differ only by capitalization are treated as the same value when comparing, deduplicating, or searching.

Should emails be stored in lowercase?

In practice, yes. The domain part of an email address is case-insensitive, and almost every mail provider treats the local part as case-insensitive too, so lowercasing emails on import prevents duplicate accounts. The email standard (RFC 5321) technically allows a case-sensitive local part, so use the lowercased value as the matching key.