Optimized for Dark Mode

The website is optimized for dark mode to enhance your user experience. Switch to dark mode to enjoy it.

Streamdown BPMN

Add streaming BPMN 2.0 diagrams to Streamdown with an installable custom renderer, progressive updates, pan, zoom, fullscreen and export.


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:

Shell
npx shadcn@latest add https://bitbasti.com/r/streamdown-bpmn.json

This 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:

React
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:

React
<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:

Markdown
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:

HelperUse 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:

React
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:

Text
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:

Text
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 bpmn and bpmnRenderers is present in plugins.renderers.
  • The loader never becomes a diagram: The response is probably missing the bpmndi:BPMNDiagram layout, 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.