@scalar/asyncapi-to-markdown
v0.2.2
Published
Create plain Markdown from AsyncAPI documents (for LLMs)
Readme
Scalar AsyncAPI to Markdown
Generate LLM-friendly Markdown from AsyncAPI descriptions without rendering HTML or Vue.
npm install @scalar/asyncapi-to-markdownimport {
createAsyncApiMarkdownRenderer,
createMarkdownFromAsyncApi,
} from '@scalar/asyncapi-to-markdown'
const markdown = await createMarkdownFromAsyncApi('./asyncapi.yaml')
// Load, upgrade and bundle references once for a documentation build.
const renderer = await createAsyncApiMarkdownRenderer('./asyncapi.yaml')
await renderer.render() // Complete description
await renderer.render({ introduction: true })
await renderer.render({ channel: 'orders' })
await renderer.render({ operation: 'receiveOrder' })
await renderer.render({ message: 'OrderCreated' })
await renderer.render({ model: 'Order' })
await renderer.render({ tag: 'Orders' })
await renderer.render({
message: { pointer: '#/channels/orders/messages/created' },
})The Node.js entry point accepts objects, JSON/YAML strings, file paths, and HTTP(S) URLs. Relative external references resolve against the source file or URL. Object and inline string inputs need absolute external references. AsyncAPI 1.0–1.2, 2.0–2.6, 3.0 and 3.1 inputs use Scalar's upgrader to produce a 3.1 document. The original version stays visible in the introduction. Input objects are not modified. Create a new renderer to pick up source changes; rendering pages does not fetch the source again.
Choose at most one selector per call. Strings identify exact, case-sensitive map keys:
channels, operations, component messages, or component schemas. Channel addresses and
human-readable titles are not selectors. Every map selector also accepts an exact JSON
pointer, with optional # and standard ~0 / ~1 escaping. Message pointers can select
channel messages as well as component messages. Invalid or missing selections reject
that call; the renderer remains usable afterward.
Operation pages include the channel address, parameters, relevant servers, security, selected messages, and reply details. Unknown channel addresses are labelled explicitly. Reply messages are rendered only when declared; request messages are not inferred as replies. Channel pages include their operations and all channel messages. Selected pages expand each shared schema once and include transitive schema dependencies. Introduction pages contain API metadata, the application identifier, default content type, and servers. Complete exports include channels, operations, component messages and schemas. Send/receive direction is from the described application's perspective; legacy publish/subscribe operations are migrated accordingly.
Message and operation traits are merged before rendering. Authored examples are preserved. Missing examples are generated from JSON schemas and labelled by context (Generated headers example or Generated payload example). XML payloads use XML examples, including examples authored on their schemas, while headers use JSON. Repeated generated examples link to their first occurrence within the same media type and context. JSON Schema and AsyncAPI schemas include properties, required markers, constraints, composition, discriminator fields and mappings, and recursive-reference notices. Property types and annotations share one line, followed by their Markdown descriptions and external documentation links. Content encoding and media-type annotations are preserved. Other schema formats, such as Avro and Protobuf, appear as labelled source blocks. Protocol bindings are preserved as JSON. Descriptions preserve Markdown, tables and code blocks using the same sanitization as the OpenAPI converter; images are excluded.
Navigation and shared models
Complete exports include a contents index with explicit anchors. Shared structured models render once and their usages link to that definition. Declared tags retain descriptions and external documentation links in complete exports. Repeated servers and security schemes also link to their first occurrence. Security alternatives are labelled explicitly; operation security applies in addition to applicable server security. Primitive schemas remain inline. Schema display normalization and description parsing are cached per prepared renderer.
Tag pages match exact tag names on channels, operations (including traits), and messages.
A channel tag includes the channel's operations and messages. An operation tag includes
that operation's context. Tags declared in info.tags retain descriptions and external links,
even when unused. Tag pages retain the API introduction, display only the selected tag, and link to
messages already rendered on the page. Unknown or duplicate tag declarations reject the render.
For documentation sites that publish model pages separately:
await renderer.render({
operation: 'receiveOrder',
schemaReferences: {
mode: 'linked',
resolveUrl: ({ name }) => `/models/${encodeURIComponent(name)}`,
inlinePrimitives: true,
},
})Linked pages expand root schemas and replace nested structured references with published
links. Returning undefined or omitting the callback keeps the model name as plain text.
Set inlinePrimitives: false to link primitive, enum and const references too.
Linked pages omit the transitive appendix and generated message examples. Selected model
pages still generate a local example with linked structured dependencies left as stubs.
Authored examples remain available in linked mode. URL callbacks receive the original reference and
its model name, preserving aliases. Empty or unsafe URLs render an unlinked name. Composition
branches retain their model links. Linked schema usages retain their descriptions, and primitive aliases
remain available when generating model examples.
Browser
Use a document already upgraded and resolved by @scalar/workspace-store:
import { createMarkdownFromAsyncApi } from '@scalar/asyncapi-to-markdown/browser'
const markdown = await createMarkdownFromAsyncApi(document, {
operation: 'receiveOrder',
})The browser entry point does not read files, fetch references, or upgrade raw documents.
Limitations
This is a documentation converter, not a validator. Unsupported versions, failed loads, and unresolved references needed for rendering reject with an error. Recursive schema expansion stops at a repeated ancestor, ten levels, or 5,000 schema nodes per expansion, with a visible notice. Generated examples exceeding 10,000 estimated values are omitted with a visible notice. Custom schema vocabularies are not interpreted, and output is not a replacement for the original schema. Publishing Markdown routes and adding copy buttons to Scalar Docs are separate integrations.
Playground and benchmarks
Run pnpm --filter @scalar/asyncapi-to-markdown dev --port 3002 --strictPort.
The playground includes local order events and a shared-schema stress fixture, page
selection and search, linked/full export modes, sanitized previews, Markdown source,
copy/download controls, output size and render timing. Click a published model link to
open that model page. It runs conversion in a worker to keep the UI responsive.
Run pnpm --filter @scalar/asyncapi-to-markdown benchmark for repeatable preparation,
cold/warm rendering, output size, and RSS measurements. See the benchmark method.
Specification coverage
Rendering follows the AsyncAPI 3.1 specification. The converter covers application metadata, servers and variables, channel parameters, operation and message traits, request/reply details, security schemes and OAuth flows, tags, external documentation, and JSON Schema Draft 7 constraints. All mandatory JSON/YAML schema-format identifiers work for both headers and payloads. Protocol bindings and other schema languages remain available as source blocks. Referenced reusable objects render at their usage sites; unused component schemas and messages are also included in complete exports. Custom extension semantics and validation remain outside the converter's scope.
