Skip to content
← Back to Blog

I built the AsyncAPI 3.1 emitter for TypeSpec because nobody else would

·4 min read

In August 2025 I modeled an event-driven API in TypeSpec. Kafka topics, MQTT patterns, WebSocket channels — the usual mess, but typed. I wrote the definitions, opened the compile targets, and stopped.

TypeSpec has an official OpenAPI emitter. It has an official JSON Schema emitter. For AsyncAPI — the spec language for exactly the systems I was describing — the target list said: nothing.

I waited

I assumed someone would close that gap. The AsyncAPI initiative maintains emitters for other source formats. Microsoft maintains the TypeSpec toolchain. Two organizations, one obvious missing piece between them. Somebody was going to build it.

Months passed. The community option, tsp-asyncapi, existed — but when I checked it in August 2026 it emitted AsyncAPI 3.0, supported 13 protocols with hand-written bindings, and verified itself with 15 property tests. AsyncAPI 3.1 had been out the whole time. The gap stayed open.

So I built typespec-asyncapi.

One decision mattered

I did not want to hand-write 22 protocol bindings. Hand-written bindings are exactly the kind of thing that drifts from the spec a year later — the spec moves, the binding stays, and every consumer inherits the difference.

So the emitter does not contain protocol bindings. It generates them, at build time, from @asyncapi/specs — the official machine-readable AsyncAPI specifications. When AsyncAPI publishes a binding change, I rebuild and the emitter speaks the new dialect. The source of truth for what a Kafka binding contains is the same file the AsyncAPI tooling reads.

You write this:

import "@lars-artmann/typespec-asyncapi";
using TypeSpec.AsyncAPI;

namespace MyAPI;

model Event {
  id: string;
  timestamp: utcDateTime;
}

@channel("events")
op publishEvent(): Event;

Run npx tsp compile api.tsp, and you get a validated AsyncAPI 3.1 document — channels, operations, messages, schemas, all referenced correctly, in YAML or JSON.

270+ tests against the official schema

Here is the part I care about most. Every document the emitter produces is validated with AJV against the official AsyncAPI 3.1.0 JSON Schema, on every build, across a compliance suite of 270+ tests. Not eyeballed in review. Not spot-checked.

A generator that emits almost-valid specs is worse than no generator, because almost-valid fails downstream at the worst possible moment — in a code generator, in a documentation portal, in a contract test someone trusted. The build is red the moment an output byte is wrong, which is the only place I want to learn about it.

That suite has caught real bugs in the emitter. It has also caught cases where my assumption about AsyncAPI’s shape was wrong and the schema disagreed. Both are mine. That is why the machine checks them.

Where it stands

The latest published version is 0.3.0-beta.1. It is pre-1.0: decorator signatures and emitter options can still change before the roadmap lands. If you need AsyncAPI 2.x output, this is not your tool — it targets 3.x deliberately.

Adoption, as of the 30 days ending September 11, 2026: npm counted 663 downloads of @lars-artmann/typespec-asyncapi. The GitHub repo has fifteen stars. The numbers tell you where spec tooling audiences actually are — and one of the consumers runs it in production at Swiss Post.

Fifteen stars, real production traffic. I will take that trade every time.

Run it against your own spec

npm install @lars-artmann/typespec-asyncapi

Point tspconfig.yaml at the emitter, compile, and check the output against what your event infrastructure actually does. The docs — including the full decorator reference for channels, operations, replies, correlations, and per-protocol bindings — live at typespec-asyncapi.lars.software.

If you maintain event-driven APIs and your AsyncAPI documents are hand-edited YAML, this is the escape hatch. Type the system once, generate the spec, let the schema validator hold the line.