error TS2341: Property 'balanceCents' is private and only accessible within class 'Account'. is reported when code outside a class reads or writes a member whose JSDoc says @private. TS2445 is the same for @protected, which also allows subclasses.
These tags are enforced by the checker only. At run time the property is an ordinary public property, which is the difference from JavaScript's own #private fields: those cannot be reached from outside the class at all, and even writing account.#balanceCents outside the class is a syntax error in JavaScript.
At a glance
| Code | TS2341 |
|---|---|
| Message | Property '{0}' is private and only accessible within class '{1}'. |
Also on this page: TS2445 | Property '{0}' is protected and only accessible within class '{1}' and its subclasses. |
| TypeScript's category | error |
| Listed under | Classes, JSX and unused code |
| Turned on by | No option: every checked file reports it |
| In .js files | Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file |
The messages are TypeScript's own text from its diagnostic messages file (Apache-2.0); {0} and {1} are filled in by tsc for each error.
What it looks like
export class Account {
/** @private */
balanceCents = 0;
/** @protected */
audit() {
return `balance ${this.balanceCents}`;
}
/** @param {number} cents */
deposit(cents) {
this.balanceCents += cents;
}
}
const account = new Account();
account.balanceCents = 1_000_000;error TS2341: Property 'balanceCents' is private and only accessible within class 'Account'.
account.audit();error TS2445: Property 'audit' is protected and only accessible within class 'Account' and its subclasses.jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} What causes it
- A member is tagged
@privateor@protectedand is used from outside the class (or, for@private, from a subclass). - The tag was added to an existing property that other code still uses directly.
How to fix it
Use the class's public methods, or make the field truly private
Change the outside code to call what the class offers (deposit), or add a method or getter for what it needs. If the member must never be touched from outside, #balanceCents enforces that in the running code too, and a getter can expose it read-only.
export class Account {
#balanceCents = 0;
/** @param {number} cents */
deposit(cents) {
this.#balanceCents += cents;
}
get balanceCents() {
return this.#balanceCents;
}
}
const account = new Account();
account.deposit(1999);
console.log(account.balanceCents);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Gotchas
- In a constructor function (not a class),
@privateon athis.x = ...assignment is reported as TS2341 on that assignment itself, because only a class body counts as inside the class; use a class for private members. - TypeScript's
privatekeyword is not allowed in a.jsfile (TS8009); the tag is its JSDoc spelling.
Sources
- JSDoc Reference: Property Modifiers · TypeScript handbook
- Classes: Member Visibility · TypeScript handbook
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.