---
title: "Auto-save & drafts"
description: "How FlowDrop auto-saves drafts to browser storage and how to manage them."
source: https://flowdrop.io/docs/tutorials/recipes/auto-save-and-drafts
site: FlowDrop documentation
---
# Auto-save & drafts
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
1. When `autoSaveDraft` is enabled (default: `true`), FlowDrop saves the current workflow to draft storage periodically
2. The save interval defaults to **30 seconds** (`autoSaveDraftInterval: 30000`)
3. Drafts are keyed by workflow ID and scoped per instance, so multiple editors on one page never collide
4. When a workflow is loaded, FlowDrop checks for a matching draft and offers to restore it
5. After a successful save to the backend, the draft is cleared
The default instance keys drafts as `flowdrop:draft:` (and
`flowdrop:draft:new` for an unsaved workflow). Editors mounted with an
`instanceId` get scoped keys: `flowdrop:draft::`.
## Configuration
```typescript
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 `localStorage` nor `sessionStorage` protects 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:
1. Call [`clearAllDrafts()` on logout](#clearing-drafts-on-logout)
2. Use `draftStorage: 'session'` so drafts are removed when the tab closes
3. Supply a custom `DraftStorageAdapter` (e.g. an in-memory store)
4. 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.
```typescript
import type { DraftStorageAdapter } from '@flowdrop/flowdrop/editor';
const memoryDrafts = new Map();
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
```typescript
features: {
autoSaveDraft: false;
}
```
### Faster Auto-Save
For critical workflows, save more frequently:
```typescript
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:
```typescript
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::` — plus the custom `draftStorageKey` you configured at mount time (if any), and returns the number of entries removed.
```typescript
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:
```typescript
import { clearAllDrafts } from '@flowdrop/flowdrop/editor';
clearAllDrafts(); // clears flowdrop:draft:* keys
clearAllDrafts(['my-custom-draft-key']); // also clears explicit custom keys
```
## Storage 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:
```typescript
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](/editor/advanced/event-system) — all lifecycle events including save
- [Framework Integration](/editor/frameworks) — mount options including features
- [Troubleshooting](/editor/troubleshooting#draft-recovery-not-working) — draft recovery issues