Getting familiar with bpmn-js, one step at a time.

This document is a work in progress. Help us to improve it.

bpmn-js is a BPMN 2.0 rendering toolkit and web modeler. It is written in JavaScript, embeds BPMN 2.0 diagrams into modern browsers and requires no server backend. That makes it easy to embed it into any web application.

The library is built in a way that it can be both a viewer and web modeler. Use the viewer to embed BPMN 2.0 into your applications and enrich it with your data. Use the modeler to create BPMN 2.0 diagrams inside your application.

This walkthrough will give you an introduction how to use the library and some insights into its internals, i.e. the components that contribute to its highly modular and extensible structure.

There are two approaches to use bpmn-js in your application. An all-in-one pre-packaged version of the library allows you to quickly add BPMN to any website. The npm version is more complicated to set-up but gives you access to individual library components and allows for easier extensibility.

This section gives you an overview over both approaches. We start with an introduction how to embed the pre-packaged version of the BPMN viewer into a website. Following that we will show how to create a BPMN modeler using the npm version of the library.

Embed the Pre-Packaged Viewer

The pre-packaged version of bpmn-js allows you to embed BPMN to your website with a simple script include.

Add a container element for the rendered diagram to your website and include the library into the page.

<!-- BPMN diagram container -->
<div id="canvas"></div>

<!-- replace CDN url with local bpmn-js path -->
<script src="https://unpkg.com/bpmn-js@0.27.0-1/dist/bpmn-viewer.development.js"></script>

The included script makes the viewer available via the BpmnJS variable. We may access it via JavaScript as seen below.

<script>
  // the diagram you are going to display
  var bpmnXML;

  // BpmnJS is the BPMN viewer instance
  var viewer = new BpmnJS({ container: '#canvas' });

  // import a BPMN 2.0 diagram
  viewer.importXML(bpmnXML, function(err) {
    if (err) {
      // import failed :-(
    } else {
      // we did well!

      var canvas = viewer.get('canvas');
      canvas.zoom('fit-viewport');
    }
  });
</script>

The snipped uses the Viewer#importXML API to display a pre-loaded BPMN 2.0 diagram. Importing a diagram is asynchronous and, once finished, the viewer notifies us via a callback about the results.

After import we may access various diagram services via Viewer#get. In the snippet above we interact with the Canvas to fit the diagram to the currently available viewport size.

Often times it is more practical to load the BPMN 2.0 diagram dynamically via AJAX. This can be accomplished using plain JavaScript (as seen below) or via utility libraries such as jQuery that provide more convenient APIs.

<script>
  var xhr = new XMLHttpRequest();

  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      viewer.importXML(xhr.response, function(err) {
        // ...
      });
    }
  };

  xhr.open('GET', 'path-to-diagram.bpmn', true);
  xhr.send(null);
</script>

Checkout the pre-packaged example as well as our starter examples to learn more.

Roll Your Own Modeler

Use bpmn-js via npm if you would like to build customizations around the library. This approach has various advantages such as access to individual library components. It also gives us more control over what to package as part of the viewer / modeler. However, it requires us to bundle bpmn-js with our application using an ES module aware bundler such as Webpack (>=2) or Rollup.

This section loosely follows the modeler example to create a BPMN modeler using the library.

Include the Library

As a first step install bpmn-js via npm:

npm install bpmn-js

Following that access the BPMN modeler via an ES import:

import Modeler from 'bpmn-js/lib/Modeler';

// create a modeler
var modeler = new Modeler({ container: '#canvas' });

// import diagram
modeler.importXML(bpmnXML, function(err) {
  // ...
});

Again, this assumes you provide an element with the id canvas as part of your HTML for the modeler to render into.

Add Stylesheets

When embedding the modeler into a webpage include the diagram-js stylesheet as well as the BPMN icon font with it. Both are shipped with the bpmn-js distribution under the dist/assets folder.

<link rel="stylesheet" href="bpmn-js/dist/assets/diagram-js.css" />
<link rel="stylesheet" href="bpmn-js/dist/assets/bpmn-font/css/bpmn-.css" />

Adding the stylesheets ensures that diagram elements receive proper styling and context pad as well as palette entries show BPMN symbols.

Bundle for the Browser

bpmn-js and its dependencies distribute ES modules. Use an ES module aware bundler such as Webpack (>=2) or Rollup to bundle bpmn-js along with your application. Learn more by following along the bundling example.

Hook into Life-Cycle Events

Events allow you to hook into the life-cycle of the modeler as well as diagram interaction. The following snippet shows how changed elements and modeling operations in general can be captured.

modeler.on('commandStack.changed', function() {
  // user modeled something or
  // performed a undo/redo operation
});

modeler.on('element.changed', function(event) {
  var element = event.element;

  // the element got changed by the users
});

