Dev.to WebDev ๐Ÿ›  Dev ๐Ÿ‘ 0 ๐Ÿ“– 9 min read

How deeply can you configure How deeply can you configure a workflow editor? The palette, panels, and theme are data

Disclaimer: I work at Synergy Codes, the team behind Workflow Builder, and this article was first published on the Workflow Builder blog. The demo and the code are mine. TL;DR: The editor in Workflow Builder is data-dr

How deeply can you configure How deeply can you configure a workflow editor? The palette, panels, and theme are data

Demo application screenshot

Disclaimer: I work at Synergy Codes, the team behind Workflow Builder, and this article was first published on the Workflow Builder blog. The demo and the code are mine.

TL;DR: The editor in Workflow Builder is data-driven. A backend serves the node palette, the property panels, and the theme as JSON and CSS variables, so you customize the editor by editing data, not by rewriting code. I built a demo with two editors that share every line of code yet look and behave like different products. Live demo ยท source.

New to Workflow Builder? It is an open-source framework for workflow orchestration you embed in your product; this post is about its visual editor. The editor in Workflow Builder is data-driven. A backend serves the node palette, the property panels, and the theme as JSON and CSS variables. So you customize the editor by editing data, not by rewriting code. The real win is flexibility: one editor can serve many use cases, clients, and brands. You can try the demo live or read the source.

Switching the profile dropdown changes the whole editor โ€“ palette, node shapes, panel fields, navigation, tagline, and theme โ€“ because it is all input data.
Switching the profile reloads a different configuration and the editor reshapes itself: palette, node shapes, panel fields, navigation, tagline, font, radii, and color mode. Nothing in the code changed, only the data did.

What "data-driven" means for a workflow editor

Workflow Builder is an open-source framework for workflow orchestration in your product. Its editor SDK, the part this post is about, puts a visual workflow editor inside your own React app. Data-driven means its configurable surfaces are data, not code. You would normally hard-code these parts. Here they are just data the editor loads when it opens: the node palette, the property panels, and the theme. The code that draws them stays the same.

To see how far it went, I built two editors that share every line of code. One is an invoice approval tool, teal and calm, top-down, light mode. The other is an AI content pipeline, amber and sharp, left-to-right, dark mode. When I switched between them, the palette, the panels, the seeded diagram, and the whole look changed. My source tree did not.

What I liked most was how easy that was. The hard part, a working editor, was already there. I only had to touch the data.

Getting the configuration from a server, not the bundle

I gave myself one rule: the configuration had to arrive over the network, the way it would from a real backend. Nothing gets imported from the config folder, and nothing sits in public/. A small Vite middleware plays the part of the backend and serves each profile as JSON:

GET /api/profiles.json           -> the switcher menu
GET /api/profiles/:id.json       -> the four files, assembled into one document
GET /api/profiles/:id/:part.json -> a single file (feeds the in-app editor)

The routes end in .json on purpose. The build can freeze them into static files, so a plain static host answers the exact same requests. The editor cannot tell a live backend from a folder of files.

You can watch the configuration land in the browser's Network tab. A profile is just JSON: a palette, a theme, a seeded diagram, and the chrome around them. The app fetches it and hands it to the editor as it loads. Point the same code at a real API, and nothing else changes. The editor already treats its own configuration as remote data.

So the config comes over HTTP, from a mock backend. But how much of the editor can it actually describe?

The palette is a list, not code

The palette is the clearest case. It is a list you hand to the editor, so instead of registering node types in React, you describe them in JSON. Here is one node from that config:

{
  "type": "ai.analyze",
  "label": "Analyze Invoice",
  "icon": "Robot",
  "properties": {
    "model": {
      "type": "string",
      "label": "Model",
      "options": [
        { "label": "Claude Opus 5", "value": "claude-opus-5", "icon": "ClaudeLogo" },
        { "label": "Claude Sonnet 5", "value": "claude-sonnet-5", "icon": "ClaudeLogo" }
        // โ€ฆ more options
      ]
    },
    "prompt": { "type": "string", "label": "Prompt", "placeholder": "โ€ฆ" }
    // โ€ฆ timeoutSeconds
  },
  "required": ["model", "prompt"],
  "ui": {
    "preset": "general",
    "elements": [
      {
        "type": "Accordion",
        "label": "Model",
        "elements": [
          { "type": "Select", "scope": "#/properties/model" },
          { "type": "VariableTextArea", "scope": "#/properties/prompt" }
        ]
      }
      // โ€ฆ an "Execution" accordion with the timeout
    ]
  }
  // โ€ฆ defaults, outputSchema
}

One quick warning before you copy this. This short shape is the demo's own. It is not what the SDK reads directly. The app first turns each entry into what the SDK wants: a JSON Schema and a JSON Forms ui schema. So it will not work if you paste it in. And that is the point. You can design your own config format. You just convert it to what the SDK expects afterward. You shape the data to fit your product. The custom node types guide shows the full node format the SDK expects.

The content pipeline's palette is a different list: a content brief, a draft node, a quality gate, an editor review.

The app even has a fingerprint button. Turn it on, and every node points back to the config entry it came from. The canvas is a projection of the data.

Each node on the canvas is labeled with the configuration entry it was compiled from, including the AI node.
The fingerprint toggle tags every node, including the AI node, with the config entry that produced it.

The palette places the nodes. The panel is where each node earns its properties, and it is the surface I most expected to need code.

The property panels are JSON Schema and JSON Forms

The panel is the deepest data surface of all. Each one comes from two structures the profile carries: a JSON Schema that declares the fields and their rules, and a JSON Forms ui schema that declares the layout. No form component here is written by hand, except a few deliberate custom controls I will come back to. It is the one place where config runs out and you drop to code.

