typescriptseverity: can-fix
TS2322

TypeScript TS2322: Type 'X' is not assignable to type 'Y'

Type is not assignable

98% fixable~5 mindifficulty: beginner

Verified against TypeScript docs: Compiler Errors, TypeScript handbook: Type Compatibility, TypeScript 5.5 release notes · Updated May 2026

> quick_fix

TypeScript found a type that doesn't fit the slot it's being assigned to. Read which property differs: the error names both types and points at the divergence. Fix one of three ways: narrow the source type with a type guard, change the target type, or add the missing property/null check.

// Common pattern: missing null check
function Component({ user }: { user: User | null }) {
  // TS2322: Type 'string | undefined' is not assignable to type 'string'
  return <p>{user.name}</p>  // err: user might be null
}

// Fix: narrow with a guard
function Component({ user }: { user: User | null }) {
  if (!user) return null
  return <p>{user.name}</p>  // user is User here
}

What causes this error

TS2322 fires when an expression's inferred type isn't assignable to the type expected by the destination - a function parameter, return type, variable annotation, or property assignment. TypeScript checks structural compatibility; the error message names the source and target types and (for object types) the specific property that differs.

> advertisementAdSense placeholder

How to fix it

  1. 01

    step 1

    Read the full error message

    TS2322 names both types and (for objects) which property is incompatible. The 'Types of property X are incompatible' suffix is the most actionable line.

    Type 'User' is not assignable to type 'AdminUser'.
      Property 'role' is missing in type 'User' but required in type 'AdminUser'.
  2. 02

    step 2

    Check for null/undefined in the source type

    Most TS2322 errors come from strictNullChecks: the source type includes null or undefined but the target doesn't. Add a guard, use the non-null assertion (sparingly), or change the target type to allow null.

    // Bad - target requires non-null
    const name: string = user?.name  // TS2322 if user can be undefined
    
    // Fix 1: narrow
    if (user) {
      const name: string = user.name
    }
    
    // Fix 2: default
    const name: string = user?.name ?? ''
    
    // Fix 3: type allows undefined
    const name: string | undefined = user?.name
  3. 03

    step 3

    Check for missing properties on object types

    If you're passing an object literal to a typed slot, TypeScript checks every required property. The error names which one is missing.

    type Props = { id: number; name: string; email: string }
    
    // TS2322: Property 'email' is missing
    const p: Props = { id: 1, name: 'a' }
    
    // Fix
    const p: Props = { id: 1, name: 'a', email: '' }
  4. 04

    step 4

    Check for excess properties

    Object literals get strict excess-property checking. If you pass a property the target type doesn't define, TypeScript rejects it. Either remove it, widen the target type, or assign through an intermediate variable to bypass excess-property checking.

    type Props = { id: number; name: string }
    
    // TS2322: 'extra' does not exist in type 'Props'
    const p: Props = { id: 1, name: 'a', extra: true }
    
    // Fix - widen type or remove
    type Props = { id: number; name: string; extra?: boolean }
  5. 05

    step 5

    Check for union vs intersection mismatches

    Assigning a union to a stricter slot fails. If a variable is 'string | number' and the target wants only 'string', narrow first with typeof or a type guard.

    function getId(): string | number { return 'abc' }
    
    // TS2322
    const s: string = getId()
    
    // Fix - narrow
    const v = getId()
    const s: string = typeof v === 'string' ? v : String(v)
  6. 06

    step 6

    Use a type guard for unknown sources

    Data from JSON.parse, fetch, or external APIs is often 'unknown' or 'any'. Validate with zod, custom type predicates, or in/instanceof checks before assigning.

    function isUser(x: unknown): x is User {
      return typeof x === 'object' && x !== null && 'id' in x && 'name' in x
    }
    
    const data: unknown = JSON.parse(raw)
    if (isUser(data)) {
      const u: User = data  // OK
    }

Why TS2322 happens at the runtime level

TypeScript's checker performs structural type assignability via the assignableTo function in src/compiler/checker.ts. For object types, it walks every required property of the target and verifies the source has a compatible property; for unions, it checks every member; for intersections, every member. When any check fails, the compiler emits TS2322 with the most specific failure path it can find. Object literals get an additional excess-property check that fires earlier than full assignability. Conditional and mapped types add another layer where the checker may produce 'deeply nested' errors that mention types by their full structural form instead of named aliases.

Common debug mistakes for TS2322

  • Forgetting strictNullChecks; assigning user.name when user could be undefined.
  • Adding a property to a literal that the target type doesn't allow (excess property check).
  • Returning a union type from a function whose signature promises a single type.
  • Using 'as' to silence the error instead of fixing the underlying type mismatch.
  • Not validating external JSON before treating it as a typed object - the cast lies.

When TS2322 signals a deeper problem

Recurring TS2322 errors in a codebase usually mean type definitions and runtime data have drifted. The architectural fix is to validate at the boundary: zod or valibot at API entry points, schema-first ORM at the database layer, and typed client SDKs for external services. With validation at the edges, internal types are trusted and TS2322 fires only on real bugs. Without it, every fetch and JSON.parse produces a typed value the compiler trusts but reality may not match - meaning TS2322 errors get silenced with 'as' and runtime crashes follow. The deeper signal is that the codebase needs a contract layer where types and runtime agree.

Frequently asked questions

Why does TypeScript reject an object literal that 'should' work?

Object literals get excess-property checking - even if all required properties exist, an extra property triggers TS2322. The reasoning: object literals are usually typos waiting to happen, and the strictness catches misspelled property names. To bypass, assign to an untyped variable first, then to the typed slot. Better: declare the type more permissively, or use Partial<T> if some properties are optional. The strictness is intentional and you should usually fix the literal rather than working around it.

How does TS2322 differ from TS2345?

TS2322 fires on assignments and return types. TS2345 fires on function-argument type mismatches. Both check structural compatibility but the error code distinguishes the location. Practically, the fix patterns are identical: narrow the source, widen the target, or add the missing properties. The two errors are closely related and the same root causes (null in non-null slots, missing properties, union mismatches) trigger both.

When should I use 'as' to silence TS2322?

Almost never. 'as' tells TypeScript 'trust me, this works' and disables the check. If you're wrong, you crash at runtime with no warning. Legitimate uses are narrow: casting unknown to a known shape after runtime validation (zod parse), and asserting a specific subtype after a runtime check the compiler can't follow. Code that uses 'as' frequently usually has a real type problem hidden behind it; fix the types instead. The 'as unknown as Foo' double-cast pattern is a code smell - whatever you're doing probably needs a different design.

What about 'satisfies' vs 'as'?

'satisfies' (TS 4.9+) checks compatibility without widening. const x = { a: 1 } satisfies Record<string, number> verifies x is assignable to the type but x's inferred type stays { a: 1 }. 'as' coerces the type, losing precision. Use satisfies when you want to assert compatibility for safety while keeping the narrower inferred type for downstream type inference. Use 'as' only after runtime validation. They solve different problems and should not be interchanged.

disclosure:Errordex runs AdSense, has zero third-party affiliate or sponsored links, and occasionally links to the editor’s own paid digital products (clearly labelled). Every fix is manually verified against official sources listed in the “sources” sidebar. If a fix here didn’t work for you, please email so we can update the page.