Streamdown BPMN
Add streaming BPMN 2.0 diagrams to Streamdown with an installable custom renderer, progressive updates, pan, zoom, fullscreen and export.
On this page
Streamdown BPMN turns fenced bpmn blocks into interactive
bpmn.io diagrams. It works with complete markdown and with
markdown that is still arriving from an LLM. Pan, zoom, fullscreen, SVG export
and BPMN export are included.
Demo
Choose a speed and replay the example. The loader remains visible until the stream contains the first drawable shape; from then on, the diagram updates as the accumulated markdown grows.
Quick start
1. Install the renderer
Run the shadcn command in your application:
npx shadcn@latest add https://bitbasti.com/r/streamdown-bpmn.jsonThis adds the renderer, its streaming utilities and styles to
components/streamdown-bpmn. Because the files live in your project, you can
adapt the controls or styling without wrapping a third-party component.
2. Register it with Streamdown
Pass bpmnRenderers to Streamdown's custom-renderer plugin slot. Set mode to
"streaming" while the assistant response is arriving and to "static" when
it has finished:
import { Streamdown } from "streamdown";
import { bpmnRenderers } from "@/components/streamdown-bpmn";
type MessageProps = {
markdown: string;
isStreaming: boolean;
};
export function Message({ markdown, isStreaming }: MessageProps) {
return (
<Streamdown
mode={isStreaming ? "streaming" : "static"}
plugins={{ renderers: bpmnRenderers }}
>
{markdown}
</Streamdown>
);
}markdown must be the complete response accumulated so far, not only the
latest token or chunk. Every update should contain the opening fence and all
BPMN XML received up to that point.
If you already use another custom renderer, combine both arrays:
<Streamdown
mode={isStreaming ? "streaming" : "static"}
plugins={{ renderers: [...rechartsRenderers, ...bpmnRenderers] }}
>
{markdown}
</Streamdown>3. Produce a bpmn code fence
The assistant response must contain BPMN 2.0 XML inside a fence whose language
is exactly bpmn:
Here is the requested approval process:
```bpmn
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions ...>
...
</bpmn:definitions>
```The XML needs both the process model and a bpmndi:BPMNDiagram section with
shapes, bounds, edges and waypoints. BPMN without this layout information has
no coordinates for the viewer to draw.
That is the complete integration. The registered renderer receives updates from Streamdown and handles incomplete XML automatically.
Streaming and the utility functions
You normally do not call the utility functions yourself. They are installed next to the renderer and used internally whenever Streamdown marks a code fence as incomplete.
The four helpers each answer one practical question:
| Helper | Use it when you need to… |
|---|---|
hasBpmnDiagram(code) | Check whether diagram layout has started arriving. This is mainly useful for diagnostics. |
hasRenderableBpmnDiagram(code) | Decide whether there is enough layout data to replace the loader with a first diagram. |
isBpmnComplete(code) | Check whether the closing definitions element has arrived. |
mendBpmn(code) | Turn the current incomplete XML snapshot into temporary XML that bpmn-js can import. |
The renderer uses them in this order:
import type { CustomRendererProps } from "streamdown";
import {
hasRenderableBpmnDiagram,
isBpmnComplete,
mendBpmn,
} from "@/components/streamdown-bpmn/bpmn-utils";
function prepareBpmn({ code, isIncomplete }: CustomRendererProps) {
if (!hasRenderableBpmnDiagram(code)) {
return { status: "loading" as const };
}
const complete = !isIncomplete && isBpmnComplete(code);
return {
status: "ready" as const,
xml: complete ? code : mendBpmn(code),
streaming: !complete,
};
}There are two completion signals on purpose. isIncomplete tells you that the
Streamdown code fence has not closed yet. isBpmnComplete verifies that the XML
document itself has its closing root element. Treat the snapshot as final only
when both agree.
Use mendBpmn only for an in-progress snapshot. Once the response is complete,
pass the original XML through unchanged so malformed final BPMN produces a real
error instead of being hidden by a repair attempt.
If you build your own BPMN renderer instead of using BpmnRenderer, pass the
returned xml to viewer.importXML(). Keep the loader visible while the status
is loading, and suppress transient import errors only while streaming is
true.
Prompting the model
Tell the model which fence to use and require diagram layout. This compact system prompt is enough for most integrations:
You are a process-modelling assistant. When the user describes a workflow,
respond with a fenced `bpmn` code block containing valid BPMN 2.0 XML.
Requirements:
- A single <bpmn:definitions> root with the standard bpmn / bpmndi / dc / di
namespaces.
- One <bpmn:process> with startEvent, tasks, gateways and endEvents wired by
<bpmn:sequenceFlow> elements.
- A <bpmndi:BPMNDiagram> section with BPMNShape / BPMNEdge bounds and waypoints
for every node and flow, so the diagram can be laid out.
Emit only the XML inside the fence. Add a short sentence of context before it.A sample user prompt:
Model our expense approval: an employee submits a request, a manager reviews it,
then an exclusive gateway splits into "approved" (process the payment, then end)
and "rejected" (end). Use a BPMN diagram.Examples
Linear process
A start event, a couple of tasks and an end event in sequence.
Exclusive gateway
An exclusive gateway branches the flow into an approved and a rejected path.
Troubleshooting
- The page shows raw XML: Confirm that the fence starts with
bpmnandbpmnRenderersis present inplugins.renderers. - The loader never becomes a diagram: The response is probably missing the
bpmndi:BPMNDiagramlayout, a shape, or complete bounds. - The diagram does not update while streaming: Pass the accumulated markdown
to Streamdown on every update, not only the newest chunk, and use
mode="streaming"while the response is active. - An error appears after streaming finishes: The final XML is invalid. Check element IDs and references, closing tags, shape bounds and edge waypoints.
Included controls
- Zoom and reset — zoom in or out and fit the diagram back to the viewport.
- Fullscreen — open the diagram in an overlay for a closer look.
- Export — download the diagram as an SVG image or as BPMN XML.