Form validation lives in the schema. On the human-approval node, an allOf block turns on an extra rule under a condition. When the approval mode is auto, the threshold must be at least 1:

"allOf": [
  {
    "if":   { "properties": { "approvalMode": { "const": "auto" } } },
    "then": { "properties": { "thresholdAmount": { "type": "number", "minimum": 1 } } }
  }
]

Because the schema is data, you can tighten a rule while the editor runs. The demo has a Config Studio, a panel that edits the live config. Raise a field's minimum there. Reopen a node that is already on the canvas, and a validation badge appears on it. The diagram never moved. Only the rule it is checked against did.

Editing the JSON Schema minimum adds a validation badge to a node already on the canvas, without changing the diagram.
Config Studio โ†’ Schema: raising thresholdAmount's minimum to 5000, then reopening a node that was already placed. A validation badge appears, and the diagram is untouched.

Layout and conditional visibility sit in the ui schema. Controls fall into accordions and columns, and a rule shows or hides one based on another field's value. Here the threshold appears only when the reviewer picks auto-approval:

{
  "type": "Text",
  "scope": "#/properties/thresholdAmount",
  "options": { "customRenderer": "CurrencyAmount", "currency": "EUR" },
  "rule": {
    "effect": "SHOW",
    "condition": { "scope": "#/properties/approvalMode", "schema": { "const": "auto" } }
  }
}

The same mechanism does HIDE, ENABLE, and DISABLE. Which fields a reviewer sees, when they are required, whether they can be edited: the profile decides all of it. So you change the form by editing data, not by editing code.

The properties panel shows and hides fields as the approval mode changes, driven entirely by JSON rules.
Changing the approval mode shows and hides thresholdAmount, dueAfterHours, and a warning. It is a JSON Forms rule plus allOf, no React.

This part surprised me the most. I did not have to build the form or style it. For example, to put controls side by side, I just wrote HorizontalLayout in the data. Normally you do all of this by hand: the node look, the panel styling, the form logic. Here the basic controls come ready. That saved me a lot of time.

Fields and rules covered behavior. The last surface to move was the look.

The look is a set of tokens

The theme is a map of design tokens. They use the SDK's own CSS variable names, split across a base, a light mode, and a dark one. My two profiles differ here by only a few values. Yet they read like products from different companies. The shell and the canvas draw from the same tokens:

// Invoice approval โ€“ theme.json (light)
{ "--wb-ds-colors-acc1-500": "#0f766e", "--wb-public-font-family": "\"Poppins\", sans-serif",
  "--wb-public-node-border-radius": "12px", "--wb-public-transition": "160ms" }

// Content pipeline โ€“ theme.json (dark)
{ "--wb-ds-colors-acc1-500": "#d97706", "--wb-public-font-family": "\"JetBrains Mono\", monospace",
  "--wb-public-node-border-radius": "0px",  "--wb-public-transition": "0s" }

Change one brand value and everything repaints together: buttons, node borders, active ports, icons, the task inbox, the timeline. Changing the brand means editing token values, not CSS. A design system, in this setup, is a file you serve rather than a build you ship.

A single design-token change recolors the whole editor at once; a Copy theme.json button exports the served config file.
Dragging the brand token recolors the whole editor at once. Copy theme.json hands back the exact file a backend would serve.

Where the data stops

Almost everything is data. The exception is custom rendering: the React that draws a control or a node when the built-in ones are not enough. A node's fields, validation, layout, icon, and built-in shape all come from config. But when a control has to behave in a way JSON Forms cannot express, you write a custom renderer in React, like the currency input on the threshold field. Even then the choice stays in data: a single key, customRenderer, says which field uses it.

A few things are code by nature: a custom node or edge, a plugin, and connection validation. Connection validation is the rule for which ports may link on the canvas, not the form validation the schema does. Run-time node behavior is code too: with Temporal, each node type needs an executor function. A new process built from node types that already exist needs no new code, but a new node type needs its executor before it can run.

The subject: a human-in-the-loop workflow

The workflow the demo is built around is a human-in-the-loop invoice approval: an automated run reaches a decision, pauses for a person, and continues once they approve. The demo mocks the execution with local state. So the run, the task inbox, and the timeline show the experience, not a real engine. In Workflow Builder, that engine is Temporal: the framework's Temporal plugin runs the workflow and pauses it at the approval step until a person decides.

Even the routing is a configured value, not a coded one. The branch that sends large invoices to a human is just a condition in the data. Raise its threshold past the mocked amount, and the next run auto-approves. No task is created.

Here is the business case underneath the demo, and it is the part I find exciting. Because the configuration arrives from a server, a profile is really a tenant. Serve a different profile per customer. Each one gets its own node vocabulary, its own validation, and its own branding, all from data and one deployment. One editor, many tenants, no fork. From one codebase, you can build different views, profiles, or whole product variants. Anyone who can edit the data can extend the app, without touching the code. It is an app each customer configures like a car rather than one you re-manufacture per account.

If I had to sum up working with Workflow Builder: get the base right, and the data does the rest.

Workflow Builder is an open-source framework for workflow orchestration in your product. Its editor SDK, used in this post, is a production-ready React workflow editor built on React Flow, with a schema-driven properties panel, plugin architecture, and JSON serialization. The framework also includes a Temporal plugin that runs the diagrams your users draw as durable workflows, and an execution backend that starts runs and streams each step to your app. You can use the whole stack or only the editor: it saves workflows as JSON, so you can also run them on an engine you already have. The Community Edition (open source, Apache 2.0) contains all of these parts, and the Enterprise Edition adds advanced canvas plugins, such as auto-layout and edge routing. Everything in this post uses the free Community Edition.

๐Ÿ“ฐ Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.