Skip to main content

Guide

type-fest vs ts-essentials vs utility-types 2026

Compare type-fest, ts-essentials, and utility-types for advanced TypeScript utility types. Deep readonly, branded types, template literal types, and which.

·PkgPulse Team·
0

TL;DR

type-fest is the most popular TypeScript utility type collection — 200+ types by Sindre Sorhus, includes JsonValue, SetRequired, CamelCase, template literal types, and more. ts-essentials provides essential advanced types — DeepReadonly, DeepPartial, DeepRequired, Merge, StrictOmit, with a focus on deep recursive types. utility-types offers Flow-style utility types — $Keys, $Values, $Diff, $PropertyType, plus standard mapped types. In 2026: type-fest for the most comprehensive collection, ts-essentials for deep recursive types, utility-types for Flow-to-TypeScript migration.

Key Takeaways

  • type-fest: ~30M weekly downloads — 200+ types, template literals, JSON types, by Sindre Sorhus
  • ts-essentials: ~5M weekly downloads — deep recursive types, strict versions, focused collection
  • utility-types: ~3M weekly downloads — Flow-style types, $-prefixed helpers, by Piotr Witek
  • TypeScript's built-in utilities (Partial, Required, Pick, Omit) cover basics
  • These libraries fill gaps: deep variants, branded types, string manipulation
  • type-fest is the largest and most actively maintained

type-fest

type-fest — comprehensive utility types:

JSON types

import type { JsonValue, JsonObject, JsonArray, JsonPrimitive } from "type-fest"

// Type-safe JSON handling:
function parseJSON(str: string): JsonValue {
  return JSON.parse(str)
}

function saveConfig(config: JsonObject): void {
  fs.writeFileSync("config.json", JSON.stringify(config))
}

// JsonValue = string | number | boolean | null | JsonObject | JsonArray
// Prevents non-serializable values (functions, undefined, symbols)

Object manipulation

import type {
  SetRequired, SetOptional, SetReadonly,
  ReadonlyDeep, PartialDeep,
  Merge, Simplify,
} from "type-fest"

interface User {
  id: string
  name?: string
  email?: string
  settings?: {
    theme?: string
    notifications?: boolean
  }
}

// Make specific fields required:
type CreateUser = SetRequired<User, "name" | "email">
// → { id: string; name: string; email: string; settings?: {...} }

// Deep partial (all nested fields optional):
type UserPatch = PartialDeep<User>
// → { id?: string; name?: string; settings?: { theme?: string; notifications?: boolean } }

// Deep readonly:
type FrozenUser = ReadonlyDeep<User>
// → { readonly id: string; readonly settings?: { readonly theme?: string; ... } }

// Merge types:
type UserWithRole = Merge<User, { role: "admin" | "user" }>
// → User fields + { role: "admin" | "user" }

// Simplify (flatten intersections for better IntelliSense):
type Flat = Simplify<Pick<User, "id"> & { role: string }>
// IntelliSense shows: { id: string; role: string } instead of Pick<...> & {...}

String manipulation types

import type {
  CamelCase, KebabCase, PascalCase, SnakeCase,
  Split, Join, Replace, Trim,
} from "type-fest"

type A = CamelCase<"hello-world">       // "helloWorld"
type B = KebabCase<"helloWorld">        // "hello-world"
type C = PascalCase<"hello_world">      // "HelloWorld"
type D = SnakeCase<"helloWorld">        // "hello_world"

type E = Split<"a.b.c", ".">           // ["a", "b", "c"]
type F = Join<["a", "b", "c"], "-">    // "a-b-c"
type G = Replace<"hello world", " ", "-"> // "hello-world"
type H = Trim<"  hello  ">             // "hello"

Branded types

import type { Tagged } from "type-fest"

// Create branded/tagged types for type safety:
type UserId = Tagged<string, "UserId">
type PostId = Tagged<string, "PostId">

function getUser(id: UserId): User { /* ... */ }
function getPost(id: PostId): Post { /* ... */ }

