# elkjs: ELK's Layout Algorithms for JavaScript, Without the Java

> elkjs exposes the Eclipse Layout Kernel's layered layout engine to JavaScript, so a diagram editor can compute node and edge positions in the browser or in Node. It computes geometry only, and its GWT-transpiled core is the source of most of its real problems.

**kieler/elkjs** — ELK's layout algorithms for JavaScript

- Repository: https://github.com/kieler/elkjs
- Stars: 2,779 · Forks: 124
- Language: JavaScript
- License: NOASSERTION
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/kieler-elkjs

## What elkjs solves, and for whom

Placing boxes and routing lines between them is a graph layout problem, and it is hard enough that most teams should not write their own solver. elkjs takes the layout-relevant part of the Eclipse Layout Kernel, a Java project, and makes it callable from JavaScript. The README describes the flagship as a layer-based layout algorithm "particularly suited for node-link diagrams with an inherent direction and ports", based on ideas originally introduced by Sugiyama et al.

The audience is narrow and specific. If you are building a diagram editor, a dataflow view, a build pipeline visualiser or an infrastructure graph, and you already have a rendering layer, elkjs gives you the coordinates. It is the successor of klayjs, so teams migrating from that project have a direct path. What it is not is a diagramming framework: the README states that elkjs computes positions for the elements of a diagram and nothing else. No rendering, no styling, no interaction. That boundary is the single most important thing to understand before installing it, because a large share of the confusion around the library comes from people expecting a drawing tool and receiving a geometry engine.

## The graph object, the worker, and where the Java went

The architecture is visible in the file layout. The library ships two main files: elk-api.js, which provides the API and only the API, and elk-worker.js, which is generated from ELK's Java code base using GWT and contains the code that actually knows how to lay out a graph. A bundled version, elk.bundled.js, combines the two for a plain script tag, and main.js is the Node entry point so that require('elkjs') works.

That split exists for a practical reason. Layout can be slow, and the README notes that the project does not want to freeze your UI, so Web Workers are supported out of the box. In Node, the library uses the web-worker package as a wrapper around worker_threads, which is API-compatible with a browser's Worker. That package is deliberately not installed automatically, to avoid the dependency for everyone who does not need a worker, and a warning is raised if you request a worker without it. The README also says elkjs falls back to the non-worker path in that case.

Data flows in one direction. You hand the library a graph with children, edges, widths and heights, plus a layoutOptions object attached to the graph element. The engine returns the same structure with positions filled in. Layout options can be scoped three ways: on an element, as a second argument to layout, or as defaultLayoutOptions in the constructor. The README warns that option suffixes may be ignored when they are not unique, and recommends always starting options with the elk. prefix.

## Installing elkjs and laying out your first graph

Installation is a single npm command for the latest released version. The README also documents a development version based on ELK's master branch, installed as elkjs@next.

```bash
npm install elkjs
```

With the package in place, the README's Node example builds a small graph object and calls layout on it. The graph has an id, a layoutOptions object selecting the layered algorithm, three children with explicit width and height, and two edges expressed as sources and targets arrays. Note that edges reference node ids, and that every node must carry a size before layout, because the algorithm has no way to guess one.

```js
const ELK = require('elkjs')
const elk = new ELK()

const graph = {
  id: "root",
  layoutOptions: { 'elk.algorithm': 'layered' },
  children: [
    { id: "n1", width: 30, height: 30 },
    { id: "n2", width: 30, height: 30 },
    { id: "n3", width: 30, height: 30 }
  ],
  edges: [
    { id: "e1", sources: [ "n1" ], targets: [ "n2" ] },
    { id: "e2", sources: [ "n1" ], targets: [ "n3" ] }
  ]
}

elk.layout(graph)
   .then(console.log)
   .catch(console.error)
```

The promise resolves with the graph structure carrying computed coordinates, which is what console.log prints. If something goes wrong, the README suggests switching to the non-minified version to get a proper stack trace, which is worth remembering because minified GWT output is not readable.

To move layout off the main thread in Node, pass a workerUrl pointing at the packaged worker file. The path below is the one the README gives.

```js
const ELK = require('elkjs')
const elk = new ELK({
  workerUrl: './node_modules/elkjs/lib/elk-worker.min.js'
})
```

For a browser script tag, elk.bundled.js exposes ELK as a global variable when run in a browser. The full catalogue of layout options lives in ELK's own documentation, not in this repository.

## Where elkjs breaks: GWT, bundlers and module resolution

The README does not hide its problems; it lists them under a heading about recurring issues, and they cluster around one cause. The core is transpiled from Java by GWT, and the README points to issues reporting errors such as `g is not defined`, `Can't resolve web-worker`, and general trouble using the library inside React, webpack and similar toolchains. It also links an issue titled "Poor modularization" and invites contributions.

That is a real constraint, not a cosmetic one. A bundler that cannot resolve the worker file will either fail the build or silently fall back to main-thread layout, and the fallback is exactly the thing Web Workers exist to prevent. If your diagram is large, main-thread layout blocks the UI for as long as the algorithm runs, and the README gives no latency figures, no node or edge thresholds and no guidance on when to switch.

