TypeScriptTypes in JSDoc

Types in JSDoc

JSDoc type annotations let you get TypeScript's type checking and IDE autocompletion in plain JavaScript files without converting them to .ts. This is useful when you cannot change the file extension, when working in a JS-only project, or when migrating incrementally.

Enabling Type Checking in JS Files

There are two ways to enable TypeScript's type checker in a .js file:

  • Add // @ts-check at the top of a specific file

  • Enable checkJs: true in tsconfig.json to check all JS files in the project

JS
// @ts-check

// TypeScript now type-checks this file.
// Hover over variables in VS Code to see inferred types.

const message = 'Hello, world!'
// message: string

const count = 42
// count: number

// This will show a type error:
// const result = message + count  // OK (string concatenation)
// const bad = message.toFixed(2)  // Error: toFixed not on string
Note
// @ts-check must be the very first comment in the file (or at least before any code). Without it, TypeScript ignores the file even if checkJs is enabled globally.
@type Annotation

The @type tag annotates variables, parameters, and return values. It accepts any TypeScript type expression inside {}.

JS
// @ts-check

/** @type {string} */
let username

/** @type {number | null} */
let age = null

/** @type {string[]} */
const tags = []

/** @type {{ id: number; name: string; active: boolean }} */
const user = { id: 1, name: 'Alice', active: true }

/** @type {Map<string, number>} */
const scores = new Map()

/** @type {Promise<string>} */
const fetchName = fetch('/name').then(r => r.text())

// Import types from TypeScript declaration files
/** @type {import('express').Request} */
let req
@param and @returns

Use @param to type function parameters and @returns (or @return) for the return type.

JS
// @ts-check

/**
 * Formats a person's full name.
 *
 * @param {string} firstName
 * @param {string} lastName
 * @param {{ title?: string }} [options]
 * @returns {string}
 */
function formatName(firstName, lastName, options = {}) {
  const prefix = options.title ? `${options.title} ` : ''
  return `${prefix}${firstName} ${lastName}`
}

const name1 = formatName('Alice', 'Smith')
// name1: string

const name2 = formatName('Bob', 'Jones', { title: 'Dr.' })
// name2: string

/**
 * Fetches a user from the API.
 *
 * @param {string} id
 * @returns {Promise<{ id: string; name: string; email: string }>}
 */
async function fetchUser(id) {
  const response = await fetch(`/api/users/${id}`)
  return response.json()
}

// TypeScript knows the resolved type — fully autocompleted
fetchUser('123').then(user => {
  console.log(user.name)  // string
  console.log(user.email) // string
})
@typedef for Complex Types

@typedef creates a named type that can be reused across a file (or exported and used in other files).

JS
// @ts-check

/**
 * @typedef {Object} User
 * @property {string}   id
 * @property {string}   name
 * @property {string}   email
 * @property {number}   age
 * @property {boolean}  [active]   - Optional field
 * @property {string[]} [roles]    - Optional array
 */

/** @typedef {'admin' | 'editor' | 'viewer'} UserRole */

/**
 * @typedef {Object} PaginatedResponse
 * @property {User[]} data
 * @property {number} total
 * @property {number} page
 * @property {number} pageSize
 */

/**
 * @param {User} user
 * @returns {string}
 */
function getUserLabel(user) {
  return `${user.name} <${user.email}>`
}

/**
 * @param {PaginatedResponse} response
 * @returns {number}
 */
function getTotalPages(response) {
  return Math.ceil(response.total / response.pageSize)
}
@template for Generics

@template defines generic type parameters in JSDoc functions, matching TypeScript's &lt;T&gt; syntax.

JS
// @ts-check

/**
 * Returns the first element of an array or undefined.
 *
 * @template T
 * @param {T[]} array
 * @returns {T | undefined}
 */
function first(array) {
  return array[0]
}

const n = first([1, 2, 3])     // n: number | undefined
const s = first(['a', 'b'])    // s: string | undefined
const u = first([])            // u: undefined

/**
 * Creates a pair of values.
 *
 * @template T, U
 * @param {T} first
 * @param {U} second
 * @returns {[T, U]}
 */
function pair(first, second) {
  return [first, second]
}

const p = pair('hello', 42) // p: [string, number]

/**
 * Wraps a value in a Promise.
 *
 * @template T
 * @param {T} value
 * @returns {Promise<T>}
 */
function wrap(value) {
  return Promise.resolve(value)
}

const wrapped = wrap({ id: 1 }) // Promise<{ id: number }>
Importing Types from .d.ts Files

You can import TypeScript types into JSDoc annotations using the import() type syntax. This lets you consume types from TypeScript libraries without converting your file.

JS
// @ts-check

/**
 * @param {import('express').Request}  req
 * @param {import('express').Response} res
 * @returns {void}
 */
function handler(req, res) {
  const userId = req.params.id // string — typed!
  res.json({ userId })
}

// Import from your own .d.ts files
/**
 * @param {import('../types').User} user
 * @returns {import('../types').UserDto}
 */
function toDto(user) {
  return { id: user.id, displayName: user.name }
}

// Reuse an import with @typedef
/** @typedef {import('express').Application} ExpressApp */

/**
 * @param {ExpressApp} app
 */
function registerRoutes(app) {
  app.get('/', handler)
}
@ts-ignore and @ts-expect-error

