@workflowbuilder/sdk

August 14, 2026 · View on GitHub

React SDK for Workflow Builder. Embed a visual, flow-based workflow editor in your app.

npm version license

What you get

An embeddable React SDK that ships a complete workflow editor. One compound component (<WorkflowBuilder.Root>) is the embed point. Behind it: a plugin architecture, three persistence strategies, a JsonForms-driven schema layer, i18n, theming tokens, and a diagram-template system. You bring node types and a save strategy. The SDK handles canvas, palette, connections, properties, and persistence.

Typical fit:

  • Visual editors for AI agent workflows (LLM chains, tool calls, decision branches)
  • Drag-and-drop node-based programming UIs
  • ETL or data-pipeline designers
  • BPMN-style process editors
  • Any flow editor where you want JsonForms-driven node properties plus a swappable persistence layer

Battle-tested in production by teams shipping AI workflow products.

Install

npm install @workflowbuilder/sdk @xyflow/react zustand

Requires React 18 or 19. Everything else the SDK needs (JsonForms, i18next, immer, …) is a regular dependency and installs automatically. See Peer dependencies below.

Quick start

import { WorkflowBuilder } from '@workflowbuilder/sdk';

import '@workflowbuilder/sdk/style.css';

export function App() {
  return <WorkflowBuilder.Root name="my-workflow" />;
}

The editor renders with the default layout (top bar, palette, canvas, properties panel) and persists state to localStorage automatically (the default integration strategy). The palette is empty until you pass nodeTypes — see <Root> props, the Add a custom node type guide, and the node schemas reference for how to design the data and UI schemas.

Copy-paste-ready node implementations live in the source repo at apps/demo/src/app/data/nodes/. Each folder is a self-contained node type with 4 files (<name>.ts, default-properties-data.ts, schema.ts, uischema.ts). Available examples: trigger, action, decision, conditional, delay, notification, multi-port, ai-agent — each documented on the built-in nodes reference. Drop the folder into your app, import the node definition, and pass it to nodeTypes.

Compose a custom layout

Pass subcomponents as children to skip the default layout and place each part where you want it:

<WorkflowBuilder.Root nodeTypes={myNodeTypes}>
  <header>
    <WorkflowBuilder.TopBar />
  </header>
  <aside>
    <WorkflowBuilder.Palette />
  </aside>
  <main>
    <WorkflowBuilder.Canvas />
  </main>
  <aside>
    <WorkflowBuilder.PropertiesPanel />
  </aside>
</WorkflowBuilder.Root>

To add custom overlays alongside the default layout, mount it explicitly:

<WorkflowBuilder.Root nodeTypes={myNodeTypes}>
  <WorkflowBuilder.DefaultLayout />
  <MyToastBanner />
</WorkflowBuilder.Root>

Each subcomponent is also exported under a named alias (WorkflowBuilderTopBar, WorkflowBuilderPalette, WorkflowBuilderCanvas, WorkflowBuilderPropertiesPanel, WorkflowBuilderDefaultLayout) for consumers who prefer the classic style.

If you omit <WorkflowBuilder.TopBar />, use useWorkflowBuilderActions() to trigger save / import / export / settings / read-only / theme / layout-direction from your own controls.

<WorkflowBuilder.Root> props

PropTypeDescription
nodeTypesPaletteItemOrGroup[]Node type definitions. Appear in the palette and drive validation. Must be a stable reference — declare at module scope or memoize.
nodeTemplatesWorkflowBuilderNodeTemplatesPer-node-type custom renderers. Map of data.type → React component, overriding the default node renderer for that type. Stable reference required (same as nodeTypes).
edgeTemplatesWorkflowBuilderEdgeTemplatesPer-edge-type custom renderers. Map of edge.type → React component taking ReactFlow EdgeProps, overriding the built-in labelEdge. Unregistered types fall back to the default edge. Stable reference required (same as nodeTypes).
diagramTemplatesTemplateModel[]Diagram templates available in the template selector. Stable reference required (same as nodeTypes).
pluginsWorkflowBuilderPlugin[]Functions registering decorators. Synchronous, executed once.
jsonFormWorkflowBuilderJsonFormConfigCustom JsonForms renderers, cells, translations.
integrationWorkflowBuilderIntegrationData source / sink. Defaults to localStorage.
namestringWorkflow name shown in the header.
logoWorkflowBuilderLogoReplaces the built-in app-bar logo: an image URL, { light, dark } per-theme URLs, or a custom element.
logoHrefstringWraps the app-bar logo in a link opened in a new tab.
layoutDirection'DOWN' | 'RIGHT'Initial flow direction.
initialNodesWorkflowBuilderNode[]Starting diagram nodes.
initialEdgesWorkflowBuilderEdge[]Starting diagram edges.
isValidConnectionWorkflowBuilderIsValidConnectionValidate connections as the user draws them. See Connection validation. Stable reference required.
reactFlowPropsWorkflowBuilderReactFlowPropsAdvanced escape hatch for the underlying ReactFlow canvas. See Advanced: ReactFlow props. Treat as static config (runtime value changes may not apply immediately).
childrenReactNodeCustom layout. Omit for the default floating-overlay layout (top bar, palette, canvas, properties panel). See Compose a custom layout.

