← Back to articles
Technology

Normalize Input Once, at the Door

Normalize Input Once, at the Door

"Search can't find it." "I registered twice with the same email." The cause is often not business logic but input variation: full-width digits, stray spaces, characters that look identical but are not. Where you normalize them is a design decision to make during implementation.

Same look, different value

  • "09012345678" vs "09012345678": full-width and half-width digits do not match
  • "taro@example.com " (trailing space): user lookup misses it
  • "Taro@Example.com" vs "taro@example.com": case differences allow duplicate accounts
  • "é" as one code point vs "e" plus a combining accent: identical on screen, unequal in comparison

What to normalize

  • Full-width and half-width forms: digits, letters, symbols, half-width katakana
  • Whitespace: leading and trailing spaces, ideographic spaces, zero-width spaces
  • Line endings: mixed CRLF and LF
  • Unicode normalization: combining characters
  • Letter case: only for fields that are case-insensitive, such as email addresses

Normalize in exactly one place

If every screen and job tidies input on its own, one miss becomes a duplicate record or a failed search. Normalize once at the entry point (the controller or form submit handler) and let everything downstream handle only normalized values.

export function normalizeText(input: string): string {
  return input
    .normalize("NFKC")                  // unify width forms and combining marks
    .replace(/\r\n?/g, "\n")            // line endings to LF
    .replace(/[​-‍]/g, "") // strip zero-width characters
    .trim();                            // strip surrounding whitespace
}

export function normalizeEmail(input: string): string {
  return normalizeText(input).toLowerCase();
}

NFKC also turns the ideographic space (U+3000) into a plain space, so run it before trim.

When to keep a separate display value

NFKC turns "①" into "1" and "㈱" into "(株)". For fields where the typed form matters, such as names or product titles, keep the original for display and use the normalized value only for search and duplicate checks.

  • Email, phone number, postal code: storing only the normalized value is fine
  • Names, addresses, product titles: keep the original, add a separate column for search

Checklist after implementing

  1. Save a record with full-width digits, then search with half-width digits. Is it found?
  2. Submit with a trailing ideographic space. Is it stripped?
  3. Register two emails that differ only in case. Is the second rejected?
  4. Does the normalizer exist in only one place? (grep to confirm)
  5. Does the DB unique constraint apply to the normalized value?

Putting it into practice with Bugoon

Input-variation bugs are hard to reproduce when the reporter forgets what they typed. With the Bugoon widget embedded in your site, each report carries an annotated screenshot and the recorded operation steps, so developers can see the entered value and the screen state. Reports can flow into GitHub Issues, and the MCP server lets Claude Code or Cursor pick up the fix.

Streamline bug reporting for your team.

Bugoon is free to get started. Add one line of code to your site and transform how your team handles bugs.

Get Started