Mdat plugin to generate API documentation from TypeScript source files using JSDoc and type information.
Important
This plugin is an exploratory prototype.
This is a plugin for the mdat CLI tool, which is a simple Markdown templating system optimized for embedding dynamic content in repository readmes and the like.
This plugin documents a TypeScript package's public exports in your readme. It runs TypeDoc over the package's entry point and turns the result into Markdown that fits inside a readme: signatures with parameter and return types, JSDoc descriptions, property and parameter tables, and @example blocks. Since the documentation is read from the source, it can't drift from the implementation.
Two formats are available. The full format documents every export in detail. The compact format renders one table row per export and suits large or namespaced APIs. Both are shown below, generated from a small sample library, and the API section of this readme is generated by the plugin from its own source.
- Node.js 24.16.0 or newer (specifically
^24.16.0 || >=26.3.0) - mdat
^3.0.0(peer dependency) - typescript
^5.0.0 || ^6.0.0(peer dependency)
You'll need mdat installed either globally or in your project. TypeDoc uses your project's TypeScript to read your code.
Add it to your project as a development dependency:
npm install --save-dev mdat-plugin-api-helpRegister the plugin in your mdat config file, e.g. mdat.config.ts:
import { defineConfig } from 'mdat'
import apiHelpPlugin from 'mdat-plugin-api-help'
export default defineConfig({
...apiHelpPlugin,
})Add an <!-- api-help --> placeholder comment to your Markdown file:
<!-- api-help -->Then run the mdat CLI from your package root to expand the placeholder into an "API" section. The plugin resolves the entry point and tsconfig.json relative to the working directory.
The rule is also aliased under the <!-- api --> keyword.
Options are passed as a JSON5 object in the placeholder comment. See ApiHelpRuleOptions for the full list.
<!-- api-help({ format: 'compact', include: ['sync*', '*Options'] }) -->Every export gets a heading, its signature with parameter and return types, its description, and tables of documented parameters or properties, followed by any @example blocks. Exports are grouped under Functions, Classes, Type Aliases, and similar headings, in source order.
Parameter and return sections that would only repeat the signature are left out, so an undocumented function takes two lines rather than twelve. Type references link to their headings when the type is documented in the same placeholder.
This is the default format.
Here's the full format applied to one function from the sample library, with groupByKind: false to skip the kind heading and heading: false to skip the section heading:
greet(
name:string,options?:GreetingOptions):GreetingResult
Generate a personalized greeting.
| Parameter | Type | Description |
|---|---|---|
name |
string |
The name of the person to greet |
options? |
GreetingOptions |
Configuration for the greeting |
GreetingResult
A greeting result with the message and metadata
const result = greet('World')
console.log(result.message) // "Hello, World!"const result = greet('Professor', { formal: true })
console.log(result.message) // "Good day, Professor."This format creates one table row per export: the signature, the return type or definition, and the first paragraph of the description. Namespaces become nested sections with their own tables. Object types are expanded one level deep, and anything nested deeper is summarized as object.
Here's the compact format applied to the whole sample library, again with heading: false:
| Function | Returns | Description |
|---|---|---|
greet(name: string, options?: GreetingOptions) |
GreetingResult |
Generate a personalized greeting. |
translate(greeting: GreetingResult, language: Language) |
GreetingResult |
Translate a greeting to a different language. |
hello(name: string) |
string |
Create and immediately invoke a greeting with sensible defaults. |
| Class | Constructor | Description |
|---|---|---|
GreetingGenerator |
new GreetingGenerator(language?: Language) |
A greeting generator that maintains state. |
| Type | Definition | Description |
|---|---|---|
GreetingOptions |
{ formal?: boolean; maxLength?: number } |
Configuration options for the greeting system. |
GreetingResult |
{ message: string; timestamp: Date } |
A greeting result with metadata. |
Language |
"en" | "es" | "fr" | "ja" |
Supported greeting languages. |
| Variable | Type | Description |
|---|---|---|
MAX_GREETING_LENGTH |
100 |
Default maximum greeting length. |
include and exclude take top-level export names, with * as a wildcard. A namespace is selected as a whole. Use several placeholders to document different parts of an API in different places, or to mix formats. Pass heading: false to all but the first so the section heading isn't repeated:
<!-- api-help({ format: 'compact', exclude: ['*Options'] }) -->
<!-- api-help({ include: ['*Options'], groupByKind: false, heading: false }) -->Namespaces (export * as things from './things') are folded into the document under their own heading. In the full format, their members are documented under qualified names like things.list(), so headings and anchors stay unique when the same name appears in several namespaces.
The rule emits its own "API" section heading at headingLevel, which defaults to 4 for placement under a level-three "Library" section, as in mdat's readme template:
## Usage
### Library
<!-- api-help -->
#### ExamplesPass a string to heading to change the heading text, or heading: false to leave it out when you've written your own heading above the placeholder or when several placeholders share one section.
Generated headings start one level below headingLevel, whether or not the section heading is shown. Full-format output for classes can nest several levels deeper. Headings that would pass level 6 are rendered as bold text instead, which at the default level includes the Parameters, Returns, and Examples headings under each export. Pass a shallower headingLevel to keep them as headings:
<!-- api-help({ headingLevel: 2 }) -->Everything exported from the entry point, except private and protected class members and anything tagged @internal. Default exports are documented under their declared name, and match both that name and default in include and exclude patterns.
Types that are referenced but not exported from the entry point are shown by name without a link. TypeDoc's warnings about these are logged at the debug level, since exporting a type just for the readme's sake is often not what you want.
Without an entryPoint option, the plugin reads package.json in the working directory and tries, in order:
- Every path under
exports["."] typesmainmodule- Common defaults:
src/index.ts,src/lib/index.ts,index.ts,lib/index.ts
Build output paths are mapped back to their source, so a field pointing at ./dist/index.d.ts or ./dist/lib/index.js resolves to ./src/index.ts or ./src/lib/index.ts.
The plugin logs through lognow. TypeDoc's own messages are routed through the same logger, with its informational output and validation warnings at the debug level. Pass your own logger to the exported setLogger() function to capture everything; it accepts a console-like object or a LogLayer instance.
ApiHelpRuleOptions =
object
Options for the <!-- api-help --> rule.
Pass them as a JSON5 object in the placeholder comment, e.g. <!-- api-help({ format: 'compact', include: ['greet', '*Options'] }) -->.
| Property | Type | Default value | Description |
|---|---|---|---|
entryPoint? |
string |
undefined |
Path to the TypeScript entry point, relative to the working directory. When omitted, it's inferred from package.json (exports, types, main, module), mapping build output like ./dist/index.js back to ./src/index.ts, and finally from common defaults like src/index.ts. |
exclude? |
string[] |
undefined |
Names of top-level exports to leave out. Supports * wildcards, e.g. ['setLogger', 'default*']. Applied after include. |
format? |
"compact" | "full" |
'full' |
Output style. full documents every export completely: signatures, parameter and property tables, and examples. compact renders one table row per export, with a subsection per namespace. |
groupByKind? |
boolean |
true |
Group exports under Functions, Classes, Type Aliases, etc. headings. When false, exports are listed together in sort order. |
heading? |
boolean | string |
true |
Section heading above the generated documentation. true emits an "API" heading, false leaves the heading out, and a string replaces the heading text. |
headingLevel? |
number |
4 |
Heading level for the section heading (1–6). Generated headings are nested below it, whether or not the section heading is shown. Nested headings that would exceed level 6 are rendered as bold text. The default suits placement under a level-three "Library" section, as in mdat's readme template. |
include? |
string[] |
undefined |
Names of top-level exports to document. Supports * wildcards, e.g. ['greet', '*Options']. A namespace is included with all of its members. Everything is documented when omitted. |
sort? |
SortStrategy[] |
['source-order'] |
TypeDoc sort strategies applied to members, in priority order. source-order and alphabetical are the useful ones for a readme; kind, static-first, instance-first, visibility, and required-first also affect class and interface members. |
tsconfig? |
string |
undefined |
Path to a tsconfig.json, relative to the working directory. TypeDoc finds the nearest one when omitted. |
Issues are welcome and appreciated.
Please open an issue to discuss changes before submitting a pull request. Unsolicited PRs (especially AI-generated ones) are unlikely to be merged.
This repository uses @kitschpatrol/shared-config (via its ksc CLI) for linting and formatting, plus MDAT for readme placeholder expansion.