TypeScript error

TS8009: private, readonly or another TypeScript modifier in a .js file

  • Error TS8009
  • Tested with TypeScript 5.9

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

CodeTS8009
MessageThe '{0}' modifier can only be used in TypeScript files.
TypeScript's categoryerror
Listed underJavaScript 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

account.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: account.js(3,3): error TS8009: The 'private' modifier can only be used in TypeScript files. account.js(5,3): error TS8009: The 'readonly' modifier can only be used in TypeScript files. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. account.js(3,3): error TS8009: The 'private' modifier can only be used in TypeScript files. account.js(5,3): error TS8009: The 'readonly' modifier can only be used in TypeScript files. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. account.js(3,3): error TS8009: The 'private' modifier can only be used in TypeScript files. account.js(5,3): error TS8009: The 'readonly' modifier can only be used in TypeScript files. checked 21 October 2026
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;
  }
}
TypeScript's member modifiers in a .js class.
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 .ts file.

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].

account.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
export class Account {
  #balance = 0;
  /** @readonly */
  id = crypto.randomUUID();

  /** @param {number} cents */
  deposit(cents) {
    this.#balance += cents;
  }

  get balance() {
    return this.#balance;
  }
}
A real private field, and JSDoc for read-only.
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; assign this.bank = bank in the constructor instead.
  • @private and #private differ: the tag is checked by tsc only (TS2341 on misuse), while # fields cannot be reached from outside the class at run time.

Sources

Last reviewed 20 October 2026.