Quick start: two editors via the mount API
Quick start: two editors in Svelte
Create an instance per editor withcreateFlowDropInstance() and pass it via
the instance prop:
WorkflowEditor, Playground, PlaygroundStudio, PlaygroundModal, and
PlaygroundApp accept the same instance prop.
The default instance
You only needinstanceId 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
| State | Default instance | Instance with instanceId: 'left' |
|---|---|---|
| Workflow drafts | flowdrop:draft:default:<workflowId> | flowdrop:draft:left:<workflowId> |
| Pipeline panel open | fd-pipeline-panel-open:default | fd-pipeline-panel-open:left |
| Pipeline view mode | fd-pipeline-view-mode:default | fd-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 withgetInstance() (during component init):
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-themeand 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
PortCompatibilityCheckeratfd.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
ApiContextatfd.api, configured by mount viafd.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.
Why SSR needs a per-render instance
Why SSR needs a per-render instance
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 explicitinstanceIds 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.