type-fest

SKILL.md

type-fest: Advanced TypeScript Utility Types

type-fest provides 180+ compile-time utility types with zero runtime overhead for production TypeScript applications. Use it when built-in TypeScript utilities are insufficient for strict type operations, deep transformations, or complex type-level programming.

When to Use This Skill

Activate for:

  • API response/request type transformations (snake_case ↔ camelCase)
  • Deep configuration merging and partial updates
  • Immutable state management with readonly guarantees
  • JSON serialization type safety (Date → string, etc.)
  • Removing/selecting object properties with strict checking
  • Type-safe path access and property selection
  • Async function return type extraction
  • Conditional type operations

Skip for:

  • Runtime validation (use zod/yup/io-ts - type-fest is compile-time only)
  • Simple cases covered by built-in Partial, Pick, Omit
  • Runtime type guards (complement type-fest with runtime checks)

Installation

npm install type-fest

Import granularly for optimal tree-shaking:

import type {Except, Merge, PartialDeep, Jsonify} from 'type-fest';

Essential Utilities

Except<T, K> - Strict Property Removal

Use when: Removing keys from objects with compile-time verification.

Advantage over Omit: Catches typos - requires removed keys to actually exist.

import type {Except} from 'type-fest';

interface User {
  id: number;
  email: string;
  password: string;
  role: string;
}

// Remove sensitive fields for public API
type PublicUser = Except<User, 'password'>;
// Result: { id: number; email: string; role: string }

// Catches typos
type Bad = Except<User, 'pasword'>; // ✗ Error: 'pasword' doesn't exist

// Strict mode prevents re-adding omitted props
type StrictPublic = Except<User, 'password', {requireExactProps: true}>;

Merge<T, U> - Type-Safe Object Merging

Use when: Combining types where second type's properties override the first.

import type {Merge} from 'type-fest';

interface BaseConfig {
  host: string;
  port: number;
  timeout: number;
}

interface ProdOverrides {
  port: 3306;  // Literal type
  ssl: boolean;
  poolSize: number;
}

type ProdConfig = Merge<BaseConfig, ProdOverrides>;
// Result: { host: string; port: 3306; timeout: number; ssl: boolean; poolSize: number }

PartialDeep<T> - Recursive Optional Properties

Use when: Deep configuration objects or nested partial updates.

import type {PartialDeep} from 'type-fest';

interface AppSettings {
  theme: {
    colors: {
      primary: string;
      secondary: string;
    };
    fontSize: number;
  };
  features: {
    autoSave: boolean;
  };
}

// User can override any nested property
function updateSettings(updates: PartialDeep<AppSettings>) {
  // updates.theme?.colors?.primary is allowed
}

// Valid calls
updateSettings({theme: {colors: {primary: '#ff0000'}}});
updateSettings({features: {autoSave: false}});
updateSettings({theme: {fontSize: 16}});

Jsonify<T> - JSON Serialization Types

Use when: Typing API responses/requests that undergo JSON serialization.

What it does:

  • Datestring
  • Map, Set{}
  • Functions, undefined → removed
  • Recursively transforms nested types
import type {Jsonify} from 'type-fest';

interface Activity {
  userId: number;
  timestamp: Date;
  metadata: Map<string, string>;
  handler: () => void;
}

type ApiActivity = Jsonify<Activity>;
// Result: { userId: number; timestamp: string; metadata: {} }
// (handler and Map removed, Date → string)

async function fetchActivity(id: string): Promise<Activity> {
  const response = await fetch(`/api/activity/${id}`);
  const json: ApiActivity = await response.json();
  
  // Reconstruct proper types
  return {
    userId: json.userId,
    timestamp: new Date(json.timestamp), // string → Date
    metadata: new Map(),
    handler: () => console.log('loaded')
  };
}

ReadonlyDeep<T> - Deep Immutability

Use when: Enforcing immutability in state management, config objects.

import type {ReadonlyDeep} from 'type-fest';

interface State {
  users: Array<{
    id: number;
    profile: {name: string};
  }>;
}

type ImmutableState = ReadonlyDeep<State>;

const state: ImmutableState = {users: [{id: 1, profile: {name: 'John'}}]};

// All modification attempts cause errors
state.users.push(...); // ✗ Error
state.users[0].profile.name = 'Jane'; // ✗ Error

CamelCase<T> - Case Transformation

Use when: Converting API/database naming to JavaScript conventions.

import type {CamelCase} from 'type-fest';

interface DbUser {
  'user-id': number;
  'email_address': string;
  'created_at': string;
}

