How to fix Property 'x' is private and only accessible within class 'Y' in TypeScript

TypeScriptBEGINNERMEDIUMUpdated Dec 8, 2025· 147 views

This TypeScript error occurs when you attempt to access a private class member from outside the class definition. TypeScript's private modifier restricts visibility to within the class itself, preventing external code from accessing private properties or methods.

Quick fix

Fixes TS2341

// BEFORE: Error TS2341
class User {
  private email: string;

  constructor(email: string) {
    this.email = email;
  }
}

const user = new User("john@example.com");
console.log(user.email); // Error: Property 'email' is private

// AFTER: Make it public
class User {
  public email: string; // or just: email: string;

  constructor(email: string) {
    this.email = email;
  }
}

const user = new User("john@example.com");
console.log(user.email); // OK: "john@example.com"
  1. Change the access modifier to public
  2. Use protected for inheritance access
  3. Add a public getter method
  4. Use TypeScript getter/setter accessors
typescript — error
// BEFORE: Error TS2341
class User {
private email: string;
constructor(email: string) {
this.email = email;
}
}
const user = new User("john@example.com");
console.log(user.email); // Error: Property 'email' is private
// AFTER: Make it public
class User {
public email: string; // or just: email: string;
constructor(email: string) {
this.email = email;
}
}
const user = new User("john@example.com");
console.log(user.email); // OK: "john@example.com"
Property 'x' is private and only accessible within class 'Y'

What this error means

TypeScript's access modifiers control the visibility of class members. The `private` modifier ensures that a property, method, or accessor is only accessible within the class where it's defined. When you try to access a private member from outside the class—whether from another class, a subclass, or external code—TypeScript's type checker throws error TS2341. This is a compile-time protection mechanism. TypeScript enforces encapsulation by restricting access to internal implementation details, helping you write more maintainable and secure code. The private modifier signals that a member is an internal implementation detail that should not be accessed or relied upon by external code. It's important to understand that TypeScript's `private` keyword only provides compile-time protection. When TypeScript is transpiled to JavaScript, the `private` modifier is stripped away, and the property becomes accessible at runtime. For true runtime privacy, you need to use JavaScript's native private fields (denoted with `#`) introduced in ES2022.

Typical symptoms

  • TypeScript compiler error TS2341 when trying to access a property or method
  • Error message: 'Property X is private and only accessible within class Y'
  • Red squiggly underline in your IDE under the private member access
  • Code compiles in JavaScript but fails in TypeScript
  • Accessing a property from a class instance that was marked as private
  • Subclass attempting to access parent class's private members

Common causes

  1. Attempting to access a private property from outside the class definition
  2. Trying to access a private method on a class instance
  3. Subclass trying to access private members from its parent class
  4. External code attempting to read or modify private fields
  5. Accessing private members through object destructuring or property access
  6. Forgetting to use a public getter/setter method for controlled access
  7. Migrating JavaScript code to TypeScript without considering access modifiers

How to fix "Property 'x' is private and only accessible within class 'Y'"

1Change the access modifier to public

If the property should be accessible from outside the class, change it from private to public (or remove the modifier, as public is the default):

typescript
// BEFORE: Error TS2341
class User {
  private email: string;

  constructor(email: string) {
    this.email = email;
  }
}

const user = new User("john@example.com");
console.log(user.email); // Error: Property 'email' is private

// AFTER: Make it public
class User {
  public email: string; // or just: email: string;

  constructor(email: string) {
    this.email = email;
  }
}

const user = new User("john@example.com");
console.log(user.email); // OK: "john@example.com"

Only use this approach if the property truly needs to be externally accessible.

2Use protected for inheritance access

If you want subclasses to access the property but keep it private from external code, use protected instead of private:

typescript
// BEFORE: Error in subclass
class Animal {
  private name: string;

  constructor(name: string) {
    this.name = name;
  }
}

class Dog extends Animal {
  bark() {
    console.log(`${this.name} barks!`); // Error: 'name' is private
  }
}

// AFTER: Use protected
class Animal {
  protected name: string;

  constructor(name: string) {
    this.name = name;
  }
}

class Dog extends Animal {
  bark() {
    console.log(`${this.name} barks!`); // OK: protected allows subclass access
  }
}

const dog = new Dog("Rex");
dog.bark(); // OK: "Rex barks!"
console.log(dog.name); // Still Error: protected is not externally accessible

This is the middle ground between private and public.

3Add a public getter method

Keep the property private and provide controlled read access through a public getter method:

typescript
class BankAccount {
  private balance: number;

  constructor(initialBalance: number) {
    this.balance = initialBalance;
  }

  // Public getter method
  getBalance(): number {
    return this.balance;
  }

  // Public method to modify balance (controlled)
  deposit(amount: number): void {
    if (amount > 0) {
      this.balance += amount;
    }
  }
}

const account = new BankAccount(1000);
console.log(account.balance); // Error: balance is private
console.log(account.getBalance()); // OK: 1000
account.deposit(500);
console.log(account.getBalance()); // OK: 1500

This approach maintains encapsulation while providing controlled access.

4Use TypeScript getter/setter accessors

Use TypeScript's get and set accessors for a property-like syntax with private backing fields:

typescript
class User {
  private _email: string;

  constructor(email: string) {
    this._email = email;
  }

  // Getter accessor
  get email(): string {
    return this._email;
  }

