mrkeyoor.com_
Thu 06 Aug 07:40 UTC
npmUtilsupdated 06 Aug 2026

validator

validator.js is a flat collection of around a hundred string checks and a couple dozen string sanitizers. Each one is a plain function: isEmail(str) returns a boolean, trim(str) returns a string, toInt(str) returns a number or NaN. It covers the formats people keep reimplementing badly, including emails, URLs, IP addresses, UUIDs, credit cards, IBAN and BIC codes, ISO date and country codes, postal codes and mobile phone numbers by locale, hashes, JWTs, and base64. The sanitizer half handles trimming, whitelisting or blacklisting characters, stripping control characters, HTML entity escaping, and canonicalizing an email address so that two spellings of the same Gmail inbox compare equal. It is deliberately not a schema library: there is no object shape, no chaining, and no type inference, just functions you call one at a time.

Verdict

A dependable grab bag of format checks that has been maintained for over a decade, and the right answer when you need IBAN or credit card validation without writing the regex. Just do not use it as your validation layer; pair it with a schema library and import one function at a time.

API stability5/5Still on 13.x after years, with releases that add validators and tighten existing regexes rather than change signatures; the last real break was removing XSS sanitization, and every function is a standalone call with nothing to migrate
Docs3/5The README is a single exhaustive table listing every validator, every option, and every default, which is genuinely complete; there is no docs site, no search, no per-function examples beyond isEmail, and the locale lists make the page enormous to scroll
Maintenance4/5Pushed within the last few days, a named maintainer team of six, and frequent patch releases through 13.15.35 in April 2026; the backlog is the weak spot with roughly 178 open issues and 465 open issues and PRs combined, and the separately maintained @types/validator drifts behind
Ecosystem5/5Around 26M downloads a week, a transitive dependency of express-validator and class-validator, and available through npm, bower, and a CDN build; when a Stack Overflow answer needs an IBAN or credit card check in JavaScript, this is the package it reaches for

Use it if

  • You need a specific format check that is genuinely hard to get right by hand, like IBAN, BIC, credit card, ISO 6346 container ID, or a per-locale postal code
  • You already have a schema library and want to plug format checks into it, which is exactly what a zod .refine or a class-validator decorator wants
  • You want zero runtime dependencies and a per-function import path, so a single check pulls in a few hundred bytes instead of the whole library
  • You need email canonicalization: normalizeEmail knows the Gmail dot and plus rules, the Outlook and Yahoo subaddress rules, and the googlemail.com alias
  • You are validating input that is already a string, such as query parameters, form fields, or CSV cells
Skip it if

Setup reality

npm install validator and you are done: no dependencies, no build step, no peer requirements, and an engines field so loose it still claims Node 0.10. The friction shows up in two places. First, TypeScript. Nothing ships in the package, so you add @types/validator as a dev dependency, and because those types are maintained separately they lag the runtime, which means a check added in a recent patch release may have no declaration yet. Second, import shape. The package has no exports map, so what you write depends on your toolchain: require('validator') or a default ESM import gets the whole thing, validator/lib/isEmail gets one CommonJS function, and validator/es/lib/isEmail gets the tree-shakeable ESM build. Mixing those in one codebase is how you end up shipping the entire library alongside the single function you thought you were importing. Beyond that, the one rule that matters is that everything takes a string. Anything else throws, so untrusted input needs a typeof check first or a String(x) coercion, and the README suggests input + '' for exactly that reason.

Patterns

Check an email address with the options that mattervalidate-email

import validator from 'validator'

validator.isEmail('ada@example.com')                    // true
validator.isEmail('Ada Lovelace <ada@example.com>')     // false
validator.isEmail('Ada Lovelace <ada@example.com>', { allow_display_name: true })  // true

validator.isEmail(input, {
  domain_specific_validation: true,   // apply Gmail's stricter rules
  blacklisted_chars: '+',             // reject plus addressing
  host_blacklist: ['mailinator.com'],
})

A true result only means the string is syntactically an address. It says nothing about whether the mailbox exists or the domain has an MX record, so a confirmation email is still the only real check.

Validate a URL without accepting nonsensevalidate-url

validator.isURL('example.com')                      // true, no protocol needed by default
validator.isURL('example.com', { require_protocol: true })   // false

validator.isURL(input, {
  protocols: ['https'],
  require_protocol: true,
  require_valid_protocol: true,
  disallow_auth: true,
  host_blacklist: ['localhost', '127.0.0.1'],
})

The README marks require_protocol as recommended for a reason: without it, a string carrying authentication info cannot be told apart from a plain host. disallow_auth and a host blacklist are the minimum if the URL will ever be fetched server-side.

Stop a number or undefined from throwingguard-non-strings

function isEmailSafe(value: unknown): boolean {
  return typeof value === 'string' && validator.isEmail(value)
}

// req.query values can be string | string[] | undefined
const raw = req.query.email
if (!isEmailSafe(raw)) return res.status(400).json({ error: 'bad email' })

validator throws on any non-string input rather than returning false. Express query parameters, JSON bodies, and form data all deliver non-strings often enough that an unguarded call is a 500 waiting to happen.

Import one function instead of the whole librarytree-shakeable-import

// ESM, tree-shakeable build
import isEmail from 'validator/es/lib/isEmail'
import isURL from 'validator/es/lib/isURL'

