Auto-save & drafts
How FlowDrop auto-saves drafts to browser storage and how to manage them.
FlowDrop automatically saves drafts of the current workflow to browser storage (localStorage by default), preventing data loss when the browser closes unexpectedly.
How It Works
- When
autoSaveDraftis enabled (default:true), FlowDrop saves the current workflow to draft storage periodically - The save interval defaults to 30 seconds (
autoSaveDraftInterval: 30000) - Drafts are keyed by workflow ID and scoped per instance, so multiple editors on one page never collide
- When a workflow is loaded, FlowDrop checks for a matching draft and offers to restore it
- After a successful save to the backend, the draft is cleared
Configuration
const app = await mountFlowDropApp(container, {
features: {
autoSaveDraft: true, // default: true
autoSaveDraftInterval: 30000 // default: 30000ms (30 seconds)
},
// Optional: custom storage key prefix
draftStorageKey: 'my-app-flowdrop-draft',
// Optional: storage backend — 'local' (default), 'session', or a custom adapter
draftStorage: 'local'
});Security Considerations
Drafts contain the complete workflow JSON, including node configuration values. If your users enter API keys, tokens, or other secrets into node configs, those values end up in browser storage in plain text. Keep in mind:
- On the default
'local'backend, drafts remain stored on the device even after the tab or browser is closed, until they are saved or cleared - Neither
localStoragenorsessionStorageprotects against same-origin script access (XSS) — both are readable by any script running on your page - On shared browser profiles, leftover drafts are readable by the next user via DevTools
Mitigations, in increasing order of strictness:
- Call
clearAllDrafts()on logout - Use
draftStorage: 'session'so drafts are removed when the tab closes - Supply a custom
DraftStorageAdapter(e.g. an in-memory store) - Disable drafts entirely with
features: { autoSaveDraft: false }
End users can also opt out themselves at any time via the "Store Drafts in Browser" toggle in the Behavior tab of the settings panel — turning it off stops draft writes and removes the current draft.
The user toggle applies per tab.
Other tabs that are already open read settings at load time and keep writing drafts until they are reloaded. If you need a hard guarantee across tabs, disable drafts at mount time instead.
Choosing a Storage Backend
The draftStorage mount option controls where drafts live:
| Value | Backend | Survives reload | Survives tab close | Notes |
|---|---|---|---|---|
'local' (default) | localStorage | ✅ | ✅ | Best crash recovery; clear on logout for shared devices |
'session' | sessionStorage | ✅ | ❌ | Per-tab; drafts do not survive crash-and-reopen |
| custom adapter | up to you | — | — | Implement DraftStorageAdapter |
The resolved backend is captured per mount, so multiple FlowDrop instances on one page can use different backends without interfering. (The standalone clearAllDrafts() helper uses the most recent mount's backend unless you pass it an adapter explicitly.)
A custom adapter implements four synchronous methods. Async backends — IndexedDB, network storage, WebCrypto encryption — cannot implement the interface directly; put a synchronous in-memory cache in front and flush to the async backend separately. Beware that an async method will type-check here (a Promise is assignable to void), but its errors are silently swallowed.
import type { DraftStorageAdapter } from '@flowdrop/flowdrop/editor';
const memoryDrafts = new Map<string, string>();
const inMemoryAdapter: DraftStorageAdapter = {
getItem: (key) => memoryDrafts.get(key) ?? null,
setItem: (key, value) => void memoryDrafts.set(key, value),
removeItem: (key) => void memoryDrafts.delete(key),
keys: () => [...memoryDrafts.keys()]
};
const app = await mountFlowDropApp(container, {
draftStorage: inMemoryAdapter
});Disabling Auto-Save
features: {
autoSaveDraft: false;
}Faster Auto-Save
For critical workflows, save more frequently:
features: {
autoSaveDraft: true,
autoSaveDraftInterval: 10000 // every 10 seconds
}Manual Draft Management with Events
Use the onBeforeUnmount event to save a final draft when the editor is destroyed:
eventHandlers: {
onBeforeUnmount: (workflow, isDirty) => {
if (isDirty) {
localStorage.setItem(`flowdrop-draft-${workflow.id}`, JSON.stringify(workflow));
}
};
}Clearing Drafts on Logout
On the default 'local' backend, drafts persist until they are explicitly cleared. FlowDrop has no notion of authentication, so it cannot clear drafts when the user signs out of your application — you must do this from the host application's logout handler. On a shared browser profile, leftover drafts could otherwise be readable by the next user via DevTools.
The mounted FlowDrop instance exposes clearAllDrafts() for this purpose. It removes every key beginning with flowdrop:draft: — including instance-scoped sub-namespaces like flowdrop:draft:<instanceId>:<workflowId> — plus the custom draftStorageKey you configured at mount time (if any), and returns the number of entries removed.
const app = await mountFlowDropApp(container, {
/* ... */
});
async function logout() {
app.clearAllDrafts();
await authService.signOut();
}If you need to clear drafts after the editor has already been unmounted, import the standalone helper:
import { clearAllDrafts } from '@flowdrop/flowdrop/editor';
clearAllDrafts(); // clears flowdrop:draft:* keys
clearAllDrafts(['my-custom-draft-key']); // also clears explicit custom keysStorage Limits
Browsers typically limit localStorage to 5-10MB. Large workflows with many nodes and complex configurations could approach this limit.
If storage is full:
- The draft save fails silently
- The editor continues working normally
- No data is lost from the active session
Combining with Backend Save
A typical save flow:
eventHandlers: {
onDirtyStateChange: (isDirty) => {
// Show/hide "unsaved changes" indicator
indicator.style.display = isDirty ? 'block' : 'none';
},
onAfterSave: async (workflow) => {
// Draft is automatically cleared after successful save
showToast('Saved!');
},
onSaveError: async (error, workflow) => {
// Draft is preserved — user can retry
showToast('Save failed. Your changes are still saved locally.');
}
}Next Steps
- Event System — all lifecycle events including save
- Framework Integration — mount options including features
- Troubleshooting — draft recovery issues