TS8010Semantic Error
Since TS 1.5Updated in TS 3.8

Fix TS8010: Type Annotations in JavaScript Files

Learn why TypeScript throws TS8010 when a .js file contains type annotations, and how to fix it by renaming to .ts/.tsx or using JSDoc types instead.

error TS8010: Type annotations can only be used in TypeScript files

What This Error Means

TS8010 means the file extension and the file contents disagree. The name says JavaScript, the code inside says TypeScript. The moment TypeScript sees : number, : string, or any other type annotation inside a .js, .jsx, .mjs, or .cjs file, it stops and reports Type annotations can only be used in TypeScript files.

This is not a type-checking failure — nothing is mistyped. TypeScript's parser is deliberately permissive and reads the annotation fine, but a separate grammar check runs afterwards and flags every TypeScript-only construct it finds in a JavaScript file. The reason is practical: a .js file is usually handed to Node or a browser as-is, and const count: number = 1 is a syntax error there.

Where you see it depends on the tool. tsc only reports TS8010 when the JavaScript file is part of the program at all, which means allowJs is on (checkJs is not required). Editors are stricter: VS Code runs the TypeScript language service over every JavaScript file by default, so the red squiggle appears even in a project with no tsconfig.json at all.

// The general shape of the error — in a file named orders.js:
// const orderTotal: number = 0
//                 ~~~~~~~~
//                 TS-only syntax in a .js file

Common Causes

1. TypeScript Syntax in a .js File

The most common cause by far: the file was never TypeScript, or it was renamed from .ts to .js at some point and the annotations came along.

// ❌ Broken — retry.js
const retryCount: number = 3
//             ~~~~~~~~ Error: Type annotations can only be used in TypeScript files.
 
function greetUser(userName: string) {
  //                      ~~~~~~~~ Error: Type annotations can only be used in TypeScript files.
  return `Hello, ${userName}`
}
// ✅ Fixed — retry.js, same types expressed as JSDoc
/** @type {number} */
const retryCount = 3
 
/**
 * @param {string} userName
 * @returns {string}
 */
function greetUser(userName) {
  return `Hello, ${userName}`
}

Renaming the file to retry.ts and keeping the annotations works just as well, and is the better move if the project already compiles TypeScript.

2. A React Component in .jsx with Typed Props

Typed props are the single most copied TypeScript pattern, and they land in .jsx files constantly — usually when a component is pasted from a tutorial into a project that was scaffolded as JavaScript.

// ❌ Broken — UserCard.jsx
export function UserCard({ name, email }: { name: string; email: string }) {
  //                                     ~ Error: Type annotations can only be used in TypeScript files.
  return (
    <div className="user-card">
      <strong>{name}</strong>
      <span>{email}</span>
    </div>
  )
}
// ✅ Fixed — rename the file to UserCard.tsx and the annotation is legal
export function UserCard({ name, email }: { name: string; email: string }) {
  return (
    <div className="user-card">
      <strong>{name}</strong>
      <span>{email}</span>
    </div>
  )
}

Vite, Next.js, and Create React App all pick up .tsx automatically once a tsconfig.json exists — you rarely need to configure anything beyond the rename.

3. Class Fields and Methods Annotated in a .js Module

A class is a dense source of annotations: the field type, each parameter, and the return type are all TypeScript-only, so one pasted class can produce a wall of TS8010s.

// ❌ Broken — orders.js
export class OrderQueue {
  pending: string[] = []
  //     ~~~~~~~~~~ Error: Type annotations can only be used in TypeScript files.
 
  enqueue(orderId: string): void {
    //           ~~~~~~~~  ~~~~~ Error: Type annotations can only be used in TypeScript files.
    this.pending.push(orderId)
  }
}
// ✅ Fixed — orders.js, JSDoc keeps the file valid JavaScript
export class OrderQueue {
  /** @type {string[]} */
  pending = []
 
  /**
   * @param {string} orderId
   * @returns {void}
   */
  enqueue(orderId) {
    this.pending.push(orderId)
  }
}

4. Flow or Babel-Compiled Code That the Editor Checks Anyway

React Native templates and older Flow codebases annotate .js files on purpose, and Babel's TypeScript preset can be configured to strip types out of .js files too. In both cases the build is perfectly happy — only the editor complains, because VS Code's JavaScript validation is the TypeScript language service and it knows neither Flow nor your Babel config.

