error TS8009: The 'private' modifier can only be used in TypeScript files. is reported for the TypeScript keywords that modify a declaration: private, protected, public, readonly, abstract, override, declare, and the ? that marks an optional parameter or property.
JavaScript has its own private fields (#balance), enforced at run time, and JSDoc has tags for the rest (@private, @protected, @readonly, @override, brackets for optional parameters), which the checker enforces.
At a glance
| Code | TS8009 |
|---|---|
| Message | The '{0}' modifier can only be used in TypeScript files. |
| TypeScript's category | error |
| Listed under | JavaScript files only |
| 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 message is TypeScript's own text from its diagnostic messages file (Apache-2.0); {0} is filled in by tsc for each error.
What it looks like
export class Account {
private balance = 0;error TS8009: The 'private' modifier can only be used in TypeScript files.
readonly id = crypto.randomUUID();error TS8009: The 'readonly' modifier can only be used in TypeScript files.
/** @param {number} cents */
deposit(cents) {
this.balance += cents;
}
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} What causes it
- Class members or parameters were written with TypeScript modifiers, usually in code pasted from a
.tsfile.
How to fix it
Use #private fields and JSDoc tags
#balance makes the field private in the running code, not only to the checker; add a getter if callers need to read it. For read-only fields, put /** @readonly */ on the line above; for optional parameters, write @param {string} [note].
export class Account {
#balance = 0;
/** @readonly */
id = crypto.randomUUID();
/** @param {number} cents */
deposit(cents) {
this.#balance += cents;
}
get balance() {
return this.#balance;
}
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Gotchas
- Parameter properties (
constructor(private bank)) are a separate code, TS8012; assignthis.bank = bankin the constructor instead. @privateand#privatediffer: the tag is checked by tsc only (TS2341 on misuse), while#fields cannot be reached from outside the class at run time.
Sources
- JSDoc Reference: Property Modifiers · TypeScript handbook
- Private elements (#) · MDN Web Docs
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.