  // Setter accessor with validation
  set email(value: string) {
    if (!value.includes("@")) {
      throw new Error("Invalid email format");
    }
    this._email = value;
  }
}

const user = new User("john@example.com");
console.log(user.email); // OK: "john@example.com" (calls getter)
user.email = "jane@example.com"; // OK: sets via setter
user.email = "invalid"; // Throws error: Invalid email format
console.log(user._email); // Error: _email is private

This approach lets you use property syntax while maintaining validation and encapsulation.

5Access the property from within the class

If you need to work with the private property, move the logic into a public method of the class:

typescript
class Timer {
  private startTime: number;

  constructor() {
    this.startTime = Date.now();
  }

  // Public method that uses private property internally
  getElapsedSeconds(): number {
    return (Date.now() - this.startTime) / 1000;
  }

  reset(): void {
    this.startTime = Date.now();
  }
}

const timer = new Timer();
console.log(timer.startTime); // Error: startTime is private
console.log(timer.getElapsedSeconds()); // OK: 0.123
timer.reset(); // OK: resets internal startTime

This keeps internal state private while exposing necessary functionality through public methods.

6Use JavaScript private fields (#) for runtime privacy

If you need true runtime privacy that persists after compilation, use JavaScript's native private fields with the # prefix:

typescript
class SecureData {
  #apiKey: string; // JavaScript private field

  constructor(key: string) {
    this.#apiKey = key;
  }

  validateKey(input: string): boolean {
    return input === this.#apiKey;
  }
}

const data = new SecureData("secret-123");
console.log(data.#apiKey); // Error at compile-time AND runtime
console.log((data as any).#apiKey); // Still error at runtime
console.log(data.validateKey("secret-123")); // OK: true

Differences between `private` and `#`:

- private keyword: Compile-time only, stripped in JavaScript output
- # private fields: Runtime enforcement, truly inaccessible even with type casting

Use # when you need hard privacy guarantees that cannot be bypassed.

Advanced notes

### Compile-Time vs Runtime Privacy

TypeScript's private modifier is a compile-time construct only. After TypeScript is compiled to JavaScript, the modifier is completely removed:

typescript
// TypeScript source
class Example {
  private secret: string = "hidden";
}

// Compiled JavaScript output (ES2015+)
class Example {
  constructor() {
    this.secret = "hidden"; // No longer "private"
  }
}

// At runtime, anyone can access it
const ex = new Example();
console.log((ex as any).secret); // Works: "hidden" (casting bypasses TypeScript)
console.log(ex["secret"]); // Works in plain JavaScript

For true runtime privacy, use ECMAScript private fields (#):

typescript
class Example {
  #secret: string = "hidden";
}

// Compiled JavaScript preserves the # syntax
// Cannot access #secret at runtime, even with casting or bracket notation

### Private vs Protected vs Public

| Modifier | Class Itself | Subclasses | External Code |
|----------|--------------|------------|---------------|
| private | ✅ Yes | ❌ No | ❌ No |
| protected | ✅ Yes | ✅ Yes | ❌ No |
| public | ✅ Yes | ✅ Yes | ✅ Yes |

Best practice: Start with the most restrictive modifier (private) and only loosen as needed.

### When to Use Each Access Modifier

Use `private` when:
- The property is an internal implementation detail
- External code should never directly access it
- You want to prevent accidental misuse

Use `protected` when:
- Subclasses need access for inheritance
- External code should not access it
- You're building an extensible class hierarchy

Use `public` when:
- The property is part of the class's public API
- External code needs direct access
- The value has no validation or special handling

### TypeScript and JavaScript Interoperability

When writing library code that may be consumed by JavaScript projects, remember:

typescript
// In your TypeScript library
export class Config {
  private apiKey: string;

  constructor(key: string) {
    this.apiKey = key;
  }
}

// JavaScript consumer (after compilation)
const config = new Config("key-123");
console.log(config.apiKey); // Works! No compile-time checks in JS

To prevent this, use # private fields for real privacy, or document your API clearly.

### Static Private Members

Private modifiers also work with static members:

typescript
class Database {
  private static instance: Database;

  private constructor() {} // Private constructor (singleton pattern)

  static getInstance(): Database {
    if (!Database.instance) {
      Database.instance = new Database();
    }
    return Database.instance;
  }
}

const db = new Database(); // Error: constructor is private
const db = Database.getInstance(); // OK: uses public static method

### Parameter Properties Shorthand

TypeScript allows declaring and initializing private properties directly in the constructor:

typescript
// Verbose way
class User {
  private name: string;
  private email: string;

  constructor(name: string, email: string) {
    this.name = name;
    this.email = email;
  }
}

// Shorthand (parameter properties)
class User {
  constructor(
    private name: string,
    private email: string
  ) {}
}

Both produce the same result, but the shorthand is more concise.

Frequently asked questions

What does "TS2341" mean?
TypeScript's access modifiers control the visibility of class members. The private modifier ensures that a property, method, or accessor is only accessible within the class where it's defined. When you try to access a private member from outside the class—whether from another class, a subclass, or external code—TypeScript's type checker throws error TS2341.
How do I fix "TS2341"?
This TypeScript error occurs when you attempt to access a private class member from outside the class definition. TypeScript's private modifier restricts visibility to within the class itself, preventing external code from accessing private properties or methods.
What causes "TS2341"?
Attempting to access a private property from outside the class definition Trying to access a private method on a class instance Subclass trying to access private members from its parent class
Is the "TS2341" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading