<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Workflow Builder</title>
    <description>The latest articles on DEV Community by Workflow Builder (@workflowbuilder).</description>
    <link>https://dev.to/workflowbuilder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F13501%2F64ca000e-b872-411c-a228-258f05840d5a.jpg</url>
      <title>DEV Community: Workflow Builder</title>
      <link>https://dev.to/workflowbuilder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/workflowbuilder"/>
    <language>en</language>
    <item>
      <title>I built a React circuit editor in a day with Workflow Builder</title>
      <dc:creator>Kacper Cierzniewski</dc:creator>
      <pubDate>Mon, 01 Jun 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/workflowbuilder/i-built-a-react-circuit-editor-in-a-day-with-workflow-builder-12a0</link>
      <guid>https://dev.to/workflowbuilder/i-built-a-react-circuit-editor-in-a-day-with-workflow-builder-12a0</guid>
      <description>&lt;h2&gt;
  
  
  In short
&lt;/h2&gt;

&lt;p&gt;Workflow Builder is generic enough to build non-workflow tools. In one day I built an interactive electrical-circuit editor with custom Battery, Switch, Lightbulb and Resistor nodes, a sub-200-line plugin that solves Ohm's law on every diagram change, and an LED-vs-incandescent cost comparison showing ~7× lower running cost for LEDs at the same brightness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;As a frontend engineer at Synergy Codes I usually work on projects built from scratch. In my latest project we spent a lot of time building a custom diagram tool for electrical circuits, and I wanted to see how far I could get with Workflow Builder instead, in roughly one day of work.&lt;/p&gt;

&lt;p&gt;The end result is a small interactive circuit editor with batteries, switches, resistors, lightbulbs, meters and an energy-cost calculator that compares LED and incandescent bulbs. This is the writeup of that day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the docs first
&lt;/h2&gt;

&lt;p&gt;Before opening the editor I went through the documentation. Two things stood out.&lt;/p&gt;

&lt;p&gt;First, there is a lot of it. Not only an API reference (which I would expect), but a proper introduction, an architecture overview, and a set of guides covering the things you actually need: custom nodes, plugins, persistence, JsonForms control. For a relatively young SDK that was a pleasant surprise.&lt;/p&gt;

&lt;p&gt;Second, the docs are very explicit that Workflow Builder is the editor layer and execution is up to you. The framing matters, because it sets expectations: the SDK is going to give you the hooks rather than the answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Genericity is the keyword
&lt;/h3&gt;

&lt;p&gt;The thing that makes Workflow Builder usable for something like a circuit editor is that almost nothing about it is workflow-specific.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building custom nodes
&lt;/h3&gt;

&lt;p&gt;Each node is described by a JSON Schema and a UI schema. There is a long list of building blocks already in the SDK (text inputs, labels, dropdowns, switches, accordions), and you arrange them in a vertical layout that drives the properties panel. The pattern is 4 small files per node, documented in &lt;a href="https://www.workflowbuilder.io/docs/guides/add-a-custom-node/" rel="noopener noreferrer"&gt;Add a custom node&lt;/a&gt;. I will show what that looks like further down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedding it as a React component
&lt;/h3&gt;

&lt;p&gt;Workflow Builder ships as a React component on npm (&lt;code&gt;@workflowbuilder/sdk&lt;/code&gt;), so dropping it into an existing app is a one-liner. You install the package, import &lt;code&gt;WorkflowBuilder&lt;/code&gt; and its stylesheet, and mount &lt;code&gt;&amp;lt;WorkflowBuilder.Root /&amp;gt;&lt;/code&gt; anywhere in your tree. All configuration – node types, plugins, persistence strategy, layout direction – is passed as props on that component, so the editor stays declarative and there is no separate factory or builder step to keep in sync.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WorkflowBuilder&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@workflowbuilder/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@workflowbuilder/sdk/style.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;demoPaletteItems&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./features/workflowbuilder/palette&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;circuitSolverPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./features/workflowbuilder/plugins/circuit-solver/plugin-exports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;circuitReadoutsPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./features/workflowbuilder/plugins/circuit-readouts/plugin-exports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WorkflowBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Root&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Bulb Comparison"&lt;/span&gt;
      &lt;span class="na"&gt;layoutDirection&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"RIGHT"&lt;/span&gt;
      &lt;span class="na"&gt;nodeTypes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;demoPaletteItems&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;integration&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localStorage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;circuitSolverPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;circuitReadoutsPlugin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire &lt;code&gt;App.tsx&lt;/code&gt; for the circuit editor. Everything else in this writeup lives in the &lt;code&gt;nodeTypes&lt;/code&gt; and &lt;code&gt;plugins&lt;/code&gt; arrays.&lt;/p&gt;

&lt;h3&gt;
  
  
  Different built-in ways of data storing
&lt;/h3&gt;

&lt;p&gt;Another thing that surprised me. Workflow Builder does not force any specific way of saving the diagram. There are 3 persistence strategies, picked via the &lt;code&gt;integration&lt;/code&gt; prop on &lt;code&gt;&amp;lt;WorkflowBuilder.Root /&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first one is &lt;strong&gt;localStorage&lt;/strong&gt;. This is the default, so if you do nothing you already have it. The SDK reads the diagram from &lt;code&gt;localStorage&lt;/code&gt; when the editor mounts and writes it back when the user clicks Save (or when they close the tab, thanks to a &lt;code&gt;beforeunload&lt;/code&gt; hook). Zero configuration, no backend, everything stays in the browser. Limitations are the obvious ones: data is per-browser and per-origin, capped around 5 MB.&lt;/p&gt;

&lt;p&gt;The second one is &lt;strong&gt;REST API&lt;/strong&gt;. You give the SDK 2 endpoints (one to &lt;code&gt;GET&lt;/code&gt; the initial diagram from, one to &lt;code&gt;POST&lt;/code&gt; the saved diagram to) and it handles the wiring for you.&lt;/p&gt;

&lt;p&gt;The third one is &lt;strong&gt;callback (&lt;code&gt;props&lt;/code&gt; strategy)&lt;/strong&gt;. You pass an &lt;code&gt;onDataSave&lt;/code&gt; function and the SDK calls it whenever the diagram needs to persist. This is the escape hatch: if you need auth headers, custom error handling, file uploads, or any non-standard shape, this is the option that gives you full control.&lt;/p&gt;

&lt;p&gt;For my one-day experiment I stayed with &lt;code&gt;localStorage&lt;/code&gt;, but it is nice to know I will not have to rewrite anything when a real backend comes into the picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's build something
&lt;/h2&gt;

&lt;p&gt;The starting point is a plain Vite + React app with &lt;code&gt;@workflowbuilder/sdk&lt;/code&gt; installed and the snippet above as &lt;code&gt;App.tsx&lt;/code&gt;. Standard commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pnpm i&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pnpm dev&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the editor is up with the default (empty) diagram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01wod576a9ir2i05cbzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01wod576a9ir2i05cbzo.png" alt="Default Workflow Builder editor on first start" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tinker with the code
&lt;/h3&gt;

&lt;p&gt;The first thing I wanted to modify is the node library. Based on the docs section on Node schemas, it looks like I just need to add a few schema files. Under &lt;code&gt;src/feWorkflow Builder is great as a visualization tool and very generic, so iatures/workflowbuilder/nodes/&lt;/code&gt; I added the nodes I needed for a simple circuit: Battery, Switch, Lightbulb, Ammeter, Voltmeter.&lt;/p&gt;

&lt;h4&gt;
  
  
  How I did it: the Battery example
&lt;/h4&gt;

&lt;p&gt;For each node I created a folder with 4 files, following the canonical &lt;code&gt;action&lt;/code&gt; example in the demo app. For Battery it looks like this.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;battery/schema.ts&lt;/code&gt; describes the data the node holds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sharedProperties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;statusOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@workflowbuilder/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NodeSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@workflowbuilder/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;sharedProperties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statusOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;voltage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;NodeSchema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BatteryNodeSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sharedProperties&lt;/code&gt; spread gives me the standard &lt;code&gt;label&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; fields every node has, so I do not have to redeclare them.&lt;br&gt;
Workflow Builder is great as a visualization tool and very generic, so i&lt;br&gt;
&lt;code&gt;battery/uischema.ts&lt;/code&gt; tells the SDK how to render the properties panel, in this case just a Text input for the voltage. &lt;code&gt;battery/default-properties-data.ts&lt;/code&gt; holds the default values when the user drags a new Battery onto the canvas (I went with 9 V).&lt;/p&gt;

