Skip to content
D
Documentation

Create custom nodes

how-to
2 min readUpdated

Use a custom node when the inside of a node needs application-specific content. Keep a stable id for each node, give the node a real size, and let Grafloria keep ownership of selection, dragging, ports, routing, and serialization.

Choose the rendering surface

In the framework bindings, declaring a renderer for a node type opts matching nodes into the HTML layer. In JavaScript, set custom: true explicitly. In every case, the renderer fills a host whose geometry comes from position and size.

Render a custom node

The following examples render two connected cards. Each card keeps its stable id; the engine still owns the edge and node behavior.

js
import { Grafloria, render } from '@grafloria/element';

Grafloria.registerNodeType('card', (node, element) => {
  const card = document.createElement('div');
  card.style.cssText = 'height:100%;box-sizing:border-box;padding:12px;border:2px solid #4f46e5;border-radius:10px;background:#fff;font:14px system-ui';

  const title = document.createElement('strong');
  title.textContent = String(node.getData('title'));
  card.append(title);

  const id = document.createElement('div');
  id.textContent = `node: ${node.id}`;
  card.append(id);
  element.append(card);
});

const host = document.getElementById('diagram');
if (!host) throw new Error('Missing #diagram');
host.style.height = '400px';

const instance = render({
  nodes: [
    { id: 'build', type: 'card', custom: true, position: { x: 80, y: 100 }, size: { width: 220, height: 100 }, data: { title: 'Build' } },
    { id: 'deploy', type: 'card', custom: true, position: { x: 400, y: 100 }, size: { width: 220, height: 100 }, data: { title: 'Deploy' } },
  ],
  edges: [{ id: 'build-to-deploy', source: 'build', target: 'deploy' }],
}, host);
The custom card and its connected edge rendered inside the sized diagram host.

The mounted result is two application-rendered cards joined by a routed edge. Select or drag either card: its host moves with the node, while the edge remains attached. In React and Qwik, selected changes the border; the engine supplies that state through NodeProps.

Use the live model when needed

Framework renderers receive a live NodeModel. Use tracked setters for model changes, then repaint through the DiagramInstance when you need a synchronous frame:

ts
import { render } from '@grafloria/element';

const host = document.getElementById('diagram');
if (!host) throw new Error('Missing #diagram');
host.style.height = '400px';

const instance = render({
  nodes: [{ id: 'build', position: { x: 80, y: 100 }, size: { width: 220, height: 100 } }],
}, host);

const node = instance.getModel().getNode('build');
if (!node) throw new Error('Missing build node');
node.setMetadata('label', 'Updated build');
instance.renderNow();

The instance returned by render() is the live handle. It also exposes the model that owns nodes and links, so the custom body does not need to implement selection, ports, or edge geometry.

Add ports without putting handles in the body

Ports belong to the node spec, not to the custom component. Declare them when a connection must use named or typed endpoints:

ts
const nodes = [{
  id: 'build', type: 'card', custom: true,
  position: { x: 80, y: 100 }, size: { width: 220, height: 100 },
  ports: [
    { id: 'out', side: 'right', type: 'output', dataType: 'build' },
  ],
}];

Pitfalls

  • In JavaScript, omitting custom: true sends the node through the stock SVG path, so the registered renderer is never called. Register the type before mounting; a host that mounted without a renderer stays empty.
  • Keep position: { x, y } and size: { width, height } on the spec. Top-level x and y are not node geometry.
  • Make the custom root fill the box with height: 100% and box-sizing: border-box.
  • A custom renderer runs at mount, not on every data mutation. Update DOM you own or use a component with its own reactive state. See Build ER and UML diagrams for the dashboard update pattern.
  • Give the canvas parent a resolved height, or use Style a diagram; a zero-height parent produces a blank canvas.

See it running

Open the custom-nodes demo to see custom bodies, dragging, ports, and connected edges together. The source is custom-nodes.html.

Related: Ports and validation, Model and document, and Element versus render().

Was this page helpful?

Create custom nodes — Grafloria