Skip to content

Shapes and Stencils

David Benson edited this page Oct 7, 2026 · 1 revision

This page explains how draw.io draws a shape from the shape key of a cell style, and how to add your own: JavaScript shape classes, XML stencils, inline stencils and image shapes, plus the user-level custom libraries. It is for developers who write shapes for a fork, a plugin or a shape library. For the style syntax itself see Styles; for putting shapes into the sidebar see Extending the editor.

Three kinds of shapes

Kind Defined by Style Strengths Limits
JavaScript shape A class registered with mxCellRenderer.registerShape shape=myShape Any drawing logic, style-driven parameters, custom handles, computed connection points Must be loaded wherever the diagram is rendered
XML stencil A <shape> element in a stencil set, registered with mxStencilRegistry shape=mxgraph.lib.name, or shape=stencil(...) inline Declarative, scales with the cell, colours from the style, editable in the Edit Shape dialog Fixed geometry, no logic
Image Any image URL or data URI shape=image;image=... Icons and logos, no code Not styleable beyond the image itself (SVG colours can be edited)

The built-in libraries use all three: JavaScript shapes for parametric shapes (for example in mxBasic.js), stencils for most icon sets, and image files under src/main/webapp/img/lib for others (clip art, azure2, ibm, sap and more).

How a shape name is resolved

mxCellRenderer.prototype.createShape (mxCellRenderer.js) reads style.shape and:

  1. If the name starts with stencil(, the override in Graph.js decompresses the stencil XML in the parentheses and draws it (inline stencils).
  2. If a class is registered under the name (mxCellRenderer.defaultShapes), it creates an instance.
  3. Otherwise it asks mxStencilRegistry.getStencil(name). draw.io's version of this function in Graph.js loads stencil sets on demand: for a name mxgraph.<lib>.<...>.<shape>, getBasenameForStencil returns <lib>/<...>, and the files listed in mxStencilRegistry.libraries[<basename>] are loaded (.xml stencil sets and .js shape files), or, if there is no entry, STENCIL_PATH/<basename>.xml.
  4. If nothing matches, the cell is drawn with the default shape (a rectangle for vertices).

Stencil names are registered in lower case with spaces replaced by _ (mxStencilRegistry.parseStencilSet), so the style must use the lower-case form. An unknown shape is not an error: the diagram opens and the cell is a rectangle. This is what users see when a diagram with custom shapes is opened somewhere the shape is not loaded.

Connection points come from, in this order: the points style key, the shape's getConstraints(style, w, h), the stencil's <connections>, and the shape's constraints array (see the getAllConnectionConstraints override in Graph.js).

JavaScript shapes

The core shapes are in mxgraph/src/shape and registered in mxCellRenderer.js; draw.io adds about a hundred in grapheditor/Shapes.js (cube, step, note, table, umlLifeline, ...). The shape libraries add their own in src/main/webapp/shapes (mxBasic.js registers mxgraph.basic.*, mxDFD.js mxgraph.dfd.*, and so on). The merge target of etc/build/build.xml concatenates shapes/**/*.js into js/shapes-14-6-5.min.js, which production mode, the desktop app and viewer-static.min.js load as a whole; in development mode the files are loaded on demand through mxStencilRegistry.libraries (see Building).

A shape class:

function TagShape()
{
	mxActor.call(this);
};

mxUtils.extend(TagShape, mxActor);

// Default for the 'size' style key
TagShape.prototype.size = 20;

// mxActor.paintVertexShape translates to (x, y), begins a path,
// calls redrawPath and fills and strokes the path
TagShape.prototype.redrawPath = function(c, x, y, w, h)
{
	var s = Math.max(0, Math.min(w, parseFloat(
		mxUtils.getValue(this.style, 'size', this.size))));

	c.moveTo(0, 0);
	c.lineTo(w - s, 0);
	c.lineTo(w, h / 2);
	c.lineTo(w - s, h);
	c.lineTo(0, h);
	c.close();
};

// Fixed connection points (x and y relative to the bounds)
TagShape.prototype.constraints = [
	new mxConnectionConstraint(new mxPoint(0, 0.5), true),
	new mxConnectionConstraint(new mxPoint(1, 0.5), true)];

mxCellRenderer.registerShape('myTag', TagShape);

Use it with the style shape=myTag;whiteSpace=wrap;html=1;size=30;. The parts:

  • Base class. Extend mxShape and implement paintVertexShape(c, x, y, w, h), or extend mxActor and implement redrawPath(c, x, y, w, h) for a single filled path. mxCylinder, mxRectangleShape and the classes in Shapes.js are further bases.
  • Canvas. c is an mxAbstractCanvas2D (SVG on screen, other canvases for export): moveTo, lineTo, quadTo, curveTo, arcTo, close, begin, end, fill, stroke, fillAndStroke, rect, roundrect, ellipse, text, image, translate, rotate, save, restore, and the set... functions for colours, dashes, alpha and shadows. Call c.setShadow(false) before details that should not cast a shadow.
  • Style values. Read your own keys with mxUtils.getValue(this.style, key, default). this.fill, this.stroke and this.strokewidth already hold the resolved colours and width.
  • Connection points. A constraints array of mxConnectionConstraint(point, perimeter, name, dx, dy), or getConstraints(style, w, h) to compute them from the size.
  • Handles. In the editor, Graph.handleFactory[name] = function(state) { return [Graph.createHandle(state, keys, getPositionFn, setPositionFn)]; } adds yellow handles that edit style keys. Graph.handleFactory and Graph.createHandle are defined in every bundle (as empty stand-ins where the handle code is not included), so the assignment is safe in the viewer too.
  • Format panel. customProperties on the prototype (an array of {name, dispName, type, min, max, defVal}) lists style keys in the Property table of the Style tab (see mxBasic.js).

A handle for the size key of the example:

Graph.handleFactory['myTag'] = function(state)
{
	return [Graph.createHandle(state, ['size'], function(bounds)
	{
		var size = Math.max(0, Math.min(bounds.width, parseFloat(
			mxUtils.getValue(this.state.style, 'size', TagShape.prototype.size))));

		return new mxPoint(bounds.x + bounds.width - size, bounds.y);
	}, function(bounds, pt)
	{
		this.state.style['size'] = Math.round(Math.max(0,
			Math.min(bounds.width, bounds.x + bounds.width - pt.x)));
	})];
};

A JavaScript shape must be loaded everywhere the diagram is drawn: in the editor (a fork, or a plugin that registers it at load time, as rackF5.js does), in the viewer and lightbox, and in exports. For a fork, add the file to src/main/webapp/shapes/, register the shapes with an mxgraph.<lib>. prefix, add an entry to mxStencilRegistry.libraries (diagramly/Editor.js) for development mode and the viewer.min.js bundle, and rebuild so that the file is part of shapes-14-6-5.min.js. Diagrams opened on app.diagrams.net or viewer.diagrams.net show your shapes as rectangles, so prefer stencils for shapes that travel with the file.

XML stencils

A stencil describes a shape as drawing commands in its own coordinate system (w by h, default 100 by 100), scaled to the cell. The language is documented in the header of mxStencil.js, in the stencil schema and, element by element, on drawio.com.

<shapes name="mxgraph.mylib">
  <shape name="Ticket" w="100" h="50" aspect="variable" strokewidth="inherit">
    <connections>
      <constraint x="0" y="0.5" perimeter="0" name="W"/>
      <constraint x="1" y="0.5" perimeter="0" name="E"/>
    </connections>
    <background>
      <path>
        <move x="0" y="0"/>
        <line x="100" y="0"/>
        <line x="100" y="18"/>
        <arc rx="7" ry="7" x-axis-rotation="0" large-arc-flag="0" sweep-flag="0" x="100" y="32"/>
        <line x="100" y="50"/>
        <line x="0" y="50"/>
        <line x="0" y="32"/>
        <arc rx="7" ry="7" x-axis-rotation="0" large-arc-flag="0" sweep-flag="0" x="0" y="18"/>
        <close/>
      </path>
    </background>
    <foreground>
      <fillstroke/>
      <dashed dashed="1"/>
      <path>
        <move x="25" y="0"/>
        <line x="25" y="50"/>
      </path>
      <stroke/>
    </foreground>
  </shape>
</shapes>

This set registers mxgraph.mylib.ticket: a ticket with notched sides, a dashed tear-off line and connection points on the left and right.

Element Attributes Notes
shapes name A stencil set; the name is the prefix of every shape in it
shape name, w, h, aspect (variable or fixed), strokewidth (number or inherit) One shape
connections > constraint x, y (0 to 1), perimeter (0 or 1), name Connection points
background At most one path, rect, roundrect or ellipse, without fill or stroke; used for the shadow and the outline
foreground Everything else; if there is a background, the first element must be fill, stroke or fillstroke to paint it
path rounded, arcSize Contains move (x, y), line (x, y), quad (x1, y1, x2, y2), curve (x1 to y3), arc (rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y, as in SVG) and close. rounded="1" only rounds paths made of move and line.
rect, roundrect, ellipse x, y, w, h; arcsize for roundrect Must be followed by fill, stroke or fillstroke
fill, stroke, fillstroke Paint the current path
fillcolor, strokecolor, fontcolor color, default A colour, or fill, stroke, font for the cell's colours. Any other value is read as a style key, with default as the fallback; such keys appear in the format panel.
strokewidth width, fixed fixed="1" does not scale the width
dashed, dashpattern dashed; pattern For example pattern="5 3"
linecap, linejoin, miterlimit cap (flat, round, square); join; limit
alpha, fillalpha, strokealpha alpha 0 to 1. In the current code all three set the overall alpha.
fontsize, fontstyle, fontfamily size; style (bits: 1 bold, 2 italic, 4 underline); family
text str, x, y, align, valign, localized, vertical, rotation, align-shape, flip-shape Fixed text inside the shape (the cell label is separate)
image src, x, y, w, h, flipH, flipV An image URL or data URI
include-shape name, x, y, w, h Draws another stencil by its registered name
labelBounds if, x, y, w, h draw.io extension (grapheditor/Shapes.js): the label area in stencil coordinates, used while the style key named in if is 1 (for example boundedLbl); the key appears as a checkbox in the Property table
save, restore Push and pop the drawing state

Loading stencil sets

Situation How
Built-in libraries Bundled in js/stencils.min.js for the editor; the committed viewer bundles fetch them from STENCIL_PATH (see Embedding)
Your own file in a deployment or fork Put <shapes name="mxgraph.mylib"> in src/main/webapp/stencils/mylib.xml and use shape=mxgraph.mylib.<name>; it is loaded from STENCIL_PATH the first time it is needed
A library with stencils and JavaScript mxStencilRegistry.libraries['mylib'] = [STENCIL_PATH + '/mylib.xml', SHAPES_PATH + '/mxMyLib.js'];
From code (for example a plugin) mxStencilRegistry.parseStencilSet(xmlDoc.documentElement) for a parsed set, mxStencilRegistry.loadStencilSet(url) for a URL, or mxStencilRegistry.addStencil(name, new mxStencil(shapeNode)) for one shape
A sidebar palette of all shapes in a set Sidebar.prototype.addStencilPalette(id, title, stencilFile, style, ...)

The built-in stencil XML is not part of this repository. The library sources are built outside it into js/stencils.min.js, which holds every stencil set in a compact encoding, keyed by its path below STENCIL_PATH, and replaces mxStencilRegistry.loadStencil so that those paths are decoded from the bundle while every other path is still fetched from the server. The public build.xml therefore keeps the shipped stencils.min.js (it only runs the converter when stencil XML is present). You cannot rebuild the built-in sets, but you can add sets of your own as above; the dynamic loading works in production and development mode. To see the XML of a built-in shape, select it and open Edit Shape.

Inline stencils

A style can carry the stencil itself: shape=stencil(<data>), where <data> is the XML compressed with Graph.compress (URI-encoded, deflated without a header, then Base64, the same encoding as compressed diagrams). The Edit Shape dialog (EditShapeDialog in diagramly/Dialogs.js) writes this: Arrange > Insert > Shape creates a new shape, and Edit Shape (in the Edit menu of the format panel's Style tab, and under Edit in the context menu) opens the XML of a selected stencil-based shape, including the built-in ones. Preview draws the XML inside the dialog; applying it writes the shape=stencil(...) style to the cell.

var xml = '<shape name="Badge" w="40" h="40"><background>' +
	'<ellipse x="0" y="0" w="40" h="40"/></background><foreground>' +
	'<fillstroke/><rect x="10" y="10" w="20" h="20"/><stroke/>' +
	'</foreground></shape>';

graph.insertVertex(graph.getDefaultParent(), null, '', 20, 20, 40, 40,
	'shape=stencil(' + Graph.compress(xml) + ');html=1;');

Inline stencils need no files and render wherever the diagram is opened, including app.diagrams.net, the viewer and exports, which makes them the best choice for shapes in custom libraries and templates.

Image shapes

The image named style in styles/default.xml sets shape=image with the label below the image, so image;image=https://example.com/logo.svg; is a complete style. Useful keys are imageAspect (0 to stretch), aspect=fixed (keep the ratio when resizing), imageBackground, imageBorder and clipPath; see the style reference.

Because ; separates style entries, data URIs are written without ;base64: image=data:image/png,iVBORw0.... mxGraph.prototype.postProcessCellStyle restores the full form before rendering. For SVG images stored as data URIs (data:image/svg+xml,...), the Style tab of the format panel offers colour editing. Images referenced by URL are loaded by the browser of whoever views the diagram, so they must be reachable from there; use data URIs for diagrams that must be self-contained.

Custom libraries for users

A custom library is a file of reusable entries (shapes, groups, images or whole diagrams) that appears in the sidebar. It is the way to share shapes without code: an <mxlibrary> element with a JSON array of entries, each with w, h, an optional title and either xml (a compressed or escaped mxGraphModel) or data (an image data URI).

  • Create one with File > New Library, drag cells or image files into it and export it. Format details: custom shape library format; user guide: custom shape libraries.
  • Load libraries by URL with clibs=U<url-encoded URL> (several separated by ;), or for a deployment with the libraries and defaultCustomLibraries configuration keys (see Configuration).
  • jgraph/drawio-libs collects libraries in this format, with ready-made clibs links.
  • Entries that use stencils should use inline stencils, and entries must not depend on JavaScript shapes, so that they work on any draw.io installation.

To turn a set of SVG files into a stencil set, jgraph/svg2xml is a Java tool that converts SVG files or folders into stencil XML (one library per folder). It was written for internal use and its README lists the options that are not implemented. Check the result in the Edit Shape dialog.

Putting shapes into the sidebar

Palettes refer to shapes only by style: createVertexTemplateEntry('shape=mxgraph.mylib.ticket;whiteSpace=wrap;html=1;', 100, 50, '', 'Ticket'). The palette files in js/diagramly/sidebar show the conventions (a common style prefix per library, getTagsForStencil for search tags, setCurrentSearchEntryLibrary to group the search results). How to register a palette is described in Extending the editor and, for plugins, in Plugins.

Security notes

Stencils and shapes are rendered for every viewer of a diagram, so treat them like diagram content. Inline stencils and stencil files can come from untrusted diagrams and libraries. mxStencil.allowEval (default false) controls whether the text content of text and image elements is evaluated as a function; keep it off. mxStencilRegistry.allowEval controls whether .js files listed in mxStencilRegistry.libraries are fetched and evaluated on demand; production mode and the desktop app turn it off and load the shape bundle instead. JavaScript shapes that draw text must not pass diagram text to c.text(..., format='html', ...) without Graph.sanitizeHtml. See Security.

See also

Clone this wiki locally