&lt;p&gt;And finally &lt;code&gt;battery/battery.ts&lt;/code&gt; ties everything together into a &lt;code&gt;PaletteItem&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PaletteItem&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@workflowbuilder/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defaultPropertiesData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./default-properties-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BatteryNodeSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;uischema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./uischema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;battery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PaletteItem&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BatteryNodeSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;battery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BatteryFull&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Battery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Power source providing voltage to the circuit.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;defaultPropertiesData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;uischema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The icon name (&lt;code&gt;BatteryFull&lt;/code&gt;) is one of many ready-made icons exposed by the SDK – you reference them by string, no extra import needed. The last step is registering the node in &lt;code&gt;src/features/workflowbuilder/palette.ts&lt;/code&gt; so it shows up in the left sidebar. That is the whole node. The other 3 (Switch, Lightbulb, Ammeter) follow exactly the same pattern.&lt;/p&gt;

&lt;h4&gt;
  
  
  Build the schema
&lt;/h4&gt;

&lt;p&gt;Now I can see all the defined nodes in the palette:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxzpdmjst5n0h668msdjv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxzpdmjst5n0h668msdjv.png" alt="Editor palette with the custom electrical nodes" width="409" height="977"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can drag and drop them onto the canvas and try to build something:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ura4cbzwd64b9ug0t3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ura4cbzwd64b9ug0t3g.png" alt="Simple circuit assembled from the custom nodes" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is not interactive yet – this is only a visual presentation – but to this point it took me roughly 2 hours, including reading docs and writing this article, to get a working visualization. As I still had a lot of spare time, I figured I could try to make it more interactive. For example, when I turn off the switch, there should be no current and the bulb should stop glowing. The plugin API is well documented, so this should be doable.&lt;/p&gt;

&lt;h3&gt;
  
  
  The idea is crystallizing
&lt;/h3&gt;

&lt;p&gt;I started with a simple lightbulb schema, but it looks like we can do a lot more. It should be possible to add some real logic and compare an LED bulb to a traditional one in terms of efficiency and cost. That is what I want to build.&lt;/p&gt;

&lt;h4&gt;
  
  
  Small adjustments
&lt;/h4&gt;

&lt;p&gt;To make this work I added a few missing nodes. A Wattmeter to read the power going through a load. An Efficiency property on the Lightbulb (lumens per watt) so I can describe LED vs incandescent behavior. A Resistor so the circuit's total resistance is more than just the bulbs. And an Energy Stats node to show how much power and how much money the circuit costs over time.&lt;/p&gt;

&lt;h4&gt;
  
  
  Let's implement it
&lt;/h4&gt;

&lt;p&gt;To make the circuit actually react to changes I wrote a small plugin called &lt;code&gt;circuit-solver&lt;/code&gt;. The pattern is exactly what the docs describe. I registered a hook via the SDK plugin API (the &lt;code&gt;OptionalHooks&lt;/code&gt; slot) that runs every time any node or edge in the diagram changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerComponentDecorator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@workflowbuilder/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CircuitSolverRunner&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./circuit-solver-runner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;registerComponentDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OptionalHooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CircuitSolverRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CircuitSolver&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runner reads &lt;code&gt;nodes&lt;/code&gt; and &lt;code&gt;edges&lt;/code&gt; from the SDK store via &lt;code&gt;useStore&lt;/code&gt; and runs a pure solver function. The solver walks the diagram starting from every Battery node, follows the outgoing edges around the loop, sums the resistances of every Resistor and Lightbulb it passes through (Lightbulb resistance is derived from &lt;code&gt;V_nominal² / P_nominal&lt;/code&gt;), applies Ohm's law (&lt;code&gt;I = V / R&lt;/code&gt;), and writes the resulting voltage, current and power back for every node in the loop. A Switch in the loop with &lt;code&gt;isOn === false&lt;/code&gt; drops the current to zero everywhere.&lt;/p&gt;

&lt;p&gt;The computed values land in a separate Zustand store. The readout plugin (which is the thing that paints the values directly on each node) just subscribes to that store. The solver stays pure, the readout stays React, and the two never collide. The whole solver fits in one file of around 250 lines.&lt;/p&gt;