// ❌ Broken in the editor — pricing.js, a Flow-annotated file
// @flow
export function formatPrice(amount: number, currency: string) {
  //                              ~~~~~~~~ Error: Type annotations can only be used in TypeScript files.
  return `${currency} ${amount.toFixed(2)}`
}
// ✅ Fixed — pricing.js, JSDoc types that both Flow and TypeScript tolerate
/**
 * @param {number} amount
 * @param {string} currency
 * @returns {string}
 */
export function formatPrice(amount, currency) {
  return `${currency} ${amount.toFixed(2)}`
}

If the codebase is genuinely Flow and converting is not on the table, install the Flow extension and switch the built-in validator off for that workspace only, in .vscode/settings.json:

{
  "javascript.validate.enable": false
}

How to Fix It

  1. Rename the file to .ts or .tsx. This is the real fix in any project that already has a TypeScript toolchain. The annotations become legal, you lose nothing, and the rest of the codebase can keep importing the module by the same specifier. Use .tsx for anything containing JSX.

  2. If the file must stay JavaScript, move the types into JSDoc. /** @type {string[]} */ above a declaration and @param / @returns above a function express everything a simple annotation does, and the file stays runnable without a build step. Add "checkJs": true to your tsconfig.json so TypeScript actually enforces those comments instead of ignoring them.

  3. Check which tool is reporting the error. If npx tsc --noEmit is silent and only the editor is red, the JavaScript file is outside your tsconfig.json program and VS Code is checking it on its own. If tsc reports it too, the file was pulled in by allowJs plus your include patterns.

  4. For Flow projects, scope the workaround to the workspace. Setting "javascript.validate.enable": false in the project's .vscode/settings.json silences the 80xx family for that repository. Never set it in your user settings — you would lose JavaScript diagnostics in every other project you open.

  5. Don't reach for // @ts-nocheck or // @ts-ignore. The 80xx diagnostics are grammar-level, so suppression comments are unreliable against them, and even where they work you have only hidden a file whose extension is lying about its contents. Fix the extension or fix the syntax.

  6. Keep it from coming back: set the extension at creation time. If a project compiles TypeScript, new files should be born .ts/.tsx. If it doesn't, standardise on JSDoc with checkJs so contributors have a types story that doesn't tempt them to paste annotations into .js.

FAQ

What causes TypeScript error TS8010?

TS8010 fires when a file with a JavaScript extension (.js, .jsx, .mjs, .cjs) contains TypeScript-only syntax such as a type annotation. The parser accepts the annotation but a follow-up grammar check rejects it, because the file ships to Node or the browser without a compile step that could strip it.

It is usually an editor error rather than a build error. tsc only sees the file when allowJs is on, whereas VS Code validates every JavaScript file with the TypeScript language service out of the box. If several TypeScript constructs are in the same file you will also see its siblings — TS8006 for interface and type declarations, TS8009 for modifiers like public, TS8016 for type assertions.

How do I add types to a JavaScript file without renaming it to .ts?

Use JSDoc. TypeScript understands it as a first-class type syntax:

/** @type {Map<string, number>} */
const inventoryBySku = new Map()
 
/**
 * @param {string} sku
 * @returns {number}
 */
function getStock(sku) {
  return inventoryBySku.get(sku) ?? 0
}

Turn on "allowJs": true and "checkJs": true in tsconfig.json and these comments are checked exactly like annotations would be, including errors at call sites. The file remains plain JavaScript that Node can run directly.

Why does VS Code show TS8010 when my build works fine?

Because the two use different rules. Your bundler may run Babel, esbuild, or SWC with a TypeScript plugin configured to strip types out of .js files, which happily produces working output. VS Code, meanwhile, hands every JavaScript file to the TypeScript language service, which applies the standard grammar check and reports TS8010.

The cleanest resolution is to give those files the extension that matches their contents — every one of those bundlers handles .ts and .tsx without extra configuration. If you are on Flow instead, add "javascript.validate.enable": false to the workspace's .vscode/settings.json.

Note that the wording changed in TypeScript 3.8. Older toolchains print 'types' can only be used in a .ts file. for the same situation, which is why search results for this error are split between two phrasings.

Related Errors

Practice This

Browse all TypeScript practice challenges to keep sharpening your type-level skills.

Share this reference

Become a TypeScript Pro

Track your progress through 200+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges