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
#fieldfrom an instance outside the class definition. - Trying to read a parent's
#fieldfrom 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
privatefor 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.

