Skip to main content
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 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
  });
};

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.