Sometimes you need to suppress a specific type error — for example, when testing error conditions or working with intentionally untyped third-party code.

JS
// @ts-check

// @ts-ignore suppresses the next line's error (use sparingly)
// @ts-ignore
const result = someUntypedFunction()

// @ts-expect-error is better — it REQUIRES an error to exist.
// If the error is later fixed, TypeScript will warn you to remove the comment.
// @ts-expect-error — intentionally passing wrong type to test error handling
processUser(null)
Warning
Prefer @ts-expect-error over @ts-ignore. If the code is later fixed and the error disappears, @ts-ignore silently becomes dead weight. @ts-expect-error will flag itself as unnecessary so you can remove it.
@satisfies in JSDoc

The @satisfies operator (TypeScript 4.9) validates a value against a type while keeping the narrower inferred type. In JSDoc, use it via a type cast expression.

JS
// @ts-check

/** @typedef {'red' | 'green' | 'blue'} Color */
/** @typedef {Record<string, Color | [number, number, number]>} ColorMap */

// JSDoc does not have a native @satisfies tag, but you can simulate
// it using an inline type assertion cast:
const palette = /** @type {ColorMap} */ ({
  primary:   'red',
  secondary: [0, 128, 255],
})

// In TypeScript .ts files you would write:
// const palette = { ... } satisfies ColorMap

// Alternatively, assign to a typed variable (this validates the shape):
/** @type {ColorMap} */
const palette2 = {
  primary:   'red',
  secondary: [0, 128, 255],
}
@overload

@overload lets you declare multiple call signatures for a JSDoc-annotated function, just like TypeScript function overloads.

JS
// @ts-check

/**
 * @overload
 * @param {string} value
 * @returns {string}
 *//**
 * @overload
 * @param {number} value
 * @returns {number}
 *//**
 * @param {string | number} value
 * @returns {string | number}
 */
function double(value) {
  if (typeof value === 'string') {
    return value + value
  }
  return value * 2
}

const s = double('abc') // string
const n = double(5)     // number
// const x = double(true) // Error: boolean not assignable
VS Code IntelliSense with JSDoc

VS Code uses TypeScript's language server to power IntelliSense in JavaScript files. JSDoc annotations feed directly into this system, giving you:

  • Hover-over type information on variables and function calls

  • Autocompletion for object properties and function arguments

  • Rename refactoring that tracks all usages

  • Go to definition for types defined in .d.ts files

  • Inline error squiggles for type mismatches

  • Parameter hints showing expected types while you type

JS
// @ts-check

/**
 * @typedef {Object} Product
 * @property {string} id
 * @property {string} name
 * @property {number} price
 * @property {string} [category]
 */

/**
 * @param {Product[]} products
 * @param {number}    maxPrice
 * @returns {Product[]}
 */
function filterByPrice(products, maxPrice) {
  // VS Code autocompletes product.name, product.price, etc.
  return products.filter(p => p.price <= maxPrice)
}

// Usage: VS Code shows argument types as you type
filterByPrice(/* hover sees Product[] */, /* hover sees number */)
When to Use JSDoc vs .ts Files

Scenario

Recommendation

New project

Use .ts files — full TypeScript from day one

Migrating a large JS project incrementally

Add @ts-check + JSDoc to JS files while converting

Configuration files (vite.config.js, etc.)

JSDoc works well — avoids compilation step

Library that must ship as plain JS

Author in .ts, compile to .js + .d.ts for consumers

Quick script or one-off tool

JSDoc is lighter than setting up a full TS build

Team unfamiliar with TypeScript

JSDoc is gentler introduction than full .ts migration

Complex generics and advanced types

Use .ts — JSDoc generic syntax is verbose and limiting

Complete JSDoc Example

Here is a complete .js module with full JSDoc types — equivalent to what you would write in TypeScript.

JS
// @ts-check

/**
 * @typedef {Object} User
 * @property {string}              id
 * @property {string}              name
 * @property {string}              email
 * @property {'admin'|'user'}      role
 * @property {Date}                createdAt
 */

/**
 * @typedef {Object} CreateUserInput
 * @property {string}         name
 * @property {string}         email
 * @property {'admin'|'user'} [role]
 */

/** @type {User[]} */
const users = []

/**
 * Creates a new user.
 *
 * @param {CreateUserInput} input
 * @returns {User}
 */
function createUser(input) {
  /** @type {User} */
  const user = {
    id:        Math.random().toString(36).slice(2),
    name:      input.name,
    email:     input.email,
    role:      input.role ?? 'user',
    createdAt: new Date(),
  }
  users.push(user)
  return user
}

/**
 * Finds a user by id.
 *
 * @param {string} id
 * @returns {User | undefined}
 */
function findUser(id) {
  return users.find(u => u.id === id)
}

/**
 * Updates a user's role.
 *
 * @param {string}         id
 * @param {'admin'|'user'} role
 * @returns {User | null}
 */
function setUserRole(id, role) {
  const user = findUser(id)
  if (!user) return null
  user.role = role
  return user
}

module.exports = { createUser, findUser, setUserRole }
Success
JSDoc types give you the full power of TypeScript's type checker and VS Code IntelliSense in plain JavaScript files. Use them when you cannot rename files to .ts, want a lighter migration path, or need to ship JS that is still fully typed for consumers.