Fix: Property is not accessible because it has a private identifier (TS18013)

intermediate🔵 TypeScript2026-07-13| TypeScript 3.8+, Node.js, Modern Browsers (ES2020+)

Error Message

Property '#field' is not accessible outside class 'X' because it has a private identifier. (TS18013)
#typescript#javascript#oop#web-development

Why This Error Happens

You’re likely seeing this error because you’re using ECMAScript Private Fields (the # prefix). Unlike the standard private keyword in TypeScript, the # syntax enforces "hard privacy." This means the JavaScript engine itself blocks access at runtime.

Common triggers for TS18013 include:

  • Accessing a #field from an instance outside the class definition.
  • Trying to read a parent's #field from a subclass.
  • Attempting to bypass privacy with (obj as any).#field, which results in a syntax error.

Solution 1: Expose Data via Getters

The most reliable fix is to define a public getter. This keeps your internal state protected while providing a controlled window for external code to read the value.

class User {
  #apiToken: string;

  constructor(token: string) {
    this.#apiToken = token;
  }

  // Provide a read-only view of the data
  get maskedToken(): string {
    return `${this.#apiToken.substring(0, 4)}****`;
  }
}

const admin = new User("secret_9921_token");
// console.log(admin.#apiToken); // Error TS18013
console.log(admin.maskedToken); // Works: "secr****"

Solution 2: Use the 'private' Keyword for "Soft" Privacy

If you only need to prevent accidental usage but want to keep the code flexible for unit testing, swap the # for the private keyword. TypeScript's private is "soft"—it checks for errors during development but disappears once the code is compiled to JavaScript.

class Config {
  // Using 'private' instead of '#'
  private port: number = 8080;

  public getUrl() {
    return `http://localhost:${this.port}`;
  }
}

const cfg = new Config();
// cfg.port; // TS Error, but less restrictive than #

Solution 3: Switch to 'protected' for Subclasses

Private identifiers (#) are completely invisible to child classes. If a subclass needs to access a property from its parent, you must use the protected keyword. Note that protected does not work with the # symbol.

class Parent {
  // #internalValue = 10; // Subclasses can't see this
  protected sharedValue = 10; // Subclasses CAN see this
}

class Child extends Parent {
  logValue() {
    console.log(this.sharedValue); // Success
  }
}

Solution 4: Move Logic Inside the Class

Sometimes this error is a hint that your class design is leaking. Instead of pulling data out to process it, move that logic into a method. This follows the "Tell, Don't Ask" principle of object-oriented programming.

// Avoid: Reaching into the class
// if (account.#balance > 100) { ... }

// Better: Let the class handle its own state
class Account {
  #balance: number = 500;

  public canAfford(amount: number): boolean {
    return this.#balance >= amount;
  }
}

Quick Comparison: # vs. private

  Feature
  # Identifier
  private Keyword




  Privacy Level
  Hard (Runtime)
  Soft (Compile-time)


  Subclass Access
  No
  No (Use protected)


  Bypass via 'any'
  Impossible
  Possible


  JS Output
  Stays as #field
  Becomes a normal property

Verification and Testing

To ensure the error is gone, run npx tsc to trigger a full type check. If you’ve implemented a getter or changed the visibility to private, your IDE (like VS Code) should immediately stop showing the red underline. For runtime verification, check your browser console—accessing # fields externally will throw a SyntaxError even before the code runs.

Pro Tips

  • Use # for library internals where you must prevent users from touching sensitive state.
  • Stick to private for standard app development; it makes writing unit tests significantly easier.
  • Remember that # fields are slightly slower in older JS engines, though modern V8 (Chrome/Node.js) has optimized them heavily.

Related Error Notes