Skip to main content
FlowDrop supports multiple editor instances on one page. Each mount gets its own state container — workflow data, undo/redo history, playground sessions, interrupts, and panel state are fully isolated between instances. Editing, deleting, or undoing in one editor never affects another, and destroying one leaves its siblings working.

Quick start: two editors via the mount API

import { mountFlowDropApp } from '@flowdrop/flowdrop/editor';

const left = await mountFlowDropApp(document.getElementById('editor-left'), {
  workflow: workflowA,
  nodes: nodeTypes,
  instanceId: 'left' // scopes draft/panel storage keys
});

const right = await mountFlowDropApp(document.getElementById('editor-right'), {
  workflow: workflowB,
  nodes: nodeTypes,
  instanceId: 'right'
});

// Each handle controls only its own editor:
left.isDirty(); // false
right.getWorkflow(); // workflowB
left.destroy(); // `right` keeps working

Quick start: two editors in Svelte

Create an instance per editor with createFlowDropInstance() and pass it via the instance prop:
<script lang="ts">
  import { App, createFlowDropInstance } from '@flowdrop/flowdrop/editor';

  const left = createFlowDropInstance({ id: 'left' });
  const right = createFlowDropInstance({ id: 'right' });
</script>

<App instance={left} workflow={workflowA} nodes={nodeTypes} />
<App instance={right} workflow={workflowB} nodes={nodeTypes} />
WorkflowEditor, Playground, PlaygroundStudio, PlaygroundModal, and PlaygroundApp accept the same instance prop.

The default instance

You only need instanceId when mounting more than one editor. The first mount without an instanceId becomes the page-default instance, which is what getInstance() resolves to for single-editor embeds with no explicit provider. Instances are the API: there are no module-level store singletons. Resolve the owning instance with getInstance() inside the component tree, or hold the mount handle’s .instance outside it. Each instance keeps its own scoped localStorage keys (flowdrop:draft:default:<workflowId>, fd-pipeline-panel-open:default for the default instance). Additional mounts without an explicit id get auto-generated ones (fd-1, fd-2, …). Prefer explicit ids whenever drafts are enabled, so each editor’s drafts land under a stable, predictable key.

Instance-scoped storage keys

StateDefault instanceInstance with instanceId: 'left'
Workflow draftsflowdrop:draft:default:<workflowId>flowdrop:draft:left:<workflowId>
Pipeline panel openfd-pipeline-panel-open:defaultfd-pipeline-panel-open:left
Pipeline view modefd-pipeline-view-mode:defaultfd-pipeline-view-mode:left
clearAllDrafts() sweeps everything under flowdrop:draft: — instance sub-namespaces included — so a logout handler still clears all editors at once.

Accessing instance state programmatically

Inside FlowDrop’s component tree, resolve the current instance with getInstance() (during component init):
<script lang="ts">
  import { getInstance } from '@flowdrop/flowdrop/editor';

  const fd = getInstance();
  // Reactive getters — read them inside $derived or templates:
  const name = $derived(fd.workflow.name);
  const canUndo = $derived(fd.historyBindings.canUndo);
</script>
Outside the tree, hold on to the container you created (or the mount handle). The FlowDropInstance exposes workflow, history, historyBindings, playground, interrupts, categories, portCoordinates, pipelinePanel, and destroy().

What stays page-global (by design)

Some state is deliberately shared across all instances on a page:
  • Theme and settings — one data-theme and one settings store per page. This includes UI toggles like console-open, sidebar-collapsed, and the bottom-panel tab: toggling them in one editor affects all editors.
  • Port-compatibility config — each instance owns a PortCompatibilityChecker at fd.portCompatibility, re-initialized by mount from the backend’s port config; the last mount’s fetched config wins for shared backends.
  • API endpoint config — each instance owns an ApiContext at fd.api, configured by mount via fd.api.configure(config, authProvider).
  • Playground live polling — playground session/message state is isolated per instance, but the polling timer is page-global: only one playground actively polls at a time. If two playgrounds need concurrent live updates, push responses yourself via the mount handle’s pushMessages() with your own transport (WebSocket/SSE).

SSR (SvelteKit)

Server rendering works without any extra setup. During SSR, the provider components (App, WorkflowEditor, …) create a fresh per-render instance automatically.
The page-default instance is browser-only — module-level mutable state on the server would leak between requests. So FlowDrop gives each render its own instance. Resolving the default instance (e.g. via getInstance()) during SSR outside a FlowDrop component tree throws with an explanatory error instead of leaking state.

Troubleshooting

Two editors share state — two un-keyed mounts both claimed the default instance. Pass explicit instanceIds and resolve state via getInstance() / the mount handle’s .instance. Drafts collide between editors — both mounts omitted instanceId, so the second got an auto-generated id that changes across reloads. Pass stable explicit ids.