&lt;p&gt;One simplification: this only handles series circuits, one loop per battery, no branches. Parallel circuits and proper mesh analysis need Kirchhoff's laws, which is a much bigger task and not what I want to spend my one day on.&lt;/p&gt;

&lt;p&gt;Now I need to draw the whole circuit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp2gk1sj2dkm4mok3m36.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp2gk1sj2dkm4mok3m36.png" alt="Circuit diagram with the switch in the OFF position" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  It's alive!
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdegxwbl7gebvx7jg6xem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdegxwbl7gebvx7jg6xem.png" alt="Same circuit after turning the switch on" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I turned the switch on, the logic from the plugin kicked in for all the new nodes and it looks like it is working correctly. The bulbs are shining and the Resistor value is taken into account. When I increase the resistance, the bulbs are barely glowing and the Energy Stats reacts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbvl2em07aqxmz0bikx7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbvl2em07aqxmz0bikx7.png" alt="Higher resistance, bulbs barely glow, Energy Stats drops" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Compare LED vs traditional bulb
&lt;/h3&gt;

&lt;p&gt;To make a proper comparison I need two separate circuits and then I can check the Energy Stats node on each. Everything is customizable here, so you can easily adjust the electricity cost in your area and how many hours per day each bulb is on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0abka1mdm53ulq710awd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0abka1mdm53ulq710awd.png" alt="Two separate circuits, incandescent on the left, LED on the right" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One small thing about the Energy Stats node. When I first built it, the cost was just a number with no currency unit, which on a diagram looks a bit weird. I went back and added a &lt;code&gt;currency&lt;/code&gt; text field that defaults to &lt;code&gt;$&lt;/code&gt; but accepts anything: &lt;code&gt;PLN&lt;/code&gt;, &lt;code&gt;€&lt;/code&gt;, &lt;code&gt;zł&lt;/code&gt;, whatever fits. It is a tiny change, but a nice example of how cheap adding a property is in this architecture. One new entry in the schema, one in the UI schema, one default value, and the readout picks it up.&lt;/p&gt;

&lt;h4&gt;
  
  
  Cost comparison
&lt;/h4&gt;

&lt;p&gt;To compare costs properly I tuned the bulbs' nominal power so both produce the same brightness in lumens. To keep it realistic I aimed for 1000 lm, which is a moderate amount of light for a small room.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fetx1c1tc9ir4edmu1dby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fetx1c1tc9ir4edmu1dby.png" alt="Cost comparison: incandescent vs LED" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The focus now is on the Energy Stats nodes. About &lt;code&gt;$20&lt;/code&gt; per year for the traditional bulb running 5 hours a day, and only about &lt;code&gt;$3&lt;/code&gt; for the LED. That is roughly 7 times less cost for the same brightness. And that is one bulb.&lt;/p&gt;

&lt;h4&gt;
  
  
  Efficiency comparison
&lt;/h4&gt;

&lt;p&gt;I already added the logic that makes a bulb dimmer when it runs below its rated voltage, so I can also check how efficient the LED is compared to the traditional bulb at the same power consumption.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffp24g2xtcx2het4ntnvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffp24g2xtcx2het4ntnvr.png" alt="Same power, different efficiency, LED visibly brighter" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course this is just a representation, real bulbs do not literally glow at these exact intensities, but Workflow Builder is flexible enough that I can adjust it for my needs. The point comes across: the LED produces much more visible light for the same wattage, while the Energy Stats confirms identical consumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Overall, a great experience
&lt;/h3&gt;

