Much older JavaScript documents types in the style of Google's Closure Compiler. TypeScript reads most of that syntax in .js files, so existing comments keep working: {?string} is string | null, {Array.<string>} is string[], {function(string): boolean} is a function type. The table below lists each form with its TypeScript meaning.
Two forms do not mean what they did in Closure. {!Type} is read as plain Type, so it does not strip null (the second example fails with TS2322 under strict). And the postfix = works for parameters but not for properties inside an object type, where it is a syntax error.
At a glance
| Use | JSDoc in a .js file | TypeScript in a .ts file |
|---|---|---|
| Nullable | {?string} | string | null |
| Non-nullable (no effect) | {!string} | string |
| Any type | {*} or {?} | any |
| Arrays | {Array.<string>} | string[] |
| Dictionaries | {Object.<string, number>} | { [x: string]: number } |
| Functions | {function(string, number=): boolean} | (sku: string, min?: number) => boolean |
| Constructors | {function(new: Date, string)} | new (value: string) => Date |
| Optional parameter | @param {string=} label | label?: string |
| Rest parameter | @param {...number} prices | ...prices: number[] |
Examples
/** @type {?string} */
let coupon = null;
/** @type {*} */
let anything = 1;
/** @type {Array.<string>} */
const sizes = ["S", "M"];
/** @type {Object.<string, number>} */
const stock = { "TEE-01": 12 };
/** @type {function(string, number=): boolean} */
const inStock = (sku, min) => (stock[sku] ?? 0) >= (min ?? 1);
/** @type {function(new: Date, string)} */
const DateFrom = Date;
/**
* @param {string=} label
* @param {...number} prices
*/
export function summary(label, ...prices) {
return `${label ?? "Total"}: ${prices.reduce((a, b) => a + b, 0)}`;
}
coupon = "SPRING";
anything = "two";
inStock("TEE-01");
new DateFrom("2026-10-20").getFullYear();
summary(undefined, 19.5, 12);
export { coupon, anything, sizes };let coupon: string | null = null;
let anything: any = 1;
const sizes: Array<string> = ["S", "M"];
const stock: { [x: string]: number } = { "TEE-01": 12 };
const inStock: (sku: string, min?: number) => boolean = (sku, min) => (stock[sku] ?? 0) >= (min ?? 1);
const DateFrom: new (value: string) => Date = Date;
export function summary(label?: string, ...prices: number[]) {
return `${label ?? "Total"}: ${prices.reduce((a, b) => a + b, 0)}`;
}
coupon = "SPRING";
anything = "two";
inStock("TEE-01");
new DateFrom("2026-10-20").getFullYear();
summary(undefined, 19.5, 12);
export { coupon, anything, sizes };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @type {!HTMLElement} */
const link = document.querySelector("a");error TS2322: Type 'HTMLAnchorElement | null' is not assignable to type 'HTMLElement'.
Type 'null' is not assignable to type 'HTMLElement'.
export { link };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @type {Array<string>} */
const tags = [];
/** @type {array} */error TS2552: Cannot find name 'array'. Did you mean 'Array'?
const legacyTags = [];
/** @param {promise} pending */error TS2552: Cannot find name 'promise'. Did you mean 'Promise'?
export function settle(pending) {
return pending;
}
export { tags, legacyTags };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Old type names
TypeScript maps some names from older JSDoc to its own: String, Number, Boolean, Void, Undefined and Null to the lowercase types, function to Function. The handbook lists array, promise, Object and object as well, mapped to any-based types, but only while noImplicitAny is off. With strict, lowercase array and promise are TS2552 ("Cannot find name 'array'. Did you mean 'Array'?"), as the third example shows.
Modernizing old comments
There is no need to rewrite working comments in one pass. Convert them as you touch the code: ?string to string | null, Array.<T> to T[], function(…) to arrow syntax. TypeScript's own messages print the modern forms, so matching them makes errors easier to read.
Under TypeScript 6.0 and 7.0
Every example on this page also runs under TypeScript 6.0.3 and 7.0.2 when this site is built. One of them reports something different from 5.9.3, the version the page describes; the ✗ on its badge links to its entry below.
Closure function types do not parse
The pattern: {function(string): boolean}, {function(number, string=): string} and {function(new: Date, string)} in JSDoc.
Why: The port's list of changes removes Closure function types from the JSDoc parser. The type is a syntax error (TS1005), so the parameters it should type are untyped (TS7006 under strict) and calls through it fail (TS2769, TS2351). Source: CHANGES.md: changes between TypeScript 6 and the native port.
Closure-style types TypeScript still accepts legacy.js
- TypeScript 5.9.3
No errors.
- TypeScript 6.0.3same as 5.9.3
No errors.
- TypeScript 7.0.2differs from 5.9.3
legacy.js(13,20): error TS1005: '}' expected. legacy.js(14,18): error TS7006: Parameter 'sku' implicitly has an 'any' type. legacy.js(14,23): error TS7006: Parameter 'min' implicitly has an 'any' type. legacy.js(16,20): error TS1005: '}' expected. legacy.js(30,5): error TS2351: This expression is not constructable. Type 'Function' has no construct signatures.
What works in every version: Arrow syntax: {(sku: string) => boolean}, {(amount: number, currency?: string) => string}, {new (iso: string) => Date}.
/** @typedef {{ sku: string, price: number }} Product */
/** @type {(amount: number, currency?: string) => string} */
export const formatPrice = (amount, currency = "USD") =>
new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
/**
* @param {Product[]} products
* @param {(a: Product, b: Product) => number} compare
*/
export function sorted(products, compare) {
return [...products].sort(compare);
}
sorted([{ sku: "TEE-01", price: 19.5 }], (a, b) => a.price - b.price);
formatPrice(19.5);type Product = { sku: string; price: number };
export const formatPrice: (amount: number, currency?: string) => string = (amount, currency = "USD") =>
new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
export function sorted(products: Product[], compare: (a: Product, b: Product) => number) {
return [...products].sort(compare);
}
sorted([{ sku: "TEE-01", price: 19.5 }], (a, b) => a.price - b.price);
formatPrice(19.5);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Gotchas
{?}and{*}both meanany, notunknown; they turn checking off.{?string}only addsnull; a value that may beundefinedneeds{string | undefined}.- Closure's record type
{{a: number}}is the same as a TypeScript object type and needs no change. - TypeScript 7.0 drops most of these forms:
{?}, Closure function types and the postfix!are not read there (TypeScript 7.0; the section Under TypeScript 6.0 and 7.0 has what 7.0.2 reports for the examples above). The TypeScript spellings in the table work in every version.
Questions
Does TypeScript support Closure Compiler JSDoc syntax?
Mostly. Nullable ?T, *, Array.<T>, Object.<K, V>, function(…) types and the = and ... parameter forms all work in .js files; !T is accepted but ignored.
Sources
- JSDoc Reference: Closure syntax, legacy names and unsupported patterns · TypeScript handbook
- Closure Compiler type annotations (the original syntax) · Closure Compiler on GitHub
Last reviewed 21 October 2026.