Skip to main content

type-fest vs ts-essentials vs utility-types: Advanced TypeScript Utility Types (2026)

·PkgPulse Team

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

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 →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.