Skip to content

Event System

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;
}
}
});

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 TypeTriggered when
node_addA node is added to the canvas
node_removeA node is deleted
node_moveA node is dragged to a new position
node_configA node’s configuration values change
edge_addA connection is drawn between nodes
edge_removeA connection is deleted
metadataWorkflow metadata changes
nameThe workflow name is edited
descriptionThe 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
});
};

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);
};

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';
};

These three events form the save lifecycle: before → after (success) or error (failure).

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
}
};

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`);
};

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 });
};

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
};

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');
}
};

These events fire during Agent Spec workflow execution.

Called when an Agent Spec execution begins.

onAgentSpecExecutionStarted?: (executionId: string) => void;

Called when execution completes successfully.

onAgentSpecExecutionCompleted?: (
executionId: string,
results: Record<string, unknown>
) => void;

Called when execution fails.

onAgentSpecExecutionFailed?: (executionId: string, error: Error) => void;

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);
}

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));
}
}
}
});

For the complete TypeScript interface, see Core Types — Event Handlers.