An intersection {Product & Stored} describes a value that has everything Product has and everything Stored has. It is how JSDoc types are combined and extended: a @typedef cannot use extends, but @typedef {Base & { extra: string }} Extended does the same job.
A value of an intersection type must satisfy every part. An object literal missing the id that one part requires fails (TS2322, with the missing property named underneath), as the second example shows.
At a glance
| Use | JSDoc in a .js file | TypeScript in a .ts file |
|---|---|---|
| Two types at once | /** @typedef {Product & Stored} StoredProduct */ | type StoredProduct = Product & Stored; |
| Adding a property inline | /** @param {Product & { discount: number }} item */ | (item: Product & { discount: number }) |
Examples
/** @typedef {{ sku: string, price: number }} Product */
/** @typedef {{ id: string, createdAt: Date }} Stored */
/** @typedef {Product & Stored} StoredProduct */
/** @type {StoredProduct} */
const tee = { id: "p_1", createdAt: new Date(), sku: "TEE-01", price: 19.5 };
/** @param {Product & { discount: number }} item */
export function salePrice(item) {
return item.price * (1 - item.discount);
}
salePrice({ ...tee, discount: 0.2 });
export { tee };type Product = { sku: string; price: number };
type Stored = { id: string; createdAt: Date };
type StoredProduct = Product & Stored;
const tee: StoredProduct = { id: "p_1", createdAt: new Date(), sku: "TEE-01", price: 19.5 };
export function salePrice(item: Product & { discount: number }) {
return item.price * (1 - item.discount);
}
salePrice({ ...tee, discount: 0.2 });
export { tee };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @typedef {{ sku: string, price: number }} Product */
/** @typedef {{ id: string }} WithId */
/** @type {Product & WithId} */
const draft = { sku: "TEE-01", price: 19.5 };error TS2322: Type '{ sku: string; price: number; }' is not assignable to type 'Product & WithId'.
Property 'id' is missing in type '{ sku: string; price: number; }' but required in type 'WithId'.
export { draft };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @type {{ id: string } & { id: number }} */
const record = { id: "p_1" };error TS2322: Type 'string' is not assignable to type 'never'.
export { record };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Union or intersection?
A union A | B is one or the other: you may only use what they share until you narrow it. An intersection A & B is both at once: you may use everything from either. For object types the intersection has more properties and accepts fewer values.
Conflicting properties
When both parts declare the same property with incompatible types, the property's type is the intersection of the two, which may be never. { id: string } & { id: number } makes id impossible to set, and the error says a string is not assignable to never, as the third example shows.
Gotchas
- Intersections of two different primitive types are
never:string & numberhas no values. - Error messages show the expanded intersection, which can be long; a
@typedefname keeps them readable.
Questions
How do I extend a @typedef in JSDoc?
Intersect it with the extra properties: /** @typedef {Product & { id: string }} StoredProduct */.
Sources
- Object Types: intersection types · TypeScript handbook
- Everyday Types: type aliases and interfaces · TypeScript handbook
Last reviewed 20 October 2026.