type AppUser = {
  [K in keyof DbUser as CamelCase<K>]: DbUser[K]
};
// Result: { userId: number; emailAddress: string; createdAt: string }

// Also available: SnakeCase, KebabCase, PascalCase

LiteralUnion<T, U> - Autocomplete-Preserving Unions

Use when: Creating extensible string enums that preserve IDE autocomplete.

Problem solved: 'red' | 'blue' | string loses autocomplete for 'red' and 'blue'.

import type {LiteralUnion} from 'type-fest';

// Without LiteralUnion - no autocomplete
type BadColor = 'red' | 'blue' | 'green' | string;

// With LiteralUnion - IDE autocompletes known values
type Color = LiteralUnion<'red' | 'blue' | 'green', string>;

function setTheme(color: Color) {
  // IDE suggests 'red', 'blue', 'green'
  // But also accepts '#FF5733', 'custom-color', etc.
}

AsyncReturnType<T> - Unwrap Promise Types

Use when: Typing async function results without manually unwrapping Promise.

import type {AsyncReturnType} from 'type-fest';

async function fetchUser(id: number) {
  const res = await fetch(`/api/users/${id}`);
  const data = await res.json();
  return {
    id,
    name: data.name,
    email: data.email,
    roles: data.roles as string[]
  };
}

type User = AsyncReturnType<typeof fetchUser>;
// Result: { id: number; name: any; email: any; roles: string[] }

// Use in callbacks
function processUsers(users: User[]) {
  users.forEach(u => console.log(u.name));
}

RequireAtLeastOne<T, K> - Conditional Required Props

Use when: Enforcing "at least one of these fields must be present".

import type {RequireAtLeastOne} from 'type-fest';

interface SearchParams {
  query?: string;
  userId?: number;
  email?: string;
  tags?: string[];
}

type ValidSearch = RequireAtLeastOne<SearchParams, 'query' | 'userId' | 'email'>;

function search(params: ValidSearch) {
  // At least one of: query, userId, or email is guaranteed
}

search({query: 'test'}); // ✓
search({userId: 123, tags: ['tag']}); // ✓
search({tags: ['tag']}); // ✗ Error: need query, userId, or email

ConditionalPick<T, Condition> - Select by Type

Use when: Extracting properties that match a specific type.

import type {ConditionalPick} from 'type-fest';

interface Endpoint {
  path: string;
  method: 'GET' | 'POST';
  timeout: number;
  retries: number;
  handler: (req: any) => Promise<any>;
  validate: (data: any) => boolean;
}

// Extract only functions
type EndpointFns = ConditionalPick<Endpoint, Function>;
// Result: { handler: ...; validate: ... }

// Extract only numbers
type EndpointNums = ConditionalPick<Endpoint, number>;
// Result: { timeout: number; retries: number }

Paths<T> - Type-Safe Property Paths

Use when: Building type-safe get/set utilities like lodash.

import type {Paths} from 'type-fest';

interface Config {
  database: {
    host: string;
    credentials: {
      user: string;
      password: string;
    };
  };
  api: {endpoint: string};
}

type ConfigPath = Paths<Config>;
// Union: 'database' | 'database.host' | 'database.credentials' | 
//        'database.credentials.user' | 'database.credentials.password' | 
//        'api' | 'api.endpoint'

function get<P extends ConfigPath>(config: Config, path: P): any {
  return path.split('.').reduce((obj: any, key) => obj?.[key], config);
}

get(config, 'database.credentials.user'); // ✓ Autocompletes
get(config, 'invalid.path'); // ✗ Type error

Simplify<T> - Flatten Intersection Types

Use when: Improving IDE hints and type assignability for composed types.

import type {Simplify} from 'type-fest';

type Position = {x: number; y: number};
type Size = {width: number; height: number};
type Styles = {color: string; opacity: number};

// Without Simplify - IDE shows: Position & Size & Styles
type Complex = Position & Size & Styles;

// With Simplify - IDE shows flat object
type Simple = Simplify<Position & Size & Styles>;
// Result: { x: number; y: number; width: number; height: number; color: string; opacity: number }

Common Patterns

API Client with Type Transformations

import type {Except, Jsonify, CamelCase, AsyncReturnType, ReadonlyDeep} from 'type-fest';

// External API (snake_case)
interface ApiUser {
  user_id: number;
  email_address: string;
  created_at: string;
}

// Internal app (camelCase)
type AppUser = {
  [K in keyof ApiUser as CamelCase<K>]: ApiUser[K]
};

// Public user (no sensitive data)
type PublicUser = Except<AppUser, 'emailAddress'>;

// Immutable state
type UserState = ReadonlyDeep<AppUser>;

