# JSDoc for type-hints

December 19, 2025 Javascript JSDoc Typescript

Plain Javascript projects without any sort of type-checking can be hell. Here is how to take of advantages of Typescript in plain Javascript projects.

Configuration

File package.json

{
	"name": "sandbox",
	"version": "1.0.0",
	"main": "src/index.mjs",
	"license": "MIT",
	"scripts": {
		"start": "node ./src/index.mjs",
		"check": "tsc --project jsconfig.json"
	},
	"imports": {
		"#src/*": "./src/*"
	},
	"devDependencies": {
		"@types/node": "^18.14.5",
		"typescript": "^4.9.5"
	}
}

File jsconfig.json

{
	"compilerOptions": {
		"strict": true,
		"strictNullChecks": true,
		"strictFunctionTypes": true,
		"strictPropertyInitialization": true,
		"alwaysStrict": true,
		"target": "ES2015",
		"moduleResolution": "node",
		"noImplicitOverride": false,
		"paths": {
			"#src/*": ["./src/*"]
		}
	},
	"include": ["src"],
	"exclude": ["node_modules", "**/node_modules/*"]
}

Usage

/** @type {string} */
const str = "Lorem ipsum";

/** @type {number} */
const num = 300;

/** @type {boolean} */
const isTrue = true;

/** @type {number|string} */
const numOrStr = "union type";

/** @type {number[]} */
const numbers = [10, 20, 30];

/** @type {{name: string, email: string}} */
const params = {
	name: "sample",
	email: "sample@site.com",
};

/** @type {Map<string, string>} */
const capitals = new Map([
	["China", "Beijing"],
	["England", "London"],
]);

/** @type {function (string): void } */
const greeter = (name) => console.log("Hello, %s", name);

Classes

class Point {
	/** @type {number} */
	x;

	/** @type {number} */
	y;

	/**
	 * @param {number} x
	 * @param {number} y
	 */
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
}

class Entity {
	/**
	 * @readonly
	 * @type {string}
	 */
	name;

	/** @type {Point} */
	position;

	/** @type {number} */
	#area;

	/**
	 * @param {string} name
	 * @param {number} x
	 * @param {number} y
	 */
	constructor(name, x, y) {
		this.name = name;
		this.position = new Point(x, y);
		this.#area = x * y;
	}

	/**
	 * @public
	 * @returns {number}
	 */
	get area() {
		return this.#area;
	}
}

Note: Access modifiers i.e. @public, @private, @protected can be used with properties and methods.

Extends and Implements

/**
 * @template T
 * @extends {Set<T>}
 */
class SortableSet extends Set {
	// ...
}
/** @implements {Print} */
class TextBook {
	print() {
		// TODO
	}
}

Example

/* file: index.d.mjs */
/**
 * @typedef User
 * @property {number} id
 * @property {string} name
 * @property {string} email
 * @property {string} password
 */

export {};
/* file: index.mjs */

/* eslint-ignore-next-line no-unused-vars */
import * as t from "index.d.mjs";

/** @type {function (t.User): void}*/
function greetUser(user) {
	console.log("Hello, %s", user.name);
}

/** @type {function(): Promise<void>} */
async function main() {
	/** @type {t.User} */
	const user = {
		id: 1,
		name: "Sample",
		email: "sample@site.com",
		password: "123123123",
	};

	greetUser(user);
}

main().catch(console.error);

Generics

/**
 * @template T
 * @param {T} x - A generic parameter that flows through to the return type
 * @returns {T}
 */
function id(x) {
	return x;
}

const a = id("string");
const b = id(123);
const c = id({});

Note: Generic classes can also be defined this way.

Cast to specific Data type

/** @type {unknown} */
const rawData = {
	id: 1,
	name: "user",
	email: "user@site.com",
};

const data = /** @type {{ id: number, name: string, email: string}} */ (
	rawData
);

Note: The comment style and the placement of the parentheses during casting is important.

Cast as const

const schema = /** @type {const} */ (anotherVariable);

Note: The parenthesis around the value are required for this to work.

Usage with Ajv

import Ajv from "ajv";

const schema = /** @type {const} */ ({
	type: "object",
	properties: {
		id: { type: "number" },
		name: { type: "string" },
		email: { type: "string" },
	},
	required: ["id", "name", "email"],
	additionalProperties: false,
});

/** @typedef {import(""json-schema-to-ts"").FromSchema<typeof schema>} Schema */

/** @returns {Promise<void>} */
export async function main() {
	const validator = new Ajv();

	/** @type {unknown} */
	const rawData = {
		id: 1,
		name: "sample",
		email: "sample@site.com",
	};

	const isValid = validator.validate(schema, rawData);
	if (!isValid) {
		return console.log({ isValid });
	}

	const body = /** @type {Schema} */ (rawData);
	console.log(body);
}

main().catch(console.error);