// CommonJS single function
const isEmail = require('validator/lib/isEmail')

// pulls in everything, about 39 KB gzipped
import validator from 'validator'

The package has no exports map, so these three forms all resolve and all behave differently in a bundle. One stray full import anywhere in the dependency graph undoes the per-function imports everywhere else.

Canonicalize an address before storing itnormalize-email

validator.normalizeEmail('Foo.Bar+news@GoogleMail.com')
// 'foobar@gmail.com'

validator.normalizeEmail(input, {
  gmail_remove_dots: false,       // keep dots if your users expect them preserved
  gmail_remove_subaddress: true,
  all_lowercase: true,
})

This does not validate, so run isEmail first. Storing only the normalized form blocks the plus-addressing trick for duplicate signups, but it also merges accounts some users consider separate, so many apps store both the raw and normalized values.

Trim, strip control characters, and restrict the alphabetsanitize-input

let name = validator.trim(input)
name = validator.stripLow(name)                 // drop chars < 32 and 127
name = validator.blacklist(name, '<>\\\\')       // remove these characters

const slug = validator.whitelist(input.toLowerCase(), 'a-z0-9\\-')
const notes = validator.stripLow(input, true)   // keep \n and \r

blacklist and whitelist put your characters straight into a RegExp character class, so backslashes and brackets need escaping. stripLow removes newlines unless you pass true as the second argument, which quietly flattens multi-line text.

Escape HTML entities, and know what that does not coverescape-html

validator.escape('<script>alert(1)</script>')
// '&lt;script&gt;alert(1)&lt;&#x2F;script&gt;'

validator.unescape('&lt;b&gt;')   // '<b>'

escape() replaces < > & ' " ` \ and / with entities. That is safe for text nodes and nothing else: it will not protect an href, a style block, or an inline event handler. For HTML you intend to render, use DOMPurify, which the README recommends after XSS sanitization was removed from this library.

Convert strings to numbers, booleans, and datescoerce-values

const page = validator.toInt(req.query.page ?? '', 10)
if (Number.isNaN(page)) return badRequest('page must be an integer')

validator.toFloat('3.14')          // 3.14
validator.toBoolean('yes')         // true  (anything but '0', 'false', '')
validator.toBoolean('yes', true)   // false (strict: only '1' and 'true')
validator.toDate('2026-08-06')     // Date, or null if unparseable

toInt and toFloat return NaN on failure rather than throwing, and NaN is falsy, so a plain if check treats a failed parse and a legitimate 0 the same way. toDate returns null instead, so the two failure modes need different handling.

Score or gate a passwordstrong-password

validator.isStrongPassword(pw, {
  minLength: 12,
  minLowercase: 1,
  minUppercase: 1,
  minNumbers: 1,
  minSymbols: 1,
})  // boolean

const score = validator.isStrongPassword(pw, { returnScore: true })
// number: use it to drive a strength meter

returnScore flips the return type from boolean to number, which TypeScript cannot narrow for you through a variable options object. This is composition rules only, so it happily accepts Passw0rd! and knows nothing about breach lists.

Check UUIDs, JSON, and other identifiersvalidate-ids

validator.isUUID(id)              // any RFC 9562 version
validator.isUUID(id, '4')         // v4 only
validator.isUUID(id, 'loose')     // UUID-shaped hex, ignoring version and variant bits

validator.isJSON(body)                              // objects and arrays only
validator.isJSON('true', { allow_primitives: true }) // true
validator.isJWT(token)
validator.isBase64(blob, { urlSafe: true })

isUUID defaults to strict RFC checking, which rejects the version-less identifiers some older systems emit; that is what 'loose' is for. isJSON runs JSON.parse internally, so validating a large body costs a full parse you are about to repeat.

Validate locale-specific formatslocale-validators

validator.isPostalCode('SW1A 1AA', 'GB')
validator.isMobilePhone('+919876543210', 'en-IN', { strictMode: true })
validator.isMobilePhone(input, ['en-IN', 'en-US'])
validator.isAlpha('Zoë', 'nl-NL')

validator.isAlphaLocales        // the supported list, at runtime
validator.isMobilePhoneLocales

Passing no locale means 'any', which matches if a single locale in the whole list matches and is nearly useless as a check. These are regex tables maintained by contributors, so coverage varies by country and lags number plan changes.

Get types, and work around the ones that are missingtypescript-setup

$ npm i -D @types/validator

import isEmail from 'validator/lib/isEmail'
import isIBAN from 'validator/lib/isIBAN'

export function assertEmail(value: unknown): asserts value is string {
  if (typeof value !== 'string' || !isEmail(value)) {
    throw new TypeError('not an email')
  }
}

@types/validator is community maintained on DefinitelyTyped and trails the runtime package, so a validator added in a recent patch may not have a declaration. An assertion function like this is also how you get the typeof guard and the format check into one call site.

Alternatives

PackageRegistryPick it when
zodnpmYou are validating whole objects and want a schema plus inferred TypeScript types, not a bag of string predicates
valibotnpmYou want schema validation in a browser bundle and care about every kilobyte, since only the validators you import are included
libphonenumber-jsnpmPhone numbers are the thing you actually need, and you want parsing, normalization, and formatting rather than a locale regex