Learn why TypeScript throws TS2339 when accessing a property that doesn't exist on a type, and how to fix missing property errors.
TS2339 means you're trying to read or write a property that TypeScript doesn't know about on the given type. TypeScript tracks the shape of every value, and when you access .something that isn't part of that shape, it raises this error.
This is one of the biggest advantages of TypeScript over plain JavaScript. In JavaScript, accessing a missing property silently returns undefined and can cause subtle bugs. TypeScript catches these mistakes before your code runs.
// The general shape of the error:
// Property 'middleName' does not exist on type 'User'.
// ~~~~~~~~~~ ~~~~
// property you accessed the type TS knows aboutThe most common cause is a simple spelling mistake. TypeScript error 2551 ("Did you mean...?") sometimes appears instead, but when the name isn't close enough to any known property, you get 2339.
// ❌ Broken
interface Product {
name: string
price: number
inStock: boolean
}
function displayProduct(product: Product) {
console.log(product.naem) // Error: Property 'naem' does not exist on type 'Product'.
}// ✅ Fixed — correct the typo
function displayProduct(product: Product) {
console.log(product.name)
}You're accessing a property that genuinely isn't part of the type definition. Maybe the interface is out of date, or the property was added to the API response but not to your types.
// ❌ Broken
interface ApiResponse {
data: unknown
status: number
}
function handleResponse(response: ApiResponse) {
console.log(response.headers)
// ~~~~~~~ Error: Property 'headers' does not exist on type 'ApiResponse'.
}// ✅ Fixed — add the property to the interface
interface ApiResponse {
data: unknown
status: number
headers: Record<string, string>
}
function handleResponse(response: ApiResponse) {
console.log(response.headers)
}When a variable has a union type, TypeScript only allows access to properties shared by all members of the union. Properties unique to one member require narrowing first.
// ❌ Broken
interface CreditCardPayment {
type: "credit_card"
cardNumber: string
expiryDate: string
}
interface BankTransferPayment {
type: "bank_transfer"
accountNumber: string
routingNumber: string
}
type Payment = CreditCardPayment | BankTransferPayment
function processPayment(payment: Payment) {
console.log(payment.cardNumber)
// ~~~~~~~~~~ Error: Property 'cardNumber' does not exist on type 'Payment'.
}// ✅ Fixed — narrow the union with a type guard
function processPayment(payment: Payment) {
if (payment.type === "credit_card") {
console.log(payment.cardNumber) // OK — TypeScript knows this is CreditCardPayment
} else {
console.log(payment.accountNumber) // OK — TypeScript knows this is BankTransferPayment
}
}document.getElementById and DOM ElementsDOM methods often return a base type like HTMLElement that doesn't have properties specific to <input>, <canvas>, or other element types.
// ❌ Broken
const searchInput = document.getElementById("search")
console.log(searchInput.value)
// ~~~~~ Error: Property 'value' does not exist on type 'HTMLElement'.// ✅ Fixed — narrow to the specific element type
const searchInput = document.getElementById("search") as HTMLInputElement | null
if (searchInput) {
console.log(searchInput.value) // OK — value exists on HTMLInputElement
}Check for typos. Compare the property name you wrote against the type definition letter by letter. IDE autocomplete helps prevent this.
Inspect the type definition. Hover over the variable in your editor to see what type TypeScript has inferred. If the type is too narrow or a base type, the property won't be available.
Add the property to the type if it legitimately belongs there. Update your interface or type alias.
Narrow the type if you're dealing with a union. Use discriminated unions, typeof checks, in operator checks, or custom type guards.
// Using the 'in' operator to narrow
function getLabel(item: Product | Category) {
if ("price" in item) {
return `${item.name} — $${item.price}` // Product
}
return item.categoryName // Category
}any.TS2339 fires when you try to access a property or method on a value whose type doesn't include that property. TypeScript only allows access to properties it knows about at compile time.
This is different from JavaScript, where accessing a missing property just gives you undefined. TypeScript's approach catches bugs early: misspelled property names, outdated interfaces, and incorrect assumptions about data shapes all surface as TS2339 at compile time rather than as mysterious undefined values at runtime.
The fix depends on why the property is missing:
HTMLInputElement).If you're working with data from an external API and the shape changes frequently, consider using a validation library like Zod to define and validate the shape at runtime, which also generates the TypeScript types for you.
TypeScript uses a concept called "type narrowing" with unions. When you have A | B, only properties that exist on both A and B are safe to access directly. That is because at runtime, the value could be either type.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number }
function getArea(shape: Shape) {
// shape.radius — Error! Not all shapes have radius
// shape.width — Error! Not all shapes have width
// Narrow first:
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2 // OK
}
return shape.width * shape.height // OK
}The kind property works as a discriminant because it exists on both members of the union. Once you check its value, TypeScript narrows the type automatically.
Get the latest TypeScript tips, tutorials, and course updates delivered straight to your inbox.