const userId = "user-123" as UserId
const postId = "post-456" as PostId

getUser(userId)  // ✅
getUser(postId)  // ❌ Type error — PostId is not UserId

More utilities

import type {
  Promisable, AsyncReturnType,
  ConditionalKeys, ConditionalPick,
  Entries, ValueOf,
  FixedLengthArray,
  RequireAtLeastOne, RequireExactlyOne,
} from "type-fest"

// Promisable — value or promise:
function process(input: Promisable<string>) { /* ... */ }
process("sync")            // ✅
process(Promise.resolve("async")) // ✅

// Get async function return type:
async function fetchUser() { return { id: "1", name: "Royce" } }
type User = AsyncReturnType<typeof fetchUser>
// → { id: string; name: string }

// Require at least one field:
type SearchParams = RequireAtLeastOne<{
  query?: string
  category?: string
  tags?: string[]
}>
// Must provide at least one of query, category, or tags

ts-essentials

ts-essentials — deep recursive types:

Deep types

import type {
  DeepReadonly, DeepPartial, DeepRequired,
  DeepNonNullable, DeepWritable,
} from "ts-essentials"

interface Config {
  database: {
    host: string
    port: number
    credentials: {
      username: string
      password: string
    }
  }
  cache: {
    ttl: number
    maxSize: number
  }
}

// All fields readonly at every level:
type FrozenConfig = DeepReadonly<Config>
// config.database.host = "new"  ❌ Cannot assign to readonly

// All fields optional at every level:
type PartialConfig = DeepPartial<Config>
// { database?: { host?: string; port?: number; credentials?: { ... } } }

// All fields required at every level:
type FullConfig = DeepRequired<Config>

// Remove null/undefined at every level:
type CleanConfig = DeepNonNullable<Config>

Strict mapped types

import type { StrictOmit, StrictExtract, StrictExclude } from "ts-essentials"

interface User {
  id: string
  name: string
  email: string
}

// StrictOmit — errors if you try to omit non-existent keys:
type WithoutEmail = StrictOmit<User, "email">  // ✅
type Invalid = StrictOmit<User, "phone">        // ❌ Error — "phone" not in User

// Built-in Omit doesn't catch this:
type NoError = Omit<User, "phone">              // ✅ No error (unsafe!)

Merge and dictionary types

import type { Merge, Dictionary, SafeDictionary } from "ts-essentials"

// Merge (second type wins on conflicts):
type Base = { id: string; name: string; role: "user" }
type Admin = Merge<Base, { role: "admin"; permissions: string[] }>
// → { id: string; name: string; role: "admin"; permissions: string[] }

// Dictionary — typed record:
type PackageMap = Dictionary<{ version: string; downloads: number }>
// Same as: Record<string, { version: string; downloads: number }>

// SafeDictionary — values may be undefined:
type SafeMap = SafeDictionary<string>
// Same as: Record<string, string | undefined>
// Forces undefined checks on access

Tuple types

import type { Head, Tail, Last, Cons } from "ts-essentials"

type T = [string, number, boolean]

type First = Head<T>   // string
type Rest = Tail<T>    // [number, boolean]
type End = Last<T>     // boolean
type Prepend = Cons<"new", T>  // ["new", string, number, boolean]

utility-types

utility-types — Flow-style types:

$-prefixed types

import type {
  $Keys, $Values, $Diff, $PropertyType, $ElementType,
} from "utility-types"

const theme = {
  primary: "#3b82f6",
  secondary: "#6b7280",
  success: "#10b981",
} as const

// $Keys — union of object keys:
type ThemeKey = $Keys<typeof theme>
// → "primary" | "secondary" | "success"

// $Values — union of object values:
type ThemeColor = $Values<typeof theme>
// → "#3b82f6" | "#6b7280" | "#10b981"

// $PropertyType — type of a specific property:
type Primary = $PropertyType<typeof theme, "primary">
// → "#3b82f6"