A second limitation is conceptual. There is no rendering, so every visual concern, including whether the computed coordinates are usable at your zoom levels, is yours. And there is no built-in story for incremental work: the README's own FAQ list points to issues about considering previous layout results, dynamic layout, and incrementally adding nodes and edges to an existing layout. If your editor re-lays out on every edit and you want nodes to stay put, that behaviour is a discussion in the issue tracker, not a documented feature.

## elkjs vs dagre: two different bets on layout

The most common comparison is with dagre, and the difference is not which one produces prettier pictures. It is where the algorithm runs and what it is built from.

dagre is a JavaScript implementation of a Sugiyama-style layered layout, written natively for the language. elkjs is a JavaScript binding to a Java code base, compiled with GWT, with the API and the engine split into two files so the engine can run in a worker. That distinction drives everything downstream. A native implementation is easier to bundle, easier to debug, and easier to patch when it misbehaves in a specific framework. A compiled one inherits a much larger catalogue of layout options and algorithms from ELK, plus the port model the README highlights, but it also inherits the transpilation issues listed above and a debugging experience that ends at generated code.

If your graph is modest and your build is a standard webpack or Vite setup, dagre is the lower-friction choice. If you need ports, layered layout tuned through ELK's option set, or you are already committed to the Eclipse ecosystem, elkjs is the one that gives you that surface. Both compute positions and neither renders. The decision should follow the graph, not the bundle size.

## Versioning, licence and what upgrading costs

Releases are partly synchronised with ELK: the README states that the minor version number is always the same but the revision number may diverge, because fixes that concern only elkjs need to ship independently of ELK. So elkjs 0.12.0 corresponds to ELK 0.12.0 in functionality, while a hypothetical 0.12.1 might not match ELK 0.12.1. Reading the changelog of ELK alone will not tell you what changed in the JavaScript wrapper. The latest release listed is 0.12.0 from 2026-07-17, with 0.11.1 and 0.11.0 before it, and the package.json in the repository carries 0.13.0, which is the in-development version rather than a published one. The last push to the repository was on 2026-09-17.

Licensing is the other thing to check before you build on it. The package.json declares `EPL-2.0 OR GPL-3.0-or-later`, which is a dual licence, not a single permissive one. The repository's LICENSE.md is the file to read, and the top-level metadata does not resolve to a single SPDX identifier. Whether the GPL branch of that disjunction is acceptable depends on how you distribute your product, and that is a question for your own legal review rather than something this article can settle.

Upgrade cost is mostly option drift. Because layout options can be written with or without the elk. prefix and non-unique suffixes may be ignored, a minor bump can change layout without throwing an error. Pinning the version and re-rendering a representative diagram after each bump is cheaper than discovering the shift in production.

## Conclusion

elkjs is the right choice when you have a node-link diagram with direction, ports or nested structure and you are willing to feed it a graph object and render the result yourself. It is the wrong choice if you want a diagramming framework, an interactive editor, or anything that draws on screen: the README states plainly that elkjs is a layout engine only and provides no rendering or styling. Before adopting it, verify three things in your own build: that your bundler resolves the worker file (the README lists 'Can't resolve web-worker' among recurring issues), that your diagram sizes stay inside a latency budget you have measured, and that the layered algorithm actually suits your graph, since the layout options you choose determine the result far more than the library version does.

## FAQ

### What is elkjs?

elkjs is a JavaScript library that takes the layout-relevant part of the Eclipse Layout Kernel and makes it available to JavaScript. It computes positions for the elements of a diagram, using a layer-based algorithm suited to node-link diagrams with an inherent direction and ports. It is not a diagramming framework and provides no rendering or styling.

### How does elkjs compare to d3 for graph layout?

The README does not compare elkjs with d3. What it does say is that elkjs is a layout engine only: it computes positions for nodes and edges and leaves rendering, styling and interaction to whatever you build around it. d3 is not mentioned anywhere in the README or package.json.

### How do I install elkjs?

Run npm install elkjs for the latest released version, or npm install elkjs@next for a development version based on ELK's master branch. The package is published to npm and exposes require('elkjs') through its main file.

### Does elkjs render the diagram for me?

No. The README states that elkjs is a graph layout engine only and that no rendering, styling or similar is provided. You pass in a graph with node sizes and edges, and you get back computed positions that your own rendering layer has to draw.

### Can elkjs run layout in a Web Worker?

Yes. Web Workers are supported out of the box, and you enable them by passing a workerUrl to the ELK constructor, for example './node_modules/elkjs/lib/elk-worker.min.js'. In Node the library uses the web-worker package as a wrapper around worker_threads, and that package is not installed automatically, so a warning is raised and elkjs falls back to the non-worker path if it is missing.

### Which licence does elkjs use?

The package.json declares EPL-2.0 OR GPL-3.0-or-later, a dual licence. The repository's LICENSE.md is the file to read for the exact terms, and the choice between the two branches depends on how you distribute your own software.

## Sources

- [Issues](https://github.com/kieler/elkjs/issues)
- [kieler/elkjs on GitHub](https://github.com/kieler/elkjs)
- [README](https://github.com/kieler/elkjs/blob/master/README.md)
- [Releases](https://github.com/kieler/elkjs/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kieler-elkjs
