Recipe · Objects and data

How do I write an assertion function in JSDoc?

  • Node.js (ES modules)
  • Assertion functions
  • @returns
  • asserts
  • Checked with TypeScript 5.9

The problem I have an assert(condition, message) helper that throws, but tsc does not know that the code after it can rely on the condition. How do I tell it, in JSDoc?

The short answer

Write @returns {asserts condition} for a helper that throws when its argument is falsy, or @returns {asserts value is string} for one that checks a type:

orders.js orders.ts 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
/**
 * @param {unknown} condition
 * @param {string} message
 * @returns {asserts condition}
 */
export function assert(condition, message) {
  if (!condition) throw new Error(message);
}

/**
 * @param {unknown} value
 * @param {string} name
 * @returns {asserts value is string}
 */
export function assertString(value, name) {
  if (typeof value !== "string") throw new TypeError(`${name} must be a string`);
}

/** @param {{ id?: string, note: unknown }} order */
export function printOrder(order) {
  assert(order.id, "order has no id");
  assertString(order.note, "note");
  console.log(order.id.toUpperCase(), order.note.trim());
}
export function assert(condition: unknown, message: string): asserts condition {
  if (!condition) throw new Error(message);
}

export function assertString(value: unknown, name: string): asserts value is string {
  if (typeof value !== "string") throw new TypeError(`${name} must be a string`);
}

export function printOrder(order: { id?: string; note: unknown }) {
  assert(order.id, "order has no id");
  assertString(order.note, "note");
  console.log(order.id.toUpperCase(), order.note.trim());
}
An assertion function narrows the rest of the caller.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

After assert(order.id, …), order.id is a string, not string | undefined; after assertString(order.note, …), order.note is a string, not unknown. The narrowing lasts for the rest of the function.

Why it works

An assertion function returns normally only when its check passes; otherwise it throws. asserts condition tells tsc exactly that, so the code after the call is analyzed as if the condition were true. asserts value is string combines it with a type predicate: if the call returns, the argument is a string.

The same thing in TypeScript

The TypeScript equivalent tab writes the same text as the return type: function assert(condition: unknown, message: string): asserts condition. Assertion functions return nothing, so the asserts clause takes the place of void.

What goes wrong

For tsc to use an assertion, it must know the function’s type where it is declared, without inferring it. A function declaration qualifies; a variable that holds the result of a factory does not, and the call fails with TS2775, after which nothing is narrowed (TS18046):

notes.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: notes.js(17,3): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation. notes.js(19,15): error TS18046: 'note' is of type 'unknown'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. notes.js(17,3): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation. notes.js(19,15): error TS18046: 'note' is of type 'unknown'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. notes.js(17,3): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation. notes.js(19,15): error TS18046: 'note' is of type 'unknown'. checked 21 October 2026
/** @param {string} name */
function makeStringAssert(name) {
  /**
   * @param {unknown} value
   * @returns {asserts value is string}
   */
  return (value) => {
    if (typeof value !== "string") throw new TypeError(`${name} must be a string`);
  };
}

const assertNote = makeStringAssert("note");

/** @param {unknown} note */
export function printNote(note) {
  assertNote(note);error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
  console.log(note.trim());error TS18046: 'note' is of type 'unknown'.
}
An assertion function returned by a factory, stored without a type.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Give the variable a type with @type and the same call works:

notes.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
/** @param {string} name */
function makeStringAssert(name) {
  /**
   * @param {unknown} value
   * @returns {asserts value is string}
   */
  return (value) => {
    if (typeof value !== "string") throw new TypeError(`${name} must be a string`);
  };
}

/** @type {(value: unknown) => asserts value is string} */
const assertNote = makeStringAssert("note");

/** @param {unknown} note */
export function printNote(note) {
  assertNote(note);
  console.log(note.trim());
}
The same function with a @type on the variable.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Gotchas

  • Every name in the call needs a declared type. asserts.string(note) on an untyped const asserts = { … } object is TS2775 too; give the object a type, or export plain functions.
  • A const with JSDoc on its arrow function counts. /** @param … @returns {asserts value is string} */ const assertString = (value) => … works, because the JSDoc declares the function’s type.
  • Assertions do not return values. A function with asserts in its @returns cannot return anything else; return values belong to type guards.

Questions

Can I use Node.js's own assert module?

Yes. @types/node declares assert(value) and ok(value) as assertion functions, so after import assert from "node:assert" a call like assert(order.id) narrows the same way.

Which TypeScript version added assertion functions?

TypeScript 3.7 added them. Its release notes show only the TypeScript syntax, so this page claims the JSDoc form only for the version its examples run on, TypeScript 5.9.

Sources

Last reviewed 21 October 2026.