FlowDrop Docs
EditorAdvanced

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 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.

On this page