// $ElementType — array element type:
type Item = $ElementType<string[], number>
// → string

$Diff and $Subtract

import type { $Diff, Subtract } from "utility-types"

interface Props {
  name: string
  age: number
  email: string
}

interface DefaultProps {
  age: number
}

// $Diff — remove default props:
type RequiredProps = $Diff<Props, DefaultProps>
// → { name: string; email: string }

// Useful for HOC patterns:
function withDefaults<P extends DefaultProps>(
  Component: React.FC<P>
): React.FC<$Diff<P, DefaultProps>> {
  return (props) => <Component {...props} age={25} />
}

Mapped type helpers

import type {
  Optional, DeepReadonly, DeepRequired, DeepPartial,
  Brand, UnionToIntersection,
} from "utility-types"

// Optional — make specific keys optional:
type Opt = Optional<{ a: string; b: number; c: boolean }, "b" | "c">
// → { a: string; b?: number; c?: boolean }

// Brand — branded primitive types:
type USD = Brand<number, "USD">
type EUR = Brand<number, "EUR">

const price: USD = 10 as USD
const euros: EUR = 8.5 as EUR
// Cannot mix USD and EUR accidentally

// UnionToIntersection:
type UI = UnionToIntersection<{ a: string } | { b: number }>
// → { a: string } & { b: number }

Feature Comparison

Featuretype-festts-essentialsutility-types
Total types200+50+40+
Deep recursive✅ (focus)
String manipulation✅ (CamelCase, etc.)
Branded/tagged types
JSON types
Strict Omit/Pick
$-prefixed (Flow)
Tuple utilities
RequireAtLeastOne
Simplify
Maintained✅ (Sindre Sorhus)⚠️
Weekly downloads~30M~5M~3M

When to Use Each

Use type-fest if:

  • Want the most comprehensive type utility collection
  • Need string manipulation types (CamelCase, KebabCase)
  • Need JSON types for type-safe serialization
  • Want actively maintained, well-documented types

Use ts-essentials if:

  • Need deep recursive types (DeepReadonly, DeepPartial)
  • Want strict versions of built-in types (StrictOmit)
  • Need tuple manipulation types
  • Prefer a focused, smaller collection

Use utility-types if:

  • Migrating from Flow to TypeScript ($Keys, $Values, $Diff)
  • Need $Diff for React HOC patterns
  • Want Flow-familiar naming conventions

Practical Applications in Production Codebases

TypeScript utility type libraries earn their place in production codebases by solving real type system problems that appear repeatedly across projects. One of the most common use cases for type-fest is the Simplify type, which flattens intersection types and mapped types into their constituent properties — when TypeScript's IntelliSense shows Pick<User, "id" | "email"> & { role: string } instead of the expected { id: string; email: string; role: string }, wrapping the type in Simplify produces the more readable representation without changing the actual type. The JsonValue type from type-fest is another high-value primitive: using it as the type for any value that will be serialized to JSON catches type errors at compile time when you accidentally pass functions, class instances, or undefined values to JSON.stringify. Production codebases that handle API responses, configuration files, and message payloads benefit from explicit JsonValue boundaries that document serialization contracts in the type system.

Deep Type Operations and Performance Impact

Deep recursive types like DeepReadonly and DeepPartial from ts-essentials are powerful but have TypeScript performance implications that matter in large codebases. TypeScript's type checker evaluates these types lazily, but very deep object hierarchies (10+ levels of nesting) can cause type checking to become noticeably slow. The DeepReadonly type from ts-essentials is implemented with conditional type recursion, and applying it to complex domain objects with many nested levels can slow tsc and IDE response times. In practice, the performance impact is acceptable for most objects up to 5-6 levels of nesting, which covers the majority of configuration objects and API response types. For very deeply nested types, a pragmatic approach is to apply readonly at the top one or two levels manually and accept that deeper nesting is not readonly-enforced, which preserves both type safety where it matters and type checker performance. ts-essentials' implementation is one of the more performance-optimized approaches to deep types available.