class UserService {
  async getUser(id: number): Promise<AppUser> {
    const res = await fetch(`/api/users/${id}`);
    const data: Jsonify<ApiUser> = await res.json();
    
    return {
      userId: data.user_id,
      emailAddress: data.email_address,
      createdAt: data.created_at
    };
  }
}

type User = AsyncReturnType<UserService['getUser']>;

Configuration Management

import type {PartialDeep, Merge, Simplify} from 'type-fest';

interface BaseConfig {
  database: {host: string; port: number};
  api: {timeout: number};
}

interface EnvOverrides {
  database: {port: 3306};
  api: {retries: number};
}

// Merge and flatten
type FinalConfig = Simplify<Merge<BaseConfig, EnvOverrides>>;

function loadConfig(
  defaults: BaseConfig,
  overrides: PartialDeep<EnvOverrides>
): FinalConfig {
  // Deep merge logic
  return {...defaults, ...overrides} as FinalConfig;
}

Type-Safe Builders

import type {RequireAtLeastOne, Merge} from 'type-fest';

interface BaseRequest {
  timeout?: number;
  headers?: Record<string, string>;
}

interface RequiredFields {
  url: string;
}

type HttpRequest = RequireAtLeastOne<
  Merge<RequiredFields, BaseRequest>,
  'url'
>;

class HttpClient {
  request(config: HttpRequest) {
    // config.url guaranteed to exist
    fetch(config.url, {
      method: 'GET',
      headers: config.headers
    });
  }
}

Composition Strategy

Combine utilities for powerful type transformations:

import type {Merge, Simplify, PartialDeep, Except} from 'type-fest';

// Base + overrides + flatten
type CleanMerge<T, U> = Simplify<Merge<T, U>>;

// Remove sensitive + deep partial
type SafePartial<T> = PartialDeep<Except<T, 'password' | 'secret'>>;

// Chain multiple operations
type ComplexType = Simplify<
  Merge<
    Merge<BaseType, OverrideType>,
    FinalType
  >
>;

Important Gotchas

1. Runtime vs Compile-Time

CRITICAL: type-fest types don't validate at runtime.

// DON'T assume runtime safety
function process(data: Jsonify<MyType>) {
  const date = new Date(data.timestamp); // Could fail if data is malformed
}

// DO add runtime validation
function process(data: Jsonify<MyType>) {
  if (typeof data.timestamp !== 'string') {
    throw new Error('Invalid timestamp');
  }
  const date = new Date(data.timestamp);
}

2. Deep Type Nesting Limits

Avoid excessive PartialDeep/ReadonlyDeep nesting (3-4 levels max):

// DON'T
type Bad = PartialDeep<PartialDeep<DeepType>>;

// DO
type Good = PartialDeep<DeepType>;

3. Import Only What You Need

Tree-shaking works best with granular imports:

// DO
import type {Except, Merge} from 'type-fest';

// DON'T
import type * as TypeFest from 'type-fest';

4. LiteralUnion Scope

Don't use with already-narrow types:

// DON'T
type Bad = LiteralUnion<'active', 'active' | 'inactive'>;

// DO
type Good = LiteralUnion<'active' | 'inactive', string>;

Quick Reference

Need type-fest Utility Alternative
Remove keys (strict) Except<T, K> Omit<T, K> (lenient)
Merge objects Merge<T, U> T & U (intersection)
Deep partial PartialDeep<T> Partial<T> (shallow)
Deep readonly ReadonlyDeep<T> Readonly<T> (shallow)
JSON-safe types Jsonify<T> Manual transformation
Case conversion CamelCase<T> Manual mapping
Extensible enums LiteralUnion<T, U> T | U (no autocomplete)
Unwrap Promise AsyncReturnType<T> Manual extraction
Require some keys RequireAtLeastOne<T, K> Custom conditional types
Pick by type ConditionalPick<T, C> Custom mapped types
Type paths Paths<T> String literals
Flatten types Simplify<T> Intersection types

Additional Utilities

Also available (180+ total):

  • RequireExactlyOne - Exactly one key required
  • SetRequired, SetOptional - Modify individual properties
  • SnakeCase, KebabCase, PascalCase - Other case conversions
  • IsEqual<T, U> - Type equality checking
  • Get<T, Path> - Type-safe property access
  • Opaque<T> - Branded types
  • PromiseValue<T> - Extract Promise value
  • Plus 170+ more specialized utilities

Browse the full list: https://github.com/sindresorhus/type-fest

Resources

Weekly Installs
11
First Seen
Feb 2, 2026
Installed on
opencode11
gemini-cli10
github-copilot10
codex10
kimi-cli10
amp9