is
TypeScript-first runtime type checking with type guards, assertions, and generic-aware inference for over 100 JavaScript and TypeScript value types.
Repository Health
Technical Analysis
@sindresorhus/is is a single-purpose utility for checking the type of a JavaScript value at runtime, distinguishing itself from the plain typeof operator by returning consistent, camelCased names for the full range of built-in object types (Array, Map, Promise, RegExp, and dozens more) in addition to primitives, and by treating edge cases like NaN and boxed primitives (new String('foo')) explicitly rather than silently.
Beyond the top-level is(value) type-detector, the package exposes over a hundred individual predicate methods (is.string, is.plainObject, is.nonEmptyArray, is.urlString, and so on) that double as TypeScript type guards, narrowing the type of a value in the branch where the check passes. A parallel assert namespace mirrors every predicate but throws a descriptive TypeError (with an optional custom message) instead of returning a boolean, which is useful for input validation at function boundaries. Combinator helpers like is.any, is.all, and is.arrayOf let predicates be composed, and every method is also available as a named export so bundlers can tree-shake unused checks.
The library is written entirely in TypeScript and makes deliberate use of branded types internally so that type guards preserve useful narrowing in both the true and false branches — a subtlety documented in the project’s own AGENTS.md notes about why numeric guards like is.integer return a branded type rather than plain number. It has no runtime dependencies and ships as a single ESM module.
What You Get
- Unified type detector -
is(value)returns a consistent, camelCased type name for any JS value (primitives and 40+ built-in object types), unlike rawtypeof. - 100+ typed predicates - Methods like
is.string,is.plainObject,is.nonEmptyArray, andis.urlStringdouble as TypeScript type guards that narrow the checked value’s type. - Throwing assertions - A parallel
assert.*namespace mirrors every predicate but throws aTypeError(with optional custom message) instead of returningfalse, suited to guarding function inputs. - Predicate combinators -
is.any,is.all, andis.arrayOfcombine existing predicates into new ones without hand-written boilerplate. - Tree-shakeable named exports - Every method is also exported individually (e.g.
import {isString} from '@sindresorhus/is') so bundlers only include the checks actually used. - Careful edge-case handling - Deviates from
typeofwhere it matters, e.g.is.number(NaN)returnsfalse, and boxed primitives likenew String('foo')throw rather than silently misreport as'Object'.
Common Use Cases
- Runtime input guarding - Validating function arguments or parsed JSON at the boundary of a module before trusting their shape, using
assert.*to fail fast with a clear error. - TypeScript narrowing in generic code - Using
is.*predicates as type guards inside conditionals to narrowunknownor union types without unsafe casts. - Cross-realm/object type detection - Distinguishing
Map,Set,Promise,RegExp, typed arrays, and other built-ins reliably, including cases where naiveinstanceofchecks fail across realms or with subclassing. - Building higher-level validation utilities - Composing
is.any/is.all/is.arrayOfas primitives inside a project’s own validation or parsing layer instead of re-implementing type checks from scratch.
Under The Hood
Architecture
The entire package is a flat, single-module design: source/index.ts defines every is.* predicate and assert.* function as a plain exported TypeScript function, then reassembles them onto a callable is object via Object.assign(detect, {...}) near the top of the file, so is(value) and is.string(value) are two views over the same function table rather than separate mechanisms. Type detection itself funnels through a small internal detect/getObjectType pair that switches on typeof, then falls back to Object.prototype.toString tag-sniffing for object types, with dedicated handling carved out for Observable, Buffer, Promise-like thenables, and boxed primitives (which intentionally throw via isBoxedPrimitiveObject). Shared type-level plumbing (ArrayLike, Predicate, branded numeric types) lives in source/types.ts, and the only non-trivial runtime helper (keysOf) is isolated in source/utilities.ts. Nothing here is layered or dependency-injected — correctness and TypeScript inference are the organizing constraints, not runtime extensibility, so changing the shared detect/getObjectType core would ripple through nearly every predicate that relies on it.
Tech Stack
The project is 100% TypeScript, built with plain tsc (no bundler) into a distribution/ folder consumed via the package’s exports map, targeting Node.js 22+ as an ESM-only module with sideEffects: false for tree-shaking. There are no runtime dependencies at all; devDependencies are limited to tooling — xo (ESLint preset) for linting, typescript for compilation and type-checking, del-cli for clean builds, and a small test-only set (jsdom, rxjs, zen-observable, tempy, expect-type) used purely to exercise type detection against real-world object shapes (DOM elements, Observables, temp files) without adding them as production dependencies.
Code Quality
Testing is thorough and dual-layered: test/test.ts (2,800+ lines) uses Node’s built-in node:test runner to assert runtime behavior for every predicate against a wide matrix of inputs, while test/type-tests.ts uses expect-type to assert the TypeScript inference itself (that a guard actually narrows to the expected type). The test script chains tsc --noEmit twice (once for source, once for the test project) before running tests, and CI (.github/workflows/main.yml) runs this full suite against Node 22 and 24 on every push and PR. Error handling is explicit and typed throughout (TypeError with descriptive messages, never silent false-on-error), naming is consistent (is/assert + camelCase noun), and the repo’s own AGENTS.md documents a deliberate branded-type convention specifically to keep type guards useful in their non-matching branch — a sign of unusually careful attention to TypeScript ergonomics rather than just runtime correctness.
API Design
The public surface is unusually consistent for a 100+-method library: every predicate follows the same is<Noun>(value) / assert<Noun>(value, message?) naming pair, every method is both a property on the default is/assert objects and an individually tree-shakeable named export, and the README documents each one under a shared template (signature, short note, edge-case caveats). Getting started requires a single import and no configuration — import is from '@sindresorhus/is' or import {isString} from '@sindresorhus/is' — with zero setup cost, and assertions optionally accept a custom error message as their second argument, letting callers produce domain-specific errors without extra wrapping code.