TypeScript Version Compatibility and Breaking Changes

TypeScript utility type libraries are closely coupled to the TypeScript version they target, because newer TypeScript versions introduce new type capabilities (template literal types, infer in conditional types, const type parameters) that older versions don't support. type-fest v4.x requires TypeScript 5.x and uses features like const type parameters and the NoInfer utility type introduced in TypeScript 5.4. If your project targets TypeScript 4.x for compatibility with older tooling, type-fest v3.x provides a compatible subset. ts-essentials v10.x targets TypeScript 5.x as well, with deep type operations that leverage the improved recursive type evaluation in TypeScript 5.x. utility-types is the most conservative about TypeScript version requirements, maintaining compatibility with TypeScript 3.x and above, which is part of its continued relevance for legacy codebases that have not yet migrated to TypeScript 5. When choosing between these libraries, verifying compatibility with your minimum supported TypeScript version should be a prerequisite.

Branded and Tagged Type Patterns in Domain Modeling

Branded types (also called opaque types, nominal types, or tagged types) are one of the most valuable type-level patterns for domain modeling, and both type-fest and utility-types provide implementations. The core use case is preventing accidentally mixing semantically different values that share the same structural type. A UserId and an OrderId are both strings structurally, but passing an OrderId where a UserId is expected should be a compile-time error. type-fest's Tagged<string, "UserId"> creates a brand at the type level without any runtime overhead — the brand is erased during compilation, so there is no performance cost. The as UserId cast required when creating a branded value from a plain string is the only ceremony required. This pattern is particularly valuable in repositories and service layers where entity IDs are passed across boundaries, and accidentally correlating entities by mismatched IDs is a real class of production bugs that branded types prevent entirely.

Migration and Adoption Strategy for Teams

Introducing TypeScript utility type libraries into an existing codebase should be incremental rather than wholesale. A practical adoption strategy starts with identifying the pain points: types that are difficult to reason about, IntelliSense that shows unhelpfully complex type names, or runtime errors from incorrect JSON handling that TypeScript didn't catch. Applying Simplify to intersection types in public API surfaces improves developer experience immediately with no semantic change. Adding JsonValue constraints to serialization boundaries requires a one-time audit of the affected functions but catches real bugs immediately. Adopting DeepReadonly for configuration objects and API response types reduces a category of mutation bugs in data transformation code. For teams migrating from Flow to TypeScript, starting with utility-types provides familiar $-prefixed aliases that reduce cognitive overhead during the transition. Once the team is comfortable with TypeScript's type system, gradually replacing Flow-style types with type-fest equivalents produces a more idiomatic TypeScript codebase without requiring a big-bang migration.

Template Literal Types and String Manipulation

One area where type-fest pulls decisively ahead of ts-essentials and utility-types is string manipulation at the type level. TypeScript 4.1 introduced template literal types, and type-fest has built an extensive collection of string utility types on top of this capability. CamelCase, PascalCase, KebabCase, and SnakeCase convert string literal types between naming conventions, which is valuable when building APIs that accept property names in one casing convention but must map them to a different convention internally. The Split<"a.b.c", "."> type produces a tuple ["a", "b", "c"] from a dot-delimited string literal, enabling type-safe deep property path operations. Trim removes leading and trailing whitespace from string literals at the type level, which matters for types derived from user-facing configuration where whitespace variations are possible. These string manipulation types have no equivalents in ts-essentials or utility-types, making type-fest the only choice when your type system needs to reason about string transformations — a need that arises frequently in ORM design, CSS-in-JS libraries, and API client generators that derive TypeScript types from schema definitions.


Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on type-fest v4.x, ts-essentials v10.x, and utility-types v3.x.

Compare TypeScript utilities and developer tooling on PkgPulse →

See also: Lit vs Svelte and Lodash vs Radash vs Native JavaScript, acorn vs @babel/parser vs espree.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.