&lt;p&gt;The architecture is generic enough to build well outside the workflow domain – this article is one example. An electrical circuit editor is about as far from a "workflow" as you can get, and I did not have to fight the SDK at any point. At the same time the genericity does not come at the cost of structure. There are clear patterns to follow (the 4-file node, the plugin decorators), so you are not dropped into a freeform mess. Without too much effort I was able to create proper node schemas to represent my needs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The plugin API let me add domain-specific logic without touching the app core, which is a sign of good architecture. With more time I can imagine designing more nodes for other electrical devices and building a full home circuit that calculates how much power it draws.&lt;/li&gt;
&lt;li&gt;Workflow Builder ships with a design system, including dark mode out of the box:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fagp2ealv6g7fmo5q3mh1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fagp2ealv6g7fmo5q3mh1.png" alt="The circuit editor in dark mode" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Idea for improvement
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The default node template gives you one input on the left and one output on the right, which fits a workflow but not a symmetric domain like circuits, where a battery has + and -, a switch has two poles, and a resistor has two terminals. In this writeup I worked around it by placing nodes carefully on the canvas. The SDK has a proper way to do this: &lt;code&gt;defineNodeTemplate&lt;/code&gt; lets you author a custom React component for a node type and declare whatever port topology you need, registered through the &lt;code&gt;nodeTemplates&lt;/code&gt; prop on &lt;code&gt;&amp;lt;WorkflowBuilder.Root /&amp;gt;&lt;/code&gt;. A natural next step for this project would be a dedicated two-terminal template for the passive components so the wiring matches real schematics instead of relying on layout tricks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is it possible to check the source code?
&lt;/h3&gt;

&lt;p&gt;Yes, here are the links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/kacpercierzniewski/electric-schema-workflowbuilder/" rel="noopener noreferrer"&gt;Repository of the project&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/synergycodes/workflowbuilder/" rel="noopener noreferrer"&gt;Workflow Builder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Can Workflow Builder be used for non-workflow domains?
&lt;/h3&gt;

&lt;p&gt;Yes. The SDK is generic – nodes are described by JSON Schema and a UI schema, and plugins can react to any diagram change. The only constraint I hit was port topology (one input on the left, one output on the right), which is limiting for symmetric domains like electrical circuits.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does it take to add a new node type?
&lt;/h3&gt;

&lt;p&gt;About 15-20 minutes per node once you know the pattern. Each node is 4 files (data schema, UI schema, default-properties, PaletteItem) plus 1 line in &lt;code&gt;palette.ts&lt;/code&gt; to register it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does the plugin API work?
&lt;/h3&gt;

&lt;p&gt;Plugins register decorators through &lt;code&gt;registerComponentDecorator(slot, { content, name })&lt;/code&gt;. The slot tells the SDK where to mount your component – &lt;code&gt;OptionalHooks&lt;/code&gt; runs every time nodes or edges change. From there you read the SDK store with &lt;code&gt;useStore&lt;/code&gt; and write derived data back into your own Zustand store. The circuit-solver in this article fits in around 250 lines this way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which persistence strategies does Workflow Builder support?
&lt;/h3&gt;

&lt;p&gt;3 strategies, picked via the &lt;code&gt;integration&lt;/code&gt; prop on &lt;code&gt;&amp;lt;WorkflowBuilder.Root /&amp;gt;&lt;/code&gt;: &lt;code&gt;localStorage&lt;/code&gt; (default, ~5 MB cap), REST API (you supply a GET and a POST endpoint), and &lt;code&gt;props&lt;/code&gt; callback (you pass &lt;code&gt;onDataSave&lt;/code&gt; and handle everything yourself).&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Synergy Codes build a domain-specific editor like this?
&lt;/h3&gt;

&lt;p&gt;Yes. Synergy Codes specializes in diagramming interfaces, workflow editors, and visualization tools, and has delivered 170+ custom editors for clients including Siemens, BMW, and Canon. Workflow Builder, ngDiagram, and Overflow are the open-source products we use as starting points – see &lt;a href="https://www.synergycodes.com/" rel="noopener noreferrer"&gt;synergycodes.com&lt;/a&gt; for case studies.&lt;/p&gt;

&lt;h2&gt;
  
  
  See also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.workflowbuilder.io/docs/guides/add-a-custom-node/" rel="noopener noreferrer"&gt;Add a custom node&lt;/a&gt;, the four-file pattern this article leans on&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.workflowbuilder.io/docs/guides/build-a-plugin/" rel="noopener noreferrer"&gt;Build a plugin&lt;/a&gt;, the registration API used by the circuit-solver plugin&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.workflowbuilder.io/docs/guides/configuring-the-editor/" rel="noopener noreferrer"&gt;Configuring the editor&lt;/a&gt;, where &lt;code&gt;nodeTypes&lt;/code&gt;, &lt;code&gt;plugins&lt;/code&gt; and &lt;code&gt;integration&lt;/code&gt; live&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