Use Viewer#on to register for events or the EventBus inside extension modules. Stop listening for events using the Viewer#off method. Check out the interaction example to see listening for events in action.

Extend the Modeler

You may use the additionalModules option to extend the Viewer and Modeler on creation. This allows you to pass custom modules that amend or replace existing functionality.

import OriginModule from 'diagram-js-origin';

// create a modeler
var modeler = new Modeler({
  container: '#canvas',
  additionalModules: [
    OriginModule,
    require('./custom-rules'),
    require('./custom-context-pad')
  ]
});

A module (cf. Module System section) is a unit that defines one or more named services. These services configure bpmn-js or provide additional functionality, i.e. by hooking into the diagram life-cycle.

Some modules, such as diagram-js-origin or diagram-js-minimap provide generic user interface additions. Built-in bpmn-js modules, such as bpmn rules or modeling provide highly BPMN specific functionality.

One common way to extend the BPMN modeler is to add custom modeling rules. Doing so you are able to limit or extend the modeling operations allowed by the user.

Other examples for extensions are:

Check out the bpmn-js-examples project for many more toolkit extension show cases.

Build a Custom Distribution

If you would like to create your own pre-packaged version of your custom modeler or viewer refer to the custom-bundle example. This could make sense if you carried out heavy customizations that you would like to ship to your users in simple way.

This section explores some bpmn-js internals.

As depicted in the architecture diagram below bpmn-js builds on top of two important libraries: diagram-js and bpmn-moddle.

bpmn-js architecture: parts and responsibilities
bpmn-js Architecture: Parts and Responsibilities

We use diagram-js to draw shapes and connections. It provides us with ways to interact with these graphical elements as well as additional tools such as overlays that help users to build powerful BPMN viewers. For advanced use cases such as modeling it contributes the context pad, palette and facilities like redo/undo.

bpmn-moddle knows about the BPMN 2.0 meta-model defined in the BPMN 2.0 standard. It allows us to read and write BPMN 2.0 schema compliant XML documents and access BPMN related information behind shapes and connections drawn on the diagram.

On top of these two libraries bpmn-js defines the BPMN specifics such as look and feel, modeling rules and tooling (i.e. palette). We will go into detail about the individual components in the following paragraphs.

Diagram Interaction / Modeling (diagram-js)

diagram-js is a toolbox for displaying and modifying diagrams on the web. It allows us to render visual elements and build interactive experiences on top of them.

It provides us with a very simple module system for building features and dependency injection for service discovery. As part of that system it provides a number of core services that implement the diagram essentials.

On top of that diagram-js defines a data model for graphical elements and their relationships.

Module System

Under the hood, diagram-js employs dependency injection (DI) to wire and discover diagram components. This mechanism is built on top of didi.

When talking about modules in the context of diagram-js we refer to units that provide named services to along with their implementation. A service in that sense is a function or instance that may consume other services to do stuff in the context of the diagram.

The following shows a service that hooks into life-cycle events. It does so by registering an event via the eventBus, another well-known service:

function MyLoggingPlugin(eventBus) {
  eventBus.on('element.changed', function(event) {
    console.log('element ', event.element, ' changed');
  });
}

// ensure the dependency names are still available after minification
MyLoggingPlugin.$inject = [ 'eventBus' ];

We must publish the service under a unique name using a module definition:

import CoreModule from 'diagram-js/lib/core';

// export as module
export default {
  __depends__: [ CoreModule ], // {2}
  __init__: [ 'myLoggingPlugin' ], // {3}
  myLoggingPlugin: [ 'type', MyLoggingPlugin ] // {1}
};

The definition tells the DI infrastructure that the service is called myLoggingPlugin {1}, that it depends on the diagram-js core module {2} and that the service should be initialized upon diagram creation {3}. For more details have a look at the didi documentation.

We may now bootstrap diagram-js, passing the our custom module:

import MyLoggingModule from 'path-to-my-logging-module';

var diagram = new Diagram({
  modules: [
    MyLoggingModule
  ]
});

To plug-in the module into bpmn-js you would use the additionalModules option as shown in the Extending the Modeler section.

Core Services

The diagram-js core is built around a number of essential services:

  • Canvas - provides APIs for adding and removing graphical elements; deals with element life cycle and provides APIs to zoom and scroll.

  • EventBus - the libraries global communication channel with a fire and forget policy. Interested parties can subscribe to various events and act upon them once they are emitted. The event bus helps us to decouple concerns and to modularize functionality so that new features can hook up easily with existing behavior.

  • ElementFactory - a factory for creating shapes and connections according to diagram-js internal data model.

  • ElementRegistry - knows all elements added to the diagram and provides APIs to retrieve the elements and their graphical representation by id.

  • GraphicsFactory - responsible to create graphical representations for shapes and connections. The actual look and feel is defined by renderers, i.e. the DefaultRenderer inside the draw module.

Data Model

