Event system
Hook into FlowDrop's lifecycle with all 11 event handlers.
FlowDrop provides event handlers that let your parent application react to workflow lifecycle events — changes, saves, errors, and execution.
All events are passed via the eventHandlers option when mounting:
const app = await mountFlowDropApp(container, {
eventHandlers: {
onWorkflowChange: (workflow, changeType) => {
console.log(`Changed: ${changeType}`);
},
onDirtyStateChange: (isDirty) => {
saveButton.disabled = !isDirty;
}
}
});Workflow change events
onWorkflowChange
Called on every modification to the workflow — nodes added/removed/moved, edges changed, config updated.
onWorkflowChange?: (workflow: Workflow, changeType: WorkflowChangeType) => void;The changeType parameter tells you exactly what changed:
| Change Type | Triggered when |
|---|---|
node_add | A node is added to the canvas |
node_remove | A node is deleted |
node_move | A node is dragged to a new position |
node_config | A node's configuration values change |
edge_add | A connection is drawn between nodes |
edge_remove | A connection is deleted |
metadata | Workflow metadata changes |
name | The workflow name is edited |
description | The workflow description is edited |
Example: Track changes for analytics
onWorkflowChange: (workflow, changeType) => {
analytics.track('workflow_modified', {
workflowId: workflow.id,
changeType,
nodeCount: workflow.nodes.length,
edgeCount: workflow.edges.length
});
};onWorkflowLoad
Called after a workflow is loaded and initialized. Fires on both initial load and subsequent loads.
onWorkflowLoad?: (workflow: Workflow) => void;Example: Set up external state
onWorkflowLoad: (workflow) => {
document.title = `${workflow.name} - Editor`;
breadcrumb.update(workflow.name);
};onDirtyStateChange
Called when the workflow transitions between saved and unsaved states.
onDirtyStateChange?: (isDirty: boolean) => void;Example: Unsaved changes indicator
onDirtyStateChange: (isDirty) => {
saveButton.disabled = !isDirty;
document.title = isDirty ? '● Unsaved - Editor' : 'Editor';
};Save lifecycle events
These three events form the save lifecycle: before → after (success) or error (failure).
onBeforeSave
Called before a save operation. Return false to cancel the save.
onBeforeSave?: (workflow: Workflow) => Promise<boolean | void>;Example: Confirm before saving
onBeforeSave: async (workflow) => {
if (workflow.nodes.length === 0) {
alert('Cannot save an empty workflow');
return false; // cancels save
}
};onAfterSave
Called after a successful save. The workflow may include server-assigned IDs or updated timestamps.
onAfterSave?: (workflow: Workflow) => Promise<void>;Example: Show success notification
onAfterSave: async (workflow) => {
showNotification(`Saved "${workflow.name}" successfully`);
};onSaveError
Called when a save operation fails.
onSaveError?: (error: Error, workflow: Workflow) => Promise<void>;Example: Report errors
onSaveError: async (error, workflow) => {
errorReporter.capture(error, { workflowId: workflow.id });
};Error & cleanup events
onApiError
Called on any API request failure (save, load, fetch nodes, etc.). Return true to suppress FlowDrop's default error toast.
onApiError?: (error: Error, operation: string) => boolean | void;The operation parameter describes what failed: "save", "load", "fetchNodes", "fetchCategories", etc.
Example: Custom error handling
onApiError: (error, operation) => {
if (error.message.includes('401')) {
redirectToLogin();
return true; // suppress default toast
}
// return void to show default toast
};onBeforeUnmount
Called before FlowDrop is destroyed/unmounted. Use this for cleanup or prompting to save.
onBeforeUnmount?: (workflow: Workflow, isDirty: boolean) => void;Example: Warn about unsaved changes
onBeforeUnmount: (workflow, isDirty) => {
if (isDirty) {
console.warn('Unmounting with unsaved changes');
}
};Agent Spec execution events
These events fire during Agent Spec workflow execution.
onAgentSpecExecutionStarted
Called when an Agent Spec execution begins.
onAgentSpecExecutionStarted?: (executionId: string) => void;onAgentSpecExecutionCompleted
Called when execution completes successfully.
onAgentSpecExecutionCompleted?: (
executionId: string,
results: Record<string, unknown>
) => void;onAgentSpecExecutionFailed
Called when execution fails.
onAgentSpecExecutionFailed?: (executionId: string, error: Error) => void;onAgentSpecNodeStatusUpdate
Called when a node's execution status changes during a run.
onAgentSpecNodeStatusUpdate?: (nodeId: string, status: NodeExecutionInfo) => void;Example: Track execution progress
onAgentSpecExecutionStarted: (executionId) => {
progressBar.show();
},
onAgentSpecNodeStatusUpdate: (nodeId, status) => {
progressBar.update(nodeId, status.status);
},
onAgentSpecExecutionCompleted: (executionId, results) => {
progressBar.hide();
showResults(results);
},
onAgentSpecExecutionFailed: (executionId, error) => {
progressBar.hide();
showError(error.message);
}Complete example
Here's a full integration using all lifecycle events:
import { mountFlowDropApp } from '@flowdrop/flowdrop/editor';
import { createEndpointConfig } from '@flowdrop/flowdrop/core';
const app = await mountFlowDropApp(document.getElementById('editor'), {
endpointConfig: createEndpointConfig('/api/flowdrop'),
eventHandlers: {
// Track all changes
onWorkflowChange: (workflow, changeType) => {
console.log(`[${changeType}] ${workflow.nodes.length} nodes`);
},
// Update UI for dirty state
onDirtyStateChange: (isDirty) => {
document.getElementById('save-btn').disabled = !isDirty;
},
// Initialize on load
onWorkflowLoad: (workflow) => {
document.title = workflow.name;
},
// Validate before saving
onBeforeSave: async (workflow) => {
if (workflow.nodes.length === 0) {
return false; // cancel save
}
},
// Notify on success
onAfterSave: async (workflow) => {
showToast('Saved!');
},
// Handle save failures
onSaveError: async (error, workflow) => {
showToast(`Save failed: ${error.message}`, 'error');
},
// Centralized error handling
onApiError: (error, operation) => {
if (error.message.includes('401')) {
window.location.href = '/login';
return true; // suppress toast
}
},
// Cleanup on unmount
onBeforeUnmount: (workflow, isDirty) => {
if (isDirty) {
localStorage.setItem('unsaved-workflow', JSON.stringify(workflow));
}
}
}
});Reference
For the complete TypeScript interface, see Core Types — Event Handlers.