Exact prop types come from the auto-generated WorkflowBuilderRootProps reference. For how and when to reach for each prop, see Configuring the editor.

Persistence

Pick one of three strategies via the integration prop.

StrategySource / sinkWhen to use
localStorageBrowser localStoragePrototyping, quick starts
apiGET / POST to user-provided endpointsBackend-managed persistence
propsonDataSave callback + initialNodes/initialEdges propsHost-app-managed persistence
<WorkflowBuilder.Root
  integration={{
    strategy: 'api',
    endpoints: { load: '/api/load', save: '/api/save' },
  }}
/>

Full guide: Persistence strategies on the docs site.

Plugins

The SDK exposes three extension points. Pass plugin functions to the plugins prop:

  • Component decorators (registerComponentDecorator). Inject UI into named slots: OptionalAppBarControls, OptionalAppBarTools, OptionalAppChildren, OptionalEdgeProperties, OptionalFooterContent, OptionalHooks (invisible provider slot), OptionalNodeContent (receives nodeId).
  • Function decorators (registerFunctionDecorator). Observe or wrap SDK internals (e.g. trackFutureChange) before / after / wrapping the call.
  • Translations (registerPluginTranslation). Extend i18next keys with plugin-specific strings.
import { WorkflowBuilder, type WorkflowBuilderPlugin, registerComponentDecorator } from '@workflowbuilder/sdk';

import { MyToolbarButton } from './my-toolbar-button';

const myPlugin: WorkflowBuilderPlugin = () => {
  registerComponentDecorator('OptionalAppBarControls', {
    content: MyToolbarButton,
    name: 'MyPlugin',
  });
};

export function App() {
  return <WorkflowBuilder.Root plugins={[myPlugin]} />;
}

Plugins are synchronous. If you need async work (config fetch, WASM load, feature flag lookup), await it outside the SDK and construct the plugin around the resolved value before passing it to <Root>.

Full guide: Build a plugin.

Connection validation

isValidConnection decides whether a dragged connection is allowed. Return false to reject it: no edge is created, no flicker. It receives the resolved sourceNode / targetNode (plus the raw connection), so you branch on node data without reaching into the store.

import { WorkflowBuilder, type WorkflowBuilderIsValidConnection } from '@workflowbuilder/sdk';

// Module scope keeps the reference stable.
const isValidConnection: WorkflowBuilderIsValidConnection = ({ sourceNode, targetNode }) =>
  !(sourceNode.data.type === 'start' && targetNode.data.type === 'start');

export function App() {
  return <WorkflowBuilder.Root isValidConnection={isValidConnection} />;
}

Validates interactive drags only, not programmatic edge writes (templates, paste, setStoreEdges). Fail-open: if an endpoint can't be resolved to a node, the connection is allowed.

Advanced: ReactFlow props

reactFlowProps forwards extra props to the underlying ReactFlow canvas for things the SDK doesn't expose directly (zoom limits, key codes, onNodeClick, performance flags, …).

const reactFlowProps = {
  maxZoom: 1.5,
  zoomOnDoubleClick: false,
  onNodeClick: (_, node) => console.log(node.id),
} satisfies WorkflowBuilderReactFlowProps;

<WorkflowBuilder.Root reactFlowProps={reactFlowProps} />;

Props the SDK owns (graph data, the connection / selection / change handlers, type maps, colorMode, …) can't be set here. To observe SDK events use the listener APIs (addNodeChangedListener, …); to theme use Theming. Treat reactFlowProps as static config: runtime value changes may not apply immediately.

Theming

The editor exposes a small set of CSS custom properties for top-level styling. Override them on :root or scope to your app shell:

:root {
  --wb-background-color: #fafafa;
  --wb-font-family: 'Inter', system-ui, sans-serif;
  --wb-transition: 0.15s ease-in;
}

Available --wb-* tokens: --wb-background-color, --wb-font-family, --wb-transition, plus scrollbar styling (--wb-scroll-width, --wb-scroll-radius, --wb-scroll-thumb-color, --wb-scroll-track-color).

Deeper color and spacing customization (palette, semantic UI tokens) goes through the --ax-* token layer from @workflowbuilder/ui. Full guide: Design system and customization.

CSS — global resets

@workflowbuilder/sdk/style.css contains a small set of global resets, including:

body {
  margin: 0;
  background-color: var(--wb-background-color);
  overflow: hidden;
}

The stylesheet does not size your document — the editor fills whatever container you give it, so give it one. A typical full-viewport host adds in its own CSS:

html,
body,
#root {
  height: 100vh;
}

The shipped resets live inside @layer ui.base (the lowest layer in the cascade order), so any consumer-level rule on body / html wins automatically — no specificity hacks or !important needed. Two things to be aware of:

  • overflow: hidden on <body> — the editor expects a full-viewport canvas with no page-level scrollbar. If you embed the editor inside a scrolling page, override body { overflow: auto; } (or scope the SDK to a sized container with its own overflow: hidden) and let the editor's own viewport handle pan/zoom.
  • background-color: var(--wb-background-color) — the body picks up the SDK's background token. If you don't ship the SDK style sheet but render <WorkflowBuilder.Root />, the token is undefined and the body falls back to transparent. Either ship the CSS or set --wb-background-color yourself.

If you don't want the body resets at all, just override them in your own global stylesheet — unlayered consumer CSS beats @layer ui.base regardless of import order.

Single-instance constraint

Mount one <WorkflowBuilder.Root> per page. Plugin / JsonForms / i18n registries and the useStore.{getState,setState,subscribe} facade all share a module-level singleton, so two Roots on the same page would silently clash. If you need multiple "workflows" on one page, render them sequentially (mount → save → unmount → mount next).

TypeScript

dist/index.d.ts ships with all required types inlined: icon names (WBIcon), domain types (WorkflowBuilderNode, WorkflowBuilderEdge, NodeData, DiagramModel, PaletteItemOrGroup, TemplateModel, …), plugin contracts, and integration types. No extra @types/* package needed.

Full API reference: https://www.workflowbuilder.io/docs/api/core/workflowbuilder/.

Browser and runtime

  • Client-side only. The SDK holds module-level singletons (zustand store identity, i18next instance, immer's frozen-object cache) and uses i18next-browser-languagedetector. Mount inside 'use client' boundaries on Next.js, or behind a client-only wrapper (dynamic(() => import('./Editor'), { ssr: false })) on other SSR frameworks.
  • Modern browsers. Last two versions of Chrome, Firefox, Safari, Edge.
  • React 18 or 19.

Peer dependencies

Only the libraries your app touches directly are peers. Install them alongside the SDK (ranges declared in peerDependencies):

  • react, react-dom (^18.0.0 || ^19.0.0) — already in any React app
  • @xyflow/react (^12.0.0) — Node, Edge, EdgeProps, NodeChange types you use
  • zustand (^5.0.0) — useStore / useChangesTrackerStore return zustand store types

They stay peers so the consumer app and the SDK share a single copy of each — required for singletons (zustand store identity, xyflow's React context). The remaining libraries the SDK relies on — @jsonforms/core, @jsonforms/react, i18next, react-i18next, i18next-browser-languagedetector, immer — are regular dependencies: they install automatically and are still externalized from the build, so package managers dedupe them to a single copy via the caret ranges (they hold singletons too — the i18next instance, immer's frozen-object cache, the JsonForms contexts — which is why they are never bundled).

Writing custom JsonForms renderers, cells, or testers still needs only the SDK: the authoring primitives (withJsonFormsControlProps, rankWith, uiTypeIs, useJsonForms, ControlProps, …) are re-exported from @workflowbuilder/sdk. Import them from there, never from @jsonforms/* directly — that guarantees your renderer uses the SDK's single JsonForms copy instead of a second one that would break the React context.

Support

Built and maintained by Synergy Codes - the engineers behind the SDK shipping workflow tools for 15 years across 20+ industries. Available for integration, customization, UX, back-end logic, and end-to-end delivery.

Talk to the team →

License

Apache 2.0 — see LICENSE.