Underlying diagram-js is a simple data model consisting of shapes and connections.

diagram-js data model: shapes and connections
Data Model Essentials: Shapes and Connections

A shape has a parent, a list of children as well as a list of incoming and outgoing connections.

A connection has a parent as well as a source and target, pointing to a shape.

The ElementRegistry is responsible for creating shapes and connections according to that model. During modeling element relationships will be updated according to user operations by the Modeling service.

Auxiliary Services (i.e. the Toolbox)

Around the data model as well as its core services diagram-js provides a rich toolbox of additional helpers.

  • CommandStack - responsible for redo and undo during modeling.
  • ContextPad - provides contextual actions around an element.
  • Overlays - provides APIs for attaching additional information to diagram elements.
  • Modeling - provides APIs for updating elements on the canvas (moving, deleting)
  • Palette
  • ...

Let's move on to the BPMN magic that is happening behind the scenes.

BPMN Meta-Model (bpmn-moddle)

bpmn-moddle encapsulates the BPMN 2.0 meta-model and provides us with facilities to read and write BPMN 2.0 XML documents. On import it parses the XML document into a JavaScript object tree. That tree is edited and validated during modeling and then exported back to BPMN 2.0 XML once the user wishes to save the diagram. Because bpmn-moddle encapsulates knowledge about BPMN we are able to validate during import and modeling. Based on the results we can constrain certain modeling actions and output helpful error messages and warnings to the user.

Much like bpmn-js, the foundations of bpmn-moddle are based on top of two libraries:

In essence bpmn-moddle adds the BPMN spec as a meta-model and offers a simple interface for BPMN schema validation. From the library perspective it provides the following API:

  • fromXML - create a BPMN tree from a given XML string
  • toXML - write a BPMN object tree to BPMN 2.0 XML

The BPMN meta-model is an essential for bpmn-js, as it allows us to validate BPMN 2.0 documents we consume, provide proper modeling rules and export valid BPMN documents that all compliant BPMN modelers can understand.

Plugging Things together (bpmn-js)

We learned bpmn-js is built on top of diagram-js and bpmn-moddle. It ties both together and adds the BPMN look and feel. This includes a BPMN palette, BPMN context pad as well as BPMN 2.0 specific rules. In this section, we'll be explaining how that works in different phases of modeling.

When we import a BPMN 2.0 document, it is parsed by bpmn-moddle from XML into an object tree. bpmn-js renders all visible elements of that tree, i.e. it creates the respective shapes and connections on the canvas. Thereby it ties both the BPMN elements and the graphical elements together. This results into a structure as shown below for a start event shape.

{
  id: 'StartEvent_1',
  x: 100,
  y: 100,
  width: 50,
  height: 50,
  businessObject: {
    $attrs: Object
    $parent: {
      $attrs: Object
      $parent: ModdleElement
      $type: 'bpmn:Process'
      flowElements: Array[1]
      id: 'Process_1'
      isExecutable: false
    }
    $type: 'bpmn:StartEvent'
    id: 'StartEvent_1'
  }
}

From each graphical element you may access the underlying BPMN type via the businessObject property.

bpmn-js also knows how each BPMN element looks like and decides so through the BpmnRenderer. By plugging into the render cycle you may define custom representations of individual BPMN elements, too.

We can start modeling once the importing is done. We use rules to allow or disallow certain modeling operations. These rules are defined by BpmnRules. We base these rules on the BPMN 2.0 standard as defined by the OMG. But as mentioned earlier others may hook up with the rule evaluation to contribute different behavior, too.

The modeling module bundles BPMN 2.0 related modeling functionality. It adds BPMN 2.0 specific modeling behaviors and is responsible to update the BPMN 2.0 document tree with every modeling operation carried out by the user (cf. BpmnUpdater). Check it out to learn get a deeper insight into rules, behaviors and the BPMN update cycle.

When looking at bpmn-js purely from the library perspective it is worth mentioning is that it can be used in three variants:

The only difference between the versions is that they bundle a different set of functionality. The NavigatedViewer adds modules for navigating the canvas and the Modeler adds a whole lot of functionality for creating, editing and interacting elements on the canvas.

In the first part of this walkthrough we focused on using bpmn-js as a BPMN viewer as well as modeler. This should have given you a good understanding of the toolkit from the library perspective.

In the second part, we focused on bpmn-js internals. We presented diagram-js as well as bpmn-moddle, the two foundations bpmn-js is built upon and gave you an overview how bpmn-js plugs all these together.

There exists a number of additional resources that allow you to progress further:

  • Examples - a few example projects that showcase various ways to embed and extend bpmn-js.
  • Source Code (bpmn-js, diagram-js) - mostly well documented; should give you great insights in the library internals.
  • Forum - a good place to get help for using and extending bpmn-js.

Was there anything that we could have explained better / you got stuck with? Propose an improvement to this document or tell us about it in our forums.