AsyncAPI
Drop in an AsyncAPI spec and get a native event reference — one real page per send and receive operation, in your sidebar and search.
Event-driven APIs use the asyncapi() adapter from blume/reference, listed under reference beside any OpenAPI or GraphQL adapters. It takes the same options as openapi() and renders with the same native renderer. Each send/receive operation becomes a real page with message payload and header schema tables, channel parameters, protocol bindings, an Authorization section derived from the spec’s securitySchemes (server-level and operation-level, alternatives as “or” groups, and when a channel’s servers differ, which servers each scheme applies to), and a Try it message composer. Because each operation is a genuine Blume page, it gets its own URL, shows up in site search and llms.txt, and gets an Open Graph image — the same as any hand-written doc.
import { defineConfig } from "blume";
import { asyncapi } from "blume/reference";
export default defineConfig({
reference: [asyncapi({ spec: "./asyncapi.yaml" })],
});
That mounts the reference at /events (an overview page) with each operation on its own page beneath it. The spec is either an http(s) URL or a path to a local file in your project, JSON or YAML. As with every reference, it doesn’t add a header tab on its own — point a navigation tab at its route to surface it and scope the operations sidebar:
navigation: {
tabs: [{ label: "Events", path: "/events" }],
}
Document Kafka events with AsyncAPI walks through it for Kafka topics, with producer and consumer code beside the reference.
Spec versions
AsyncAPI 2.x specs are normalized to 3.x automatically with the official AsyncAPI converter, so publish/subscribe channels map onto send/receive operation pages with stable URLs — later upgrading the spec file itself through the converter moves nothing. The converter is an optional peer dependency, so a site with a 1.x or 2.x spec installs it (npm install @asyncapi/converter); without it the build fails with that install command. A 3.x spec needs nothing extra. Operations group by tag; untagged operations group under their channel address.
Code samples
Code samples are protocol-aware, keyed off the operation’s binding (or its servers’ protocol): wscat and a browser WebSocket snippet for WebSockets, kcat for Kafka, mosquitto_pub/mosquitto_sub for MQTT. codeSamples filters that set, the same way it picks languages on openapi(), and codeSamples: false shows none. A protocol without a supported tool renders the message payload example alone rather than a fabricated client.
The kcat sample reads the Kafka bindings too. A channel binding’s topic replaces the channel address as the topic name. A message binding’s key keys the message: the sample produces key|payload lines with -K '|', and consumes with the same flag so each key prints before its message. The key value comes from the key schema like a payload’s does, so give the schema an examples entry. When the server declares a SASL scheme (plain, scramSha256, scramSha512, or gssapi), the sample adds the -X settings librdkafka needs, reading the username and password from $KAFKA_USERNAME and $KAFKA_PASSWORD. A kafka-secure server adds TLS.
Shared options
Everything documented for OpenAPI carries over, playground included: route, sources with label/route, expandSchemas, the per-source indexing flags (seoDescriptionSuffix too — the generated sentence names the channel and action instead of an endpoint), and search indexing by operation summary, description, tag, and endpoint (SEND user/signup).
Embedding Scalar instead
asyncapi() always renders Blume’s own pages. To embed Scalar’s UI instead, list a scalar() adapter pointed at the AsyncAPI document — its embed detects the document type and renders channels, operations, messages, and a Models section. Scalar has no AsyncAPI playground of its own, so that swap trades the composer away, and only noindex of the per-source controls applies to it.
Try it for events
Operation pages rendered natively ship a Try it panel here too, on the same terms as the OpenAPI panel: server-rendered collapsed, with its JavaScript loaded only when a reader first opens it.
Whatever the protocol, the panel opens with a payload editor prefilled from the message’s examples — or, when the message declares none, from a value sampled out of the payload schema — validated against the message payload schema as you type. Under it sit an input per channel parameter and a server picker fed by the channel’s servers (with host and path variables at their defaults), with a free-text field for any other URL. The protocol-aware code samples stay in lockstep with the form exactly as curl, js, and python do on an HTTP operation: the channel address template is filled in with the parameter values you type, so a copied wscat, WebSocket, kcat, or mosquitto_pub snippet matches what the form says.
Live connect is WebSocket-only. On a ws or wss binding the panel connects to the resolved channel URL, shows the connection state, and logs every frame with a timestamp. AsyncAPI 3 states an action from the API’s side, and the panel follows it: a receive operation is one the API receives from you, so it gets a Send button that publishes the composed payload; a send operation only streams messages at you, so it connects and logs. There’s no reconnect logic — once a socket closes, it stays closed until you connect again. Kafka, MQTT, AMQP, and every other protocol get the composer and the copyable CLI samples, and the panel says as much on the page: Blume doesn’t fake broker connectivity from a browser tab.
asyncapi()’s playground mirrors openapi()’s — on by default with the native renderer, and false is the entire off switch:
reference: [asyncapi({ spec: "./asyncapi.yaml", playground: false })],
The event composer collects no broker credentials. Each operation page’s Authorization section documents what the broker expects, and a WebSocket connect carries only what’s already in the URL. Nothing is persisted for event operations.