type-fest vs ts-essentials vs utility-types: Advanced TypeScript Utility Types (2026)
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
| Feature | type-fest | ts-essentials | utility-types |
|---|---|---|---|
| Total types | 200+ | 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 →