Store system
Understand FlowDrop's reactive state management for programmatic access.
FlowDrop uses Svelte 5 runes for reactive state management. Each mounted editor owns an isolated FlowDropInstance container holding its stores. While most developers won't need to interact with stores directly, they're essential for advanced integrations.
Reaching a store
There are no module-level store functions — instances are the API. Reach a store one of two ways:
getInstance()inside a FlowDrop component returns the owningFlowDropInstance. For single-editor embeds it resolves the page-default instance automatically.- The mount handle's
.instance(const fd = (await mountFlowDropApp(...)).instance) lets you reach a specific editor's stores from vanilla JS or another framework. You can also construct one withcreateFlowDropInstance({ id })from@flowdrop/flowdrop/editorand pass it as theinstanceprop.
The instance exposes these members:
| Member | Holds |
|---|---|
fd.workflow | Workflow state, nodes, edges, dirty tracking |
fd.history / fd.historyBindings | Undo/redo (service and reactive wrapper) |
fd.playground | Playground sessions and messages |
fd.interrupts | Human-in-the-loop interrupt state |
fd.categories | Node category definitions |
fd.portCoordinates | Port handle positions |
fd.pipelinePanel | Pipeline execution view state |
fd.api | API client |
fd.nodes / fd.fields | Node and form-field registries |
fd.formats / fd.portCompatibility | Workflow formats and port rules |
See the multiple instances guide for scoping multiple editors.
import { getInstance } from '@flowdrop/flowdrop/editor';
const fd = getInstance();Workflow store (fd.workflow)
The central store holding the current workflow state.
Reading state
// Reactive getters (re-evaluate when state changes)
const workflow = fd.workflow.current; // Workflow | null
const isDirty = fd.workflow.isDirty; // boolean
const nodes = fd.workflow.nodes; // WorkflowNode[]
const edges = fd.workflow.edges; // WorkflowEdge[]
const name = fd.workflow.name; // string
const validation = fd.workflow.validation;
// { hasNodes, hasEdges, nodeCount, edgeCount, isValid }Modifying state
const { actions } = fd.workflow;
// Initialize with a loaded workflow
actions.initialize(workflow);
// Node operations
actions.addNode(newNode);
actions.removeNode('node-id');
actions.updateNode('node-id', { data: { ...updates } });
// Edge operations
actions.addEdge(newEdge);
actions.removeEdge('edge-id');
// Metadata
actions.updateName('New Workflow Name');
actions.updateMetadata({ tags: ['production'] });
// Batch update (single history entry)
actions.batchUpdate({
nodes: updatedNodes,
edges: updatedEdges,
name: 'Updated Name'
});
// Clear everything
actions.clear();Dirty state
// Check dirty state
if (fd.workflow.isDirty) {
console.log('There are unsaved changes');
}
// Clear dirty flag after saving
fd.workflow.markAsSaved();History store (fd.historyBindings)
Manages undo/redo with snapshot-based history. fd.historyBindings is the reactive rune wrapper around fd.history (the underlying HistoryService).
// Check availability (reactive getters)
const canUndo = fd.historyBindings.canUndo; // boolean
const canRedo = fd.historyBindings.canRedo; // boolean
// Perform undo/redo (bound actions — safe to detach)
fd.historyBindings.undo(); // returns boolean (success)
fd.historyBindings.redo(); // returns boolean (success)
// Manual history management
fd.historyBindings.pushState(workflow, { description: 'Bulk import' });
fd.historyBindings.clear(fd.workflow.current);
// Transactions (group multiple changes into one undo step)
fd.historyBindings.startTransaction(fd.workflow.current, 'Rearrange nodes');
// ... make multiple changes ...
fd.historyBindings.commitTransaction();
// or: fd.historyBindings.cancelTransaction();Settings store
User preferences for theme, editor behavior, and UI. Settings are page-global by design — they are not instance-scoped, so these remain module-level functions.
import {
getSettings,
themeSettings,
editorSettings,
uiSettings,
updateSettings,
resetSettings,
onSettingsChange
} from '@flowdrop/flowdrop/settings';
// Read settings (reactive)
const settings = getSettings(); // FlowDropSettings
const theme = themeSettings(); // ThemeSettings
const editor = editorSettings(); // EditorSettings
const ui = uiSettings(); // UISettings
// Update settings
updateSettings({
theme: { preference: 'dark' },
editor: { snapToGrid: true, gridSize: 20 }
});
// Reset to defaults
resetSettings(); // reset all
resetSettings(['theme']); // reset only theme
// Subscribe to changes
const unsubscribe = onSettingsChange((newSettings, oldSettings) => {
console.log('Settings changed:', newSettings);
});
// Later: unsubscribe();Theme control
Theme is also page-global:
import { theme, resolvedTheme, setTheme, toggleTheme, cycleTheme } from '@flowdrop/flowdrop/core';
theme(); // 'light' | 'dark' | 'auto'
resolvedTheme(); // 'light' | 'dark' (actual applied theme)
setTheme('dark');
toggleTheme(); // light ↔ dark
cycleTheme(); // light → dark → auto → lightPlayground store (fd.playground)
Manages interactive testing sessions and messages.
// Read state (reactive getters)
const session = fd.playground.currentSession; // PlaygroundSession | null
const sessions = fd.playground.sessions; // PlaygroundSession[]
const messages = fd.playground.messages; // PlaygroundMessage[]
const chatMsgs = fd.playground.chatMessages; // PlaygroundMessage[] (user/assistant only)
const logMsgs = fd.playground.logMessages; // PlaygroundMessage[] (system only)
const isRunning = fd.playground.isExecuting; // booleanInterrupt store (fd.interrupts)
Manages human-in-the-loop interrupt state.
// Read state
const pending = fd.interrupts.getPending(); // InterruptWithState[]
const resolved = fd.interrupts.getResolved(); // InterruptWithState[]
const pendingCount = fd.interrupts.getPendingCount(); // numberUsing stores in Svelte components
Since stores use Svelte 5 runes, read their getters inside $derived:
<script>
import { getInstance } from '@flowdrop/flowdrop/editor';
const fd = getInstance();
const nodes = $derived(fd.workflow.nodes);
const isDirty = $derived(fd.workflow.isDirty);
</script>
<p>Nodes: {nodes.length}</p><p>Unsaved: {isDirty ? 'Yes' : 'No'}</p>Using stores outside Svelte
For vanilla JS or other frameworks, hold the mount handle's .instance and read its getters at call time:
const fd = (await mountFlowDropApp(container, options)).instance;
// Polling pattern
setInterval(() => {
const workflow = fd.workflow.current;
const dirty = fd.workflow.isDirty;
externalUI.update({ workflow, dirty });
}, 1000);For event-driven updates, use the event system instead of polling:
eventHandlers: {
onWorkflowChange: (workflow) => externalUI.update(workflow),
onDirtyStateChange: (isDirty) => externalUI.setDirty(isDirty)
}