---
title: "BPMN"
description: "The entry point @tiptap-pro/extension-diagrams/bpmn draws diagrams stored as BPMN 2.0 XML with bpmn-js."
canonical_url: "https://tiptap-docs.lin52025i.qzz.io/diagrams/renderers/bpmn"
---

# BPMN

The entry point @tiptap-pro/extension-diagrams/bpmn draws diagrams stored as BPMN 2.0 XML with bpmn-js.

## Quick start

```ts
import { Diagrams } from '@tiptap-pro/extension-diagrams'
import { bpmnDiagramRenderer } from '@tiptap-pro/extension-diagrams/bpmn'
import rendererUrl from '@tiptap-pro/extension-diagrams/bpmn-renderer?url&no-inline'
import viewerUrl from 'bpmn-js/dist/bpmn-viewer.production.min.js?url&no-inline'

Diagrams.configure({
  diagramRenderers: [bpmnDiagramRenderer({ viewerUrl, rendererUrl })],
})

editor.commands.insertFlowDiagram({ source: bpmnXml, format: 'bpmn', title: 'Payment check' })
```

bpmn-js is not a dependency of the package. Install it yourself, serve its bundles as files, and
accept its own license.

## Options

| Option        | Type             | Description                                                                                             |
| ------------- | ---------------- | ------------------------------------------------------------------------------------------------------- |
| `viewerUrl`   | `string`         | Required. A bpmn-js bundle: `bpmn-js/dist/bpmn-viewer.production.min.js`. The modeler bundle works too  |
| `rendererUrl` | `string`         | Required. `@tiptap-pro/extension-diagrams/bpmn-renderer`, served as a file. With Vite, `?url&no-inline` |
| `scriptNonce` | `string \| null` | For pages with a nonce-based Content Security Policy                                                    |
| `id`          | `string`         | Default `'bpmn'`                                                                                        |
| `label`       | `string`         | Default `'BPMN'`                                                                                        |

## How it draws

bpmn-js runs in one iframe per page, with `sandbox="allow-scripts"` and a policy that allows no
network request. The iframe is laid out off screen, because bpmn-js measures labels while it
draws. It loads for the first BPMN diagram, and each drawing is cached by source.

- **Layout.** The XML must carry its layout (BPMN DI), as a modeler saves it. A process without
  a layout does not draw.
- **Colors.** A diagram is drawn black on white, on a page of its own. The `--tt-flow-diagram-*`
  custom properties do not reach it.
- **PNG, WebP, JPEG and Word exports.** Labels are SVG text, so the file looks like the diagram
  in the editor.
- **On a server.** A BPMN diagram cannot be exported, because bpmn-js lays out text in a browser.

### The bpmn.io attribution

The Renderer's rules ask for a "Powered by bpmn.io" link with the bpmn.io logo. It is shown below
the diagram and drawn into every exported file. `rendererRules` cannot remove it. See
[Rules of your Renderer](https://tiptap-docs.lin52025i.qzz.io/diagrams/renderers/custom.md#rules-of-your-renderer).

## What a BPMN source is

- **The root element** is `definitions` in the namespace
  `http://www.omg.org/spec/BPMN/20100524/MODEL`, under any prefix. The root element declares the
  namespace.
- **The XML ends** with the closing tag of that root element, such as `</bpmn:definitions>`.
- **The document stores it as written**, layout included. Only a byte-order mark and the white
  space around the XML are removed.
- **Empty source** is allowed, for a process you have not modeled yet.
- **Pasting BPMN XML as text** gives a BPMN diagram.
- **`detectDiagramInput`** returns `{ kind: 'bpmn' }` for it.

`insertFlowDiagram` and `updateFlowDiagram` return `false` for anything else, and for source
longer than 1,000,000 characters.

## Editing in the modeler

`connectBpmnEditor` runs the bpmn-js modeler in an iframe you created, sandboxed with scripts
only, and opens a diagram in it. You build the dialog, the iframe and the buttons. The package
gives you the XML when you ask for it.

```ts
import { connectBpmnEditor } from '@tiptap-pro/extension-diagrams/bpmn'
import rendererUrl from '@tiptap-pro/extension-diagrams/bpmn-renderer?url&no-inline'
import fontUrl from 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css?url&no-inline'
import bpmnStyleUrl from 'bpmn-js/dist/assets/bpmn-js.css?url&no-inline'
import diagramStyleUrl from 'bpmn-js/dist/assets/diagram-js.css?url&no-inline'
import modelerUrl from 'bpmn-js/dist/bpmn-modeler.production.min.js?url&no-inline'

const frame = document.createElement('iframe')
dialog.append(frame)

const connection = connectBpmnEditor(frame, {
  modelerUrl,
  rendererUrl,
  styleUrls: [diagramStyleUrl, bpmnStyleUrl, fontUrl],
  source: diagram.source,
  onReady: () => (saveButton.disabled = false),
})

saveButton.onclick = async () => {
  const source = await connection.save()
  editor.commands.updateFlowDiagram({ pos, source, format: 'bpmn' })
  connection.destroy()
  dialog.close()
}
```

### connectBpmnEditor(frame, options)

| Option             | Type                | Description                                                                                                |
| ------------------ | ------------------- | ---------------------------------------------------------------------------------------------------------- |
| `modelerUrl`       | `string`            | Required. The bpmn-js modeler bundle, `bpmn-js/dist/bpmn-modeler.production.min.js`                        |
| `rendererUrl`      | `string`            | Required. `@tiptap-pro/extension-diagrams/bpmn-renderer`, the same file the Renderer uses                  |
| `styleUrls`        | `readonly string[]` | Required. The modeler's stylesheets: `diagram-js.css`, `bpmn-js.css` and `bpmn-font/css/bpmn-embedded.css` |
| `scriptNonce`      | `string \| null`    | For pages with a nonce-based Content Security Policy                                                       |
| `source`           | `string`            | The BPMN XML to open. Empty opens a new process with a start event                                         |
| `onReady()`        | `() => void`        | Called when the diagram is open and can be edited                                                          |
| `onChange()`       | `() => void`        | Called after every change in the modeler                                                                   |
| `onError(message)` | `(message) => void` | Called when the modeler did not load, or could not open the source                                         |

It returns `{ save, destroy }`:

| Function    | Returns                                                                                                          |
| ----------- | ---------------------------------------------------------------------------------------------------------------- |
| `save()`    | A Promise of the diagram as it is now, as BPMN XML. It rejects when the modeler sends something that is not BPMN |
| `destroy()` | Nothing. Stops listening and empties the iframe. The iframe stays yours                                          |

Use the embedded font stylesheet, because the modeler's fonts must come from a `data:` URL or
from the origin of a stylesheet.

The modeler works on a copy. If others change the same diagram while it is open, the next save
replaces their changes.
