FlowDrop Docs

Saving workflows

Persist workflow data with save callbacks and understand the workflow data model.

What you'll learn

The workflow data structure (nodes and edges), how to implement save callbacks, and how to handle workflow lifecycle events.

With a pre-built workflow loaded, clicking Save in the toolbar runs the save flow. Users can modify the workflow and save their changes.

The workflow data structure

When you save or export a workflow, FlowDrop produces a JSON object with two main arrays: nodes and edges.

Nodes

Each node on the canvas is represented as:

{
  "id": "text_input.1",
  "type": "universalNode",
  "position": { "x": 0, "y": 100 },
  "data": {
    "label": "Text Input",
    "config": {
      "placeholder": "Enter text..."
    },
    "metadata": {
      "node_type_id": "text_input",
      "name": "Text Input",
      "type": "simple",
      "category": "inputs"
    }
  }
}
  • position — where the node sits on the canvas (x, y coordinates)
  • data.config — the user's configuration values (from the config form)
  • data.metadata — the full node definition (type, ports, schema)

Edges

Each connection between nodes is an edge:

{
  "id": "e-text_input-ai_analyzer",
  "source": "text_input.1",
  "target": "ai_content_analyzer.1",
  "sourceHandle": "text_input.1-output-text",
  "targetHandle": "ai_content_analyzer.1-input-content"
}
  • source / target — the node IDs being connected
  • sourceHandle / targetHandle — the specific port IDs (format: {nodeId}-{direction}-{portId})

Event handlers

FlowDrop provides lifecycle hooks to respond to workflow changes and saves:

const app = await mountFlowDropApp(container, {
  nodes,
  categories,
  endpointConfig: createEndpointConfig('/api/flowdrop'),
  showNavbar: true,

  eventHandlers: {
    // Called before save — return false to cancel
    onBeforeSave: async (workflow) => {
      console.log('Saving workflow:', workflow.name);
      const isValid = workflow.nodes.length > 0;
      return isValid;
    },

    // Called after successful save
    onAfterSave: async (workflow) => {
      console.log('Workflow saved!', workflow.id);
    },

    // Called when save fails
    onSaveError: async (error, workflow) => {
      console.error('Save failed:', error.message);
    },

    // Called on any workflow change
    onWorkflowChange: (workflow, changeType) => {
      // changeType: 'node_add', 'node_remove', 'node_move',
      //             'node_config', 'edge_add', 'edge_remove',
      //             'metadata', 'name', 'description'
      console.log(`Change: ${changeType}`);
    },

    // Called when dirty state changes
    onDirtyStateChange: (isDirty) => {
      // Update your UI (e.g., show unsaved indicator)
      document.title = isDirty ? '* My Editor' : 'My Editor';
    }
  }
});

Implementing a save endpoint

FlowDrop sends the workflow data to your API when the user clicks Save. Here's a minimal backend example:

// Express.js example
app.put('/api/flowdrop/workflows/:id', (req, res) => {
  const { id } = req.params;
  const { nodes, edges, name, description } = req.body;

  // Save to your database
  db.workflows.update(id, { nodes, edges, name, description });

  res.json({
    success: true,
    data: { id, nodes, edges, name, description },
    message: 'Workflow saved'
  });
});

The API response should follow the pattern { success: boolean, data: Workflow, message: string }.

Complete setup

Here's everything from the tutorial combined into a single setup:

import { mountFlowDropApp } from '@flowdrop/flowdrop/editor';
import { createEndpointConfig } from '@flowdrop/flowdrop/core';
import '@flowdrop/flowdrop/styles';

const nodes = [
  { id: 'text_input', name: 'Text Input', type: 'simple', category: 'inputs' /* ... */ },
  { id: 'text_output', name: 'Text Output', type: 'simple', category: 'outputs' /* ... */ },
  { id: 'ai_analyzer', name: 'AI Analyzer', type: 'tool', category: 'ai' /* ... */ }
  // ...more nodes
];

const categories = [
  { id: 'inputs', name: 'Inputs', icon: 'mdi:import', color: '#22c55e' },
  { id: 'outputs', name: 'Outputs', icon: 'mdi:export', color: '#ef4444' },
  { id: 'ai', name: 'AI & ML', icon: 'mdi:brain', color: '#9C27B0' }
  // ...more categories
];

const app = await mountFlowDropApp(document.getElementById('editor'), {
  nodes,
  categories,
  endpointConfig: createEndpointConfig('/api/flowdrop'),
  height: '100vh',
  showNavbar: true,
  eventHandlers: {
    onAfterSave: async (wf) => console.log('Saved:', wf.id),
    onDirtyStateChange: (dirty) => {
      document.title = dirty ? '* Editor' : 'Editor';
    }
  }
});

What's next

You've completed the tutorial! Here are some areas to explore next:


Tutorial — Step 5 of 5 · Complete!

← Nodes & categories

On this page