TL;DR
TypeDoc generates beautiful HTML documentation directly from TypeScript types and JSDoc comments — the standard for TypeScript library documentation. JSDoc is the language-agnostic comment standard that both TypeDoc and API Extractor read, plus a standalone doc generator for JavaScript projects. API Extractor (Microsoft) is for library authors — it generates the canonical public API surface (d.ts rollup), detects breaking changes, and produces API reports for review. For documenting a TypeScript package: TypeDoc. For managing a library's public API contract: API Extractor. For JavaScript-only projects: JSDoc standalone.
Key Takeaways
- typedoc: ~3M weekly downloads — TypeScript-native HTML docs, reads types + JSDoc
- jsdoc: ~5M weekly downloads — the comment standard; also a standalone HTML generator
- @microsoft/api-extractor: ~15M weekly downloads — d.ts rollup, API reports, breaking change detection
- TypeDoc and JSDoc solve "generate readable docs from your code"
- API Extractor solves "enforce public API contracts and detect breaking changes"
- API Extractor produces
api-report.mdfiles that review breaking changes in PRs - Most TypeScript library authors use TypeDoc for docs + API Extractor for API management
Download Trends
| Package | Weekly Downloads | HTML Docs | d.ts Rollup | API Reports | Breaking Change Detection |
|---|---|---|---|---|---|
typedoc | ~3M | ✅ | ❌ | ❌ | ❌ |
jsdoc | ~5M | ✅ | ❌ | ❌ | ❌ |
@microsoft/api-extractor | ~15M | ❌ | ✅ | ✅ | ✅ |
TypeDoc
TypeDoc — the standard for TypeScript HTML documentation:
Setup
npm install -D typedoc
// typedoc.json
{
"entryPoints": ["src/index.ts"],
"out": "docs",
"plugin": [],
"name": "PkgPulse SDK",
"readme": "README.md",
"excludePrivate": true,
"excludeProtected": false,
"excludeInternal": true,
"includeVersion": true,
"theme": "default"
}
// package.json
{
"scripts": {
"docs": "typedoc",
"docs:watch": "typedoc --watch"
}
}
JSDoc comments (TypeDoc reads these)
/**
* Calculate the health score for an npm package based on various metrics.
*
* The health score considers download trends, maintenance activity,
* dependency count, and TypeScript support.
*
* @param packageName - The npm package name (e.g., "react")
* @param options - Optional configuration for the calculation
* @param options.weights - Custom weights for each metric (must sum to 1.0)
* @param options.includeDeprecated - Include deprecated packages in comparison
* @returns A promise resolving to the health score (0-100) and detailed breakdown
*
* @example
* ```typescript
* const health = await getPackageHealth("react")
* console.log(health.score) // 95
* console.log(health.breakdown) // { downloads: 98, maintenance: 92, ... }
* ```
*
* @throws {PackageNotFoundError} If the package doesn't exist in npm registry
* @throws {RateLimitError} If the npm API rate limit is exceeded
*
* @see {@link https://www.pkgpulse.com/docs/health-score} Health Score Documentation
* @since 2.0.0
*/
export async function getPackageHealth(
packageName: string,
options?: HealthScoreOptions
): Promise<PackageHealth> {
// Implementation
}
TypeDoc annotations
/**
* Configuration for the PkgPulse SDK client.
*
* @remarks
* The `apiKey` is required. Get yours at {@link https://www.pkgpulse.com/api-keys}.
*
* @example
* ```typescript
* const client = new PkgPulseClient({
* apiKey: process.env.PKGPULSE_API_KEY!,
* baseUrl: "https://api.pkgpulse.com",
* timeout: 5000,
* })
* ```
*/
export interface PkgPulseConfig {
/** API key from pkgpulse.com/api-keys */
apiKey: string
/**
* Base URL for the API.
* @defaultValue "https://api.pkgpulse.com"
*/
baseUrl?: string
/**
* Request timeout in milliseconds.
* @defaultValue 10000
*/
timeout?: number
/**
* @internal
* Not part of the public API — used for testing.
*/
_mockMode?: boolean
}
/**
* Supported alert types for package monitoring.
* @enum
*/
export type AlertType =
/** Triggered when weekly downloads drop by more than the threshold percentage */
| "downloads_drop"
/** Triggered when a new version is published */
| "version_update"
/** Triggered when a security vulnerability is reported */
| "security"
Deploy to GitHub Pages
# .github/workflows/docs.yml
name: Deploy Docs
on:
push:
branches: [main]
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run docs
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
JSDoc
JSDoc — the universal documentation comment standard:
JSDoc as a comment standard (used by everyone)
/**
* Fetches package download statistics from the npm registry.
*
* @param {string} packageName - npm package name
* @param {"last-week"|"last-month"|"last-year"} period - Time period
* @returns {Promise<{downloads: number, start: string, end: string, package: string}>}
* @throws {Error} If the package doesn't exist
*
* @example
* const stats = await getDownloadStats("react", "last-week")
* console.log(stats.downloads) // 45000000
*/
async function getDownloadStats(packageName, period = "last-week") {
const res = await fetch(
`https://api.npmjs.org/downloads/point/${period}/${packageName}`
)
if (!res.ok) throw new Error(`Package ${packageName} not found`)
return res.json()
}
JSDoc with TypeScript (type checking from comments)
// @ts-check — enables TypeScript checking in JavaScript files
/**
* @typedef {Object} PackageHealth
* @property {string} name - Package name
* @property {number} score - Health score 0-100
* @property {boolean} deprecated - Whether the package is deprecated
* @property {string[]} tags - Associated tags
*/
/**
* @param {string} name
* @returns {Promise<PackageHealth>}
*/
async function getHealth(name) {
// TypeScript will type-check this based on the @returns annotation
}
JSDoc standalone generator
// jsdoc.json (config file for jsdoc HTML generator)
{
"source": {
"include": ["src/"],
"includePattern": ".+\\.js(doc)?$",
"excludePattern": "(^|\\/|\\\\)_"
},
"plugins": ["plugins/markdown"],
"templates": {
"cleverLinks": false,
"monospaceLinks": false
},
"opts": {
"destination": "./docs/",
"recurse": true,
"readme": "README.md"
}
}
API Extractor
@microsoft/api-extractor — for TypeScript library authors:
Setup
npm install -D @microsoft/api-extractor
npx api-extractor init # Creates api-extractor.json
// api-extractor.json
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "<projectFolder>/dist/index.d.ts",
"apiReport": {
"enabled": true,
"reportFolder": "<projectFolder>/etc/" // Store in git — review changes in PRs
},
"docModel": {
"enabled": true,
"apiJsonFilePath": "<projectFolder>/temp/<unscopedPackageName>.api.json"
},
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-untrimmed.d.ts",
"publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts"
},
"tsdocMetadata": { "enabled": true }
}
What API Extractor generates
// API Report (etc/pkgpulse-sdk.api.md) — committed to git:
// This file is checked to detect breaking changes in PRs.
// After running api-extractor run:
/* etc/pkgpulse-sdk.api.md */
// @public
export function getPackageHealth(packageName: string, options?: HealthScoreOptions): Promise<PackageHealth>;
// @public
export interface PackageHealth {
breakdown: HealthBreakdown;
name: string;
score: number;
}
// @internal (not included in public .d.ts rollup)
export function _internalHelper(): void;
Breaking change detection in CI
# .github/workflows/api-review.yml
- name: Run API Extractor
run: npx api-extractor run --local --verbose
# If the public API changed (api-report.md would differ from committed version):
# → API Extractor exits with error code
# → PR fails until the api-report.md is updated and committed
# → Forces explicit review of all breaking changes
API annotations
/**
* @public — included in public .d.ts
*/
export function getPackageHealth(name: string): Promise<PackageHealth> { ... }
/**
* @beta — public but not stable
*/
export function getExperimentalMetrics(name: string): Promise<unknown> { ... }
/**
* @alpha — very early, may change frequently
*/
export function _dangerousLowLevelAccess(): void { ... }
/**
* @internal — stripped from public .d.ts rollup
*/
export function _privateHelper(): void { ... }
/**
* @deprecated Use getPackageHealth() instead.
*/
export function getHealth(name: string): Promise<PackageHealth> { ... }
Feature Comparison
| Feature | TypeDoc | JSDoc (standalone) | API Extractor |
|---|---|---|---|
| HTML documentation | ✅ Beautiful | ✅ Basic | ❌ |
| TypeScript-native | ✅ | ❌ (comment-based) | ✅ |
| d.ts rollup | ❌ | ❌ | ✅ |
| Breaking change detection | ❌ | ❌ | ✅ |
| API reports | ❌ | ❌ | ✅ |
| Plugins | ✅ Rich | ✅ | ❌ |
| @alpha/@beta/@internal | ✅ | ❌ | ✅ |
| Markdown integration | ✅ | ✅ | ✅ |
| CI integration | ✅ | ✅ | ✅ |
When to Use Each
Choose TypeDoc if:
- Publishing a TypeScript library on npm and want HTML docs
- You want docs generated from your TypeScript types directly
- Deploying documentation to GitHub Pages or a docs site
- The standard choice for TypeScript library documentation
Choose JSDoc (comments) everywhere:
- JSDoc comments are read by TypeDoc, API Extractor, VS Code IntelliSense, and TypeScript
- Even if you never run jsdoc standalone, add JSDoc comments to all exported functions
- Use JSDoc standalone for pure JavaScript libraries (not TypeScript)
Choose API Extractor if:
- Publishing a TypeScript library with a stable public API
- You want to detect breaking changes in PRs before they ship
- Your library has multiple consumers who rely on API stability
- You need a clean d.ts rollup (single file instead of many)
- Working at an organization with formal API review processes
Common pattern for npm library authors:
TypeDoc → HTML docs hosted on GitHub Pages
API Extractor → d.ts rollup + API report in git (for breaking change detection)
Both together → comprehensive library publishing setup
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on typedoc v0.26.x, jsdoc v4.x, and @microsoft/api-extractor v7.x.
TypeScript Comment Standards and IDE Integration
JSDoc comments serve two audiences simultaneously: the documentation generator that turns them into HTML, and the developer's IDE that surfaces them as hover tooltips and IntelliSense completions. TypeScript's language server reads JSDoc comments even in .js files when // @ts-check is enabled, making the comment standard useful far beyond documentation output. The @param, @returns, @throws, and @example tags are universally understood — TypeDoc generates HTML from them, API Extractor includes them in API reports, VS Code displays them in tooltips, and TypeScript uses @type and @typedef for type checking in JavaScript projects.
The @remarks and @example tags deserve specific attention. @remarks in TypeDoc renders as a secondary description block after the main description, ideal for explaining implementation notes or behavioral edge cases that would clutter the main description. @example blocks render as syntax-highlighted code in TypeDoc's output and are tested by TypeDoc's doctest feature — typedoc-plugin-runnable-examples can actually execute your example code and verify it doesn't throw. This is a lightweight form of documentation testing that catches API examples that fall out of sync with implementation.
The @deprecated tag is handled by all three tools. TypeDoc renders it as a visible warning in generated docs. API Extractor includes deprecation information in the API report and will flag uses of deprecated APIs in the review diff. VS Code marks deprecated functions with strikethrough text in IntelliSense. For library authors managing backward compatibility, adding @deprecated to functions being phased out is the most reliable way to communicate across all these surfaces simultaneously.
Versioned Documentation with TypeDoc and GitHub Pages
TypeDoc's output is a static HTML site, which means it can be versioned and hosted anywhere static files are served. The most common pattern for open-source TypeScript libraries is to generate docs on every release and deploy to GitHub Pages using the gh-pages branch, making https://yourorg.github.io/yourlib the canonical API reference. TypeDoc's --includeVersion flag adds the current package version to the site title, giving users confidence they're reading documentation for the version they've installed.
For libraries that need to maintain multiple documentation versions simultaneously — a stable v1 and a beta v2 — the standard approach is to use typedoc-plugin-versions alongside a deployment script that pushes each release to a versioned subdirectory. This pattern is common in larger TypeScript projects like TS-Belt, Effector, and similar libraries where users may be pinned to older versions. TypeDoc's theming system also supports custom CSS and layout overrides through the --customCss option, letting you match documentation branding to your website without rebuilding the entire theme.
One TypeDoc feature that's underused is the @packageDocumentation comment, which populates the index page of the generated documentation. Adding a module-level JSDoc block with overview text, a quick-start example, and links to guides makes the generated docs usable as a landing page rather than just a raw API reference.
API Extractor in Monorepo Library Publishing Workflows
API Extractor becomes essential in monorepos where multiple packages share dependencies and where the public API surface must be carefully controlled. Rush and Nx projects frequently configure API Extractor as part of their build pipeline, with the generated api-report.md checked into version control. When a contributor changes a function signature or adds a new export, the changed API report appears as a diff in the pull request, making breaking changes visible during code review rather than discovered by users after release.
The @internal annotation in API Extractor serves a different purpose than TypeScript's private keyword. private is a compile-time constraint that prevents external code from calling the method. @internal is a publishing constraint — the method can be public at the TypeScript level (for cross-package access within a monorepo) but won't appear in the trimmed .d.ts file distributed to npm consumers. This distinction matters for monorepo packages where internal packages need to call methods that should be hidden from external consumers.
A complete library publishing setup combines both tools: TypeDoc generates the human-readable documentation at every release, API Extractor enforces the contract and produces the clean .d.ts rollup that ships in the npm package. Teams using Rush or Nx typically gate releases on passing API Extractor checks — if the API report would change without a corresponding version bump, the CI pipeline fails. This enforces semantic versioning discipline automatically rather than relying on humans to remember when changes are breaking.
Compare documentation and developer tool packages on PkgPulse →
See also: cac vs meow vs arg 2026 and Ink vs @clack/prompts vs Enquirer, acorn vs @babel/parser vs espree.