# Cancel Agent Spec execution Source: https://flowdrop.mintlify.app/api-reference/agent-spec/cancel-agent-spec-execution /api-reference/openapi.yaml post /agentspec/executions/{id}/cancel Cancel a running Agent Spec execution. Only executions in 'running' status can be cancelled. # Check Agent Spec runtime health Source: https://flowdrop.mintlify.app/api-reference/agent-spec/check-agent-spec-runtime-health /api-reference/openapi.yaml get /agentspec/health Verify that the configured Agent Spec runtime is available and responding. Used to check connectivity before execution. # Execute an Agent Spec flow Source: https://flowdrop.mintlify.app/api-reference/agent-spec/execute-an-agent-spec-flow /api-reference/openapi.yaml post /agentspec/flows/execute Submit an Agent Spec flow for execution on the configured runtime (WayFlow, PyAgentSpec, or other compatible runtimes). The flow is posted as Agent Spec JSON. The runtime returns an execution ID for tracking progress via polling or WebSocket. # Export workflow as Agent Spec JSON Source: https://flowdrop.mintlify.app/api-reference/agent-spec/export-workflow-as-agent-spec-json /api-reference/openapi.yaml get /workflows/{id}/export/agentspec Convert a FlowDrop workflow to Agent Spec format and return it. The conversion: - Maps FlowDrop node types to Agent Spec component types - Splits unified edges into control_flow_connections and data_flow_connections - Stores FlowDrop-specific data (positions, dynamic ports) in metadata extensions - Validates the workflow for Agent Spec compatibility before export # Get Agent Spec execution results Source: https://flowdrop.mintlify.app/api-reference/agent-spec/get-agent-spec-execution-results /api-reference/openapi.yaml get /agentspec/executions/{id}/results Retrieve the final results of a completed Agent Spec execution. Returns null/404 if the execution is still running or was cancelled. # Get Agent Spec execution status Source: https://flowdrop.mintlify.app/api-reference/agent-spec/get-agent-spec-execution-status /api-reference/openapi.yaml get /agentspec/executions/{id} Retrieve the current status and per-node execution info for a running or completed Agent Spec execution. # Import workflow from Agent Spec JSON Source: https://flowdrop.mintlify.app/api-reference/agent-spec/import-workflow-from-agent-spec-json /api-reference/openapi.yaml post /workflows/import/agentspec Convert an Agent Spec flow or document to a FlowDrop workflow. The conversion: - Maps Agent Spec component types to FlowDrop node types - Merges control_flow_connections and data_flow_connections into unified edges - Auto-layouts nodes if no position metadata is present - Preserves Agent Spec component_type in FlowDrop extensions # List available agents on the runtime Source: https://flowdrop.mintlify.app/api-reference/agent-spec/list-available-agents-on-the-runtime /api-reference/openapi.yaml get /agentspec/agents Retrieve a list of agents registered on the Agent Spec runtime. These agents can be referenced by `agent_node` components. # List available tools on the runtime Source: https://flowdrop.mintlify.app/api-reference/agent-spec/list-available-tools-on-the-runtime /api-reference/openapi.yaml get /agentspec/tools Retrieve a list of tools registered on the Agent Spec runtime. These tools can be referenced by `tool_node` components. # Stream Agent Spec execution updates (WebSocket) Source: https://flowdrop.mintlify.app/api-reference/agent-spec/stream-agent-spec-execution-updates-websocket /api-reference/openapi.yaml get /agentspec/executions/{id}/stream WebSocket endpoint for real-time execution updates. Streams per-node status changes and execution events as they occur. **Protocol**: WebSocket (upgrade from HTTP) **Messages sent by server**: - `{"type": "node_status", "node": "", "status": {...}}` - `{"type": "execution_complete", "results": {...}}` - `{"type": "execution_error", "error": ""}` # Validate Agent Spec flow on runtime Source: https://flowdrop.mintlify.app/api-reference/agent-spec/validate-agent-spec-flow-on-runtime /api-reference/openapi.yaml post /agentspec/flows/validate Validate an Agent Spec flow specification against the runtime without executing it. Checks for structural correctness, valid node references, and property compatibility. # Validate workflow for Agent Spec export Source: https://flowdrop.mintlify.app/api-reference/agent-spec/validate-workflow-for-agent-spec-export /api-reference/openapi.yaml get /workflows/{id}/validate/agentspec Check if a FlowDrop workflow can be exported as Agent Spec. Returns validation errors and warnings without performing the export. Checks include: - Exactly one start node (terminal/trigger type) - At least one end node (terminal/output type) - Gateway nodes have branches defined - All nodes are reachable from start # Clear chat conversation history Source: https://flowdrop.mintlify.app/api-reference/chat/clear-chat-conversation-history /api-reference/openapi.yaml delete /workflows/{id}/chat/messages Clear the conversation history for a workflow's chat session. This resets the chat context, starting a fresh conversation. # Get chat conversation history Source: https://flowdrop.mintlify.app/api-reference/chat/get-chat-conversation-history /api-reference/openapi.yaml get /workflows/{id}/chat/messages Retrieve the conversation history for a workflow's chat session. Returns an array of messages with role (user/assistant) and content. # Send a chat message Source: https://flowdrop.mintlify.app/api-reference/chat/send-a-chat-message /api-reference/openapi.yaml post /workflows/{id}/chat/messages Send a natural language message to the LLM chat backend for a specific workflow. The request includes the current workflow state and optional conversation history so the LLM can generate contextually relevant responses. The LLM response may contain plain text explanations and/or DSL commands in ```flowdrop fenced code blocks. The frontend extracts and previews these commands before execution. # Get category definitions Source: https://flowdrop.mintlify.app/api-reference/configuration/get-category-definitions /api-reference/openapi.yaml get /categories Retrieve all available category definitions including display labels, icons, colors, and ordering. Categories determine how nodes are organized in the sidebar. Built-in categories are always available as defaults. This endpoint allows overriding built-in category metadata and defining custom categories. # Get port configuration Source: https://flowdrop.mintlify.app/api-reference/configuration/get-port-configuration /api-reference/openapi.yaml get /port-config Retrieve the complete port configuration system including available data types, compatibility rules, and default settings. This configuration determines how nodes can be connected in workflows based on port data types. # Export workflow Source: https://flowdrop.mintlify.app/api-reference/importexport/export-workflow /api-reference/openapi.yaml get /workflows/{id}/export Export a workflow as JSON or YAML format # Import workflow Source: https://flowdrop.mintlify.app/api-reference/importexport/import-workflow /api-reference/openapi.yaml post /workflows/import Import a workflow from JSON format. A new UUID will be assigned to the imported workflow. # Cancel interrupt Source: https://flowdrop.mintlify.app/api-reference/interrupts/cancel-interrupt /api-reference/openapi.yaml post /interrupts/{interruptId}/cancel Cancel a pending interrupt without providing a response. This may not be allowed for all interrupts - check the allowCancel property in the interrupt data before attempting to cancel. # Get interrupt details Source: https://flowdrop.mintlify.app/api-reference/interrupts/get-interrupt-details /api-reference/openapi.yaml get /interrupts/{interruptId} Retrieve details about a specific interrupt request. Interrupts are created when a workflow execution requires human input. # List pipeline interrupts Source: https://flowdrop.mintlify.app/api-reference/interrupts/list-pipeline-interrupts /api-reference/openapi.yaml get /pipelines/{pipelineId}/interrupts List all interrupts associated with a pipeline execution. Useful for monitoring workflow progress and pending user actions. # List session interrupts Source: https://flowdrop.mintlify.app/api-reference/interrupts/list-session-interrupts /api-reference/openapi.yaml get /playground/sessions/{sessionId}/interrupts List all interrupts associated with a playground session. Useful for displaying pending interrupts or reviewing interrupt history. # Resolve interrupt Source: https://flowdrop.mintlify.app/api-reference/interrupts/resolve-interrupt /api-reference/openapi.yaml post /interrupts/{interruptId} Submit user response to resolve a pending interrupt. The value type depends on the interrupt type: - confirmation: boolean (true = confirmed) - choice: string or string[] (selected values) - text: string (user input) - form: object (form data matching schema) # Introduction Source: https://flowdrop.mintlify.app/api-reference/introduction The REST API that FlowDrop expects your backend to implement. FlowDrop is a frontend editor — it talks to **your backend** over a REST API. This reference documents that contract: the endpoints FlowDrop calls to discover node types, persist workflows, run pipelines, and resolve port compatibility. You implement these endpoints (or run a ready-made [server implementation](/server-implementations/overview)); FlowDrop calls them. The spec is maintained alongside the library. The endpoint pages in this section are generated from it. ## Base URL FlowDrop targets a single base path that you configure with `createEndpointConfig('/api/flowdrop')`. All endpoint paths in this reference are relative to that base — for example, `GET /nodes` resolves to `/api/flowdrop/nodes`. ## Authentication The API supports two authentication schemes, applied per your deployment: * **`BearerAuth`** — an `Authorization: Bearer ` header (JWT). In the editor, supply tokens through an [`AuthProvider`](/guides/integration/authentication-patterns) (for example `StaticAuthProvider` or `CallbackAuthProvider`). * **`SessionAuth`** — a session cookie (`SESS`), for same-origin deployments that rely on an existing session. Use whichever your backend enforces; FlowDrop attaches credentials via the configured `AuthProvider`. ## Endpoints The pages under **Endpoints** are generated directly from the OpenAPI specification and cover workflow CRUD, node-type discovery, pipeline execution and status, port configuration, and import/export. # Get all available node types Source: https://flowdrop.mintlify.app/api-reference/node-types/get-all-available-node-types /api-reference/openapi.yaml get /nodes Retrieve all available node processors with optional filtering by category, search query, and pagination. Supports filtering by node category (models, data_processing, input_output, etc.) and search queries. # Get node type by ID Source: https://flowdrop.mintlify.app/api-reference/node-types/get-node-type-by-id /api-reference/openapi.yaml get /nodes/{id} Retrieve detailed metadata for a specific node type by its unique identifier # Cancel execution Source: https://flowdrop.mintlify.app/api-reference/pipeline/cancel-execution /api-reference/openapi.yaml post /executions/{id}/cancel Cancel a running execution. Only executions in 'pending' or 'running' status can be cancelled. # Execute pipeline Source: https://flowdrop.mintlify.app/api-reference/pipeline/execute-pipeline /api-reference/openapi.yaml post /pipeline/{id}/execute Start execution of a pipeline # Execute workflow Source: https://flowdrop.mintlify.app/api-reference/pipeline/execute-workflow /api-reference/openapi.yaml post /workflows/{id}/execute Create a new pipeline and start execution in one call. This is a convenience endpoint that combines pipeline creation with immediate execution. Returns the pipeline ID and initial status for tracking. # Get execution logs Source: https://flowdrop.mintlify.app/api-reference/pipeline/get-execution-logs /api-reference/openapi.yaml get /executions/{id}/logs Retrieve detailed execution logs for a specific execution # Get execution status Source: https://flowdrop.mintlify.app/api-reference/pipeline/get-execution-status /api-reference/openapi.yaml get /executions/{id} Retrieve the current status and details of a specific execution. This is an alias for the pipeline detail endpoint. # Get pipeline execution details Source: https://flowdrop.mintlify.app/api-reference/pipeline/get-pipeline-execution-details /api-reference/openapi.yaml get /pipeline/{id} Retrieve detailed information about a pipeline execution including: - Overall pipeline status - Individual job statuses for each node - Node execution information (execution count, duration, errors) - Job status summary # Get pipeline execution logs Source: https://flowdrop.mintlify.app/api-reference/pipeline/get-pipeline-execution-logs /api-reference/openapi.yaml get /pipeline/{id}/logs Retrieve detailed execution logs for a pipeline # Get pipeline status Source: https://flowdrop.mintlify.app/api-reference/pipeline/get-pipeline-status /api-reference/openapi.yaml get /pipeline/{id}/status Retrieve only the current status of a pipeline (lightweight endpoint). Use this for polling when you only need the status, not full details. # List execution history Source: https://flowdrop.mintlify.app/api-reference/pipeline/list-execution-history /api-reference/openapi.yaml get /executions Retrieve execution history across all workflows. Supports filtering by workflow, status, and date range. # List workflow pipelines Source: https://flowdrop.mintlify.app/api-reference/pipeline/list-workflow-pipelines /api-reference/openapi.yaml get /workflow/{workflow_id}/pipelines Get all pipeline executions for a specific workflow # Stop pipeline execution Source: https://flowdrop.mintlify.app/api-reference/pipeline/stop-pipeline-execution /api-reference/openapi.yaml post /pipeline/{id}/stop Cancel a running pipeline execution # Create a new playground session Source: https://flowdrop.mintlify.app/api-reference/playground/create-a-new-playground-session /api-reference/openapi.yaml post /workflows/{id}/playground/sessions Create a new playground session for testing a workflow. The session can be named for easy identification. # Delete a playground session Source: https://flowdrop.mintlify.app/api-reference/playground/delete-a-playground-session /api-reference/openapi.yaml delete /playground/sessions/{sessionId} Permanently delete a playground session and all its messages. This action cannot be undone. # Get a single message Source: https://flowdrop.mintlify.app/api-reference/playground/get-a-single-message /api-reference/openapi.yaml get /playground/sessions/{sessionId}/messages/{messageId} Retrieve a specific message from a playground session by its ID. Returns full message details including status and metadata. # Get message status Source: https://flowdrop.mintlify.app/api-reference/playground/get-message-status /api-reference/openapi.yaml get /playground/sessions/{sessionId}/messages/{messageId}/status Retrieve only the status of a message (lightweight endpoint for polling). Useful for checking if message processing is complete without fetching full message data. # Get messages from a playground session Source: https://flowdrop.mintlify.app/api-reference/playground/get-messages-from-a-playground-session /api-reference/openapi.yaml get /playground/sessions/{sessionId}/messages Retrieve messages from a playground session with optional filtering. Supports polling via the `since` parameter to fetch only new messages. # Get playground session details Source: https://flowdrop.mintlify.app/api-reference/playground/get-playground-session-details /api-reference/openapi.yaml get /playground/sessions/{sessionId} Retrieve detailed information about a specific playground session, including its current status and configuration. # List playground sessions for a workflow Source: https://flowdrop.mintlify.app/api-reference/playground/list-playground-sessions-for-a-workflow /api-reference/openapi.yaml get /workflows/{id}/playground/sessions Retrieve all playground sessions associated with a workflow. Sessions are used to test and interact with workflows in an isolated environment. # Send a message to the playground session Source: https://flowdrop.mintlify.app/api-reference/playground/send-a-message-to-the-playground-session /api-reference/openapi.yaml post /playground/sessions/{sessionId}/messages Send a user message or trigger workflow execution with inputs. This starts or continues the conversation in the playground. The message is created with status "pending" and processing begins immediately (synchronously) or is queued (asynchronously) based on the session's execution mode. The response returns immediately with the message entity, allowing clients to poll the message status endpoint to track processing progress. Messages are processed in sequence order within a session to ensure proper conversation flow. If a previous message is not yet complete, the request will be rejected with a conflict error. # Stop playground execution Source: https://flowdrop.mintlify.app/api-reference/playground/stop-playground-execution /api-reference/openapi.yaml post /playground/sessions/{sessionId}/stop Stop the currently running execution in the playground session. This cancels any pending workflow operations. # API health check Source: https://flowdrop.mintlify.app/api-reference/system/api-health-check /api-reference/openapi.yaml get /health Check if the FlowDrop API is running and responsive. This endpoint is at the root level following industry conventions for Kubernetes liveness/readiness probes and load balancer health checks. # Get API version Source: https://flowdrop.mintlify.app/api-reference/system/get-api-version /api-reference/openapi.yaml get /system/version Retrieve the current API version information # Get system configuration Source: https://flowdrop.mintlify.app/api-reference/system/get-system-configuration /api-reference/openapi.yaml get /system/config Retrieve public system configuration settings # Validate workflow Source: https://flowdrop.mintlify.app/api-reference/validation/validate-workflow /api-reference/openapi.yaml post /workflows/validate Validate a workflow structure without saving it. Checks for: - Valid node connections and port compatibility - Required configuration fields - Circular dependencies - Orphaned nodes - Missing required inputs # Create a new workflow Source: https://flowdrop.mintlify.app/api-reference/workflows/create-a-new-workflow /api-reference/openapi.yaml post /workflows Create a new workflow with the provided name, description, nodes, and edges. The workflow will be assigned a unique UUID and metadata will be automatically generated. # Delete workflow Source: https://flowdrop.mintlify.app/api-reference/workflows/delete-workflow /api-reference/openapi.yaml delete /workflows/{id} Permanently delete a workflow and all associated data including execution history # Get all workflows Source: https://flowdrop.mintlify.app/api-reference/workflows/get-all-workflows /api-reference/openapi.yaml get /workflows Retrieve all workflows with optional search filtering and pagination. Returns workflow metadata including nodes, edges, and execution history. # Get workflow by ID Source: https://flowdrop.mintlify.app/api-reference/workflows/get-workflow-by-id /api-reference/openapi.yaml get /workflows/{id} Retrieve a specific workflow with all its nodes, edges, and metadata # Update workflow Source: https://flowdrop.mintlify.app/api-reference/workflows/update-workflow /api-reference/openapi.yaml put /workflows/{id} Update an existing workflow. All fields are optional - only provided fields will be updated. The updatedAt timestamp will be automatically set to the current time. # 1.x Source: https://flowdrop.mintlify.app/changelog/1x FlowDrop 1.x release highlights A glanceable summary of the 1.x line. For the full, detailed notes, see the [complete changelog on GitHub](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md). Dependent-autocomplete correctness across undo, redo, and form load. * Dependent fields keep their values on undo/redo and external config replacement. * Preloaded values survive form load (no more spurious clears). * Undo/redo restored for committed config edits. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#1150---2026-05-28) The OpenAPI spec now ships with the package, plus message pagination. * **`@flowdrop/flowdrop/openapi`** — the full backend spec is bundled and version-matched to the installed release. * Backward pagination for playground messages (`before` / `latest` cursors, `hasOlder`). * Pipeline-nesting fields on messages (`parentPipelineId`, `rootPipelineId`). [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#1140---2026-05-27) Server-driven message presentation and a big accessibility pass. * Server-emitted message annotations — `hierarchy`, `tags`, and `display` fully control rendering. * Dependent autocomplete fields via `autocomplete.params`. * Mobile + container-query playground layouts, and an a11y pass on the message stream. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#1130---2026-05-23) Kanban pipeline view and extensible pipeline surfaces. * **Kanban view** with server-configurable columns (`kanban_config`). * New `paused` and `interrupted` execution statuses across all views. * Inject custom pipeline views via `PipelineViewDef`; full playground i18n and keyboard a11y. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#1120---2026-05-16) Full-page playground chrome for any host. * **`PlaygroundApp`** pairs the Navbar with `PlaygroundStudio`, plus a framework-agnostic `mountPlaygroundApp()`. * A `settings` option on all playground mount functions. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#1110---2026-05-11) The integrated split-pane playground, packaged. * **`PlaygroundStudio`** + `mountPlaygroundStudio()` combine the pipeline panel and chat in one import. * Sequence-number polling cursor and automatic pipeline-panel refresh while following the latest run. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#1100---2026-05-10) Live pipeline view inside the playground. * Toggleable, resizable pipeline panel alongside the chat. * Run picker with a "latest" toggle; session chip dropdown. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#190---2026-05-09) Fixes an AI Assistant freeze on corrupted command batches with an unclosed `"""` block. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#181---2026-04-28) The typed, overridable i18n system. * Every user-facing string renders from a single `Messages` tree; override any subset via the `messages` prop (paraglide-js friendly). * **`clearAllDrafts()`** for logout integrations. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#180---2026-04-28) * **`workflowSettingsSchema`** lets you inject custom fields into the Workflow Settings panel, persisted in `workflow.config`. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#170---2026-04-10) * Progressive command execution for AI-applied changes. * Auto-retry on failed command batches (the assistant self-corrects, gated by `chatAutoRetry`). [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#160---2026-04-08) Node swap, the command console, and the AI Chat panel. * **Node swap** — replace a node type while preserving connections and config. * **Command console** — a DSL for driving the canvas, with autocomplete and history. * **AI Chat panel** — natural-language prompts parsed into previewable DSL commands. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#150---2026-04-03) * Settings modal customization for vanilla-JS hosts. * Themeable logo and xyflow controls; collapsible sidebar; dynamic ports for all node types. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#140---2026-03-16) * Port ordering (`portOrder`) and manual port hiding (`hiddenPorts`). * All ports shown by default on SimpleNode and SquareNode. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#130---2026-03-15) Fixes a browser freeze when dragging a node with proximity connect enabled. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#122---2026-03-13) Icon theming and sidebar empty-state fixes. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#121---2026-03-13) * The `theme` system is now reachable from the JS mount API (`mountFlowDropApp({ theme })`). [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#120---2026-03-13) The theme and skin system. * `theme` prop accepts a built-in name (`'default'` | `'minimal'`) or a custom `FlowDropTheme`. * Full light/dark palette control via `FlowDropSkin` tokens. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#110---2026-03-12) Fixes duplicate workflow saves on UUID-keyed backends (detect existing workflows by `id` presence, not a UUID regex). [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#101---2026-03-11) First stable release of `@flowdrop/flowdrop` — production-ready after the 0.0.x series under the `@d34dman/flowdrop` namespace. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#100---2026-03-11) # 2.x Source: https://flowdrop.mintlify.app/changelog/2x FlowDrop 2.x release highlights A glanceable summary of the 2.x line. For the full, detailed notes — every fixed bug, internal change, and migration step — see the [complete changelog on GitHub](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md). Upgrading from 1.x? The [2.0 migration guide](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/MIGRATION-2.0.md) walks through every breaking change step by step. **FlowDrop 2.0 is GA.** The `latest` dist-tag moves from 1.15.0 to 2.0.0, so `npm install @flowdrop/flowdrop` now installs 2.x. * **Multiple editors per page.** Every mount creates an isolated `FlowDropInstance` — workflow, history, playground, interrupts, registries, port compatibility. The module-level singleton stores are gone, and SSR no longer leaks state across requests. * **An API surface that tells the truth.** Props the components never read are deleted rather than silently ignored, documented options that did nothing (`height`/`width`, `spellChecker`, `required`, `placeholder`) now work, and each name has exactly one home. * **A playground you can operate.** Slash commands, pipeline pause/resume/cancel, message-free run launches, session reset, and full job-detail expansion. * **Authentication through providers.** `EndpointConfig.auth` gives way to `StaticAuthProvider` / `CallbackAuthProvider` / `NoAuthProvider`, and draft storage is configurable via `draftStorage`. Code-identical to `2.0.0-beta.11` apart from one lint-hygiene fix — the eleven betas below are the release notes for this version. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200---2026-08-04) · [Migration guide →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/MIGRATION-2.0.md) An operator control lane for the playground, plus four editor fixes. * **Slash commands in the composer** — `/help`, `/run`, `/new`, `/stop`, `/reset`, `/delete`, `/pause`, `/resume`, `/cancel`, with an autocomplete palette. Commands never enter the conversation as chat turns, only those whose endpoint is configured are offered, and `//` escapes a literal leading slash. Opt-in per surface via `enableCommands`. * **Pipeline signals — pause, resume, cancel.** Backends observe them cooperatively between steps, so a signal reports `accepted` rather than claiming the run is already paused. New `signals` endpoint block. * **Start a run without posting a message.** A RUN button and `/run --key=value` launch through the new `workflows.run` endpoint instead of fabricating a user turn. * **Fixes** — the assistant no longer re-arranges hand-made layouts unasked (#36), triple-quoted blocks no longer swallow every later command (#35), node types with no config schema keep their port settings (#34), and port drags no longer start a text selection in Safari (#37). [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta11---2026-07-31) Config edits commit live, and one undo means one edit. * **Safari dropped edits made with a checkbox or select (#38).** Node config committed only on `focusout`, which WebKit never fires for those controls. It now commits live, per change. * **A single undo reverted more than one edit (#39).** The history stack stores post-change snapshots, so the stack top is the current state and one undo == one edit. * **No more freeze while typing in a config field.** An editing session applies changes cheaply and coalesces into a single undo step; the workflow event and edge refresh fire once, on finalize. * **Standalone `ConfigModal` keeps the FlowDrop font** instead of inheriting the host page's (serif on a bare Drupal page). [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta10---2026-07-22) A follow-up fix to the relocatable surfaces from beta.8. * **Modal config surfaces no longer inherit the host page's font.** The modal placement and the config panel's pop-out portal to `document.body` so their backdrop escapes any containing block — which also moved them out of the mount container carrying the app font. Each backdrop now re-declares `--fd-font-family` itself, so a portalled surface matches the editor and still honours a consumer's override. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta9---2026-07-12) The config panel and the console/AI Assistant group are no longer pinned in place. * **Configurable surface placement** — host each in the right sidebar, a centered modal, or the bottom panel, chosen independently via `configPlacement` and `consolePlacement` (both mount options and Settings entries). Defaults are unchanged, so existing embeds look exactly as before; a host default seeds the setting without overriding a returning user's choice. * **Surfaces sharing a host become tabs.** The new `TabbedSurface` keeps every tab body mounted, so console scrollback, chat history, and in-progress form edits survive a switch — and the tab bar hides itself when only one surface is present. * **`SurfaceOverlay` + `portal` action** — the modal shell closes on Esc, backdrop click, or the close button, and escapes any ancestor that establishes a containing block (`transform`, `filter`, …). [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta8---2026-07-11) Dynamic config schemas become composable and self-refreshing. All additive — the historical whole-form `replace` behaviour is still the default. * **Layer onto the static schema** — `mergeStrategy: 'replace' | 'merge'` plus a dot-path `target` let an endpoint return only the fields it drives, or drive a single region of the form, leaving everything else exactly as authored. * **Auto-refetch when inputs change** — the form watches the resolved endpoint URL, so picking a different value the endpoint depends on (e.g. a trigger's `event_type`) reloads the matching schema instead of keeping the one captured at mount. * **Endpoints can return a companion `uiSchema`** to lay out the fetched fields. * **Fixes** — config keys the active schema doesn't own are no longer dropped on save, and `invalidateSchemaCache` now matches the fetch cache key for endpoints referencing `{workflowId}`. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta7---2026-07-09) Port order and exposure become one value, and exposure becomes semantic — what you see is what runs. * **`data.config.ports` replaces `extensions.ui.portOrder`.** One ordered list per direction: position encodes display order, and an optional `exposed` flag overrides the port's `exposedByDefault`, stored only when it diverges. An untouched node persists no `ports` key. * **A not-exposed port is hidden, not wireable, and not runtime-overridable.** `hideUnconnectedHandles` is removed end-to-end — visibility no longer derives from connection state. * **One combined "Ports" widget** in the config panel handles reorder and expose/hide per port, replacing the separate Port Order panel. * **Reserved ports sort to the bottom automatically** — the `error` output and the `trigger`/`tool` control ports, via the new `NodePort.displayOrder`. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta6---2026-06-22) A node-identity cleanup that removes a long-standing duplicate id field. * **Node instance id comes from one source.** Node components now derive their id from the canonical `id` prop SvelteFlow already passes, so the duplicate `data.nodeId` field (and its load-time "healing") is gone — an intact graph can no longer look corrupted from a missing copy. * **`metadata.id` → `metadata.node_type_id`.** The node-*type* entity id is renamed to disambiguate it from the node instance id. Custom node metadata, dynamic-schema `parameterMapping`, and any code reading the type id must use `node_type_id`. * **Stricter node component typing** — six built-in node components moved off the type-erasing `$props()` onto a typed `interface Props`, which surfaced and fixed latent config-access errors in `ToolNode`. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta5---2026-06-20) A design-system consolidation pass, plus a few small embedding wins. * **Shared form-control & button primitives** — new `Button` / `IconButton` / `Input` / `Select` / `Textarea` wrappers route every control through the `base.css` class system, so themes (including Drafter) style fields and buttons consistently. * **`end` snippet on the navbar** — render custom trailing content (e.g. a theme toggle) before the settings gear without forking the navbar. * **`--fd-control-radius` theming token** — themes can tighten field/array-button/section corners independently of cards and panels (Drafter sets `2px`). * **Fixes** — the range-slider fill now tracks the thumb center, and Drafter buttons are flat and opaque. * **Docs** — the archived Astro/Starlight site is retired; the Mintlify site is canonical. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta4---2026-06-13) Finishes the navbar/theme defaults pass and adds the Drafter blueprint theme. New theme called Drafter showing FLowDrop editor. * **Drafter blueprint theme** — a third built-in editor theme (light + dark): a mint canvas, a subtle emerald line grid, and translucent green-tinted nodes. Ships with per-theme canvas grids (`dots` | `lines` | `cross`) via the new `FlowDropGridVariant`. * **Navbar & theme defaults** — the navbar is now opt-in on every mount path (`showNavbar` defaults to `false`), `light` is the default theme for embeds with no saved choice, and the header shows the FlowDrop wordmark. * **Keyboard navigation & focus** — each node is a single tab stop again (the config gear left the tab order, deferring to xyflow's node focus), backed by one centralized focus ring across the whole library. * **Full editor renders built-in editors out of the box** — markdown / code / template config fields no longer fall back to a textarea after the beta.2 light-entry split; add `features: { builtinEditors: false }` to opt out. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta3---2026-06-12) Tightens the 2.0 package boundaries and finishes the auth work. * **Light entries stay light** — `core`, `form`, and `editor` no longer statically pull heavy deps (CodeMirror, `@xyflow/svelte`, `marked`, DOMPurify), enforced by a new CI bundle guard (`pnpm run check:bundle`). * **`AuthProvider` reaches every runtime surface** — playground, chat, interrupt, settings, editor, and pipeline requests all route through the configured provider, including standalone mounts. * 500-node editor render benchmark added to the e2e suite. [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta2---2026-06-09) The headline 2.0 release: state, API, and registries are now instance-scoped. * **Multiple FlowDrop instances per page** — every mount creates an isolated `FlowDropInstance`; module-level singleton stores are gone (`app.instance.workflow`, `fd.api`, `fd.nodes`, …). * **`mode` prop** (`'edit' | 'readonly' | 'locked'`) replaces `readOnly` + `lockWorkflow`. * **Auth via `AuthProvider`** — `EndpointConfig.auth` is removed in favour of `StaticAuthProvider` / `NoAuthProvider` / `CallbackAuthProvider`. * **Slim main entry** — `@flowdrop/flowdrop` exposes only the bootstrap surface; everything else moves to its owning sub-module. * **Workflow `metadata` is required**, and `metadata.version` is renamed to `metadata.schemaVersion` (1.x JSON heals automatically on load). [Full notes →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/CHANGELOG.md#200-beta1---2026-06-07) · [Migration guide →](https://github.com/flowdrop-io/flowdrop/blob/main/libs/flowdrop/MIGRATION-2.0.md) # Pre 1.0 Source: https://flowdrop.mintlify.app/changelog/pre-1.0 The 0.0.x development series Before its 1.0 debut, FlowDrop went through 65 releases (0.0.2 → 0.0.65, November 2025 – March 2026) under the original **`@d34dman/flowdrop`** npm namespace. That series is where the editor, the form system, the playground, and the node/port model took shape. This history is kept for reference only. The package is now **`@flowdrop/flowdrop`**, and 1.0.0 is the first stable release — start from [1.x](/changelog/1x) for the current line. The pre-1.0 entries are archived in full on GitHub: Every 0.0.x release, including the breaking changes and upgrade notes from the `@d34dman/flowdrop` era. # Architecture overview Source: https://flowdrop.mintlify.app/concepts/architecture-overview How FlowDrop's modules, components, stores, and services fit together. This page explains how FlowDrop is structured internally, so you can make informed decisions about what to import, how to integrate, and where to extend. ## High-level architecture FlowDrop is a **frontend library** that communicates with **your backend** via REST. ``` ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ Browser │ │ ┌─────────────────────┐ │ │ FlowDrop │ │ ├─────────────────────┤ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │User Interface │ │ Your Backend │ │ │ │ │ │ │ ├─╼ Navbar │ │ ┌────────────────────────┐ │ ├─╼ Canvas │ │ │ Python/PHP/NodeJS │ │ │ │ ╰─╼ Config Panel │ │ │ Framework │ │ │ │ ├────────────────────────┤ │ │ ├─────────────────────┤ │ │Storage and Business │ │Browser Storage │ REST │ │Logic │ │ │ │ │ │ │───API───▶ │ │ │ │ ├─╼ Workflow │ │ │ ├─╼ Nodes │ │ │ │ ├─╼ History │ │ │ ├─╼ Workflows │ │ ╰─╼ Settings │ │ │ ├─╼ Access │ │ │ │ │ │ │ ├─╼ Persistant Storage │ ├─────────────────────┤ │ │ ╰─╼ Execution │ │ │ │Services │ │ │ │ │ │ │ │ │ │ │ │ │ ├─╼ API Client │ │ └────────────────────────┘ │ ├─╼ Drafts │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ ╰─╼ Toasts │ │ │ │ │ └─────────────────────┘ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ``` ## Module structure FlowDrop is tree-shakable. Each sub-module has different dependencies and bundle cost: | Module | What it provides | Heavy deps | | ---------------------------------- | ------------------------------------------------ | -------------------- | | `@flowdrop/flowdrop/core` | Types, utilities, auth providers, config helpers | None | | `@flowdrop/flowdrop/editor` | WorkflowEditor, mount functions, node components | @xyflow/svelte | | `@flowdrop/flowdrop/form` | SchemaForm, field components | None | | `@flowdrop/flowdrop/form/code` | Code & template editors | CodeMirror (\~300KB) | | `@flowdrop/flowdrop/form/markdown` | Markdown editor | CodeMirror | | `@flowdrop/flowdrop/display` | MarkdownDisplay | marked | | `@flowdrop/flowdrop/playground` | Playground, chat, interrupts | Editor + Form | | `@flowdrop/flowdrop/settings` | Settings panel, theme toggle | Form | | `@flowdrop/flowdrop/styles` | CSS design tokens | None | | `@flowdrop/flowdrop` | Bootstrap front door (App, mount, instances) | Bootstrap surface | ## Component hierarchy When you mount `mountFlowDropApp()`, this is the component tree: ```text theme={null} App ├── Navbar │ ├── Logo │ ├── WorkflowName (editable) │ ├── Save / Export buttons │ ├── Custom NavbarActions │ └── ThemeToggle / Settings ├── NodeSidebar │ ├── Search │ └── CategoryGroups │ └── NodeCards (draggable) ├── WorkflowEditor (@xyflow/svelte canvas) │ ├── Nodes (WorkflowNode, SimpleNode, GatewayNode, etc.) │ │ └── Ports (input/output handles) │ ├── Edges (styled by category) │ └── ConnectionLine ├── ConfigPanel (right side, on node click) │ ├── NodeHeader (name, type, icon) │ └── SchemaForm (generated from configSchema) │ └── FormFields (text, select, code, template, etc.) └── ToastContainer ``` `mountWorkflowEditor()` mounts just the canvas — no navbar, no sidebar. Each mount produces one such tree backed by its own instance; node/field registries and settings are shared across all trees on the page. ## Stores FlowDrop uses **Svelte 5 runes** for state management. Each mount creates a per-instance `FlowDropInstance` container that holds these stores: | Store | Purpose | Key state | | ----------------------- | ---------------------- | ------------------------------------------- | | **workflowStore** | Central workflow state | nodes, edges, metadata, isDirty | | **historyStore** | Undo/redo | past states, future states, canUndo/canRedo | | **settingsStore** | User preferences | theme, editor behavior, UI config | | **playgroundStore** | Playground sessions | sessions, messages, isExecuting | | **interruptStore** | Human-in-the-loop | pending/resolved interrupts | | **categoriesStore** | Node categories | category definitions, colors | | **portCoordinateStore** | Handle positions | port coordinates for edge rendering | ### Instance model Every mount creates an isolated `FlowDropInstance` container holding the stores above (workflow, history, playground, interrupts, categories, port coordinates, and pipeline-panel state), resolved through Svelte context. Multiple editors can therefore coexist on one page without sharing state. See the [multiple instances guide](/guides/multiple-instances) for details. ## Services Services handle communication and side effects: | Service | Purpose | | ---------------------- | ----------------------------------------------------------- | | **API client** | HTTP requests to your backend (nodes, workflows, execution) | | **Draft storage** | Auto-save to localStorage | | **Toast service** | Success/error/loading notifications | | **Dynamic schema** | Fetch config schemas from API at runtime | | **Playground service** | Manage sessions, poll for messages | | **Interrupt service** | Submit interrupt resolutions | | **History service** | Track and replay state changes | | **Settings service** | Load/save preferences (localStorage + API) | ## Data flow Here's what happens when a user makes a change: ``` ┌───────────────────────┐ │ User action │ │ (drag node, edit │ │ config, draw edge) │ └───────────────────────┘ │ ▼ ┌───────────────────────┐ │Component event handler│ └───────────────────────┘ │ │ ▼ ┌───────────────────────┐ │ workflowStore update │ │ (state mutation) │ └───────────────────────┘ │ │ ┌────────────────────────────┐ │ │ historyStore │ ├──────▶│ records snapshot │ │ │ (for undo) │ │ └────────────────────────────┘ │ ┌────────────────────────────┐ │ │ isDirty │ ├──────▶│ flag set to true │ │ │ │ │ └────────────────────────────┘ │ ┌────────────────────────────┐ │ │ │ ├──────▶│ UI re-renders │ │ │ │ │ └────────────────────────────┘ │ ┌────────────────────────────┐ │ │ │ │ │ onWorkflowChange(workflow, │ │ │ changeType) │ │ │ │ ├──────▶│ │ │ │ your callback — analytics, │ │ │ validation, etc. │ │ │ │ │ └────────────────────────────┘ │ ┌────────────────────────────┐ │ │ onDirtyStateChange(true) │ │ │ │ └──────▶│your callback — update save │ │ button, etc. │ │ │ └────────────────────────────┘ ``` When the user saves: ``` ┌─────────────────────┐ │ User clicks Save │ └─────────────────────┘ │ ▼ ┌───────────────────────────┐ │ onBeforeSave(workflow) │ └───────────────────────────┘ │ │ ┌──False──────┴──────────────┐ │ │ ▼ ▼ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ API client: │ │ Cancel │ │ │ │ │ │ PUT /workflows/{id} │ └─────────────────────┘ └─────────────────────┘ │ │ │ ┌─────Success─────────────┴───Error─┐ │ │ │ │ ▼ ▼ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃isDirty = false, draft cleared┃ ┃ Toast notification ┃ ┃ ┃ ┃ ┃ ┣──────────────────────────────┫ ┣──────────────────────────────┫ │ onAfterSave(workflow) │ │ onSaveError(error, workflow) │ └──────────────────────────────┘ ├──────────────────────────────┤ │ onApiError(error, 'save') │ └──────────────────────────────┘ ``` ## Registry system FlowDrop has two registries for extending the editor: ### Node component registry Register custom Svelte components for new node types against the instance's `fd.nodes` registry: ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); fd.nodes.registerCustom('my-custom-node', 'My Custom Node', MyNodeComponent); ``` ### Field component registry Register custom form fields for config schemas against `fd.fields`: ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); fd.fields.register('my-field', { component: MyFieldComponent, matcher: (schema) => schema.format === 'my-field', priority: 10 }); ``` Both registries are **instance-scoped** — seeded with builtins in the instance constructor and resolved via `getInstance()`. You can register after mounting. `BaseRegistry` tracks a version counter that invalidates dependent `$derived` reads, so registrations made after mount still take effect. ## Next steps * [What is a Workflow?](/concepts/what-is-a-workflow) — the mental model * [Quick Start](/docs/quickstart) — mount FlowDrop in your app * [Backend Implementation](/guides/integration/backend-implementation) — build the API FlowDrop expects * [Event System](/guides/advanced/event-system) — hook into every lifecycle event # Glossary Source: https://flowdrop.mintlify.app/concepts/glossary Definitions of key terms used throughout FlowDrop documentation. **Agent Spec** An open standard for defining AI agent workflows ([oracle/agent-spec](https://github.com/oracle/agent-spec)). FlowDrop can import and export this format via the `AgentSpecAdapter`. **Branch** A conditional output path on a [Gateway](#gateway) node. Each branch has a label and condition that determines which downstream path to follow. **Category** A grouping for node types in the sidebar (e.g., `inputs`, `models`, `processing`, `logic`). Categories have an icon, color, and display order. **ConfigSchema** A JSON Schema object (`{ type: 'object', properties: {...} }`) that defines what configuration fields a node has. FlowDrop renders this as an editable form. — see [Configuration Schema](/guides/config-schema) **ConfigValues** The actual key-value pairs set by a user for a node's configuration. Stored in `node.data.config`. **Data Type** A type identifier for a port (e.g., `string`, `json`, `trigger`, `file`). FlowDrop uses data types to enforce type-safe connections between ports. **Draft** An auto-saved copy of the current workflow stored in `localStorage`. Drafts prevent data loss when the browser closes unexpectedly. **Dynamic Port** A port created at runtime by the user (not defined in node metadata). Used for nodes that accept a variable number of inputs or outputs. **Dynamic Schema** A `configSchema` fetched from an API endpoint at runtime instead of being defined statically in node metadata. Allows config forms to change based on context. — see [Configuration Schema](/guides/config-schema) **Edge** A connection between two nodes. Edges have a source node/port and target node/port, and are categorized as `data`, `trigger`, `tool`, or `loopback`. **Edge Category** The semantic type of an edge: `data` (standard flow), `trigger` (control flow), `tool` (agent tool invocation), or `loopback` (feedback loop). **Endpoint Config** An object mapping FlowDrop's API operations to your backend URLs. Created with `createEndpointConfig('/api/flowdrop')`. — see [Mount API](/reference/mount-api) **Gateway** A node type (`gateway`) used for conditional branching. It has multiple output ports (branches), each with a condition that routes execution. — see [Conditional Branching](/recipes/conditional-branching) **Handle ID** The internal identifier for a port connection point, formatted as `{nodeId}-{direction}-{portId}` (e.g., `node-1-output-result`). **Interrupt** A human-in-the-loop event where workflow execution pauses to request user input. Types: `confirmation`, `choice`, `text_input`, `form`, `review`. — see [Human-in-the-Loop](/guides/interrupts) **Mount API** Functions (`mountFlowDropApp`, `mountWorkflowEditor`, `mountPlayground`) that embed FlowDrop into any HTML container element. — see [Mount API Reference](/reference/mount-api) **Namespace** A prefix for custom node types to avoid naming collisions (e.g., `mycompany:special-node`). Created with `createNamespacedType()`. **Node** A single step in a workflow. Each node has a type, position, metadata, ports, and configuration. **NodeMetadata** The definition of a node type — its name, description, icon, category, ports, and config schema. Served by your backend at `GET /nodes`. **Node Type** The visual representation of a node: `workflowNode` (full-featured), `simple` (compact), `square` (icon only), `tool`, `gateway`, `terminal`, `idea`, or `note`. **Plugin** A package of custom node types registered together via `fd.nodes.registerPlugin()` (or `createPlugin().register(fd.nodes)`). Plugins have a namespace and can define multiple node components. — see [Custom Nodes](/guides/custom-nodes) **Playground** An interactive testing interface where users can execute workflows, send messages, and see results in a chat-like UI. **Port** A typed connection point on a node. Input ports receive data; output ports send data. Each port has a data type that determines compatibility. **Port Config** The global configuration of available data types and compatibility rules. Served by your backend at `GET /port-config`. **Registry** A per-instance store of custom node components (`fd.nodes`) or custom form fields (`fd.fields`), seeded with builtins in the instance constructor. — see [Custom Nodes](/guides/custom-nodes) **Session** A playground execution session. Each session has messages, status, and can be resumed. **Store** A Svelte 5 rune-based reactive state container. FlowDrop has stores for workflow, history, settings, playground, interrupts, and categories. **Template Variable** A placeholder in the form `{{ variable.path }}` that resolves to data from upstream nodes. Used in template editor fields. — see [Template Variables](/guides/advanced/template-variables) **UISchema** A layout definition (`VerticalLayout`, `Group`, `Control`) that controls how config schema fields are arranged in the form UI. — see [Configuration Schema](/guides/config-schema) **Workflow** A directed graph of nodes connected by edges. The top-level data structure containing `id`, `name`, `nodes`, `edges`, and `metadata`. **WorkflowAdapter** A programmatic API for creating and manipulating workflows in code, without the visual editor. # Thinking in graphs Source: https://flowdrop.mintlify.app/concepts/thinking-in-graphs Build intuition for nodes, edges, ports, and config by growing a familiar shell command into a workflow graph. This page builds the intuition behind every FlowDrop workflow. We start from a single shell command you already know and grow it, one step at a time, into the vocabulary FlowDrop uses: **process**, **graph**, **node**, **edge**, **port**, and **configuration**. You'll learn: * Why a workflow is a graph of connected steps * What a node and an edge are, and why they're separate things * What ports add on top of edges * How configuration differs from runtime input Once these click, the formal terms in [What is a workflow?](/concepts/what-is-a-workflow) will read as a recap rather than a wall of new words. ## 1. From command to process Most things you run on a computer fit a simple shape: you give it something, it does work, you see a result. ```cmd theme={null} ❯ capitalize "Hello World!" ❯ HELLO WORLD! ``` Three roles in this command: | Item | Role | Function | | ---------------- | ------- | ---------------------------- | | `capitalize` | Process | Runs a task on request | | `"Hello World!"` | Input | What the process operates on | | `HELLO WORLD!` | Output | What the user sees | ``` ┌──────────┐ ┌─────────┐ ┌─────────┐ │ Input │─────▶│ Process │──────▶│ Output │ └──────────┘ └─────────┘ └─────────┘ ``` The same shape shows up everywhere: 1. A math function: ``` x ─▶ f(x) ─▶ y ``` 2. A computer: ``` ┌──────────┐ ┌─────────┐ ┌─────────┐ │ Keyboard │─────▶│Processor│──────▶│ Monitor │ └──────────┘ └─────────┘ └─────────┘ ``` A FlowDrop workflow is built from this same shape, repeated and connected. ## 2. A process without user input Some processes don't need anything from the user — you invoke them and they produce a result. ```cmd theme={null} ❯ date ❯ Tue Apr 28 13:27:37 CEST 2026 ``` ``` ┌──────────┐ ┌─────────┐ ┌─────────┐ │ Get Date │─────▶│ Clock │──────▶│ Display │ └──────────┘ └─────────┘ └─────────┘ ``` You typed `date` with no arguments — from your perspective, no input was required. But something still set the process in motion. The act of invoking `date` is a **trigger**: a signal that says "run now". `Get Date` plays that role here — the arrow into `Clock` doesn't carry a value, it just fires the process. `Clock` reads the system time on its own and hands the result to `Display`. Notice this: not every arrow in a graph carries data. Some arrows just trigger the next step. We'll come back to this distinction in [step 5](#5-graphs-nodes-and-edges), where the difference becomes **trigger edges** versus **data edges**. Keep `Get Date` and `Clock` in mind — they'll keep appearing as we add more inputs. ## 3. Multiple inputs Now we combine the no-input process from step 2 with a user-supplied value: ```cmd theme={null} ❯ date +%Y ❯ 2026 ``` `+%Y` is a **format string** the user provides. The process needs both the current date *and* the format to produce its output: ``` ┌──────────┐ │ Format │───┐ ┌─────────┐ └──────────┘ └──▶ │ ┌─────────┐ │ Clock │──────▶│ Display │ ┌──────────┐ ┌──▶ │ └─────────┘ │ Get Date │───┘ └─────────┘ └──────────┘ ``` Two arrows now feed into `Clock`, and they're not the same kind. `Get Date` is still the trigger from step 2 — it tells `Clock` to run but carries no value. `Format` is different: it carries the actual format string `+%Y` into the process. One arrow triggers, the other delivers data, and a single process can take any mix of the two. ## 4. Configuration vs. runtime input What if some inputs are settings you decide once when building the workflow, rather than values that arrive each time it runs? ```cmd theme={null} # Set the system-wide default for timezone. ❯ set TZ="Asia/Tokyo" ``` ```cmd theme={null} ❯ date ❯ Tue Apr 28 20:34:42 JST 2026 ``` ```cmd theme={null} # Override the system-wide default timezone at runtime. ❯ date -z "CET" ❯ Tue Apr 28 13:34:47 CEST 2026 ``` Same `date` command, different output — because `TZ` was set as an environment variable, not passed as an argument. It's part of how the process is configured to run, not data that flows in for this particular invocation. We draw this kind of value *inside* the process box to mark it as **configuration**: ``` ┌──────────┐ ┌────────────────────────┐ │ TimeZone │──────▶ Clock │ └──────────┘ │ │ │ │ │ │ ┌─────────┐ │ ┌──────────────┐ │───▶│ Display │ │ │ TimeZone │ │ └─────────┘ │ ├──────────────┤ │ ┌──────────┐ │ │ Format │ │ │ Get Date │──────▶ └──────────────┘ │ └──────────┘ └────────────────────────┘ ``` Notice `TimeZone` appears twice: once as an outside arrow (a runtime input that could change each run) and once inside `Clock` (a configured default). The same logical concept can be either, depending on how the workflow is wired. We'll come back to this distinction in [step 7](#7-static-configuration-on-a-node). ## 5. Graphs: nodes and edges We've been drawing the same shape — boxes connected by arrows — for four steps. That shape has a name: a **graph**. Here's the diagram from step 4 with the date-specific labels stripped away: ``` ┌──────────┐ ┌────────────────────────┐ │ Node 1 │───Edge 1─────▶ │ └──────────┘ │ │ │ │ │ │ ┌─────────┐ │ Node 3 │──Edge 3───▶│ Node 4 │ │ │ └─────────┘ │ │ ┌──────────┐ │ │ │ Node 2 │───Edge 2─────▶ │ └──────────┘ └────────────────────────┘ ``` The graph has 4 **nodes** and 3 **edges**. * A **node** is a step — a process box. * An **edge** is a connection between two nodes — an arrow. Nodes are *places*; edges are *one-way streets*. Nodes describe *what* happens; edges describe *which step feeds which*. In FlowDrop, edges carry a **category** that captures the trigger-versus-data distinction from step 2: a `data` edge passes a value from one node's output to another's input, while a `trigger` edge just fires the next node. (There are two more categories, `tool` and `loopback`, for agent and loop workflows.) See [Edge JSON](/guides/edge-json) for the full model. A FlowDrop workflow is exactly this: a graph of nodes connected by edges. ## 6. Ports: typed connection points Edges don't attach to nodes anywhere — they attach at specific spots called **ports**. * **Input ports** sit on the left of a node. They're where incoming edges land. * **Output ports** sit on the right. They're where outgoing edges leave. ``` ┌───────────────────────────────────┐ ├──────────────┐ │ ───Edge 1─────▶│ Input Port 1 │ │ ├──────────────┘ │ │ │ │ Node 3 │ │ │ ├──────────────┐ ┌──────────────┤ ────Edge 2────▶│ Input Port 2 │ │Output Port 2 ├───Edge 3───▶ ├──────────────┘ └──────────────┤ └───────────────────────────────────┘ ``` Why name ports separately from edges? Because ports are **typed**: a port that expects a number won't accept an edge carrying text. The type system catches mistakes when you build the workflow rather than when it runs. In the editor, ports are color-coded by data type — string, number, boolean, date, file, and so on. See the [Port system](/guides/port-system) for how data types and compatibility rules work. ## 7. Static configuration on a node We hinted at this in step 4: not every value needs to flow in through an edge. Some values are decided once when you build the workflow and stay put. Those are stored as **configuration** directly on the node. ``` ┌────────────────────────┐ ───Edge 1─────▶ Node 3 │ │ │ │ │ │ │ │ ┌──────────────┐ │──Edge 3───▶ │ │ TimeZone │ │ │ ├──────────────┤ │ │ │ Format │ │ ───Edge 2─────▶ └──────────────┘ │ └────────────────────────┘ ``` The inner boxes you saw all along — `TimeZone`, `Format` — are the node's configuration. The distinction matters: * **Ports** carry **dynamic** values. They're computed every time the workflow runs, by upstream nodes. * **Configuration** holds **static** values. You set them once in the editor; they don't change between runs. When you design a workflow, deciding what should be a port versus what should be configuration shapes how reusable the node is. A `TimeZone` configured on the node fixes that workflow to one zone. The same `TimeZone` exposed as a port lets each run pick its own. In FlowDrop, a node's configuration is defined by a [JSON Schema](/guides/config-schema), which the editor renders as a form when you click the node. ## Recap | Term | Meaning | | ----------------- | --------------------------------------------------------- | | **Process** | A step that turns inputs into outputs | | **Graph** | A set of processes connected by arrows | | **Node** | A single step in the graph | | **Edge** | A one-way connection between two nodes | | **Port** | A typed connection point on a node where an edge attaches | | **Configuration** | Static values set on a node when the workflow is built | ## Next steps * [What is a workflow?](/concepts/what-is-a-workflow) — the four primitives, stated formally, plus what FlowDrop does and doesn't do. * [Architecture overview](/concepts/architecture-overview) — how the pieces fit together. * Build something: [Embedding the editor](/tutorial/01-embedding-the-editor). # What is a workflow? Source: https://flowdrop.mintlify.app/concepts/what-is-a-workflow Understand the core mental model behind FlowDrop before writing any code. FlowDrop is a **visual workflow editor** — a UI component that lets users build directed graphs of processing steps by dragging, connecting, and configuring nodes on a canvas. Before diving into code, it's important to understand the mental model. ## The four primitives Every workflow in FlowDrop is built from four core concepts: ### Nodes A **node** is a single step in the workflow. It represents an action, decision, or data transformation — for example, "Send an HTTP request", "Run an LLM prompt", or "Route based on condition". Each node has: * A **type** that determines its visual appearance (default, simple, square, tool, gateway, terminal, idea, note) * **Metadata** describing its name, icon, category, and capabilities * **Configuration** — user-editable settings defined by a JSON Schema ### Edges An **edge** is a connection between two nodes. It defines the flow of data or control from one step to the next. Edges have **categories** that determine their visual style and semantic meaning: * **data** — standard data flow (default) * **trigger** — event-based activation * **tool** — tool invocation from an agent * **loopback** — feedback loops ### Ports A **port** is a typed connection point on a node. Nodes have **input ports** (receiving data) and **output ports** (sending data). Ports represent the **runtime data** that flows between nodes during execution — the actual payloads that one node passes to the next. Each port has a **data type** (e.g., `string`, `json`, `file`, `trigger`). FlowDrop enforces **type-safe connections** — you can only connect ports with compatible data types. For example, an LLM node might have: * An input port of type `string` that receives the user's prompt * An output port of type `json` that emits the model's response Ports are **unique to each connection** — every edge carries its own data between a specific pair of nodes. ### Config **Configuration** is different from ports. While ports carry runtime data that varies with each execution, config holds **shared settings** that apply consistently across all runs of the workflow. Config values are defined by a [JSON Schema](/guides/config-schema) and edited through a form UI when users click on a node. They control *how* a node behaves rather than *what* data it processes. For example, the same LLM node might have config fields for: * **Model name** — which LLM to use * **Temperature** — how creative the responses should be * **Max tokens** — the response length limit These values don't change from request to request — they're decisions the workflow author makes once, and every execution of the workflow uses them. **Ports vs. config — a quick rule of thumb.** **Ports** = "What data flows through at runtime?" (dynamic, per-execution) **Config** = "How is this node set up?" (static, set once by the workflow author) ## How it all fits together **Ports carry runtime data; config holds settings.** Each node has typed **ports** for runtime data and **config** for workflow-level settings. An **edge** connects an output port to an input port, defining data flow. Config values stay the same across executions; port data changes every time. A workflow is a **graph**: nodes are the vertices, edges are the connections, ports define where connections attach, and config controls what each node does. ## What FlowDrop does (and doesn't do) FlowDrop is a **frontend editor**. It handles: * Visual canvas with drag-and-drop, zoom, pan * Node palette and sidebar for discovery * Connection drawing with type-safe port validation * Configuration forms generated from JSON Schema * Workflow serialization to JSON * Undo/redo, auto-save drafts, import/export FlowDrop does **not** handle: * **Execution** — It doesn't run your workflows. You need your own backend execution engine. * **Storage** — It calls your REST API to persist workflows. You provide the database. * **Business logic** — Node behavior is defined by your backend, not by FlowDrop. Think of it this way: > **FlowDrop owns the UI. You own the logic.** FlowDrop gives users a beautiful way to *design* workflows. Your backend gives those workflows *meaning*. ## The frontend–backend contract FlowDrop communicates with your backend through a REST API. The contract is simple: 1. **Your backend tells FlowDrop what nodes exist** — by serving node metadata (name, ports, config schema) 2. **Users build workflows visually** — FlowDrop handles all the UI 3. **FlowDrop sends the workflow JSON to your backend** — for storage and execution ``` ┌──────────────┐ REST ┌──────────────┐ │ FlowDrop │───API────▶│ Your Backend │ └──────────────┘ └──────────────┘ ``` For a detailed breakdown of this architecture, see [Architecture Overview](/concepts/architecture-overview). ## When to use FlowDrop FlowDrop is a good fit when you need: * A **visual, no-code interface** for building multi-step processes * **AI agent workflows** with branching, tool use, and human-in-the-loop * **Data pipelines** with configurable transformations * **Automation builders** where non-technical users define business logic * **Any application** where users need to compose processing steps visually ## Next steps * [Architecture Overview](/concepts/architecture-overview) — how all the pieces fit together * [Installation](/docs/quickstart) — get FlowDrop into your project * [Tutorial](/tutorial/01-embedding-the-editor) — build your first workflow editor step by step # Swappable themes Source: https://flowdrop.mintlify.app/docs/customization/swappable-themes FlowDrop's look and feel is fully themeable through CSS custom properties — swap palettes, spacing, and radius without touching component code. FlowDrop is designed to blend into your product, not the other way around. Every surface, color, border, and spacing value is driven by semantic CSS custom properties (`--fd-*`), so you can re-skin the entire editor — nodes, sidebar, toolbar, playground, and dialogs — by overriding a handful of tokens. ## Three themes, one editor FlowDrop ships with three themes out of the box. Each is just a set of `--fd-*` token overrides — the same editor, re-skinned. Launch any of them live in the demo: Full chrome and node palette — the standard FlowDrop feel. Quieter chrome for embedding inside an existing UI. A sketch-like, low-fidelity whiteboard skin. ## How it works Themes are just sets of token overrides. Because every component reads from the same `--fd-*` semantic tokens, changing a token cascades everywhere at once: ```css theme={null} :root { --fd-primary: #8b5cf6; --fd-primary-hover: #7c3aed; --fd-radius-md: 0.75rem; } ``` Swap themes at runtime by toggling a class or `data-` attribute on a wrapping element and scoping your token overrides to it: ```css theme={null} [data-fd-theme='violet'] { --fd-primary: #8b5cf6; --fd-primary-hover: #7c3aed; --fd-accent: #8b5cf6; } ``` Dark mode works the same way — every semantic token has a dark-mode equivalent that activates automatically via `data-theme="dark"`. ## Go deeper Full token reference — surfaces, borders, status colors, spacing, radius, typography, node layout, and ready-to-use theme examples. # Drupal Source: https://flowdrop.mintlify.app/docs/framework/drupal Get FlowDrop on Drupal — the module ships the editor and the backend together. Drupal is the odd one out here, in the best way: you don't install the npm package or wire up a backend. The **Drupal module is a complete FlowDrop backend** and ships the editor with it. Install the module, and you have both the editor and a working API in one step. The canonical Drupal docs cover Composer install, enabling the module, and permissions — the source of truth for setup. ## What you get Unlike the JS frameworks — where you mount the editor and bring your own backend — the Drupal module already serves node definitions, stores workflows as config entities, runs executions, and handles auth through standard Drupal permissions. **No backend to wire up.** Because the backend is built in, there's no `endpointConfig` to set up and no "connect a backend" step. Enable the module and the editor is live. ## Next steps How the module maps to FlowDrop concepts, plus execution modes and triggers. The mental model behind the editor. # React Source: https://flowdrop.mintlify.app/docs/framework/react Mount a FlowDrop editor into a React component. In React, mount FlowDrop into a container ref inside `useEffect`, and destroy it on cleanup. No Svelte knowledge required. ## 1. Install ```bash npm theme={null} npm install @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash pnpm theme={null} pnpm add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash yarn theme={null} yarn add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` **Your bundler needs the Svelte plugin.** FlowDrop ships its UI as Svelte 5 components, so your bundler must compile them (you won't write any Svelte). With Vite, add `@sveltejs/vite-plugin-svelte` and `svelte` as dev dependencies and enable the plugin alongside the React plugin — see the **Bundler setup** section of the [quick start](/docs/quickstart) for the full config. ## 2. Mount the editor ```jsx theme={null} import { useEffect, useRef } from 'react'; import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; import { createEndpointConfig } from '@flowdrop/flowdrop/core'; import '@flowdrop/flowdrop/styles'; export function FlowDropEditor() { const containerRef = useRef(null); useEffect(() => { let app; mountFlowDropApp(containerRef.current, { endpointConfig: createEndpointConfig('/api/flowdrop'), eventHandlers: { onAfterSave: async (workflow) => console.log('Saved:', workflow.id), }, }).then((instance) => { app = instance; }); return () => app?.destroy(); }, []); return
; } ``` **Mount in `useEffect`, destroy on cleanup.** Mount after the DOM exists, not during render. Always call `app.destroy()` in the cleanup function — under React Strict Mode effects run twice in development, and skipping cleanup leaves a dangling editor. **FlowDrop needs a backend.** It serves nodes and stores workflows. Point `endpointConfig` at your API, or [run a ready-made server](/server-implementations/overview). ## Next steps The mental model behind the editor. The REST endpoints FlowDrop calls. # Svelte Source: https://flowdrop.mintlify.app/docs/framework/svelte Use FlowDrop as a native Svelte 5 component. FlowDrop is built with Svelte 5, so in a Svelte app you use its components directly — no mount API needed. ## 1. Install ```bash npm theme={null} npm install @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash pnpm theme={null} pnpm add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash yarn theme={null} yarn add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ## 2. Drop in the editor ```svelte theme={null} console.log('Saved:', workflow.id)} /> ``` ## SvelteKit FlowDrop is browser-only. In SvelteKit, guard the editor so it never renders during SSR: ```svelte theme={null} {#if browser} {/if} ``` **FlowDrop needs a backend.** It serves nodes and stores workflows. Point `endpointConfig` at your API, or [run a ready-made server](/server-implementations/overview). ## Next steps The mental model behind the editor. The REST endpoints FlowDrop calls. # Vanilla JS Source: https://flowdrop.mintlify.app/docs/framework/vanilla Drop a FlowDrop editor into any page — no framework required. With no framework, the mount API is all you need: give FlowDrop an element and an endpoint, and it takes over the rest. ## 1. Install ```bash npm theme={null} npm install @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash pnpm theme={null} pnpm add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash yarn theme={null} yarn add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` **Your bundler needs the Svelte plugin.** FlowDrop ships its UI as Svelte 5 components, so your bundler must compile them (you won't write any Svelte). With Vite, add `@sveltejs/vite-plugin-svelte` and `svelte` as dev dependencies and enable the plugin — see the **Bundler setup** section of the [quick start](/docs/quickstart) for the full config. ## 2. Mount the editor ```html theme={null}
``` The returned `app` handle lets you drive the editor programmatically — `app.save()`, `app.getWorkflow()`, `app.isDirty()`, and `app.destroy()` when you remove it from the page. **FlowDrop needs a backend.** It serves nodes and stores workflows. Point `endpointConfig` at your API, or [run a ready-made server](/server-implementations/overview). ## Next steps The mental model behind the editor. Every option and method on the mount handle. # Vue Source: https://flowdrop.mintlify.app/docs/framework/vue Mount a FlowDrop editor into a Vue 3 component. In Vue 3, mount FlowDrop on `onMounted` using a template ref, and tear it down on `onBeforeUnmount`. No Svelte knowledge required. ## 1. Install ```bash npm theme={null} npm install @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash pnpm theme={null} pnpm add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash yarn theme={null} yarn add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` **Your bundler needs the Svelte plugin.** FlowDrop ships its UI as Svelte 5 components, so your bundler must compile them (you won't write any Svelte). With Vite, add `@sveltejs/vite-plugin-svelte` and `svelte` as dev dependencies and enable the plugin alongside the Vue plugin — see the **Bundler setup** section of the [quick start](/docs/quickstart) for the full config. ## 2. Mount the editor ```vue theme={null} ``` **FlowDrop needs a backend.** It serves nodes and stores workflows. Point `endpointConfig` at your API, or [run a ready-made server](/server-implementations/overview). ## Next steps The mental model behind the editor. The REST endpoints FlowDrop calls. # Quick start Source: https://flowdrop.mintlify.app/docs/quickstart Get a live FlowDrop editor running in your app in minutes — Svelte, React, Vue, vanilla JS, or Drupal. You're three steps from a working workflow editor: install the package, point it at a backend, and mount it. Pick your framework below for copy‑paste setup, or follow the universal path underneath. ## Choose your framework Use FlowDrop as a native Svelte 5 component. Mount into a `useEffect` with a container ref. Mount on `onMounted`, tear down on unmount. Drop into any element — no framework needed. Install the module; the editor ships with it. ## 1. Install ```bash npm theme={null} npm install @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash pnpm theme={null} pnpm add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash yarn theme={null} yarn add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` ```bash bun theme={null} bun add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte ``` **No Svelte required in your app.** FlowDrop is built with Svelte 5 internally, but you don't need to write Svelte to use it. The mount API works in React, Vue, vanilla JS, or any framework. ## 2. Mount the editor This is the universal path — it works anywhere. Drop the editor into any element and point it at your backend: ```javascript theme={null} import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; import { createEndpointConfig } from '@flowdrop/flowdrop/core'; import '@flowdrop/flowdrop/styles'; const app = await mountFlowDropApp(document.getElementById('editor'), { endpointConfig: createEndpointConfig('/api/flowdrop'), }); ``` ```html theme={null}
``` That's the happy path — you now have a live editor wired to your backend. For framework‑idiomatic setup (Svelte components, React refs, Vue lifecycle), use the tiles above. ## 3. Connect a backend **FlowDrop needs a backend.** As a **frontend editor**, it relies on a backend to serve node definitions, store workflows, and run executions. Until one is connected, the canvas loads but has no nodes to place. You have two ways to get there: Skip the backend work — run a server that already speaks the FlowDrop API, like the Drupal module. Implement the REST contract on your own stack — the guide lists every endpoint FlowDrop calls. ## Going further The happy path above is all most apps need. Expand the sections below when you hit a specific case. Install these alongside `@flowdrop/flowdrop`: | Package | Version | Required | | ----------------- | -------- | ----------------------- | | `svelte` | `^5.0.0` | Yes (internal runtime) | | `@xyflow/svelte` | `^1.2` | Yes (for editor module) | | `@iconify/svelte` | `^5.0.0` | Yes (for icons) | Import from the most specific entry point you need — the main entry is a slim front door, so form fields, display components, and the playground live in their own sub‑modules. | Entry point | Description | | ---------------------------------- | ---------------------------------------------------------------------------------------- | | `@flowdrop/flowdrop` | Bootstrap front door — `App`, mount functions, instances, config helpers, auth providers | | `@flowdrop/flowdrop/core` | Types and utilities (zero heavy dependencies) | | `@flowdrop/flowdrop/editor` | Visual workflow editor components | | `@flowdrop/flowdrop/form` | Dynamic form field components | | `@flowdrop/flowdrop/form/code` | Code and JSON editor fields (requires CodeMirror) | | `@flowdrop/flowdrop/form/markdown` | Markdown editor field (requires CodeMirror) | | `@flowdrop/flowdrop/form/full` | All form components pre-bundled | | `@flowdrop/flowdrop/display` | Display-only components (MarkdownDisplay) | | `@flowdrop/flowdrop/playground` | Interactive workflow playground | | `@flowdrop/flowdrop/settings` | Settings UI components and stores | | `@flowdrop/flowdrop/styles` | Base CSS styles and design tokens | Only needed if you use the `form/code` or `form/markdown` entry points: ```bash theme={null} npm install codemirror @codemirror/state @codemirror/view @codemirror/commands \ @codemirror/language @codemirror/theme-one-dark @codemirror/autocomplete \ @codemirror/lang-json @codemirror/lang-markdown @codemirror/lint ``` Multiple FlowDrop editors can run on the same page. Each mount gets its own isolated `FlowDropInstance` (workflow, history, playground, and panel state). When mounting more than one editor with drafts enabled, pass an `instanceId` so their stored drafts don't collide. See the [multiple instances guide](/guides/multiple-instances). FlowDrop ships its UI as Svelte 5 components, so a bundler needs the Svelte plugin to compile them — even in React, Vue, or vanilla-JS apps. You still don't write any Svelte yourself. With Vite: ```bash theme={null} npm install -D @sveltejs/vite-plugin-svelte svelte ``` ```javascript theme={null} // vite.config.js import { defineConfig } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; export default defineConfig({ plugins: [svelte()], // The mountFlowDropApp examples use top-level await. build: { target: 'es2022' } }); ``` **SvelteKit** already configures the Svelte plugin — no extra setup needed. Still seeing `Failed to resolve import`? Exclude FlowDrop from Vite's dependency pre-bundling: ```javascript theme={null} optimizeDeps: { exclude: ['@flowdrop/flowdrop', '@xyflow/svelte'] } ``` Confirm the package resolved correctly: ```javascript theme={null} import { createEndpointConfig } from '@flowdrop/flowdrop/core'; const config = createEndpointConfig('/api/flowdrop'); console.log(config); // EndpointConfig object ``` If you see `Cannot find module '@flowdrop/flowdrop'`, check that the install finished without errors, that your bundler supports ES modules (Vite 5+, webpack 5+), and that you aren't importing in a server-side context. * **Node.js** v20 or later. * A bundler that handles ES modules (Vite, webpack, esbuild, Rollup, Next.js, Nuxt, SvelteKit, or similar). * **Svelte 5 internally** — FlowDrop uses Svelte 5 runes under the hood; your app does not need to be written in Svelte. * **Modern browsers only** — targets ES2020+ with no bundled polyfills. # Agent Spec integration Source: https://flowdrop.mintlify.app/guides/advanced/agent-spec Import and export workflows using Agent Spec — an open standard for AI agent definitions, originally published by Oracle. FlowDrop supports [Agent Spec](https://github.com/oracle/agent-spec) — an open standard for defining AI agent workflows, originally published by Oracle as a vendor-neutral specification. You can import Agent Spec documents into FlowDrop for visual editing and export FlowDrop workflows back to Agent Spec format. ## What is Agent Spec? Agent Spec is a JSON format for describing agent workflows with: * **Nodes** (called "steps") with component types * **Control-flow edges** for execution order * **Data-flow edges** for data passing * **Agent-level metadata** (name, description, version) ## Import / export via UI FlowDrop's toolbar includes import/export options: * **Import**: Click the import button and select an Agent Spec JSON file. FlowDrop converts it to a visual workflow with auto-layout. * **Export**: Click the export button and choose "Agent Spec" format. FlowDrop converts the visual workflow to Agent Spec JSON and downloads it. ## Programmatic usage ### Using the adapter directly ```typescript theme={null} import { AgentSpecAdapter, WorkflowAdapter } from '@flowdrop/flowdrop/core'; const workflowAdapter = new WorkflowAdapter(nodeTypes); const agentSpecAdapter = new AgentSpecAdapter(); // Import: Agent Spec JSON → FlowDrop Workflow const standardWorkflow = agentSpecAdapter.importJSON(agentSpecJsonString); const editorWorkflow = workflowAdapter.toSvelteFlow(standardWorkflow); // Export: FlowDrop Workflow → Agent Spec JSON const standardWorkflow = workflowAdapter.fromSvelteFlow(editorWorkflow); const agentSpecJson = agentSpecAdapter.exportJSON(standardWorkflow); ``` ### Using the WorkflowOperationsHelper ```typescript theme={null} import { WorkflowOperationsHelper } from '@flowdrop/flowdrop/editor'; // Export current workflow as Agent Spec const result = WorkflowOperationsHelper.exportAsAgentSpec(workflow); if (result.valid) { // Downloads .json file automatically } else { console.error('Export errors:', result.errors); console.warn('Warnings:', result.warnings); } // Import from a File object const imported = await WorkflowOperationsHelper.importFromAgentSpec(file); ``` ### Using the Mount API The mounted app exposes Agent Spec operations: ```typescript theme={null} const app = await mountFlowDropApp(container, options); // Export app.export(); // downloads JSON // Get the current workflow const workflow = app.getWorkflow(); ``` ## Validation Validate a workflow before exporting to Agent Spec: ```typescript theme={null} import { validateForAgentSpecExport } from '@flowdrop/flowdrop/core'; const result = validateForAgentSpecExport(workflow); // { valid: boolean, errors: string[], warnings: string[] } ``` Common validation issues: * Disconnected nodes (no edges) * Missing required port connections * Unsupported node types ## Conversion details ### What maps cleanly | FlowDrop | Agent Spec | | ---------------- | -------------------------- | | Node ID | Auto-generated stable name | | Node type | Component type | | Config values | Node attributes | | Trigger edges | Control-flow edges | | Data edges | Data-flow edges | | Gateway branches | `from_branch` mappings | | Node position | Preserved in metadata | ### Known limitations * **Loopback edges** don't have a direct Agent Spec equivalent and may be dropped * **Custom node types** (namespaced) may lose type-specific behavior * **UISchema** layout information is not preserved * **Dynamic ports** may not convert cleanly * **Node visual types** (simple, square, etc.) are stored in metadata but not in the Agent Spec standard ## Execution events When running Agent Spec workflows, FlowDrop fires execution events: ```typescript theme={null} eventHandlers: { onAgentSpecExecutionStarted: (executionId) => { console.log('Execution started:', executionId); }, onAgentSpecNodeStatusUpdate: (nodeId, status) => { console.log(`Node ${nodeId}: ${status.status}`); }, onAgentSpecExecutionCompleted: (executionId, results) => { console.log('Completed:', results); }, onAgentSpecExecutionFailed: (executionId, error) => { console.error('Failed:', error.message); } } ``` See [Event System](/guides/advanced/event-system) for the full event reference. ## Next steps * [Programmatic API](/guides/advanced/programmatic-api) — create workflows in code * [Event System](/guides/advanced/event-system) — all execution events * [Workflow Structure](/guides/workflow-json) — FlowDrop's native JSON format # Event system Source: https://flowdrop.mintlify.app/guides/advanced/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: ```typescript theme={null} 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. ```typescript theme={null} 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** ```typescript theme={null} 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. ```typescript theme={null} onWorkflowLoad?: (workflow: Workflow) => void; ``` **Example: Set up external state** ```typescript theme={null} onWorkflowLoad: (workflow) => { document.title = `${workflow.name} - Editor`; breadcrumb.update(workflow.name); }; ``` ### `onDirtyStateChange` Called when the workflow transitions between saved and unsaved states. ```typescript theme={null} onDirtyStateChange?: (isDirty: boolean) => void; ``` **Example: Unsaved changes indicator** ```typescript theme={null} 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.** ```typescript theme={null} onBeforeSave?: (workflow: Workflow) => Promise; ``` **Example: Confirm before saving** ```typescript theme={null} 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. ```typescript theme={null} onAfterSave?: (workflow: Workflow) => Promise; ``` **Example: Show success notification** ```typescript theme={null} onAfterSave: async (workflow) => { showNotification(`Saved "${workflow.name}" successfully`); }; ``` ### `onSaveError` Called when a save operation fails. ```typescript theme={null} onSaveError?: (error: Error, workflow: Workflow) => Promise; ``` **Example: Report errors** ```typescript theme={null} 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. ```typescript theme={null} onApiError?: (error: Error, operation: string) => boolean | void; ``` The `operation` parameter describes what failed: `"save"`, `"load"`, `"fetchNodes"`, `"fetchCategories"`, etc. **Example: Custom error handling** ```typescript theme={null} 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. ```typescript theme={null} onBeforeUnmount?: (workflow: Workflow, isDirty: boolean) => void; ``` **Example: Warn about unsaved changes** ```typescript theme={null} onBeforeUnmount: (workflow, isDirty) => { if (isDirty) { console.warn('Unmounting with unsaved changes'); } }; ``` ## Agent Spec execution events These events fire during [Agent Spec](/guides/advanced/agent-spec) workflow execution. ### `onAgentSpecExecutionStarted` Called when an Agent Spec execution begins. ```typescript theme={null} onAgentSpecExecutionStarted?: (executionId: string) => void; ``` ### `onAgentSpecExecutionCompleted` Called when execution completes successfully. ```typescript theme={null} onAgentSpecExecutionCompleted?: ( executionId: string, results: Record ) => void; ``` ### `onAgentSpecExecutionFailed` Called when execution fails. ```typescript theme={null} onAgentSpecExecutionFailed?: (executionId: string, error: Error) => void; ``` ### `onAgentSpecNodeStatusUpdate` Called when a node's execution status changes during a run. ```typescript theme={null} onAgentSpecNodeStatusUpdate?: (nodeId: string, status: NodeExecutionInfo) => void; ``` **Example: Track execution progress** ```typescript theme={null} 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: ```typescript theme={null} 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](/reference/types#event-handlers). # Programmatic API Source: https://flowdrop.mintlify.app/guides/advanced/programmatic-api Create and manipulate workflows in code using WorkflowAdapter and helpers. While FlowDrop is primarily a visual editor, you may need to create or modify workflows programmatically — for testing, migration, code generation, or batch operations. ## WorkflowAdapter The `WorkflowAdapter` provides a high-level API for workflow manipulation using a `StandardWorkflow` format. ```typescript theme={null} import { WorkflowAdapter } from '@flowdrop/flowdrop/core'; // Initialize with your node type definitions const adapter = new WorkflowAdapter(nodeTypes); ``` ### Creating workflows ```typescript theme={null} // Create a new workflow const workflow = adapter.createWorkflow('My Pipeline', 'Processes user input'); // Add nodes const inputNode = adapter.addNode( workflow, 'text_input', // node type ID { x: 100, y: 200 }, // position { placeholder: 'Enter text...' } // initial config ); const modelNode = adapter.addNode( workflow, 'chat_model', { x: 400, y: 200 }, { model: 'gpt-4', temperature: 0.7 } ); const outputNode = adapter.addNode(workflow, 'text_output', { x: 700, y: 200 }); // Connect nodes adapter.addEdge(workflow, inputNode.id, modelNode.id, 'output', 'prompt'); adapter.addEdge(workflow, modelNode.id, outputNode.id, 'response', 'input'); ``` ### Querying workflows ```typescript theme={null} // Find nodes by type const models = adapter.getNodesByType(workflow, 'chat_model'); // Get edges connected to a node const edges = adapter.getNodeEdges(workflow, inputNode.id); // Get adjacent nodes const connected = adapter.getConnectedNodes(workflow, modelNode.id); // Get statistics const stats = adapter.getWorkflowStats(workflow); // { totalNodes: 3, totalEdges: 2, nodeTypeCounts: { text_input: 1, ... }, lastModified: '...' } ``` ### Modifying workflows ```typescript theme={null} // Update node position adapter.updateNodePosition(workflow, inputNode.id, { x: 150, y: 250 }); // Update node configuration adapter.updateNodeConfig(workflow, modelNode.id, { temperature: 0.9 }); // Remove a node (also removes connected edges) adapter.removeNode(workflow, outputNode.id); // Remove an edge adapter.removeEdge(workflow, 'edge-id'); ``` ### Import/export ```typescript theme={null} // Export to JSON string const json = adapter.exportWorkflow(workflow); // Import from JSON const imported = adapter.importWorkflow(json); // Clone with new IDs const clone = adapter.cloneWorkflow(workflow, 'Copy of My Pipeline'); ``` ### Validation ```typescript theme={null} const result = adapter.validateWorkflow(workflow); // { valid: boolean, errors: string[], warnings: string[] } if (!result.valid) { console.error('Validation errors:', result.errors); } ``` ### Format conversion Convert between the adapter's `StandardWorkflow` format and FlowDrop's internal editor format: ```typescript theme={null} // Editor format → Standard format const standard = adapter.fromSvelteFlow(editorWorkflow); // Standard format → Editor format const editorFormat = adapter.toSvelteFlow(standard); ``` ## Helper classes FlowDrop's editor uses helper classes internally. These are available for advanced integrations. ### EdgeStylingHelper Determines edge visual styles based on port data types: ```typescript theme={null} import { EdgeStylingHelper } from '@flowdrop/flowdrop/editor'; // Get edge category from source port type const category = EdgeStylingHelper.getEdgeCategory('trigger'); // 'trigger' const category = EdgeStylingHelper.getEdgeCategory('string'); // 'data' const category = EdgeStylingHelper.getEdgeCategory('tool'); // 'tool' // Update all edge styles in a workflow const styledEdges = EdgeStylingHelper.updateEdgeStyles(edges, nodes); ``` Edge categories and their visual styles: | Category | Port type | Visual style | | ---------- | ------------------------ | ----------------- | | `trigger` | `trigger` data type | Solid dark line | | `tool` | `tool` data type | Dashed amber line | | `loopback` | Targets `loop_back` port | Dashed gray line | | `data` | Everything else | Gray line | ### NodeOperationsHelper Creates nodes from drag-and-drop data and loads node metadata: ```typescript theme={null} import { NodeOperationsHelper } from '@flowdrop/flowdrop/editor'; // Create a node from sidebar drop data const node = NodeOperationsHelper.createNodeFromDrop( dropDataString, // JSON string from drag event { x: 300, y: 200 }, // canvas position existingNodes // for generating unique IDs ); // Load node definitions from API const nodes = await NodeOperationsHelper.loadNodesFromApi(); ``` ### WorkflowOperationsHelper Workflow-level operations including save, export, and validation: ```typescript theme={null} import { WorkflowOperationsHelper } from '@flowdrop/flowdrop/editor'; // Save to backend const saved = await WorkflowOperationsHelper.saveWorkflow(workflow); // Export as JSON download WorkflowOperationsHelper.exportWorkflow(workflow); // Export as Agent Spec format const result = WorkflowOperationsHelper.exportAsAgentSpec(workflow); // { valid: boolean, errors: string[], warnings: string[] } // Import from Agent Spec file const imported = await WorkflowOperationsHelper.importFromAgentSpec(file); // Check for cycles const hasCycles = WorkflowOperationsHelper.checkWorkflowCycles(nodes, edges); ``` ## Use cases ### Generate workflows from templates ```typescript theme={null} function createFromTemplate(template: string, params: Record) { const adapter = new WorkflowAdapter(nodeTypes); const workflow = adapter.createWorkflow(`${template} Pipeline`); if (template === 'chat') { const input = adapter.addNode(workflow, 'text_input', { x: 100, y: 200 }); const model = adapter.addNode( workflow, 'chat_model', { x: 400, y: 200 }, { model: params.model || 'gpt-4' } ); const output = adapter.addNode(workflow, 'text_output', { x: 700, y: 200 }); adapter.addEdge(workflow, input.id, model.id, 'output', 'prompt'); adapter.addEdge(workflow, model.id, output.id, 'response', 'input'); } return adapter.toSvelteFlow(workflow); } ``` ### Batch configuration updates ```typescript theme={null} function updateAllModels(workflow: StandardWorkflow, newModel: string) { const adapter = new WorkflowAdapter(nodeTypes); const models = adapter.getNodesByType(workflow, 'chat_model'); for (const node of models) { adapter.updateNodeConfig(workflow, node.id, { model: newModel }); } return workflow; } ``` # Store system Source: https://flowdrop.mintlify.app/guides/advanced/store-system Understand FlowDrop's reactive state management for programmatic access. FlowDrop uses **Svelte 5 runes** for reactive state management. Each mounted editor owns an isolated `FlowDropInstance` container holding its stores. While most developers won't need to interact with stores directly, they're essential for advanced integrations. ## Reaching a store There are **no module-level store functions** — instances are the API. Reach a store one of two ways: * **`getInstance()` inside a FlowDrop component** returns the owning `FlowDropInstance`. For single-editor embeds it resolves the page-default instance automatically. * **The mount handle's `.instance`** (`const fd = (await mountFlowDropApp(...)).instance`) lets you reach a specific editor's stores from vanilla JS or another framework. You can also construct one with `createFlowDropInstance({ id })` from `@flowdrop/flowdrop/editor` and pass it as the `instance` prop. The instance exposes these members: | Member | Holds | | ------------------------------------- | -------------------------------------------- | | `fd.workflow` | Workflow state, nodes, edges, dirty tracking | | `fd.history` / `fd.historyBindings` | Undo/redo (service and reactive wrapper) | | `fd.playground` | Playground sessions and messages | | `fd.interrupts` | Human-in-the-loop interrupt state | | `fd.categories` | Node category definitions | | `fd.portCoordinates` | Port handle positions | | `fd.pipelinePanel` | Pipeline execution view state | | `fd.api` | API client | | `fd.nodes` / `fd.fields` | Node and form-field registries | | `fd.formats` / `fd.portCompatibility` | Workflow formats and port rules | See the [multiple instances guide](/guides/multiple-instances) for scoping multiple editors. ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); ``` ## Workflow store (`fd.workflow`) The central store holding the current workflow state. ### Reading state ```typescript theme={null} // Reactive getters (re-evaluate when state changes) const workflow = fd.workflow.current; // Workflow | null const isDirty = fd.workflow.isDirty; // boolean const nodes = fd.workflow.nodes; // WorkflowNode[] const edges = fd.workflow.edges; // WorkflowEdge[] const name = fd.workflow.name; // string const validation = fd.workflow.validation; // { hasNodes, hasEdges, nodeCount, edgeCount, isValid } ``` ### Modifying state ```typescript theme={null} const { actions } = fd.workflow; // Initialize with a loaded workflow actions.initialize(workflow); // Node operations actions.addNode(newNode); actions.removeNode('node-id'); actions.updateNode('node-id', { data: { ...updates } }); // Edge operations actions.addEdge(newEdge); actions.removeEdge('edge-id'); // Metadata actions.updateName('New Workflow Name'); actions.updateMetadata({ tags: ['production'] }); // Batch update (single history entry) actions.batchUpdate({ nodes: updatedNodes, edges: updatedEdges, name: 'Updated Name' }); // Clear everything actions.clear(); ``` ### Dirty state ```typescript theme={null} // Check dirty state if (fd.workflow.isDirty) { console.log('There are unsaved changes'); } // Clear dirty flag after saving fd.workflow.markAsSaved(); ``` ## History store (`fd.historyBindings`) Manages undo/redo with snapshot-based history. `fd.historyBindings` is the reactive rune wrapper around `fd.history` (the underlying `HistoryService`). ```typescript theme={null} // Check availability (reactive getters) const canUndo = fd.historyBindings.canUndo; // boolean const canRedo = fd.historyBindings.canRedo; // boolean // Perform undo/redo (bound actions — safe to detach) fd.historyBindings.undo(); // returns boolean (success) fd.historyBindings.redo(); // returns boolean (success) // Manual history management fd.historyBindings.pushState(workflow, { description: 'Bulk import' }); fd.historyBindings.clear(fd.workflow.current); // Transactions (group multiple changes into one undo step) fd.historyBindings.startTransaction(fd.workflow.current, 'Rearrange nodes'); // ... make multiple changes ... fd.historyBindings.commitTransaction(); // or: fd.historyBindings.cancelTransaction(); ``` ## Settings store User preferences for theme, editor behavior, and UI. Settings are **page-global by design** — they are not instance-scoped, so these remain module-level functions. ```typescript theme={null} import { getSettings, themeSettings, editorSettings, uiSettings, updateSettings, resetSettings, onSettingsChange } from '@flowdrop/flowdrop/settings'; // Read settings (reactive) const settings = getSettings(); // FlowDropSettings const theme = themeSettings(); // ThemeSettings const editor = editorSettings(); // EditorSettings const ui = uiSettings(); // UISettings // Update settings updateSettings({ theme: { preference: 'dark' }, editor: { snapToGrid: true, gridSize: 20 } }); // Reset to defaults resetSettings(); // reset all resetSettings(['theme']); // reset only theme // Subscribe to changes const unsubscribe = onSettingsChange((newSettings, oldSettings) => { console.log('Settings changed:', newSettings); }); // Later: unsubscribe(); ``` ### Theme control Theme is also page-global: ```typescript theme={null} import { theme, resolvedTheme, setTheme, toggleTheme, cycleTheme } from '@flowdrop/flowdrop/core'; theme(); // 'light' | 'dark' | 'auto' resolvedTheme(); // 'light' | 'dark' (actual applied theme) setTheme('dark'); toggleTheme(); // light ↔ dark cycleTheme(); // light → dark → auto → light ``` ## Playground store (`fd.playground`) Manages interactive testing sessions and messages. ```typescript theme={null} // Read state (reactive getters) const session = fd.playground.currentSession; // PlaygroundSession | null const sessions = fd.playground.sessions; // PlaygroundSession[] const messages = fd.playground.messages; // PlaygroundMessage[] const chatMsgs = fd.playground.chatMessages; // PlaygroundMessage[] (user/assistant only) const logMsgs = fd.playground.logMessages; // PlaygroundMessage[] (system only) const isRunning = fd.playground.isExecuting; // boolean ``` ## Interrupt store (`fd.interrupts`) Manages human-in-the-loop interrupt state. ```typescript theme={null} // Read state const pending = fd.interrupts.getPending(); // InterruptWithState[] const resolved = fd.interrupts.getResolved(); // InterruptWithState[] const pendingCount = fd.interrupts.getPendingCount(); // number ``` ## Using stores in Svelte components Since stores use Svelte 5 runes, read their getters inside `$derived`: ```svelte theme={null}

Nodes: {nodes.length}

Unsaved: {isDirty ? 'Yes' : 'No'}

``` ## Using stores outside Svelte For vanilla JS or other frameworks, hold the mount handle's `.instance` and read its getters at call time: ```typescript theme={null} const fd = (await mountFlowDropApp(container, options)).instance; // Polling pattern setInterval(() => { const workflow = fd.workflow.current; const dirty = fd.workflow.isDirty; externalUI.update({ workflow, dirty }); }, 1000); ``` For event-driven updates, use the [event system](/guides/advanced/event-system) instead of polling: ```typescript theme={null} eventHandlers: { onWorkflowChange: (workflow) => externalUI.update(workflow), onDirtyStateChange: (isDirty) => externalUI.setDirty(isDirty) } ``` # Template variables Source: https://flowdrop.mintlify.app/guides/advanced/template-variables Use dynamic variables from upstream nodes in template editor fields. Template variables let users reference data from upstream nodes using `{{ variable }}` syntax. When a config field uses `format: "template"`, FlowDrop provides autocomplete for available variables. ## How it works 1. A node has a config field with `format: "template"` 2. FlowDrop analyzes the upstream nodes connected to the current node 3. Output port schemas from those nodes become available as template variables 4. Users type `{{` and get autocomplete suggestions A Text Input node connected to a Prompt Node, where the Text Input output port resolves as a template variable that the Prompt Node references in its prompt config field, with autocomplete suggesting the variable. **Output port schemas define the variables.** The upstream node's **output port schema** determines which variables are available. Downstream nodes with `format: "template"` fields get autocomplete for those variables. ## Configuring template fields In your node's `configSchema`, use `format: "template"` with a `variables` configuration: ```json theme={null} { "type": "object", "properties": { "prompt": { "type": "string", "title": "Prompt Template", "format": "template", "variables": { "ports": ["prompt_input"], "showHints": true } } } } ``` ### Variable sources The `variables` config supports three sources: #### 1. Port-derived variables Automatically derive variables from upstream node connections: ```json theme={null} { "variables": { "ports": ["input"], "includePortName": true, "showHints": true } } ``` * `ports` — which input port IDs to derive variables from * `includePortName` — prefix variables with the port name (default: false) * `showHints` — show clickable variable hints below the editor (default: true) #### 2. Static schema Define variables explicitly: ```json theme={null} { "variables": { "schema": { "variables": { "user": { "name": "user", "type": "object", "properties": { "name": { "name": "name", "type": "string" }, "email": { "name": "email", "type": "string" } } }, "timestamp": { "name": "timestamp", "type": "string" } } } } } ``` #### 3. API-fetched variables Fetch variables from an API endpoint at runtime: ```json theme={null} { "variables": { "api": { "endpoint": { "url": "/api/flowdrop/nodes/{nodeId}/variables?workflowId={workflowId}", "method": "GET" }, "cacheTtl": 300000, "mergeWithSchema": true, "fallbackOnError": true } } } ``` The URL supports `{workflowId}` and `{nodeId}` placeholders that resolve at runtime. ## Template syntax FlowDrop's template editor supports Jinja-like syntax: ### Variable access ``` {{ variable_name }} Simple variable {{ user.name }} Object property (dot notation) {{ items[0].title }} Array index access {{ items[*].title }} All items wildcard ``` ### Supported syntax ``` {{ ... }} Variable interpolation {% ... %} Block statements {# ... #} Comments ``` ## Autocomplete behavior The template editor provides intelligent autocomplete: * **`{{`** triggers top-level variable suggestions * **`.`** after a variable triggers property drill-down * **`[`** after an array variable triggers index suggestions (`[0]`, `[1]`, `[*]`) * Type icons show the variable type: `𝑆` string, `#` number, `☑` boolean, `[]` array, `{}` object ## Port schemas for variable resolution For port-derived variables to work, upstream nodes need output ports with `schema`: ```json theme={null} { "id": "data_processor", "outputs": [ { "id": "result", "name": "Result", "type": "output", "dataType": "json", "schema": { "type": "object", "properties": { "summary": { "type": "string" }, "score": { "type": "number" }, "tags": { "type": "array", "items": { "type": "string" } } } } } ] } ``` This makes `{{ result.summary }}`, `{{ result.score }}`, and `{{ result.tags[0] }}` available as template variables in downstream nodes. ## Registering the template editor The template editor requires CodeMirror. Register it before mounting: ```typescript theme={null} import { registerTemplateEditorField } from '@flowdrop/flowdrop/form/code'; registerTemplateEditorField(); ``` Without registration, template fields fall back to plain text inputs. ## Variable schema merging When multiple sources are configured (ports + schema + API), they merge with this precedence: 1. **API variables** (highest priority) 2. **Static schema variables** 3. **Port-derived variables** (lowest priority) Configure merging behavior: ```json theme={null} { "variables": { "ports": ["input"], "schema": { "variables": { "env": { "name": "env", "type": "string" } } }, "api": { "endpoint": { "url": "/api/variables/{nodeId}" }, "mergeWithSchema": true, "mergeWithPorts": true } } } ``` # Configuration forms Source: https://flowdrop.mintlify.app/guides/config-schema Auto-generate node configuration forms from JSON Schema. FlowDrop automatically generates configuration forms from JSON Schema definitions. This guide covers static schemas, dynamic runtime forms, and layout control. ## Overview FlowDrop provides three ways to define configuration forms for nodes: | Approach | When to use | | ------------------------------------------ | ------------------------------------------------- | | **Static `configSchema`** | Fields are known ahead of time | | **Dynamic `configEdit.dynamicSchema`** | Fields depend on external data or user selections | | **External `configEdit.externalEditLink`** | Configuration is managed by a 3rd-party system | All three approaches can be combined — FlowDrop tries the dynamic schema first, then falls back to the static schema if the fetch fails. ## Quick Start Define `configSchema` on your node metadata. FlowDrop auto-renders the form: ```json highlight={4-32} theme={null} { "id": "calculator", "name": "Calculator", "configSchema": { "type": "object", "properties": { "operation": { "type": "string", "title": "Operation", "description": "Mathematical operation to perform", "default": "add", "enum": [ "add", "subtract", "multiply", "divide", "power", "sqrt", "average", "min", "max", "median", "mode" ] }, "precision": { "type": "integer", "title": "Precision", "description": "Number of decimal places", "default": 2 } } }, ... } ``` Config panel showing a form auto-generated from a JSON Schema: text input, number slider, toggle, dropdown select, and multiline textarea fields. ## Field Types and Formats ### Basic Types | `type` | Renders as | | --------- | --------------------- | | `string` | Text input | | `number` | Number input | | `integer` | Integer input | | `boolean` | Toggle switch | | `array` | Repeatable field list | | `object` | Nested fieldset | ### Format Overrides Use `format` to change how a field renders: | Format | Renders as | Notes | | -------------- | ------------------------ | --------------------------------------- | | `multiline` | Textarea | Multi-line text input | | `hidden` | Nothing | Stored in config but not shown in UI | | `range` | Slider | Requires `minimum` and `maximum` | | `json` | CodeMirror editor | JSON syntax highlighting and validation | | `code` | CodeMirror editor | Alias for `json` | | `markdown` | Markdown editor | Toolbar and preview | | `template` | CodeMirror editor | `{{ variable }}` autocomplete | | `autocomplete` | Text input + suggestions | Fetches options from API | ### Example ```json theme={null} { "type": "object", "properties": { "prompt": { "type": "string", "title": "Prompt", "format": "multiline" }, "temperature": { "type": "number", "title": "Temperature", "format": "range", "minimum": 0, "maximum": 2, "default": 0.7 }, "metadata": { "type": "object", "title": "Metadata", "format": "json" }, "internalId": { "type": "string", "format": "hidden" } } } ``` ## Select Fields ### Simple Enum ```json theme={null} { "model": { "type": "string", "title": "Model", "enum": ["gpt-4o", "gpt-4o-mini", "claude-3"], "default": "gpt-4o-mini" } } ``` ### Labeled Options with `oneOf` ```json theme={null} { "status": { "type": "string", "title": "Status", "oneOf": [ { "const": "pending", "title": "Pending" }, { "const": "in_progress", "title": "In Progress" }, { "const": "completed", "title": "Completed" } ] } } ``` ### Multi-Select (Checkboxes) ```json theme={null} { "tags": { "type": "string", "title": "Tags", "enum": ["urgent", "review", "archive"], "multiple": true } } ``` ## Autocomplete Fields Fetch suggestions from a remote API as the user types: ```json theme={null} { "userId": { "type": "string", "title": "User", "format": "autocomplete", "autocomplete": { "url": "/api/users/search", "queryParam": "q", "minChars": 2, "debounceMs": 300, "labelField": "name", "valueField": "id", "allowFreeText": false, "fetchOnFocus": true } } } ``` ## Template Fields Template fields provide CodeMirror editing with `{{ variable }}` syntax highlighting and autocomplete from connected node outputs: ```json theme={null} { "prompt": { "type": "string", "title": "Prompt Template", "format": "template", "variables": { "ports": ["data", "context"], "showHints": true } } } ``` ## UISchema Layout By default, fields render in property order. Use `uiSchema` to control layout and grouping — inspired by [JSON Forms](https://jsonforms.io/): ```json theme={null} { "type": "VerticalLayout", "elements": [ { "type": "Control", "scope": "#/properties/name" }, { "type": "Control", "scope": "#/properties/model" }, { "type": "Group", "label": "Advanced Settings", "collapsible": true, "defaultOpen": false, "elements": [ { "type": "Control", "scope": "#/properties/temperature" }, { "type": "Control", "scope": "#/properties/maxTokens" } ] } ] } ``` ### Element Types | Type | Description | | ---------------- | ----------------------------------------------------------------------- | | `Control` | Renders a single form field. `scope` is a JSON Pointer to the property. | | `VerticalLayout` | Stacks child elements vertically. | | `Group` | Wraps elements in a collapsible fieldset with a label. | ## Special Config Properties Certain property names trigger automatic behaviors: | Property | Type | Behavior | | --------------------- | --------------- | ------------------------------------------------ | | `instanceTitle` | `string` | Overrides the node's displayed title | | `instanceDescription` | `string` | Overrides the node's displayed description | | `instanceBadge` | `string` | Overrides the node's badge | | `nodeType` | `string` | Switches visual rendering type | | `dynamicInputs` | `DynamicPort[]` | Creates user-defined input handles | | `dynamicOutputs` | `DynamicPort[]` | Creates user-defined output handles | | `branches` | `Branch[]` | Creates conditional output paths (gateway nodes) | ## Dynamic Schema (Runtime) Use `configEdit.dynamicSchema` to fetch config schemas from your backend at runtime: ```typescript theme={null} const myNode: NodeMetadata = { node_type_id: 'dynamic-processor', name: 'Dynamic Processor', configEdit: { dynamicSchema: { url: '/api/nodes/{nodeTypeId}/schema', method: 'GET', parameterMapping: { nodeTypeId: 'metadata.node_type_id' }, cacheSchema: true, timeout: 10000 }, showRefreshButton: true }, // Fallback static schema configSchema: { type: 'object', properties: { apiKey: { type: 'string', title: 'API Key' } } } }; ``` The dynamic schema endpoint should return a JSON Schema object. Multiple response shapes are accepted: * Direct schema: `{ type: "object", properties: {...} }` * Wrapped: `{ data: {...} }` or `{ schema: {...} }` * With UISchema: `{ configSchema: {...}, uiSchema: {...} }` ## External Edit Links For configuration managed by a 3rd-party system: ```typescript theme={null} configEdit: { externalEditLink: { url: 'https://admin.example.com/nodes/{nodeTypeId}/configure', label: 'Configure in Admin Portal', icon: 'mdi:open-in-new', parameterMapping: { nodeTypeId: 'metadata.node_type_id' }, openInNewTab: true } } ``` ## Standalone ConfigForm You can render the `ConfigForm` component independently in svelte projects: ```svelte theme={null} { values = config; }} onSave={(config) => { /* persist */ }} /> ``` # Creating workflows Source: https://flowdrop.mintlify.app/guides/creating-workflows Build workflows visually using FlowDrop's drag-and-drop editor. FlowDrop is a **visual, no-code workflow editor**. You build workflows by dragging nodes onto a canvas, connecting them together, and configuring each step — all without writing a single line of code. ## Adding nodes Open the sidebar by clicking the **menu button** in the toolbar. The sidebar shows all available node types, organized by category. Drag any node from the sidebar onto the canvas to add it to your workflow. FlowDrop editor showing the node sidebar on the left, a workflow canvas with placed nodes in the center, and the configuration panel on the right. Each node type serves a different purpose — see [Node Types](/guides/node-types) for the full catalog. ## Connecting nodes To define the flow between steps, connect nodes by dragging from an **output port** on one node to an **input port** on another. A line (edge) appears to show the connection. You can also use **proximity connect** — simply drag a node close to another, and FlowDrop will automatically suggest a connection. For details on port types and data validation, see [Port System & Data Types](/guides/port-system). ## Configuring nodes Click on any node to open the **configuration panel** on the right side. Here you can edit the node's label, description, and any custom fields defined by its [configuration schema](/guides/config-schema). The configuration form is generated automatically from the node's JSON Schema definition. Different field types (text, select, toggle, code editor, template) render based on the schema's `type` and `format` properties. ## Saving your workflow Click the **Save** button in the toolbar to persist your workflow. FlowDrop also supports **auto-save drafts** so you never lose work in progress — drafts are saved to `localStorage` every 30 seconds by default. You can control save behavior with [event handlers](/guides/advanced/event-system): ```typescript theme={null} eventHandlers: { onBeforeSave: async (workflow) => { // Return false to cancel save }, onAfterSave: async (workflow) => { showNotification('Saved!'); } } ``` ## Import & export FlowDrop supports importing and exporting workflows in two formats: * **FlowDrop JSON** — the native format for full-fidelity round-trips * **Oracle Agent Spec** — an open standard for AI agent workflows ([oracle/agent-spec](https://github.com/oracle/agent-spec)) For programmatic import/export, see the [Programmatic API](/guides/advanced/programmatic-api) guide. For Agent Spec integration, see [Agent Spec](/guides/advanced/agent-spec). ## Keyboard shortcuts | Shortcut | Action | | ---------------------- | ---------------------------- | | `Ctrl/Cmd + Z` | Undo | | `Ctrl/Cmd + Shift + Z` | Redo | | `Ctrl/Cmd + S` | Save workflow | | `Delete` / `Backspace` | Delete selected node or edge | | `Ctrl/Cmd + A` | Select all | | Scroll wheel | Zoom in/out | ## Next steps * [Node Types](/guides/node-types) — visual catalog of all built-in node types * [Configuration Schema](/guides/config-schema) — define custom form fields * [Event System](/guides/advanced/event-system) — hook into save, change, and error events # Custom form fields Source: https://flowdrop.mintlify.app/guides/custom-form-fields Extend FlowDrop's form system with custom field components. FlowDrop generates configuration forms automatically from JSON Schema. The field registry system lets you add custom field components — for example a color picker, date picker, or rich text editor. ## Quick start **1. Write a Svelte field component:** ```svelte theme={null} onChange(e.currentTarget.value)} /> ``` **2. Register it:** ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; import ColorPickerField from './ColorPickerField.svelte'; const fd = getInstance(); // or app.instance outside the component tree fd.fields.register('color-picker', { component: ColorPickerField, matcher: (schema) => schema.format === 'color', priority: 100 }); ``` **3. Use it in a config schema:** ```json theme={null} { "accentColor": { "type": "string", "format": "color", "title": "Accent Color", "default": "#3b82f6" } } ``` ## How it works When `FormFieldLight` renders a field, it: 1. Calls `resolveFieldComponent(schema)` to check the registry 2. If a registered matcher returns `true`, renders the registered component 3. Otherwise falls back to built-in fields (text, number, toggle, select, etc.) Registrations are **priority-ordered** — higher priority matchers are checked first. ## Field component props Your component receives these props: ```typescript theme={null} interface Props { id: string; value: unknown; placeholder?: string; required?: boolean; ariaDescribedBy?: string; onChange: (value: unknown) => void; } ``` Only the props listed above are guaranteed. Read any additional schema properties (like `schema.minDate`) directly from the schema via the form context — see [Reading sibling field values](#reading-sibling-field-values) below. ## Matcher functions A matcher decides whether your component handles a given schema: ```typescript theme={null} // Match by format (schema) => schema.format === "color" // Match by type + format (schema) => schema.type === "string" && schema.format === "rich-text" // Match by custom property (schema) => schema.widget === "my-widget" ``` ## Priority-based resolution When multiple registrations match, the highest priority wins: ```typescript theme={null} // Priority 50 — general fallback fd.fields.register('text-basic', { component: BasicTextField, matcher: (schema) => schema.type === 'string', priority: 50 }); // Priority 100 — more specific, checked first fd.fields.register('rich-text', { component: RichTextField, matcher: (schema) => schema.type === 'string' && schema.format === 'rich-text', priority: 100 }); ``` You can use this to **override built-in fields** by registering your own component with a higher priority. ## Lazy registration For heavy dependencies, use dynamic imports: ```typescript theme={null} import type { FieldComponentRegistry } from '@flowdrop/flowdrop/form'; export function registerMyHeavyField(fields: FieldComponentRegistry, priority = 100): void { if (fields.has('my-heavy-field')) return; import('./MyHeavyField.svelte').then((module) => { fields.register('my-heavy-field', { component: module.default, matcher: (schema) => schema.format === 'heavy', priority }); }); } // Call with the instance's registry: registerMyHeavyField(getInstance().fields) ``` ## Built-in field types These fields are always available without registration: | Schema | Renders as | | --------------------------------------------- | --------------------------- | | `type: "string"` | Text input | | `type: "string", format: "multiline"` | Textarea | | `type: "number"` or `type: "integer"` | Number input | | `type: "number", format: "range"` | Range slider | | `type: "boolean"` | Toggle switch | | `type: "string", enum: [...]` | Select dropdown | | `type: "string", enum: [...], multiple: true` | Checkbox group | | `type: "string", oneOf: [{const, title}]` | Select with labeled options | | `type: "array", items: {...}` | Dynamic list | | `format: "hidden"` | Hidden (not rendered) | These require explicit registration (heavy dependencies): Each installer takes the target field registry (`fd.fields`) as its first argument: | Schema | Import path | Registration function | | ------------------------------------ | ---------------------------------- | ---------------------------------------- | | `format: "json"` or `format: "code"` | `@flowdrop/flowdrop/form/code` | `registerCodeEditorField(fd.fields)` | | `format: "template"` | `@flowdrop/flowdrop/form/code` | `registerTemplateEditorField(fd.fields)` | | `format: "markdown"` | `@flowdrop/flowdrop/form/markdown` | `registerMarkdownEditorField(fd.fields)` | ## Field management Field management is done through the instance's `fd.fields` registry (a `FieldComponentRegistry`): ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); // or app.instance outside the component tree fd.fields.unregister('color-picker'); // returns boolean fd.fields.getKeys(); // ["color-picker", ...] fd.fields.has('color-picker'); // true or false fd.fields.size; // number of registrations fd.fields.clear(); // clear all (useful in tests) ``` ## Reading sibling field values Custom components registered for `format: "autocomplete"` fields receive the full `schema` object as a prop and can read the current values of other fields in the same form using the `FORM_VALUES_KEY` context. This is the building block for dependent autocomplete fields — for example a `project` field whose suggestions depend on the currently selected `account`. **1. Define the schema** — use any custom property to declare the dependency: ```json theme={null} { "account": { "type": "string", "title": "Account" }, "project": { "type": "string", "format": "autocomplete", "title": "Project", "autocomplete": { "url": "/api/projects", "labelField": "name", "valueField": "id" }, "dependencies": { "account": "account" } } } ``` **2. Write the component** — wrap `FormAutocomplete` and patch the URL: ```svelte theme={null} ``` **3. Register it** — match on the custom `dependencies` property: ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; import DependentAutocomplete from './DependentAutocomplete.svelte'; const fd = getInstance(); // or app.instance outside the component tree fd.fields.register('dependent-autocomplete', { component: DependentAutocomplete, matcher: (schema) => schema.format === 'autocomplete' && 'dependencies' in schema, priority: 150 }); ``` The registered component is only activated when a schema has both `format: "autocomplete"` and a `dependencies` property. All other autocomplete fields continue to use FlowDrop's built-in `FormAutocomplete`. **`FormAutocomplete` is a named export.** Import it with `import { FormAutocomplete } from '@flowdrop/flowdrop/form/autocomplete'`. # Custom nodes Source: https://flowdrop.mintlify.app/guides/custom-nodes Register custom Svelte components as workflow node types. FlowDrop ships with 7 built-in node types, but you can register your own custom node components to extend the editor. ## Overview Registration happens against the instance's node registry, `fd.nodes` (a `NodeComponentRegistry`). There are three methods: | Approach | When to use | | --------------------------------------- | ------------------------------------------- | | **`fd.nodes.registerCustom()`** | One-off project-specific nodes | | **`fd.nodes.registerPlugin()`** | Libraries providing multiple node types | | **`createPlugin().register(fd.nodes)`** | Same as above, with a chainable builder API | Custom node types are **namespaced** (e.g., `"mylib:code-editor"`) to prevent conflicts. Resolve `fd` with `getInstance()` inside the component tree, or use the mount handle's `.instance` outside it. You can register before or after mounting. `BaseRegistry` tracks a version counter that invalidates dependent `$derived` reads. When you register a node after mount, the counter bumps and the editor re-resolves, so late registrations take effect. ## Quick start **1. Write a Svelte component:** ```svelte theme={null}

{data.label}

{data.config?.code ?? ''}
``` **2. Register it:** ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; import CodeEditorNode from './CodeEditorNode.svelte'; const fd = getInstance(); // or app.instance outside the component tree fd.nodes.registerCustom('myapp:code-editor', 'Code Editor', CodeEditorNode, { icon: 'mdi:code-braces', description: 'A custom code editor node', category: 'custom' }); ``` **3. Make it available in the sidebar** by passing `NodeMetadata` with a matching `type`: ```typescript theme={null} const app = await mountFlowDropApp(container, { nodes: [ { id: 'myapp:code-editor', name: 'Code Editor', type: 'myapp:code-editor', // Must match registered type description: 'Write and edit code', category: 'processing', inputs: [{ id: 'input', name: 'Input', type: 'input', dataType: 'string' }], outputs: [{ id: 'output', name: 'Output', type: 'output', dataType: 'string' }] } ] }); ``` ## NodeComponentProps All custom node components must accept: ```typescript theme={null} interface NodeComponentProps { id: string; // node instance id, passed by SvelteFlow data: { label: string; config: Record; metadata: NodeMetadata; executionInfo?: NodeExecutionInfo; onConfigOpen?: (node) => void; }; selected?: boolean; isProcessing?: boolean; isError?: boolean; } ``` ## Plugin registration Register multiple nodes under a shared namespace: ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); const result = fd.nodes.registerPlugin({ namespace: 'awesome', name: 'Awesome Nodes', version: '1.0.0', nodes: [ { type: 'fancy', displayName: 'Fancy Node', component: FancyNode, icon: 'mdi:sparkles' }, { type: 'glow', displayName: 'Glowing Node', component: GlowNode, icon: 'mdi:lightbulb' } ] }); // result.registeredTypes: ["awesome:fancy", "awesome:glow"] ``` ### Fluent builder ```typescript theme={null} import { createPlugin, getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); createPlugin('awesome', 'Awesome Nodes') .version('1.0.0') .node('fancy', 'Fancy Node', FancyNode, { icon: 'mdi:sparkles' }) .node('glow', 'Glowing Node', GlowNode, { icon: 'mdi:lightbulb' }) .register(fd.nodes); ``` ## Plugin management Plugin lifecycle is managed on `fd.nodes`; `isValidNamespace` remains a standalone helper: ```typescript theme={null} import { isValidNamespace, getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); fd.nodes.unregisterPlugin('awesome'); // Remove all nodes from a plugin fd.nodes.getRegisteredPlugins(); // List registered namespaces fd.nodes.getPluginNodeCount('awesome'); // Count nodes in a plugin isValidNamespace('my-lib'); // Validate namespace format ``` Namespace rules: Must match `/^[a-z][a-z0-9-]*$/` — lowercase letters, digits, and hyphens, starting with a letter. ## Built-in node types If no custom component is registered for a `type`, FlowDrop falls back to built-in types: | Type | Description | | -------------- | -------------------------------------- | | `workflowNode` | Full-featured node with inputs/outputs | | `simple` | Compact layout | | `square` | Minimal icon-only design | | `tool` | Agent tool nodes | | `gateway` | Branching control flow | | `note` | Markdown sticky notes | | `terminal` | Circular start/end nodes | Use `supportedTypes` on `NodeMetadata` to let users switch between visual types at runtime. # Edge structure Source: https://flowdrop.mintlify.app/guides/edge-json The JSON format for workflow edges — connections between nodes, handle IDs, and edge categories. Every item in a workflow's `edges` array is a **WorkflowEdge**. It represents a connection between an output port on one node and an input port on another. ## Schema ```typescript theme={null} interface WorkflowEdge { id: string; source: string; target: string; sourceHandle?: string; targetHandle?: string; type?: ConnectionLineType; selectable?: boolean; deletable?: boolean; data?: { label?: string; condition?: string; metadata?: { edgeType?: EdgeCategory; sourcePortDataType?: string; }; }; } ``` | Field | Type | Required | Description | | ---------------------------------- | -------------- | -------- | -------------------------------------------------------------- | | `id` | `string` | Yes | Unique edge identifier (e.g., `"e-loader-analyzer"`). | | `source` | `string` | Yes | ID of the source node. | | `target` | `string` | Yes | ID of the target node. | | `sourceHandle` | `string` | No | ID of the specific output port. See [Handle IDs](#handle-ids). | | `targetHandle` | `string` | No | ID of the specific input port. See [Handle IDs](#handle-ids). | | `selectable` | `boolean` | No | Whether the edge can be selected in the editor. | | `deletable` | `boolean` | No | Whether the edge can be deleted by the user. | | `data.label` | `string` | No | Display label on the edge. | | `data.condition` | `string` | No | Condition expression (used with gateway branches). | | `data.metadata.edgeType` | `EdgeCategory` | No | Visual styling category. | | `data.metadata.sourcePortDataType` | `string` | No | Data type of the source output port. | ## Handle IDs Port handles follow a deterministic naming pattern: ```text theme={null} {nodeId}-{direction}-{portId} ``` For example: * `content_loader.1-output-items` — the "items" output port on node `content_loader.1` * `analyzer.1-input-content` — the "content" input port on node `analyzer.1` * `router.1-output-high` — the "high" branch output on a gateway node This format lets FlowDrop map edges back to specific ports during rendering and execution. ## Edge Categories The `edgeType` field controls the visual style of the edge on the canvas: | Category | Visual Style | When Used | | ---------- | ----------------------------- | -------------------------------------------------------- | | `data` | Solid gray line | Default — general data flow between nodes. | | `trigger` | Solid line with trigger color | Control flow connections (port `dataType: "trigger"`). | | `tool` | Dashed amber line | Tool interface connections (port `dataType: "tool"`). | | `loopback` | Dashed gray line | Loop iteration connections (targets a `loop_back` port). | The editor sets `edgeType` automatically based on the source port's data type. You generally don't need to set it manually in JSON. ## Examples ### Data Edge A simple data connection between two ports: ```json theme={null} { "id": "e-loader-analyzer", "source": "content_loader.1", "target": "analyzer.1", "sourceHandle": "content_loader.1-output-items", "targetHandle": "analyzer.1-input-content" } ``` ### Trigger Edge A control-flow connection that triggers execution: ```json theme={null} { "id": "e-start-loader", "source": "start.1", "target": "content_loader.1", "sourceHandle": "start.1-output-trigger", "targetHandle": "content_loader.1-input-trigger", "data": { "metadata": { "edgeType": "trigger", "sourcePortDataType": "trigger" } } } ``` ### Tool Edge A dashed connection linking an agent to a tool: ```json theme={null} { "id": "e-agent-tool", "source": "search_tool.1", "target": "agent.1", "sourceHandle": "search_tool.1-output-tool", "targetHandle": "agent.1-input-tool", "data": { "metadata": { "edgeType": "tool", "sourcePortDataType": "tool" } } } ``` ### Gateway Branch Edge A conditional edge from a gateway branch: ```json theme={null} { "id": "e-router-high", "source": "router.1", "target": "urgent_handler.1", "sourceHandle": "router.1-output-high", "targetHandle": "urgent_handler.1-input-input", "data": { "condition": "priority > 8", "metadata": { "edgeType": "data" } } } ``` ## Connection Validation The editor validates connections automatically before creating edges: * **Type compatibility** — only compatible port data types can connect * **Cycle detection** — prevents circular dependencies (O(V+E) algorithm) * **Loopback prevention** — nodes cannot connect to themselves For more on data type compatibility rules, see [Port System & Data Types](/guides/port-system). ## Next Steps * [Node Structure](/guides/node-json) — the nodes that edges connect * [Port System & Data Types](/guides/port-system) — port definitions and compatibility rules * [Workflow Structure](/guides/workflow-json) — the top-level document containing edges # i18n & custom messages Source: https://flowdrop.mintlify.app/guides/i18n Override or translate every user-facing string in FlowDrop via the messages prop. Every user-facing label, tooltip, placeholder, and notice in FlowDrop is rendered from a single typed `Messages` tree. You can override any subset by passing a `messages` callback to ``. Wire that callback to your i18n library and locale changes propagate into FlowDrop without a subscription. FlowDrop is **not** an i18n library. It is a consumer of one. Translations live in your app, alongside the rest of your UI copy. ## Quick start Override a single string with a value: ```svelte theme={null} ``` `messages` is `DeepPartial` — every key is optional, missing keys fall through to the English defaults. You can also pass a callback. The two forms are equivalent for static overrides; the callback is useful when your translations come from a function call you'd rather not invoke unless the prop is actually read: ```svelte theme={null} ({ form: { schema: { save: 'Apply' } } })} /> ``` ## Translating with paraglide-js [Paraglide-js](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) compiles your `.json` translation files into typed message functions. Wire them into FlowDrop's `messages` prop: ```svelte theme={null} ``` `paraglide-js` is one option — `sveltekit-i18n`, `typesafe-i18n`, or any reactive store will work. The contract is just: a callback returning a partial tree. Passing `messages` as a callback (not a plain object) is what makes locale switching reactive. Paraglide's reactive locale store causes Svelte to re-evaluate the expression; FlowDrop's root then re-derives the merged message tree and re-renders. ## The Messages shape The full default tree lives at `libs/flowdrop/src/lib/messages/defaults.ts` and is exported as `defaultMessages` from `@flowdrop/flowdrop/core`. The shape is grouped by **domain**, not by component — file paths churn, domains don't. | Branch | What it covers | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `common` | Generic verbs reused everywhere: `save`, `cancel`, `confirm`, `close`, `delete`, `yes`, `no`. | | `form` | All form components — `array.*` (move/delete/empty/limits), `markdown.*` (toolbar, status bar, placeholder), `autocomplete.*`, `field.required`, `toggle.{enabled,disabled}`, `schema.{save,cancel,empty}`, `code.editor` (JSON), `template.editor` (Mustache). | | `interrupt` | Inline interrupt prompts — `confirmation.*`, `choice.*` (with parameterised counter), `review.*` (accept/reject/diff/summary), `text.*` (placeholder, min, submit), `form.*`, `bubble.*` (per-kind required/submitted labels, retry, cancel). Plus shared `responseSubmitted` / `responseSubmittedBy({ name })`. | | `chat` | AI Assistant panel: `aiAssistant`, `placeholder`, `send`, `autoRetry({ attempt, max })`, plus `commandPreview.*` (apply / cancel / status). | | `playground` | Playground chat: `chat.{placeholder, predefinedRun}`, `states.*` (welcome screens), `actions.*` (run/stop/send), `roles.*` (you/assistant/system/log/message), `messageTooltips.*`, `sessions.*` (list, empty, delete confirm, relative timestamps). | | `nodes.notes` | NotesNode placeholder, type names (info/warning/success/error/note), processing/error indicators, configure tooltip. | | `nodes.graph` | SvelteFlow node and port aria-labels — `workflowNode({ name })`, `gatewayNode({ title })`, `ideaNode({ title })`, `connectInputPort({ name })`, `connectOutputPort({ name })`, `connectBranch({ name })`. | | `navigation` | Navbar branding (`appName`, `tagline`), connection indicator, settings button, default primary action labels (`save`, `export`, `import`, `workflowSettings`), right-sidebar panel titles (`workflowSettingsPanelTitle`, `workflowSettingsPanelSubtitle`, `nodeConfigDescription`), close affordances (`closeSettings`, `closeConfigModal`, `copyId`), and bottom-panel tab labels (`bottomPanel.console`, `bottomPanel.chat`). | | `layout` | Sidebar/canvas landmarks (`componentsSidebar`, `workflowCanvas`, `executionLogs`, `settingsCategories`), search input (`searchComponents`), command console (`commandConsole`, `closeConsole`), resize handles (`resizeLeftSidebar`, `resizeRightSidebar`, `resizeBottomPanel`), sidebar toggle (`expandSidebar`, `collapseSidebar`), modal close affordances, swap workflow (`swapNode`, `backToConfiguration`, `backToNodeSelection`), and `loadSession({ name })`. | | `status` | `pipeline.*` (refresh/view-logs/breadcrumbs) and `overlay.*` (NodeStatusOverlay tooltip and detail labels). | Parameterised entries are functions, not template strings: ```ts theme={null} // From defaults interrupt: { responseSubmittedBy: ({ name }: { name: string }) => `Response submitted by ${name}`, choice: { selectedCount: ({ n, total }: { n: number; total: number }) => `${n} of ${total} selected` } } ``` When you override a parameterised entry, you must supply a function with the same signature. Call sites invoke it with the params object, so a plain string would throw at runtime. If your translation doesn't need the params, ignore them: `({ n: _n }) => 'Move up'`. ## Removed label props Several components used to accept individual `*Label` props that duplicated the `messages` tree. These duplicates are removed — use the corresponding `messages` key instead: | Component | Removed prop | Replace with | | --------------- | ------------- | ----------------------------- | | `` | `saveLabel` | `messages.form.schema.save` | | `` | `cancelLabel` | `messages.form.schema.cancel` | | `` | `placeholder` | `messages.chat.placeholder` | ``'s `onLabel`/`offLabel` and ``'s `addLabel` are **not** removed. They express per-instance labels the global `messages` system cannot (a toggle's "Hidden"/"Visible", a schema-derived "Add Header" button) and are documented overrides. Workflow-level overrides on interrupt configs (`config.confirmLabel`, `config.acceptAllLabel`, etc.) are **not** removed either — those are runtime data from the workflow author, not component-prop API. They keep their priority over the `messages` defaults. ## Components used outside the `` provider If you mount a flowdrop component (e.g. `` standalone, or a Storybook story) outside the root ``, it falls back silently to the English `defaultMessages`. There is no error. The trade-off: a missing provider always renders English regardless of locale. To get translations in standalone usage, set up the messages context yourself: ```svelte theme={null} ``` `setMessages` accepts a getter so reactive overrides propagate the same way they do under ``. ## Reference * Defaults: `libs/flowdrop/src/lib/messages/defaults.ts` * Public types: `Messages`, `MessagesOverride` (re-exported from `@flowdrop/flowdrop`) * Source: `libs/flowdrop/src/lib/messages/` # Framework integration Source: https://flowdrop.mintlify.app/guides/integration Use FlowDrop with Svelte, vanilla JS, React, Vue, Angular, or any framework. FlowDrop is built with Svelte 5, but can be mounted into any framework via the mount API. ## Svelte (Native) Use FlowDrop components directly in Svelte: ```svelte theme={null} console.log('Saved:', workflow.id)} /> ``` For SvelteKit, ensure FlowDrop runs only on the client: ```svelte theme={null} {#if browser} {/if} ``` ## Vanilla JS / React / Vue / Angular Use the mount API to embed FlowDrop in any container element: ```javascript theme={null} import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; import { createEndpointConfig } from '@flowdrop/flowdrop/core'; import '@flowdrop/flowdrop/styles'; const app = await mountFlowDropApp(document.getElementById('editor'), { endpointConfig: createEndpointConfig('/api/flowdrop'), eventHandlers: { onDirtyStateChange: (isDirty) => console.log('Unsaved:', isDirty), onAfterSave: async (workflow) => console.log('Saved!', workflow) } }); // Programmatic control app.save(); app.getWorkflow(); app.isDirty(); app.destroy(); ``` The returned handle also exposes `app.export()` and the mount's state container at `app.instance`. For programmatic state changes, reach through the instance — `app.instance.workflow.actions.*`, `app.instance.history.undo()`, and so on. See [Mount API](/reference/mount-api) for the complete options and return value. ## API Configuration Connect to your backend by configuring endpoints: ```typescript theme={null} import { createEndpointConfig } from '@flowdrop/flowdrop/core'; // Simple: just a base URL (all endpoints use defaults) const config = createEndpointConfig('/api/flowdrop'); // Custom: override specific endpoint paths const config = createEndpointConfig({ baseUrl: 'https://api.example.com', endpoints: { nodes: { list: '/nodes', get: '/nodes/{id}' }, workflows: { list: '/workflows', get: '/workflows/{id}', create: '/workflows', update: '/workflows/{id}' } } }); ``` See [Backend Implementation](/guides/integration/backend-implementation) for which endpoints to implement. ## Authentication FlowDrop supports three auth providers. Quick examples: ```typescript theme={null} import { NoAuthProvider, StaticAuthProvider, CallbackAuthProvider } from '@flowdrop/flowdrop/core'; // No auth (development) authProvider: new NoAuthProvider(); // Static bearer token authProvider: new StaticAuthProvider({ type: 'bearer', token: 'your-jwt' }); // Dynamic token with refresh (enterprise) authProvider: new CallbackAuthProvider({ getToken: async () => authService.getAccessToken(), onUnauthorized: async () => authService.refreshToken() }); ``` For full details on each provider, token refresh patterns, and OAuth2 integration, see [Authentication Patterns](/guides/integration/authentication-patterns). ## Event Handlers React to all 11 editor lifecycle events: ```typescript theme={null} const app = await mountFlowDropApp(container, { eventHandlers: { onWorkflowChange: (workflow, changeType) => { analytics.track('workflow_modified', { changeType }); }, onDirtyStateChange: (isDirty) => { saveButton.disabled = !isDirty; }, onBeforeSave: async (workflow) => { // Return false to cancel save }, onAfterSave: async (workflow) => { showNotification('Saved!'); }, onApiError: (error, operation) => { if (error.message.includes('401')) { redirectToLogin(); return true; // suppress default toast } } } }); ``` The mount options bag groups handlers under `eventHandlers`. The `` component instead takes the same handlers as flat `on*` props (`onAfterSave`, `onApiError`, …). For the complete event reference, see [Event System](/guides/advanced/event-system). ## Feature Flags Enable optional editor features: ```typescript theme={null} const app = await mountFlowDropApp(container, { features: { autoSaveDraft: true, // default: true autoSaveDraftInterval: 30000, // default: 30 seconds showToasts: true // default: true } }); ``` ## Read-Only & Lock Modes The `mode` option controls canvas editing. `'readonly'` and `'locked'` both disable node dragging, connecting, selecting, proximity-connect, and node swap. ```typescript theme={null} // Read-only: no canvas editing const app = await mountFlowDropApp(container, { mode: 'readonly' }); // Locked: same disabled interactions, distinct intent const app = await mountFlowDropApp(container, { mode: 'locked' }); ``` ## Next Steps * [Mount API](/reference/mount-api) — complete mount options and return values * [Backend Implementation](/guides/integration/backend-implementation) — build the API * [Authentication Patterns](/guides/integration/authentication-patterns) — secure your integration * [Deployment](/guides/integration/deployment) — production deployment patterns * [Event System](/guides/advanced/event-system) — all 11 event handlers # Authentication patterns Source: https://flowdrop.mintlify.app/guides/integration/authentication-patterns Secure your FlowDrop integration with the right auth provider. FlowDrop supports three authentication providers that control how API requests are authenticated. Choose based on your security requirements. ## No Authentication For development and prototyping when your backend doesn't require auth: ```typescript theme={null} import { NoAuthProvider } from '@flowdrop/flowdrop/core'; const app = await mountFlowDropApp(container, { authProvider: new NoAuthProvider() }); ``` `NoAuthProvider` sends no authentication headers. This is also the default if you don't specify an `authProvider`. ## Static Token Authentication For simple deployments with a fixed token: ```typescript theme={null} import { StaticAuthProvider } from '@flowdrop/flowdrop/core'; ``` ### Bearer Token ```typescript theme={null} const authProvider = new StaticAuthProvider({ type: 'bearer', token: 'your-jwt-token' }); // Sends: Authorization: Bearer your-jwt-token ``` ### API Key ```typescript theme={null} const authProvider = new StaticAuthProvider({ type: 'api_key', apiKey: 'your-api-key' }); // Sends: X-API-Key: your-api-key ``` ### Custom Headers ```typescript theme={null} const authProvider = new StaticAuthProvider({ type: 'custom', headers: { 'X-Custom-Auth': 'value', 'X-Tenant-ID': 'tenant-123' } }); ``` **Static tokens can't be refreshed.** If the token expires, FlowDrop API calls fail with 401 errors until the editor is remounted with a new token. ## Callback Authentication (Enterprise) For enterprise integrations where your application manages auth: ```typescript theme={null} import { CallbackAuthProvider } from '@flowdrop/flowdrop/core'; const authProvider = new CallbackAuthProvider({ // Called before EVERY API request getToken: async () => { return authService.getAccessToken(); // return null if not authenticated }, // Called when API returns 401 onUnauthorized: async () => { const refreshed = await authService.refreshToken(); return refreshed; // true = retry request, false = give up }, // Called when API returns 403 onForbidden: async () => { showError("You don't have permission to access this resource"); } }); ``` ### Token Refresh Pattern The callback provider supports automatic token refresh: ```typescript theme={null} const authProvider = new CallbackAuthProvider({ getToken: async () => { // Check if token is still valid if (tokenStore.isExpired()) { await tokenStore.refresh(); } return tokenStore.getToken(); }, onUnauthorized: async () => { // Token was rejected by server, try refresh try { await tokenStore.refresh(); return true; // retry the failed request } catch { redirectToLogin(); return false; // don't retry } } }); ``` ### OAuth2 / OIDC Integration ```typescript theme={null} const authProvider = new CallbackAuthProvider({ getToken: async () => { // Get token from your OAuth2 library const token = await oidcClient.getUser()?.access_token; return token || null; }, onUnauthorized: async () => { // Trigger silent token renewal try { await oidcClient.signinSilent(); return true; } catch { // Silent renewal failed, redirect to login await oidcClient.signinRedirect(); return false; } } }); ``` ## Error Handling with Auth Combine auth providers with the `onApiError` event handler for centralized error management: ```typescript theme={null} const app = await mountFlowDropApp(container, { authProvider, eventHandlers: { onApiError: (error, operation) => { if (error.message.includes('401')) { // Auth provider's onUnauthorized already handled this return true; // suppress toast } if (error.message.includes('403')) { showPermissionError(operation); return true; } // Let other errors show the default toast } } }); ``` ## Choosing a Provider | Scenario | Provider | Why | | ------------------------ | ----------------------------- | ---------------------------- | | Local development | `NoAuthProvider` | No backend auth needed | | Simple deployment | `StaticAuthProvider` | Token set once at page load | | Single-page app with JWT | `CallbackAuthProvider` | Token refresh on expiry | | Enterprise SSO / OAuth2 | `CallbackAuthProvider` | Integrates with auth library | | Multi-tenant | `StaticAuthProvider` (custom) | Add tenant headers | ## Next Steps * [Framework Integration](/guides/integration) — full integration setup * [Backend Implementation](/guides/integration/backend-implementation) — implement auth on your backend * [Event System](/guides/advanced/event-system) — `onApiError` for auth error handling # Backend implementation Source: https://flowdrop.mintlify.app/guides/integration/backend-implementation Build the REST API that FlowDrop expects to communicate with. FlowDrop is a frontend editor that calls **your** backend REST API. This guide explains what endpoints to implement, what request/response formats FlowDrop expects, and how to get a working backend running. ## Endpoint Tiers Not all endpoints are required. Here they are organized by priority: ### Tier 1: Minimum Viable Backend These 5 endpoints are the **bare minimum** to get FlowDrop working: | Method | Path | Purpose | | ------ | ---------------- | -------------------------------------------- | | `GET` | `/health` | Health check (FlowDrop checks this on mount) | | `GET` | `/nodes` | List available node types | | `GET` | `/workflows/:id` | Load a workflow | | `POST` | `/workflows` | Create a new workflow | | `PUT` | `/workflows/:id` | Update an existing workflow | ### Tier 2: Full Editor Experience These endpoints enable the complete sidebar, categories, and port validation: | Method | Path | Purpose | | -------- | ---------------- | ------------------------------------------ | | `GET` | `/categories` | Node category definitions (sidebar groups) | | `GET` | `/port-config` | Port data types and compatibility rules | | `GET` | `/nodes/:id` | Get a single node's metadata | | `GET` | `/workflows` | List all workflows | | `DELETE` | `/workflows/:id` | Delete a workflow | ### Tier 3: Advanced Features These enable playground, execution, and interrupts: | Method | Path | Purpose | | ------ | ------------------------------------ | ------------------------- | | `POST` | `/workflows/:id/execute` | Execute a workflow | | `GET` | `/executions/:id` | Get execution status | | `POST` | `/workflows/:id/playground/sessions` | Create playground session | | `GET` | `/playground/sessions/:sid/messages` | Poll for messages | | `POST` | `/playground/sessions/:sid/messages` | Send user message | | `GET` | `/interrupts/:id` | Get pending interrupt | | `POST` | `/interrupts/:id` | Resolve an interrupt | | `GET` | `/system/config` | Runtime configuration | **The OpenAPI spec is the authoritative contract.** The Tier 1–2 shapes are documented in full below, the Tier 3 shapes under [Advanced endpoint formats](#advanced-endpoint-formats-tier-3). For the exhaustive schema, see the [OpenAPI specification](/api-reference/introduction). ## Base URL Configuration All paths above are relative to a base URL you configure: ```typescript theme={null} import { createEndpointConfig } from '@flowdrop/flowdrop/core'; const endpointConfig = createEndpointConfig('/api/flowdrop'); // Nodes endpoint becomes: GET /api/flowdrop/nodes ``` ## Request & Response Formats ### `GET /health` FlowDrop calls this to verify the backend is reachable. **Response:** ```json theme={null} { "status": "ok", "version": "1.0.0" } ``` ### `GET /nodes` Returns all available node types. FlowDrop uses this to populate the sidebar. **Query parameters:** * `category` (optional) — filter by category * `search` (optional) — search name/description * `limit` (optional, default: 100) * `offset` (optional, default: 0) **Response:** ```json theme={null} { "success": true, "data": [ { "node_type_id": "text_input", "name": "Text Input", "description": "Accepts text from the user", "type": "simple", "category": "inputs", "icon": "mdi:text-box-outline", "inputs": [], "outputs": [ { "id": "output", "name": "Text", "type": "output", "dataType": "string" } ], "configSchema": { "type": "object", "properties": { "placeholder": { "type": "string", "title": "Placeholder", "default": "Enter text..." } } } } ] } ``` **Key fields in `NodeMetadata`:** * `node_type_id` (required) — unique node-type identifier * `name` (required) — display name * `type` — node visual type: `workflowNode`, `simple`, `square`, `tool`, `gateway`, `terminal`, `idea`, `note` * `category` — sidebar group: `inputs`, `outputs`, `models`, `processing`, `logic`, `tools`, etc. * `icon` — [Iconify](https://icon-sets.iconify.design/) icon ID (e.g., `mdi:text-box-outline`) * `inputs` / `outputs` — port definitions with `id`, `name`, `type`, `dataType` * `configSchema` — JSON Schema defining the configuration form ### `POST /workflows` Creates a new workflow. FlowDrop sends the full workflow JSON. **Request body:** ```json theme={null} { "name": "My Workflow", "description": "A simple workflow", "nodes": [ { "id": "node-1", "type": "simple", "position": { "x": 100, "y": 200 }, "data": { "label": "Text Input", "config": { "placeholder": "Enter text..." }, "metadata": { "node_type_id": "text_input", "name": "Text Input", "...": "..." } } } ], "edges": [ { "id": "edge-1", "source": "node-1", "sourceHandle": "output", "target": "node-2", "targetHandle": "input" } ] } ``` **Response:** ```json theme={null} { "success": true, "data": { "id": "wf-abc123", "name": "My Workflow", "nodes": [], "edges": [], "metadata": { "schemaVersion": "1.0.0", "createdAt": "2025-01-01T00:00:00Z", "updatedAt": "2025-01-01T00:00:00Z" } } } ``` ### `PUT /workflows/:id` Updates an existing workflow. Same request body format as `POST`. ### `GET /workflows/:id` Returns a single workflow by ID. Same response format as `POST` response. ### `GET /categories` Returns category definitions for the node sidebar. **Response:** ```json theme={null} { "success": true, "data": [ { "id": "inputs", "name": "Inputs", "description": "Data input nodes", "icon": "mdi:import", "color": "var(--fd-node-emerald)", "weight": 10 }, { "id": "processing", "name": "Processing", "description": "Data transformation nodes", "icon": "mdi:cog", "color": "var(--fd-node-blue)", "weight": 30 } ] } ``` ### `GET /port-config` Returns data type definitions and compatibility rules for port connections. **Response:** ```json theme={null} { "success": true, "data": { "version": "1.0.0", "defaultDataType": "string", "dataTypes": [ { "id": "string", "name": "String", "description": "Text data", "color": "#10b981", "category": "basic" }, { "id": "json", "name": "JSON", "description": "Structured data", "color": "#f59e0b", "category": "complex" } ], "compatibilityRules": [ { "from": "string", "to": "json" }, { "from": "json", "to": "string" } ] } } ``` ## Advanced endpoint formats (Tier 3) These power execution, the interactive playground, and human-in-the-loop interrupts. They use the same `{ "success": true, "data": ... }` envelope as Tier 1–2 unless noted. ### `POST /workflows/:id/execute` Starts a run. The body is optional. **Request:** ```json theme={null} { "inputs": { "text_input": "Hello" }, "options": { "timeout": 30000, "maxSteps": 50 } } ``` **Response (`202 Accepted`):** ```json theme={null} { "success": true, "data": { "execution_id": "exec-abc123", "status": "running", "started_at": "2025-01-01T00:00:00Z", "estimated_completion": "2025-01-01T00:00:05Z" } } ``` ### `GET /executions/:id` Poll for execution status using the `execution_id` returned above. **This endpoint returns the status object directly — no `success`/`data` envelope.** It is the one exception to the response wrapper. **Response:** ```json theme={null} { "status": "completed", "jobs": [], "node_statuses": { "node-1": { "status": "completed" } }, "job_status_summary": {} } ``` `status` is one of `pending`, `running`, `completed`, `failed`, `cancelled`, `paused`, `interrupted`. The playground's `isTerminalStatus` and `shouldStopPolling` callbacks key off these values. ### `POST /workflows/:id/playground/sessions` Create an isolated test session for a workflow. The body is optional. **Request:** ```json theme={null} { "name": "Test Session 1" } ``` **Response (`201`):** ```json theme={null} { "success": true, "data": { "id": "sess-abc123", "workflowId": "wf-abc123", "name": "Test Session 1", "status": "idle", "createdAt": "2025-01-01T00:00:00Z", "updatedAt": "2025-01-01T00:00:00Z" } } ``` `status` is one of `idle`, `running`, `awaiting_input`, `completed`, `failed`. ### `POST /playground/sessions/:sid/messages` Send a user message — this triggers a run. The message is created with status `pending` and processed asynchronously; poll the messages endpoint to track it. **Request:** ```json theme={null} { "content": "Process this file", "inputs": { "file_path": "/data/input.csv" } } ``` **Response (`200`):** ```json theme={null} { "success": true, "data": { "id": "msg-1", "sessionId": "sess-abc123", "role": "user", "content": "Process this file", "status": "pending", "sequenceNumber": 1, "timestamp": "2025-01-01T00:00:00Z" } } ``` Returns `409` if the previous message in the session is still processing — messages are handled in sequence. ### `GET /playground/sessions/:sid/messages` Poll for new messages. Supports `since`, `latest`, and `before` query parameters for pagination. **Response:** ```json theme={null} { "success": true, "data": [ { "id": "msg-2", "sessionId": "sess-abc123", "role": "assistant", "content": "Done — processed 42 rows.", "status": "completed", "sequenceNumber": 2, "timestamp": "2025-01-01T00:00:03Z" } ], "hasMore": false, "sessionStatus": "completed" } ``` `role` is `user`, `assistant`, `system`, or `log`. `sessionStatus` tells the poller when to stop. ### `GET /interrupts/:id` Fetch a pending human-in-the-loop interrupt. **Response:** ```json theme={null} { "success": true, "data": { "id": "int-abc123", "type": "confirmation", "status": "pending", "nodeId": "node-3", "executionId": "exec-abc123", "allowCancel": true, "config": { "message": "Do you approve this action?", "confirm_label": "Approve", "cancel_label": "Reject" }, "createdAt": "2025-01-01T00:00:00Z" } } ``` `type` is `confirmation`, `choice`, `text`, `form`, or `review`; `status` is `pending`, `resolved`, or `cancelled`. ### `POST /interrupts/:id` Resolve an interrupt by submitting the user's response. The `value` type depends on the interrupt `type`. **Request:** ```json theme={null} { "value": true } ``` | Interrupt `type` | `value` shape | | ---------------- | --------------------------------- | | `confirmation` | `boolean` | | `choice` | `string` or `string[]` | | `text` | `string` | | `form` | `object` matching the form schema | | `review` | decisions map + summary | **Response:** the updated interrupt (same shape as `GET /interrupts/:id`, now with `status: "resolved"`). ### `GET /system/config` Public runtime configuration the editor reads on mount. **Response:** ```json theme={null} { "success": true, "data": { "version": "1.0.0", "features": { "playground": true, "interrupts": true }, "limits": { "maxWorkflowNodes": 100, "maxConcurrentExecutions": 5 } } } ``` ## CORS Configuration FlowDrop runs in the browser, so your backend must allow cross-origin requests if served from a different domain: ```typescript theme={null} // Express example import cors from 'cors'; app.use( cors({ origin: 'http://localhost:5173', // your frontend URL methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'] }) ); ``` ## Error Response Format When an operation fails, return a consistent error format: ```json theme={null} { "success": false, "error": "Workflow not found", "code": "NOT_FOUND", "message": "No workflow exists with ID 'wf-xyz'" } ``` FlowDrop's API client expects standard HTTP status codes: * `200` — success * `201` — created * `400` — bad request (validation error) * `401` — unauthorized (triggers `onApiError` and auth provider's `onUnauthorized`) * `404` — not found * `500` — server error ## Static vs. Dynamic Node Serving For simple use cases, you can serve node metadata as **static JSON**: ```typescript theme={null} // nodes.json — serve as a static file const nodes = [ { id: 'text_input', name: 'Text Input', ... }, { id: 'http_request', name: 'HTTP Request', ... } ]; app.get('/api/flowdrop/nodes', (req, res) => { res.json({ success: true, data: nodes }); }); ``` For dynamic use cases, load from a database: ```typescript theme={null} app.get('/api/flowdrop/nodes', async (req, res) => { const nodes = await db .collection('nodes') .find({ ...(req.query.category && { category: req.query.category }) }) .toArray(); res.json({ success: true, data: nodes }); }); ``` ## Verify your backend (conformance checklist) FlowDrop exercises your API in a predictable order on mount. Run these requests against your base URL to confirm the contract **before** wiring up the editor — they mirror exactly what the editor does. Replace the `BASE` value with your own. ```bash theme={null} BASE=http://localhost:3001/api/flowdrop # 1. Health — FlowDrop checks this first on mount curl -s $BASE/health # 2. Nodes — populates the sidebar curl -s $BASE/nodes # 3. Categories — sidebar groups curl -s $BASE/categories # 4. Port config — connection compatibility rules curl -s $BASE/port-config # 5. Create a workflow (note the returned id) curl -s -X POST $BASE/workflows \ -H 'Content-Type: application/json' \ -d '{"name":"Smoke test","nodes":[],"edges":[]}' # 6. Load it back (use the id from step 5) curl -s $BASE/workflows/ # 7. Update it curl -s -X PUT $BASE/workflows/ \ -H 'Content-Type: application/json' \ -d '{"name":"Smoke test (edited)","nodes":[],"edges":[]}' ``` Your backend conforms when: * [ ] `GET /health` returns `200` with `{ "status": "ok" }` * [ ] Every response uses the `{ "success": true, "data": ... }` envelope (the one exception is `GET /executions/:id`) * [ ] `GET /nodes` data items include at least `id`, `name`, `type`, and `category` * [ ] Workflow `metadata` uses the exact field names `schemaVersion`, `createdAt`, `updatedAt` (not `created_at` / `updated_at`) * [ ] `POST /workflows` returns `201` and echoes a server-assigned `id` * [ ] Errors return the correct HTTP status (`404` missing, `401` unauthorized) with `{ "success": false, "error": ... }` * [ ] CORS allows your frontend origin if the backend runs on a different domain If all seven calls succeed and the checklist passes, mounting the editor against this base URL will load nodes into the sidebar and let you create, edit, and save workflows. ## Next Steps * [Backend: Express.js](/recipes/backend-express) — get a working backend in 15 minutes * [Framework Integration](/guides/integration) — connect FlowDrop to your backend * [API Overview](/reference/api-overview) — complete module and endpoint reference * [OpenAPI Specification](/api-reference/introduction) — full API contract # Deployment Source: https://flowdrop.mintlify.app/guides/integration/deployment Deploy FlowDrop in production with Docker, Node.js, or static hosting. FlowDrop is a frontend library — how you deploy depends on your application architecture. ## Bundle Size & Tree-Shaking Import from the most specific module to minimize bundle size: | Module | Added size (approx.) | | ------------------------------- | --------------------------------- | | `@flowdrop/flowdrop/core` | \~15KB (types & utils only) | | `@flowdrop/flowdrop/form` | \~40KB | | `@flowdrop/flowdrop/editor` | \~200KB (includes @xyflow/svelte) | | `@flowdrop/flowdrop/form/code` | \~300KB (CodeMirror) | | `@flowdrop/flowdrop/playground` | \~250KB (editor + chat) | | `@flowdrop/flowdrop` (full) | \~500KB+ | Only import `form/code` and `form/markdown` if you actually need code/template editing. ## CSS Import Always import FlowDrop styles. Without this, the editor renders blank: ```typescript theme={null} import '@flowdrop/flowdrop/styles'; ``` Or in CSS: ```css theme={null} @import '@flowdrop/flowdrop/styles'; ``` ## Svelte / SvelteKit FlowDrop is a Svelte 5 library, so it integrates natively: ```svelte theme={null} ``` For SvelteKit, ensure FlowDrop runs only on the client (it requires DOM): ```svelte theme={null} {#if browser} {/if} ``` ## React / Vue / Angular / Vanilla JS Use the [Mount API](/reference/mount-api) to embed FlowDrop in any container: ```typescript theme={null} import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; import '@flowdrop/flowdrop/styles'; const app = await mountFlowDropApp(document.getElementById('editor'), { endpointConfig: createEndpointConfig('/api/flowdrop') }); // Cleanup on page unmount app.destroy(); ``` ### React Example ```tsx theme={null} import { useEffect, useRef } from 'react'; import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; import '@flowdrop/flowdrop/styles'; function FlowDropEditor({ apiUrl }) { const containerRef = useRef(null); const appRef = useRef(null); useEffect(() => { mountFlowDropApp(containerRef.current, { endpointConfig: createEndpointConfig(apiUrl) }).then((app) => { appRef.current = app; }); return () => appRef.current?.destroy(); }, [apiUrl]); return
; } ``` ## Docker Package your frontend and backend together: ```dockerfile theme={null} # Build frontend FROM node:20 AS frontend WORKDIR /app COPY . . RUN npm install && npm run build # Serve with backend FROM node:20 WORKDIR /app COPY --from=frontend /app/dist ./public COPY backend/ ./backend RUN cd backend && npm install EXPOSE 3000 CMD ["node", "backend/index.js"] ``` Configure the API base URL via environment variable: ```typescript theme={null} const apiUrl = import.meta.env.VITE_API_URL || '/api/flowdrop'; const endpointConfig = createEndpointConfig(apiUrl); ``` ## Static Hosting If your frontend is a static SPA (Vite, SvelteKit adapter-static, etc.): 1. Build your app: `npm run build` 2. Deploy the `dist/` folder to any static host (Vercel, Netlify, S3, etc.) 3. Configure CORS on your backend to allow the static host's origin ```typescript theme={null} // Backend CORS configuration app.use( cors({ origin: ['https://your-app.vercel.app', 'http://localhost:5173'], methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'] }) ); ``` ## Environment Variables Common configuration to externalize: | Variable | Purpose | Example | | ----------------- | ----------------------- | --------------- | | `VITE_API_URL` | Backend API base URL | `/api/flowdrop` | | `VITE_AUTH_TOKEN` | Static auth token (dev) | `dev-token-123` | ```typescript theme={null} const endpointConfig = createEndpointConfig(import.meta.env.VITE_API_URL || '/api/flowdrop'); ``` ## Next Steps * [Framework Integration](/guides/integration) — framework-specific setup * [Authentication Patterns](/guides/integration/authentication-patterns) — secure your deployment * [Backend Implementation](/guides/integration/backend-implementation) — build your API # Human-in-the-loop Source: https://flowdrop.mintlify.app/guides/interrupts Pause workflows for human approval, input, or review. FlowDrop's interrupt system enables workflows to pause execution and request user input before continuing. This is essential for approval workflows, data collection, decision points, and quality control. Every interrupt prompt below ships as a live, interactive component. Explore its states, props, and edge cases in [FlowDrop Storybook](https://flowdrop-demo.netlify.app/storybook/). ## Interrupt types ### Confirmation Simple yes/no prompt for binary decisions: Interrupt prompts rendered in chat showing confirmation buttons. ```json theme={null} { "interrupt_type": "confirmation", "message": "Do you approve sending this email to 150 recipients?", "confirm_label": "Yes, send email", "cancel_label": "No, cancel" } ``` **Response**: `boolean` Try the confirmed, declined, submitting, and error states. ### Choice Single or multiple selection from predefined options: ```json theme={null} { "interrupt_type": "choice", "message": "Select the output format:", "options": [ { "value": "json", "label": "JSON", "description": "Structured data" }, { "value": "csv", "label": "CSV", "description": "Spreadsheet format" } ], "multiple": false } ``` **Response**: `string` (single) or `string[]` (multiple) Compare single-select and multi-select variants. ### Text input Free-form text entry: ```json theme={null} { "interrupt_type": "text_input", "message": "Provide additional context:", "placeholder": "Enter your notes...", "multiline": true, "max_length": 1000 } ``` **Response**: `string` See single-line, multiline, and length-constrained inputs. ### Form Complex data entry using JSON Schema: ```json theme={null} { "interrupt_type": "form", "message": "Complete the configuration:", "schema": { "type": "object", "properties": { "priority": { "type": "string", "enum": ["low", "medium", "high"] }, "notify": { "type": "boolean", "title": "Send notification" } } }, "default_values": { "priority": "medium", "notify": true } } ``` **Response**: `object` (matching schema structure) ### Review Review proposed field changes with per-field accept/reject decisions and visual diffs: ```json theme={null} { "interrupt_type": "review", "message": "Review these proposed changes:", "changes": [ { "field": "title", "label": "Page Title", "original": "About Us", "proposed": "About Our Company" }, { "field": "body", "label": "Body Content", "original": "

Welcome to our site.

", "proposed": "

Welcome to our company website.

" } ] } ``` **Response**: `ReviewResolution` with per-field decisions and summary counts. Inspect per-field diffs, accept/reject controls, and many-change views. ## Architecture ```mermaid theme={null} sequenceDiagram participant W as Workflow Execution participant B as Backend participant F as Frontend participant U as User W->>B: Pause & create interrupt B->>F: Send interrupt request F->>U: Render prompt U->>F: Submit response F->>B: Resolve interrupt B->>W: Resume workflow ``` ## Frontend integration The `ChatPanel` automatically detects and renders interrupts in messages. For manual integration: ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; import { InterruptService } from '@flowdrop/flowdrop/playground'; const fd = getInstance(); const interruptService = InterruptService.getInstance(); // Read pending interrupts reactively const pending = $derived(fd.interrupts.getPending()); // Resolve an interrupt async function resolveInterrupt(interruptId: string, value: unknown) { const result = fd.interrupts.startSubmit(interruptId, value); if (!result.valid) return; try { // Pass the instance's endpoint config first await interruptService.resolveInterrupt(fd.api.config, interruptId, value); fd.interrupts.submitSuccess(interruptId); } catch (error) { fd.interrupts.submitFailure(interruptId, String(error)); } } ``` ## Using prompt components directly ```svelte theme={null} handleConfirm()} onCancel={() => handleCancel()} /> ``` ## Backend integration ### Message metadata format When a workflow requires input, the backend sends a message with interrupt metadata: ```json theme={null} { "id": "msg-123", "role": "assistant", "content": "I need your approval to proceed.", "metadata": { "type": "interrupt_request", "interrupt_id": "int-456", "interrupt_type": "confirmation", "message": "Do you approve this action?", "confirm_label": "Approve", "cancel_label": "Reject" } } ``` ### API endpoints | Endpoint | Method | Purpose | | -------------------------------------- | ------ | ----------------------- | | `/interrupts/{id}` | GET | Get interrupt details | | `/interrupts/{id}` | POST | Resolve interrupt | | `/interrupts/{id}/cancel` | POST | Cancel interrupt | | `/playground/sessions/{id}/interrupts` | GET | List session interrupts | ## State management The interrupt store uses a state machine with these transitions: * **idle** — Awaiting user input * **submitting** — User response being sent * **resolved** — Successfully processed * **error** — Submission failed (can retry) Resolved interrupts remain visible but disabled, showing the user's selection. ## Best practices 1. **Clear messages** — Write actionable prompts ("Do you approve sending this email to 150 recipients?" not "Proceed?") 2. **Meaningful labels** — Use descriptive button labels ("Yes, send email" not "Yes") 3. **Default values** — Provide sensible defaults for form fields 4. **Cancel behavior** — Only set `allowCancel: false` for mandatory interrupts 5. **Error handling** — Always handle resolution failures gracefully # Multiple instances Source: https://flowdrop.mintlify.app/guides/multiple-instances Run several isolated FlowDrop editors or playgrounds on a single page. FlowDrop supports multiple editor instances on one page. Each mount gets its own **state container** — workflow data, undo/redo history, playground sessions, interrupts, and panel state are fully isolated between instances. Editing, deleting, or undoing in one editor never affects another, and destroying one leaves its siblings working. ## Quick start: two editors via the mount API ```typescript theme={null} import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; const left = await mountFlowDropApp(document.getElementById('editor-left'), { workflow: workflowA, nodes: nodeTypes, instanceId: 'left' // scopes draft/panel storage keys }); const right = await mountFlowDropApp(document.getElementById('editor-right'), { workflow: workflowB, nodes: nodeTypes, instanceId: 'right' }); // Each handle controls only its own editor: left.isDirty(); // false right.getWorkflow(); // workflowB left.destroy(); // `right` keeps working ``` ## Quick start: two editors in Svelte Create an instance per editor with `createFlowDropInstance()` and pass it via the `instance` prop: ```svelte theme={null} ``` `WorkflowEditor`, `Playground`, `PlaygroundStudio`, `PlaygroundModal`, and `PlaygroundApp` accept the same `instance` prop. ## The default instance You only need `instanceId` when mounting **more than one** editor. The first mount without an `instanceId` becomes the **page-default instance**, which is what `getInstance()` resolves to for single-editor embeds with no explicit provider. Instances are the API: there are no module-level store singletons. Resolve the owning instance with `getInstance()` inside the component tree, or hold the mount handle's `.instance` outside it. Each instance keeps its own scoped localStorage keys (`flowdrop:draft:default:`, `fd-pipeline-panel-open:default` for the default instance). Additional mounts without an explicit id get auto-generated ones (`fd-1`, `fd-2`, …). Prefer explicit ids whenever drafts are enabled, so each editor's drafts land under a stable, predictable key. ## Instance-scoped storage keys | State | Default instance | Instance with `instanceId: 'left'` | | ------------------- | ------------------------------------- | ---------------------------------- | | Workflow drafts | `flowdrop:draft:default:` | `flowdrop:draft:left:` | | Pipeline panel open | `fd-pipeline-panel-open:default` | `fd-pipeline-panel-open:left` | | Pipeline view mode | `fd-pipeline-view-mode:default` | `fd-pipeline-view-mode:left` | `clearAllDrafts()` sweeps everything under `flowdrop:draft:` — instance sub-namespaces included — so a logout handler still clears all editors at once. ## Accessing instance state programmatically Inside FlowDrop's component tree, resolve the current instance with `getInstance()` (during component init): ```svelte theme={null} ``` Outside the tree, hold on to the container you created (or the mount handle). The `FlowDropInstance` exposes `workflow`, `history`, `historyBindings`, `playground`, `interrupts`, `categories`, `portCoordinates`, `pipelinePanel`, and `destroy()`. ## What stays page-global (by design) Some state is deliberately shared across all instances on a page: * **Theme and settings** — one `data-theme` and one settings store per page. This includes UI toggles like console-open, sidebar-collapsed, and the bottom-panel tab: toggling them in one editor affects all editors. * **Port-compatibility config** — each instance owns a `PortCompatibilityChecker` at `fd.portCompatibility`, re-initialized by mount from the backend's port config; the last mount's fetched config wins for shared backends. * **API endpoint config** — each instance owns an `ApiContext` at `fd.api`, configured by mount via `fd.api.configure(config, authProvider)`. * **Playground live polling** — playground session/message *state* is isolated per instance, but the polling timer is page-global: only one playground actively polls at a time. If two playgrounds need concurrent live updates, push responses yourself via the mount handle's `pushMessages()` with your own transport (WebSocket/SSE). ## SSR (SvelteKit) Server rendering works without any extra setup. During SSR, the provider components (`App`, `WorkflowEditor`, …) create a fresh per-render instance automatically. The page-default instance is **browser-only** — module-level mutable state on the server would leak between requests. So FlowDrop gives each render its own instance. Resolving the default instance (e.g. via `getInstance()`) during SSR outside a FlowDrop component tree throws with an explanatory error instead of leaking state. ## Troubleshooting **Two editors share state** — two un-keyed mounts both claimed the default instance. Pass explicit `instanceId`s and resolve state via `getInstance()` / the mount handle's `.instance`. **Drafts collide between editors** — both mounts omitted `instanceId`, so the second got an auto-generated id that changes across reloads. Pass stable explicit ids. # Node structure Source: https://flowdrop.mintlify.app/guides/node-json The JSON format for workflow nodes — WorkflowNode, NodeMetadata, config values, and node types. Every item in a workflow's `nodes` array is a **WorkflowNode**. It combines canvas positioning with the node's type definition (metadata) and user-configured values. ## WorkflowNode ```typescript theme={null} interface WorkflowNode { id: string; type: string; position: { x: number; y: number }; deletable?: boolean; data: { label: string; config: ConfigValues; metadata: NodeMetadata; isProcessing?: boolean; error?: string; executionInfo?: NodeExecutionInfo; extensions?: NodeExtensions; }; } ``` | Field | Type | Description | | ----------------- | -------------- | --------------------------------------------------------------------------------------------------------------- | | `id` | `string` | Unique instance ID, typically `"{node_type_id}.{n}"` (e.g., `"text_input.1"`). | | `type` | `string` | Internal renderer type. Always `"universalNode"` — the visual appearance is controlled by `data.metadata.type`. | | `position` | `{x, y}` | Canvas coordinates in pixels. | | `deletable` | `boolean` | Whether the user can delete this node. Defaults to `true`. | | `data.label` | `string` | Display name shown in the node header. | | `data.config` | `ConfigValues` | User-configured settings for this instance. | | `data.metadata` | `NodeMetadata` | The node type definition — inputs, outputs, config schema, etc. | | `data.extensions` | `object` | Per-instance extension data for plugins. | ## NodeMetadata The `metadata` object defines what the node *is* — its capabilities, ports, and configuration schema. This is the same structure your backend returns from the `/nodes` API. ```typescript theme={null} interface NodeMetadata { node_type_id: string; name: string; type?: NodeType; supportedTypes?: NodeType[]; description: string; category: NodeCategory; version: string; icon?: string; color?: string; badge?: string; portDataType?: string; inputs: NodePort[]; outputs: NodePort[]; configSchema?: ConfigSchema; uiSchema?: UISchemaElement; config?: Record; tags?: string[]; formats?: WorkflowFormat[]; configEdit?: ConfigEditOptions; extensions?: NodeExtensions; } ``` ### Key Fields | Field | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `node_type_id` | Machine name (e.g., `"content_loader"`, `"ai_analyzer"`). | | `type` | Visual rendering style — see [Node Types](#node-types) below. | | `supportedTypes` | Alternative visual types the user can switch between (e.g., `["tool", "default"]`). | | `category` | Sidebar grouping — see [Categories](#categories) below. | | `icon` | [Iconify](https://icon-sets.iconify.design/) icon name (e.g., `"mdi:brain"`, `"mdi:text"`). See [Icons reference](/reference/icons). | | `color` | CSS color for the node accent (e.g., `"#9C27B0"`). | | `badge` | Short label badge in the header (e.g., `"TOOL"`, `"API"`, `"LLM"`). | | `portDataType` | Default port data type for tool nodes. Defaults to `"tool"`. | | `inputs` / `outputs` | Port definitions — see [Port System & Data Types](/guides/port-system). | | `configSchema` | JSON Schema driving the config form — see [Configuration Schema](/guides/config-schema). | | `uiSchema` | Layout hints for form rendering (groups, ordering). | | `config` | Default values for the configuration form. | | `formats` | Which workflow formats this node is compatible with. Omit for universal nodes. | | `configEdit` | Dynamic schema endpoint or external edit link for advanced configuration. | ## Node Types The `type` field controls how the node renders on the canvas: | Type | Purpose | | ---------- | ----------------------------------------------------------------- | | `default` | Full-featured — input/output port lists, icon, label, description | | `simple` | Compact — header with icon and description | | `square` | Icon-only — minimal design for simple operations | | `tool` | AI agent tools — tool metadata with badge label | | `gateway` | Branching logic — conditional output paths | | `terminal` | Start/end — circular nodes for workflow entry and exit | | `note` | Documentation — markdown sticky notes (no execution) | Custom node types can also be registered. See the [Custom Nodes](/guides/custom-nodes) guide. ## Categories Built-in categories for sidebar grouping: `triggers` · `inputs` · `outputs` · `prompts` · `models` · `processing` · `logic` · `data` · `tools` · `helpers` · `vector stores` · `embeddings` · `memories` · `agents` · `ai` You can also use any custom string — the editor will create a new sidebar group automatically. ## ConfigValues The `config` object on each node instance holds the user's configured settings: ```typescript theme={null} interface ConfigValues { /** Dynamic input ports for user-defined input handles */ dynamicInputs?: DynamicPort[]; /** Dynamic output ports for user-defined output handles */ dynamicOutputs?: DynamicPort[]; /** Branches for gateway node conditional output paths */ branches?: Branch[]; /** Any other properties defined in configSchema */ [key: string]: unknown; } ``` Most fields come from the node's `configSchema`. Three special properties trigger editor behavior: | Property | Effect | | ---------------- | --------------------------------------------------- | | `dynamicInputs` | Creates additional input port handles at runtime. | | `dynamicOutputs` | Creates additional output port handles at runtime. | | `branches` | Creates conditional output paths for gateway nodes. | Per-instance overrides are also supported via `instanceTitle`, `instanceDescription`, and `instanceBadge` — these override the metadata values for display. ## Example: Simple Input Node ```json theme={null} { "id": "text_input.1", "type": "universalNode", "position": { "x": 0, "y": 100 }, "data": { "label": "Text Input", "config": { "placeholder": "Enter text...", "defaultValue": "" }, "metadata": { "node_type_id": "text_input", "name": "Text Input", "type": "simple", "description": "Simple text input for user data", "category": "inputs", "icon": "mdi:text", "color": "#22c55e", "version": "1.0.0", "inputs": [], "outputs": [ { "id": "text", "name": "Text", "type": "output", "dataType": "string" } ], "configSchema": { "type": "object", "properties": { "placeholder": { "type": "string", "title": "Placeholder", "default": "Enter text..." } } } } } } ``` ## Example: Gateway Node with Branches ```json theme={null} { "id": "router.1", "type": "universalNode", "position": { "x": 300, "y": 200 }, "data": { "label": "Priority Router", "config": { "branches": [ { "name": "high", "label": "High Priority", "condition": "priority > 8" }, { "name": "medium", "label": "Medium Priority", "condition": "priority >= 4" }, { "name": "default", "label": "Default", "isDefault": true } ] }, "metadata": { "node_type_id": "priority_router", "name": "Priority Router", "type": "gateway", "description": "Route items by priority level", "category": "logic", "icon": "mdi:source-branch", "version": "1.0.0", "inputs": [ { "id": "input", "name": "Input", "type": "input", "dataType": "json" } ], "outputs": [] } } } ``` Gateway nodes generate output ports dynamically from the `branches` array. Each branch becomes an output handle named `router.1-output-{branch.name}`. ## Next Steps * [Edge Structure](/guides/edge-json) — how connections reference node and port IDs * [Port System & Data Types](/guides/port-system) — input/output port definitions and data types * [Configuration Schema](/guides/config-schema) — JSON Schema that powers config forms * [Node Types](/guides/node-types) — visual appearance and behavior in the editor # Node types Source: https://flowdrop.mintlify.app/guides/node-types Built-in node types and the port system in FlowDrop. FlowDrop ships with 9 built-in node types, each designed for specific workflow patterns. ## Built-in types | Type | Purpose | Description | | ---------- | ------------------- | ------------------------------------------------- | | `default` | Full-featured nodes | Input/output port lists, icon, label, description | | `simple` | Compact layout | Header with icon and description, space-efficient | | `square` | Icon-only | Minimal design for simple operations | | `tool` | AI agent tools | Tool metadata with badge label | | `gateway` | Branching logic | Conditional output paths with multiple branches | | `terminal` | Start/end points | Circular nodes for workflow entry and exit | | `idea` | Conceptual flow | BPMN-like flow nodes for conceptual diagrams | | `note` | Documentation | Markdown-enabled sticky notes (no execution) | | `atom` | Value supplier | Minimalist label-only pill that supplies a value | Catalog of FlowDrop's nine built-in node types: default, simple, square, tool, gateway, terminal, idea, note, and atom. For the complete JSON structure, see [Node Structure](/guides/node-json). For port definitions and data types, see [Port System & Data Types](/guides/port-system). ### `default` Full-featured node with input/output port lists, icon, label, and description. Suitable for most workflow steps. ### `simple` Compact layout with header icon and description. Space-efficient for nodes that don't need visible ports. ### `square` Icon-only minimal design. Ideal for simple operations where the icon alone conveys the purpose. ### `tool` Designed for AI agent tools. Displays tool metadata including version, badge label, and description. ### `gateway` Branching logic node with conditional output paths. Supports multiple branches for routing workflow execution. ### `terminal` Circular start/end point nodes. Used to mark workflow entry and exit points. ### `idea` Conceptual idea node for BPMN-like flow diagrams. Lightweight node with a colored top border accent. ### `note` Markdown-enabled sticky notes for documentation. These are non-executing nodes meant for annotations. ### `atom` A minimalist, label-only node that renders as a compact pill hugging its content — designed for "supplies a value" nodes such as a Constant (and Cast in the future). The atom owns no domain semantics of its own; what it shows and the data type it emits are driven entirely by configuration, so a single node type can back many small value-providing nodes. The body text resolves in order: 1. The value of the config key named by `valueKey` (using the field's `oneOf` titles when present) 2. The node's `label` 3. The `placeholder`, rendered dimmed The bound output port's data type can be driven dynamically from config, and a server- or definition-provided `color` accents the pill border. Display and behavior are configured through `extensions.ui.atom` (`AtomUIConfig`): | Property | Type | Description | | -------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `valueKey` | `string` | Config key whose value becomes the node body. Falls back to `data.label`. | | `valueTypeKey` | `string` | Config key holding the selected value's type (a port `dataType` id). The bound output port adopts this type. | | `outputPortId` | `string` | Output port id driven by `valueTypeKey`. Defaults to the first output port. | | `shape` | `'pill' \| 'rectangle'` | Body shape. `'pill'` (default) is fully rounded; `'rectangle'` is lightly rounded. | | `prefix` | `string` | Dimmed affordance rendered before the body (e.g. `'→ '`). Stays visible while the body ellipsizes; hidden in the empty state. | | `placeholder` | `string` | Text shown (dimmed) when the resolved body value is empty or unset. | | `maxWidth` | `number` | Max body width in px before the label ellipsizes. | ```json theme={null} { "type": "atom", "extensions": { "ui": { "atom": { "valueKey": "value", "valueTypeKey": "valueType", "shape": "pill", "prefix": "= ", "placeholder": "Set a value…", "maxWidth": 120 } } } } ``` Ports are resolved from the node's definition like any other node, so atoms participate in connection validation and proximity connect normally. The pill is a fixed 40px tall; when it has a single port, the handle centers vertically, and multiple ports distribute evenly. ## Connection validation FlowDrop validates connections automatically: * **Type compatibility** — only compatible port data types can connect * **Cycle detection** — prevents circular dependencies (O(V+E) algorithm) * **Loopback prevention** — nodes cannot connect to themselves ### Proximity connect When dragging a node near compatible ports, FlowDrop can auto-connect them. This is configurable via editor settings: ```typescript theme={null} const app = await mountFlowDropApp(container, { settings: { editor: { proximityConnect: true, proximityConnectDistance: 50 // pixels } } }); ``` ## Dynamic ports Nodes can define user-configurable ports through special config properties: ```json theme={null} { "dynamicInputs": { "type": "array", "title": "Input Ports", "items": { "type": "object", "properties": { "id": { "type": "string", "title": "Port ID" }, "name": { "type": "string", "title": "Port Name" }, "dataType": { "type": "string", "title": "Data Type", "default": "any" } } } } } ``` ## Custom node types Beyond the built-in types, you can register custom Svelte components as node types. See the [Custom Nodes guide](/guides/custom-nodes) for details. # Performance Source: https://flowdrop.mintlify.app/guides/performance Bundle size, lazy loading, SSR guard patterns, and tips for large workflows in FlowDrop. FlowDrop's editor is a rich component with many dependencies. This guide covers strategies to minimize bundle impact and ensure smooth performance at scale. ## Bundle sizes Approximate gzip sizes by entry point: | Entry Point | Approx. gzip size | Notes | | ---------------------------------- | ----------------- | ----------------------------------------- | | `@flowdrop/flowdrop/core` | \~10 KB | Types and utilities only — no heavy deps | | `@flowdrop/flowdrop/editor` | \~180 KB | Includes `@xyflow/svelte`, Svelte runtime | | `@flowdrop/flowdrop/form` | \~25 KB | Form fields without CodeMirror | | `@flowdrop/flowdrop/form/code` | \~350 KB | Includes CodeMirror and language packs | | `@flowdrop/flowdrop/form/markdown` | \~300 KB | Includes CodeMirror markdown mode | | `@flowdrop/flowdrop/playground` | \~200 KB | Editor + session management | | `@flowdrop/flowdrop` | \~400 KB | Full bundle (avoid in production) | Use specific entry points rather than `@flowdrop/flowdrop` to tree-shake unused modules. ## Lazy loading the editor The editor bundle is large. Load it only when the user navigates to the editor page: ```javascript theme={null} // Vanilla JS — dynamic import async function mountEditor(container) { const [{ mountFlowDropApp }, { createEndpointConfig }] = await Promise.all([ import('@flowdrop/flowdrop/editor'), import('@flowdrop/flowdrop/core') ]); await import('@flowdrop/flowdrop/styles'); return mountFlowDropApp(container, { endpointConfig: createEndpointConfig('/api/flowdrop') }); } ``` ### React ```jsx theme={null} import React, { Suspense, lazy } from 'react'; const FlowDropEditor = lazy(() => import('./FlowDropEditor')); export function EditorPage() { return ( Loading editor...
}> ); } ``` Where `FlowDropEditor` is a wrapper component that calls `mountFlowDropApp` in a `useEffect`. ## Deferring CodeMirror If you use code or markdown fields but not on every page, load them on demand: ```javascript theme={null} async function mountEditorWithCodeFields(container, endpointConfig) { // Load form fields that require CodeMirror only when needed const [{ mountFlowDropApp }, { createEndpointConfig }] = await Promise.all([ import('@flowdrop/flowdrop/editor'), import('@flowdrop/flowdrop/core') ]); // This registers code/markdown fields into the registry await import('@flowdrop/flowdrop/form/code'); await import('@flowdrop/flowdrop/form/markdown'); return mountFlowDropApp(container, { endpointConfig }); } ``` ## SSR guard patterns FlowDrop accesses `window`, `document`, and browser APIs — it cannot run on the server. Always guard your mount calls. ### SvelteKit ```javascript theme={null} import { browser } from '$app/environment'; import { onMount } from 'svelte'; let app; onMount(async () => { if (!browser) return; const { mountFlowDropApp } = await import('@flowdrop/flowdrop/editor'); app = await mountFlowDropApp(document.getElementById('editor'), { ... }); return () => app?.destroy(); }); ``` ### Next.js (App Router) ```javascript theme={null} 'use client'; import dynamic from 'next/dynamic'; const FlowDropEditor = dynamic(() => import('../components/FlowDropEditor'), { ssr: false }); ``` ### Nuxt ```html theme={null} ``` ### Vite `optimizeDeps` If you see `Failed to resolve import` errors during development, exclude FlowDrop from Vite's pre-bundling: ```javascript theme={null} // vite.config.ts export default defineConfig({ optimizeDeps: { exclude: ['@flowdrop/flowdrop', '@xyflow/svelte'] } }); ``` See the [Installation guide](/docs/quickstart) for more setup tips. ## Large workflow performance For workflows with many nodes (50+): ### Use batch updates ```javascript theme={null} const { actions } = app.instance.workflow; // Apply nodes and edges in a single reactive update actions.batchUpdate({ nodes: [...existingNodes, nodeA, nodeB], edges: [...existingEdges, edge] }); ``` ### Use history transactions Group related changes into a single undo step: ```javascript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); fd.historyBindings.startTransaction(fd.workflow.current, 'Bulk update'); // ... multiple changes fd.historyBindings.commitTransaction(); ``` ### Disable auto-save for programmatic updates When making many changes programmatically, temporarily disable auto-save: ```javascript theme={null} await mountFlowDropApp(container, { endpointConfig, features: { autoSaveDraft: false, // disable localStorage auto-save showToasts: false // disable toast notifications } }); ``` See [Mount API](/reference/mount-api) for the full `features` options. # Pipeline views Source: https://flowdrop.mintlify.app/guides/pipeline-views Monitor workflow execution with graph, kanban, and table views — and customize the kanban layout from your backend. The pipeline viewer displays real-time execution state for a running workflow. It sits inside the playground and offers three view modes: **Graph**, **Kanban**, and **Table**. ## Node execution statuses Every node in a running pipeline is assigned one of these statuses by the backend: | Status | Meaning | | ------------- | --------------------------------------------------------------- | | `idle` | Job created; dependencies not yet met | | `pending` | Ready to run; waiting in queue | | `running` | Currently executing | | `paused` | Execution paused (e.g. rate limit, wait step) | | `interrupted` | Waiting for human input (human-in-the-loop) | | `completed` | Finished successfully | | `skipped` | Not executed — workflow completed/branched before this node ran | | `failed` | Execution failed | | `cancelled` | Explicitly cancelled | ## Kanban view The kanban board groups nodes into columns by status. By default it uses a 4-column layout: | Column | Statuses | Logic | | --------------- | ---------------------------------- | ------------------ | | **Pending** | `idle`, `pending` | Not yet started | | **In Progress** | `running`, `paused`, `interrupted` | Actively in flight | | **Done** | `completed`, `skipped` | Terminal success | | **Failed** | `failed`, `cancelled` | Terminal failure | When a column maps multiple statuses, each card displays a **status pill** showing the node's exact status. This makes it easy to distinguish, for example, a `paused` node from a `running` one inside the same "In Progress" column. ## Customising the kanban layout Your backend can override the column layout per pipeline by returning a `kanban_config` object in the `GET /pipelines/:id` response. If this field is absent the frontend falls back to the 4-column default above. ### Response shape ```json theme={null} { "status": "running", "node_statuses": { ... }, "job_status_summary": { ... }, "kanban_config": { "columns": [ { "key": "pending", "label": "Pending", "statuses": ["idle", "pending"], "icon": "mdi:clock-outline", "color": "var(--fd-muted-foreground)" }, { "key": "in_progress", "label": "In Progress", "statuses": ["running", "paused", "interrupted"], "icon": "mdi:play-circle-outline", "color": "var(--fd-warning)" }, { "key": "done", "label": "Done", "statuses": ["completed", "skipped"], "icon": "mdi:check-circle", "color": "var(--fd-success)" }, { "key": "failed", "label": "Failed", "statuses": ["failed", "cancelled"], "icon": "mdi:alert-circle", "color": "var(--fd-error)" } ] } } ``` ### Column fields | Field | Type | Required | Description | | ---------- | ---------- | -------- | ------------------------------------------------------------------------------------ | | `key` | `string` | ✓ | Unique column identifier | | `label` | `string` | ✓ | Column heading shown in the UI | | `statuses` | `string[]` | ✓ | One or more node statuses that belong to this column | | `icon` | `string` | | [Iconify](https://icon-sets.iconify.design/) icon name, e.g. `mdi:check-circle` | | `color` | `string` | | CSS color for the column accent — any valid CSS value, including `var(--fd-success)` | ### Status pill behaviour The status pill on a card is shown automatically when a column's `statuses` array has more than one entry. Single-status columns are assumed to be self-explanatory and show no pill. ### Example: 3-column layout for a simple approval workflow ```json theme={null} "kanban_config": { "columns": [ { "key": "waiting", "label": "Waiting", "statuses": ["idle", "pending", "interrupted"], "icon": "mdi:clock-outline", "color": "var(--fd-muted-foreground)" }, { "key": "active", "label": "Active", "statuses": ["running", "paused"], "icon": "mdi:play-circle-outline", "color": "var(--fd-warning)" }, { "key": "terminal", "label": "Terminal", "statuses": ["completed", "skipped", "failed", "cancelled"], "icon": "mdi:flag-checkered", "color": "var(--fd-foreground)" } ] } ``` ## Table view The table view lists all nodes sorted by activity (running nodes first), with expandable rows showing execution details — last executed time, duration, and error message. ## Custom views Register additional views alongside the built-in three by passing a `pipelineViews` array. Each entry needs a unique `key`, a toggle button `icon` and `label`, and a Svelte component that receives `PipelineViewProps`. ### Svelte component ```svelte theme={null}
Timeline for pipeline {pipelineId}
``` ### Registering via mountPlaygroundStudio ```typescript theme={null} import { mountPlaygroundStudio, createEndpointConfig } from '@flowdrop/flowdrop/playground'; import MyTimelineView from './MyTimelineView.svelte'; await mountPlaygroundStudio(container, { workflowId: 'wf-123', endpointConfig: createEndpointConfig('/api/flowdrop'), pipelineViews: [ { key: 'timeline', label: 'Timeline', icon: 'mdi:chart-timeline-variant', component: MyTimelineView } ] }); ``` ### Registering via Svelte component ```svelte theme={null} ``` ### PipelineViewDef fields | Field | Type | Description | | ----------- | ------------------------------ | --------------------------------------------------------------------- | | `key` | `string` | Unique identifier — must not clash with `graph`, `kanban`, or `table` | | `label` | `string` | Tooltip text on the toggle button | | `icon` | `string` | Iconify icon name | | `component` | `Component` | Svelte component that receives the view props | The selected view key persists in `localStorage` under `fd-pipeline-view-mode`, so switching to a custom view and reloading restores it automatically. ## Graph view The graph view renders the workflow canvas in read-only mode with colour-coded node overlays: | Overlay colour | Statuses | | ----------------- | ---------------------------------- | | Blue (running) | `running`, `paused`, `interrupted` | | Green (completed) | `completed`, `skipped` | | Red (error) | `failed`, `cancelled` | | Amber (pending) | `pending`, `idle` | # Interactive playground Source: https://flowdrop.mintlify.app/guides/playground Test workflows interactively with the FlowDrop playground. The playground provides an interactive testing environment for workflows, featuring a chat interface, session management, and real-time execution feedback. ## Try it In your editor, you can create sessions, send messages, and watch the simulated workflow execute in real time. ## Quick start ### Mount API ```typescript theme={null} import { mountPlayground } from '@flowdrop/flowdrop/playground'; import '@flowdrop/flowdrop/styles'; const playground = await mountPlayground(container, { workflowId: 'my-workflow-id', endpointConfig: createEndpointConfig('/api/flowdrop'), onSessionStatusChange: (newStatus, previousStatus) => { console.log(`Session status: ${previousStatus} -> ${newStatus}`); } }); // Control the playground playground.startPolling(); playground.destroy(); ``` ### Svelte component ```svelte theme={null} ``` ## Features ### Session management The playground supports multiple parallel sessions. Each session represents an independent conversation with the workflow: * Create new sessions * Switch between active sessions * View session history * Delete sessions via dropdown menu ### Chat interface The chat panel displays messages from both the user and the workflow execution: * User messages are shown on the right * System/assistant messages on the left * Execution logs inline in the conversation * Interrupt prompts rendered as interactive UI elements ### Real-time polling The playground polls the backend for new messages during execution. Configure polling behavior: ```typescript theme={null} const playground = await mountPlayground(container, { playgroundConfig: { pollingInterval: 1500, shouldStopPolling: (status) => { // Stop polling on terminal statuses return ['completed', 'failed', 'cancelled'].includes(status); }, isTerminalStatus: (status) => { return ['completed', 'failed', 'cancelled'].includes(status); } } }); ``` ### Configurable lifecycle hooks ```typescript theme={null} import { defaultShouldStopPolling, defaultIsTerminalStatus } from '@flowdrop/flowdrop/playground'; ``` The `awaiting_input` status pauses polling automatically — call `playground.startPolling()` to resume after an interrupt is resolved. ### Push messages For custom transports (WebSocket, SSE), push poll responses directly: ```typescript theme={null} // Push messages from a WebSocket ws.onmessage = (event) => { const data = JSON.parse(event.data); playground.pushMessages(data); }; ``` ## Human-in-the-loop The playground integrates with FlowDrop's interrupt system. See the [Human-in-the-Loop guide](/guides/interrupts) for details on interrupt types and configuration. ## API endpoints The playground uses these backend endpoints: | Endpoint | Method | Purpose | | ------------------------------------- | ------ | ------------------------------- | | `/workflows/{id}/playground/sessions` | POST | Create a new session | | `/workflows/{id}/playground/sessions` | GET | List sessions for a workflow | | `/playground/sessions/{id}` | GET | Get session details | | `/playground/sessions/{id}` | DELETE | Delete session | | `/playground/sessions/{id}/messages` | POST | Send a message (triggers a run) | | `/playground/sessions/{id}/messages` | GET | Poll for messages | # Port system & data types Source: https://flowdrop.mintlify.app/guides/port-system Node port definitions, built-in data types, compatibility rules, dynamic ports, and gateway branches. Ports are the connection points on nodes. Each port has a **data type** that determines which other ports it can connect to and how the connection renders on the canvas. ## NodePort Every node declares its inputs and outputs as an array of `NodePort` objects: ```typescript theme={null} interface NodePort { id: string; name: string; type: 'input' | 'output' | 'metadata'; dataType: string; required?: boolean; description?: string; defaultValue?: unknown; schema?: object; } ``` | Field | Type | Description | | -------------- | --------- | --------------------------------------------------------------------------------------------------------- | | `id` | `string` | Unique port identifier within the node (e.g., `"text"`, `"trigger"`, `"tool"`). | | `name` | `string` | Display name shown next to the port handle. | | `type` | `string` | Port direction: `"input"`, `"output"`, or `"metadata"`. | | `dataType` | `string` | Data type ID — determines color and connection compatibility. | | `required` | `boolean` | Whether a connection to this port is required for execution. | | `description` | `string` | Tooltip text explaining the port's purpose. | | `defaultValue` | `unknown` | Default value when no connection is made. | | `schema` | `object` | Optional JSON Schema describing the data structure on this port. Used for template variable autocomplete. | ### Example ```json theme={null} { "inputs": [ { "id": "content", "name": "Content", "type": "input", "dataType": "string", "required": true, "description": "Text content to process" }, { "id": "trigger", "name": "Trigger", "type": "input", "dataType": "trigger", "required": false } ], "outputs": [ { "id": "result", "name": "Result", "type": "output", "dataType": "json", "description": "Processed output data" } ] } ``` ## Built-in Data Types FlowDrop ships with 21 built-in data types, each with a distinct color on the canvas: ### Basic Types | ID | Name | Category | Description | | --------- | ------- | -------- | ---------------------------- | | `trigger` | Trigger | basic | Control flow of the workflow | | `string` | String | basic | Text data | | `number` | Number | numeric | Numeric data | | `boolean` | Boolean | logical | True/false values | ### Collection Types | ID | Name | Description | | ----------- | ------------- | -------------------------- | | `array` | Array | Ordered list of items | | `string[]` | String Array | Array of strings | | `number[]` | Number Array | Array of numbers | | `boolean[]` | Boolean Array | Array of true/false values | | `json[]` | JSON Array | Array of JSON objects | | `file[]` | File Array | Array of files | | `image[]` | Image Array | Array of images | ### Complex Types | ID | Name | Description | | ------ | ---- | -------------------- | | `json` | JSON | JSON structured data | ### File & Media Types | ID | Name | Description | | ------- | ----- | ----------- | | `file` | File | File data | | `image` | Image | Image data | | `audio` | Audio | Audio data | | `video` | Video | Video data | ### Special Types | ID | Name | Description | | ---------- | -------- | ------------------------------------ | | `tool` | Tool | Tool interface for agent connections | | `url` | URL | Web address | | `email` | Email | Email address | | `date` | Date | Date value | | `datetime` | DateTime | Date and time value | | `time` | Time | Time value | Your backend can extend this list by returning additional data types from the `/port-config` API endpoint. ## Port Data Type Configuration Each data type is defined by a `PortDataTypeConfig`: ```typescript theme={null} interface PortDataTypeConfig { id: string; name: string; description?: string; color: string; // CSS color value or CSS variable category?: string; // Grouping: "basic", "numeric", "collection", etc. aliases?: string[]; // Alternative names for this data type enabled?: boolean; // Whether this type is active } ``` ## Compatibility Rules By default, ports connect only when their data types match exactly (e.g., `string` to `string`). You can add custom compatibility rules to allow cross-type connections: ```typescript theme={null} interface PortCompatibilityRule { from: string; // Source data type ID to: string; // Target data type ID description?: string; } ``` For example, to allow `string` ports to connect to `json` inputs: ```json theme={null} { "compatibilityRules": [ { "from": "string", "to": "json", "description": "Strings can be parsed as JSON" } ] } ``` Rules are configured via the `/port-config` API endpoint. ## Dynamic Ports Nodes can let users create additional ports at runtime through special config properties. When `dynamicInputs` or `dynamicOutputs` appear in a node's config, the editor creates port handles dynamically. ```typescript theme={null} interface DynamicPort { name: string; // Port identifier (used in handle IDs) label: string; // Display label description?: string; dataType: string; // Data type for color and compatibility required?: boolean; } ``` ### Example: Dynamic Input Ports In the node's `configSchema`, declare a `dynamicInputs` property: ```json theme={null} { "type": "object", "properties": { "dynamicInputs": { "type": "array", "title": "Custom Inputs", "items": { "type": "object", "properties": { "name": { "type": "string", "title": "Port ID" }, "label": { "type": "string", "title": "Display Name" }, "dataType": { "type": "string", "title": "Data Type", "default": "json" } }, "required": ["name", "label"] } } } } ``` When a user adds entries, the editor creates matching input handles. The same pattern works for `dynamicOutputs`. ## Gateway Branches Gateway nodes use `branches` in config to create conditional output paths. Each branch becomes an output port handle. ```typescript theme={null} interface Branch { name: string; // Unique identifier (used as handle ID) label?: string; // Display label (defaults to name) description?: string; value?: string; // Optional value for switch matching condition?: string; // Condition expression isDefault?: boolean; // Fallback branch } ``` ### Example ```json theme={null} { "branches": [ { "name": "success", "label": "Success", "condition": "status === 200" }, { "name": "error", "label": "Error", "isDefault": true } ] } ``` Each branch creates an output handle: `{nodeId}-output-success`, `{nodeId}-output-error`, etc. Edges connect from these handles to downstream nodes. ## Handle ID Format All port handles — static, dynamic, and branch — follow the same naming convention: ```text theme={null} {nodeId}-{direction}-{portId} ``` Examples: * `text_input.1-output-text` — static output port * `merger.1-input-extra_data` — dynamic input port * `router.1-output-success` — gateway branch output This format is used in [edge `sourceHandle` and `targetHandle` fields](/guides/edge-json#handle-ids). ## Next Steps * [Node Structure](/guides/node-json) — where ports are defined in the node JSON * [Edge Structure](/guides/edge-json) — how edges reference port handles * [Configuration Schema](/guides/config-schema) — JSON Schema for node config forms * [Node Types](/guides/node-types) — visual appearance and connection behavior in the editor # Testing Source: https://flowdrop.mintlify.app/guides/testing Unit testing workflows with WorkflowAdapter, MSW handler setup, and Playwright E2E tests for FlowDrop. FlowDrop provides several testing layers: pure unit tests via `WorkflowAdapter` (no DOM required), integration tests using Mock Service Worker, and end-to-end tests with Playwright. ## Unit testing with WorkflowAdapter `WorkflowAdapter` works in Node.js without a browser — ideal for testing your workflow logic in Vitest or Jest. ```typescript theme={null} import { WorkflowAdapter } from '@flowdrop/flowdrop/core'; import { describe, it, expect } from 'vitest'; describe('WorkflowAdapter', () => { const nodeTypes = [ { id: 'text_input', name: 'Text Input', ports: { outputs: [{ id: 'output', type: 'string' }] } }, { id: 'chat_model', name: 'Chat Model', ports: { inputs: [{ id: 'prompt', type: 'string' }], outputs: [{ id: 'response', type: 'string' }] } }, { id: 'text_output', name: 'Text Output', ports: { inputs: [{ id: 'input', type: 'string' }] } } ]; it('creates a valid workflow', () => { const adapter = new WorkflowAdapter(nodeTypes); const workflow = adapter.createWorkflow('Test Pipeline', 'A test workflow'); const input = adapter.addNode(workflow, 'text_input', { x: 100, y: 200 }); const model = adapter.addNode(workflow, 'chat_model', { x: 400, y: 200 }); const output = adapter.addNode(workflow, 'text_output', { x: 700, y: 200 }); adapter.addEdge(workflow, input.id, model.id, 'output', 'prompt'); adapter.addEdge(workflow, model.id, output.id, 'response', 'input'); const result = adapter.validateWorkflow(workflow); expect(result.valid).toBe(true); expect(result.errors).toHaveLength(0); }); it('counts nodes by type', () => { const adapter = new WorkflowAdapter(nodeTypes); const workflow = adapter.createWorkflow('Stats Test'); adapter.addNode(workflow, 'chat_model', { x: 0, y: 0 }); adapter.addNode(workflow, 'chat_model', { x: 200, y: 0 }); const stats = adapter.getWorkflowStats(workflow); expect(stats.totalNodes).toBe(2); expect(stats.nodeTypeCounts['chat_model']).toBe(2); }); it('serializes and deserializes correctly', () => { const adapter = new WorkflowAdapter(nodeTypes); const workflow = adapter.createWorkflow('Roundtrip Test'); adapter.addNode(workflow, 'text_input', { x: 0, y: 0 }); const json = adapter.exportWorkflow(workflow); const imported = adapter.importWorkflow(json); expect(imported.name).toBe('Roundtrip Test'); expect(imported.nodes).toHaveLength(1); }); }); ``` ## MSW handler setup Use [Mock Service Worker](https://mswjs.io/) to mock the FlowDrop REST API in integration tests. ### Handler definitions ```typescript theme={null} // src/test/handlers.ts import { http, HttpResponse } from 'msw'; const mockNodes = [ { id: 'text_input', name: 'Text Input', category: 'Input', ports: { outputs: [{ id: 'output', type: 'string', label: 'Output' }] }, configSchema: {} } ]; const mockWorkflow = { id: 'wf-1', name: 'Test Workflow', nodes: [], edges: [] }; export const handlers = [ http.get('/api/flowdrop/nodes', () => HttpResponse.json(mockNodes)), http.get('/api/flowdrop/workflows/:id', () => HttpResponse.json(mockWorkflow)), http.post('/api/flowdrop/workflows', async ({ request }) => { const body = await request.json(); return HttpResponse.json({ ...body, id: 'wf-new' }, { status: 201 }); }), http.put('/api/flowdrop/workflows/:id', async ({ request }) => { const body = await request.json(); return HttpResponse.json(body); }) ]; ``` ### Server setup (Vitest) ```typescript theme={null} // src/test/setup.ts import { setupServer } from 'msw/node'; import { handlers } from './handlers'; export const server = setupServer(...handlers); beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); ``` ## Mount API integration test Test that FlowDrop mounts and loads correctly in JSDOM: ```typescript theme={null} import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; import { createEndpointConfig } from '@flowdrop/flowdrop/core'; import { describe, it, expect, beforeEach, afterEach } from 'vitest'; describe('FlowDrop mount', () => { let container: HTMLElement; let app: Awaited>; beforeEach(() => { container = document.createElement('div'); container.style.width = '800px'; container.style.height = '600px'; document.body.appendChild(container); }); afterEach(() => { app?.destroy(); container.remove(); }); it('mounts without throwing', async () => { app = await mountFlowDropApp(container, { endpointConfig: createEndpointConfig('/api/flowdrop'), features: { autoSaveDraft: false, // avoid localStorage side effects in tests showToasts: false // suppress toast notifications } }); expect(app).toBeDefined(); expect(app.destroy).toBeTypeOf('function'); }); }); ``` ## Playwright E2E tests Test the full editor experience in a real browser: ```typescript theme={null} // tests/editor.spec.ts import { test, expect } from '@playwright/test'; test('loads the editor and saves a workflow', async ({ page }) => { await page.goto('/app/editor'); // Wait for the Svelte Flow canvas to render await page.waitForSelector('.svelte-flow__pane'); // Verify node sidebar is visible await expect(page.locator('[data-testid="node-sidebar"]')).toBeVisible(); // Click the save button await page.click('[data-testid="save-button"]'); // Assert save succeeded (toast or network request) await expect(page.locator('[data-testid="toast-success"]')).toBeVisible(); }); test('can drag a node onto the canvas', async ({ page }) => { await page.goto('/app/editor'); await page.waitForSelector('.svelte-flow__pane'); // Drag from the node palette to the canvas const nodeItem = page.locator('[data-node-type="text_input"]').first(); const canvas = page.locator('.svelte-flow__pane'); const canvasBox = await canvas.boundingBox(); await nodeItem.dragTo(canvas, { targetPosition: { x: canvasBox!.width / 2, y: canvasBox!.height / 2 } }); // Verify node appears on canvas await expect(page.locator('.svelte-flow__node')).toHaveCount(1); }); ``` ## Testing custom nodes Validate your node metadata structure using `WorkflowAdapter`: ```typescript theme={null} import { WorkflowAdapter } from '@flowdrop/flowdrop/core'; import { myCustomNode } from '../src/nodes/my-custom-node'; describe('Custom node metadata', () => { it('can be added to a workflow', () => { const adapter = new WorkflowAdapter([myCustomNode]); const workflow = adapter.createWorkflow('Custom Node Test'); expect(() => { adapter.addNode(workflow, myCustomNode.id, { x: 0, y: 0 }); }).not.toThrow(); }); it('has valid port definitions', () => { expect(myCustomNode.ports).toBeDefined(); // All port IDs must be unique within a node const allPorts = [ ...(myCustomNode.ports?.inputs ?? []), ...(myCustomNode.ports?.outputs ?? []) ]; const ids = allPorts.map((p) => p.id); expect(new Set(ids).size).toBe(ids.length); }); }); ``` # Theming Source: https://flowdrop.mintlify.app/guides/theming Customize FlowDrop's look and feel with CSS custom properties. FlowDrop uses a semantic-first design token system based on CSS custom properties with a `--fd-*` prefix. ## Quick start Override semantic tokens to customize FlowDrop: ```css theme={null} :root { /* Change the primary color */ --fd-primary: #8b5cf6; --fd-primary-hover: #7c3aed; /* Adjust border radius */ --fd-radius-md: 0.5rem; } ``` ## Token architecture FlowDrop's token system has three tiers: | Tier | Prefix | Description | | -------------------- | -------- | ------------------------------------- | | **Internal Palette** | `--_*` | Raw color values — not for direct use | | **Semantic Tokens** | `--fd-*` | The public API — what you customize | | **Component Tokens** | (varies) | Use semantic tokens in components | **Key principle**: Override `--fd-*` tokens. Components automatically use these tokens, so your theme cascades everywhere. ## Semantic tokens reference ### Surfaces | Token | Description | | ----------------------- | -------------------------------- | | `--fd-background` | Main background color | | `--fd-foreground` | Main text color | | `--fd-muted` | Muted background (cards, inputs) | | `--fd-muted-foreground` | Muted text | | `--fd-card` | Card background | | `--fd-card-foreground` | Card text color | ### Borders | Token | Description | | -------------------- | -------------------- | | `--fd-border` | Default border color | | `--fd-border-muted` | Muted border | | `--fd-border-strong` | Strong border | | `--fd-ring` | Focus ring color | ### Primary and accent | Token | Description | | ------------------------- | ------------------------ | | `--fd-primary` | Primary action color | | `--fd-primary-hover` | Primary hover state | | `--fd-primary-foreground` | Text on primary | | `--fd-primary-muted` | Light primary background | | `--fd-accent` | Accent color | | `--fd-accent-hover` | Accent hover | ### Status colors Each status color has `-hover`, `-foreground`, and `-muted` variants: * `--fd-success` — Green * `--fd-warning` — Amber * `--fd-error` — Red * `--fd-info` — Blue ### Spacing | Token | Value | | ---------------- | ----- | | `--fd-space-3xs` | 4px | | `--fd-space-2xs` | 6px | | `--fd-space-xs` | 8px | | `--fd-space-sm` | 10px | | `--fd-space-md` | 12px | | `--fd-space-lg` | 14px | | `--fd-space-xl` | 16px | | `--fd-space-2xl` | 20px | | `--fd-space-3xl` | 24px | ### Border radius | Token | Value | | ------------------ | ---------- | | `--fd-radius-sm` | 4px | | `--fd-radius-md` | 6px | | `--fd-radius-lg` | 8px | | `--fd-radius-xl` | 12px | | `--fd-radius-full` | Pill shape | ### Typography | Token | Value | | ---------------- | ----- | | `--fd-text-xs` | 12px | | `--fd-text-sm` | 14px | | `--fd-text-base` | 16px | | `--fd-text-lg` | 18px | | `--fd-text-xl` | 20px | ### Layout | Token | Default | | --------------------- | ------- | | `--fd-sidebar-width` | 320px | | `--fd-navbar-height` | 60px | | `--fd-toolbar-height` | 40px | ### Node layout Node dimensions use a **10px grid** for alignment with the editor snap grid: | Token | Default | | ------------------------- | ------- | | `--fd-node-default-width` | 290px | | `--fd-node-header-height` | 60px | | `--fd-node-terminal-size` | 80px | | `--fd-node-square-size` | 80px | | `--fd-handle-size` | 20px | | `--fd-handle-visual-size` | 12px | ## Theming examples ### Purple theme ```css theme={null} :root { --fd-primary: #8b5cf6; --fd-primary-hover: #7c3aed; --fd-primary-muted: #f5f3ff; --fd-accent: #8b5cf6; --fd-ring: #8b5cf6; } ``` ### Rounded theme ```css theme={null} :root { --fd-radius-sm: 0.5rem; --fd-radius-md: 0.75rem; --fd-radius-lg: 1rem; --fd-radius-xl: 1.5rem; } ``` ### Compact spacing ```css theme={null} :root { --fd-space-3xs: 0.125rem; --fd-space-xs: 0.25rem; --fd-space-md: 0.5rem; --fd-space-xl: 0.75rem; } ``` ## Dark mode FlowDrop supports dark mode. Enable it with: ```html theme={null} ``` Or via JavaScript: ```javascript theme={null} document.documentElement.setAttribute('data-theme', 'dark'); ``` Or use the built-in theme toggle: ```typescript theme={null} import { toggleTheme, setTheme } from '@flowdrop/flowdrop/core'; toggleTheme(); // Cycles through light/dark/auto setTheme('dark'); // Set explicitly ``` All semantic tokens have dark-mode equivalents that activate automatically. ## Best practices 1. **Use semantic tokens** — Override `--fd-primary` instead of individual component colors 2. **Keep it minimal** — A few token overrides can transform the entire look 3. **Test in context** — Colors look different on light vs dark backgrounds 4. **Consider accessibility** — Ensure sufficient contrast ratios 5. **Use the cascade** — Semantic tokens update all components automatically # Workflow structure Source: https://flowdrop.mintlify.app/guides/workflow-json The JSON format for FlowDrop workflows — top-level fields, metadata, and how nodes and edges fit together. A **workflow** is the top-level JSON document that FlowDrop reads and writes. It contains an array of nodes, an array of edges connecting them, and a metadata object. ## Schema ```typescript theme={null} interface Workflow { id: string; name: string; description?: string; nodes: WorkflowNode[]; edges: WorkflowEdge[]; metadata: WorkflowMetadata; } ``` | Field | Type | Required | Description | | ------------- | ---------------- | -------- | -------------------------------------------------------------------------------------- | | `id` | `string` | Yes | Unique identifier for the workflow (typically a UUID). | | `name` | `string` | Yes | Human-readable name displayed in the editor navbar. | | `description` | `string` | No | Brief summary of the workflow's purpose. | | `nodes` | `WorkflowNode[]` | Yes | Array of node instances placed on the canvas. See [Node Structure](/guides/node-json). | | `edges` | `WorkflowEdge[]` | Yes | Array of connections between nodes. See [Edge Structure](/guides/edge-json). | | `metadata` | `object` | Yes | Version tracking and authoring information. | ## Metadata ```typescript theme={null} interface WorkflowMetadata { schemaVersion: string; // Document-format version (not the workflow's own revision) createdAt: string; // ISO 8601 timestamp updatedAt: string; // ISO 8601 timestamp author?: string; tags?: string[]; versionId?: string; // UUID for this specific version updateNumber?: number; // Incrementing revision counter format?: WorkflowFormat; // "flowdrop" | "agentspec" | custom string } ``` `schemaVersion`, `createdAt`, and `updatedAt` are required on the metadata object. `schemaVersion` identifies the document format — not the workflow's own revision history. The `format` field determines which nodes appear in the sidebar and how the workflow is exported. The default is `"flowdrop"`. Set it to `"agentspec"` for workflows compatible with the [Oracle Open Agent Spec](https://github.com/oracle/agent-spec). ## Minimal Example The smallest valid workflow — an empty canvas ready for editing: ```json theme={null} { "id": "my-workflow", "name": "My Workflow", "nodes": [], "edges": [], "metadata": { "schemaVersion": "1.0.0", "createdAt": "2025-11-12T21:29:32.473Z", "updatedAt": "2025-11-12T21:29:32.473Z" } } ``` ## Full Example A workflow with two connected nodes and complete metadata: ```json theme={null} { "id": "content-pipeline", "name": "Content Processing Pipeline", "description": "Load articles and analyze them with AI", "nodes": [ { "id": "content_loader.1", "type": "universalNode", "position": { "x": 0, "y": 100 }, "data": { "label": "Content Loader", "config": { "contentType": "article", "limit": 50 }, "metadata": { "node_type_id": "content_loader", "name": "Content Loader", "type": "tool", "description": "Load content for batch processing", "category": "content", "icon": "mdi:database-import", "version": "1.0.0", "inputs": [], "outputs": [ { "id": "items", "name": "Items", "type": "output", "dataType": "array" } ] } } }, { "id": "analyzer.1", "type": "universalNode", "position": { "x": 400, "y": 100 }, "data": { "label": "AI Analyzer", "config": { "confidenceThreshold": 0.8 }, "metadata": { "node_type_id": "ai_analyzer", "name": "AI Analyzer", "type": "tool", "description": "AI-powered content analysis", "category": "ai", "icon": "mdi:brain", "version": "1.0.0", "inputs": [ { "id": "content", "name": "Content", "type": "input", "dataType": "array" } ], "outputs": [ { "id": "results", "name": "Results", "type": "output", "dataType": "json" } ] } } } ], "edges": [ { "id": "e-loader-analyzer", "source": "content_loader.1", "target": "analyzer.1", "sourceHandle": "content_loader.1-output-items", "targetHandle": "analyzer.1-input-content" } ], "metadata": { "schemaVersion": "1.0.0", "createdAt": "2025-11-12T21:29:32.473Z", "updatedAt": "2025-11-12T21:29:32.473Z", "author": "demo", "tags": ["ai", "content"], "format": "flowdrop" } } ``` ## Import and Export For programmatic access to workflows, see [Creating Workflows — Import and Export](/guides/creating-workflows#import-and-export). ## Next Steps * [Node Structure](/guides/node-json) — anatomy of each node in the `nodes` array * [Edge Structure](/guides/edge-json) — how connections in the `edges` array work * [Configuration Schema](/guides/config-schema) — JSON Schema that powers node config forms # Introduction Source: https://flowdrop.mintlify.app/index Build beautiful workflow editors in minutes, not months. A drop-in visual workflow editor for any web application. You own the backend. You own the data. You own the orchestration. FlowDrop Editor's Screenshot showing an ai based workflow Install `@flowdrop/flowdrop` and mount an editor in minutes. Understand the mental model before writing any code. ## Who is FlowDrop for? Building internal tools, CMS platforms, or SaaS products that need a visual workflow builder. You want a drop-in editor, not a workflow engine. Designing agent pipelines, RAG workflows, or tool-calling chains. FlowDrop visualizes the orchestration graph while you control execution. Creating self-service automation platforms where non-technical users design workflows through a visual interface. ## Why FlowDrop? Most workflow tools are SaaS platforms that lock you in. Your data lives on their servers, your execution logic runs in their cloud, and you pay per workflow, per user, per run. **FlowDrop is different.** It is a pure visual editor component library. You implement the backend. You control the orchestration. Your workflows run on your infrastructure, with your security policies, at your scale. No vendor lock-in. No data leaving your walls. ## Features Drag-and-drop node-based editor with built-in node types, real-time validation, and auto-layout. Pure UI component library — connect to any API: Drupal, Laravel, Express, FastAPI, or your own. Auto-generate configuration forms from JSON Schema with support for dynamic fields, UISchema layout, and autocomplete. Extensible registries for custom node types, workflow formats, and form field components. Pause workflows for human approval, input, choices, or review with a built-in interrupt system. Test workflows interactively with a chat interface, session management, and real-time execution feedback. Use as a native Svelte component or mount into React, Vue, Angular, or vanilla JS applications. ## Learning path New to FlowDrop? Follow this path: [Understand the mental model](/concepts/what-is-a-workflow). [Install FlowDrop and mount your first editor](/docs/quickstart) in minutes. [See how all the pieces fit together](/concepts/architecture-overview). # Build an AI agent workflow Source: https://flowdrop.mintlify.app/recipes/ai-agent-workflow Create a complete AI agent workflow with branching, tools, and human-in-the-loop. This recipe walks you through building a real-world AI agent workflow with four node types, conditional routing, and a human-in-the-loop review step. ## What We're Building An AI content assistant that: 1. Takes user input 2. Sends it to an LLM 3. Routes based on intent (question → answer directly, task → use tools) 4. Gets human review before outputting the final result ```text theme={null} ┌─ Question ─▸ [Text Output] [User Input] → [LLM] → [Router] ─┤ └─ Task ────▸ [Tool Call] → [Review] → [Text Output] ``` ## Step 1: Define Node Types On your backend, define these node types: ```typescript theme={null} const nodes = [ { id: 'user_input', name: 'User Input', type: 'simple', category: 'inputs', icon: 'mdi:account-outline', inputs: [], outputs: [{ id: 'message', name: 'Message', type: 'output', dataType: 'string' }], configSchema: { type: 'object', properties: { placeholder: { type: 'string', title: 'Placeholder', default: 'Ask me anything...' } } } }, { id: 'llm_call', name: 'LLM Call', type: 'workflowNode', category: 'models', icon: 'mdi:robot-outline', inputs: [{ id: 'prompt', name: 'Prompt', type: 'input', dataType: 'string' }], outputs: [ { id: 'response', name: 'Response', type: 'output', dataType: 'string' }, { id: 'metadata', name: 'Metadata', type: 'output', dataType: 'json' } ], configSchema: { type: 'object', properties: { model: { type: 'string', title: 'Model', oneOf: [ { const: 'gpt-4', title: 'GPT-4' }, { const: 'claude-3-sonnet', title: 'Claude 3 Sonnet' }, { const: 'claude-3-haiku', title: 'Claude 3 Haiku' } ], default: 'claude-3-sonnet' }, system_prompt: { type: 'string', title: 'System Prompt', format: 'template', default: 'You are a helpful assistant. Classify the user message as either a "question" or a "task".', variables: { ports: ['prompt'] } }, temperature: { type: 'number', title: 'Temperature', minimum: 0, maximum: 2, default: 0.3 } } } }, { id: 'intent_router', name: 'Intent Router', type: 'gateway', category: 'logic', icon: 'mdi:directions-fork', inputs: [ { id: 'input', name: 'Input', type: 'input', dataType: 'string' }, { id: 'metadata', name: 'Metadata', type: 'input', dataType: 'json' } ], outputs: [{ id: 'default', name: 'Default', type: 'output', dataType: 'string' }], configSchema: { type: 'object', properties: { condition_field: { type: 'string', title: 'Condition Field', default: 'intent' } } } }, { id: 'text_output', name: 'Text Output', type: 'simple', category: 'outputs', icon: 'mdi:text', inputs: [{ id: 'input', name: 'Text', type: 'input', dataType: 'string' }], outputs: [], configSchema: { type: 'object', properties: { format: { type: 'string', title: 'Format', enum: ['plain', 'markdown', 'json'], default: 'markdown' } } } } ]; ``` ## Step 2: Configure the Gateway The `intent_router` node uses the **gateway** type. After adding it to the canvas, add branches in its configuration: * **Branch "Question"**: Routes when intent is "question" → connect to a direct `text_output` * **Branch "Task"**: Routes when intent is "task" → connect to a tool processing chain Each branch creates a new output port on the gateway node. ## Step 3: Add Template Variables The `llm_call` node's `system_prompt` field uses `format: "template"` with `variables: { ports: ['prompt'] }`. This means: 1. Connect `user_input.message` → `llm_call.prompt` 2. In the LLM's system prompt, type `{{` to see `prompt` as an autocomplete suggestion 3. Write: `Classify this message: {{ prompt }}` The template editor highlights `{{ prompt }}` and shows hints below the editor. ## Step 4: Wire It Together In FlowDrop's visual editor: 1. Drag all four node types onto the canvas 2. Connect: `user_input.message` → `llm_call.prompt` 3. Connect: `llm_call.response` → `intent_router.input` 4. Connect: `llm_call.metadata` → `intent_router.metadata` 5. Add gateway branches and connect each branch output to the appropriate downstream node ## Step 5: Add Human-in-the-Loop For the "Task" branch, you want human review before the final output. This uses FlowDrop's [interrupt system](/guides/interrupts): On your backend, when the workflow reaches the review step, create an interrupt: ```typescript theme={null} // Backend: create a review interrupt const interrupt = { id: crypto.randomUUID(), type: 'review', status: 'pending', config: { title: 'Review AI Output', description: 'Please review the AI-generated content before it is sent.', content: aiGeneratedContent, actions: ['approve', 'reject', 'edit'] } }; ``` FlowDrop's playground UI renders this as a review prompt with approve/reject/edit buttons. ## Step 6: Test in the Playground 1. Open the workflow playground (toolbar button or mount `mountPlayground()`) 2. Type a message like "What is the capital of France?" 3. Watch it route through the "Question" branch 4. Type "Write me a blog post about AI" and watch it route through "Task" → human review ## Complete Workflow JSON The final workflow JSON looks like this: ```json theme={null} { "id": "ai-agent-workflow", "name": "AI Content Assistant", "nodes": [ { "id": "node-1", "type": "simple", "position": { "x": 100, "y": 300 }, "data": { "label": "User Input", "metadata": { "node_type_id": "user_input" }, "config": { "placeholder": "Ask me anything..." } } }, { "id": "node-2", "type": "workflowNode", "position": { "x": 400, "y": 300 }, "data": { "label": "LLM Call", "metadata": { "node_type_id": "llm_call" }, "config": { "model": "claude-3-sonnet", "system_prompt": "Classify: {{ prompt }}", "temperature": 0.3 } } }, { "id": "node-3", "type": "gateway", "position": { "x": 700, "y": 300 }, "data": { "label": "Intent Router", "metadata": { "node_type_id": "intent_router" }, "branches": [ { "id": "question", "label": "Question" }, { "id": "task", "label": "Task" } ] } } ], "edges": [ { "id": "e1", "source": "node-1", "sourceHandle": "node-1-output-message", "target": "node-2", "targetHandle": "node-2-input-prompt" }, { "id": "e2", "source": "node-2", "sourceHandle": "node-2-output-response", "target": "node-3", "targetHandle": "node-3-input-input" } ] } ``` ## Next Steps * [Human-in-the-Loop](/guides/interrupts) — full interrupt system reference * [Template Variables](/guides/advanced/template-variables) — advanced template patterns * [Configuration Schema](/guides/config-schema) — complex form fields # Auto-save & drafts Source: https://flowdrop.mintlify.app/recipes/auto-save-and-drafts How FlowDrop auto-saves drafts to browser storage and how to manage them. FlowDrop automatically saves drafts of the current workflow to browser storage (`localStorage` by default), preventing data loss when the browser closes unexpectedly. ## How It Works 1. When `autoSaveDraft` is enabled (default: `true`), FlowDrop saves the current workflow to draft storage periodically 2. The save interval defaults to **30 seconds** (`autoSaveDraftInterval: 30000`) 3. Drafts are keyed by workflow ID and scoped per instance, so multiple editors on one page never collide 4. When a workflow is loaded, FlowDrop checks for a matching draft and offers to restore it 5. After a successful save to the backend, the draft is cleared The default instance keys drafts as `flowdrop:draft:` (and `flowdrop:draft:new` for an unsaved workflow). Editors mounted with an `instanceId` get scoped keys: `flowdrop:draft::`. ## Configuration ```typescript theme={null} const app = await mountFlowDropApp(container, { features: { autoSaveDraft: true, // default: true autoSaveDraftInterval: 30000 // default: 30000ms (30 seconds) }, // Optional: custom storage key prefix draftStorageKey: 'my-app-flowdrop-draft', // Optional: storage backend — 'local' (default), 'session', or a custom adapter draftStorage: 'local' }); ``` ## Security Considerations Drafts contain the **complete workflow JSON, including node configuration values**. If your users enter API keys, tokens, or other secrets into node configs, those values end up in browser storage in plain text. Keep in mind: * On the default `'local'` backend, drafts remain stored on the device **even after the tab or browser is closed**, until they are saved or cleared * Neither `localStorage` nor `sessionStorage` protects against same-origin script access (XSS) — both are readable by any script running on your page * On shared browser profiles, leftover drafts are readable by the next user via DevTools Mitigations, in increasing order of strictness: 1. Call [`clearAllDrafts()` on logout](#clearing-drafts-on-logout) 2. Use `draftStorage: 'session'` so drafts are removed when the tab closes 3. Supply a custom `DraftStorageAdapter` (e.g. an in-memory store) 4. Disable drafts entirely with `features: { autoSaveDraft: false }` End users can also opt out themselves at any time via the **"Store Drafts in Browser"** toggle in the Behavior tab of the settings panel — turning it off stops draft writes and removes the current draft. **The user toggle applies per tab.** Other tabs that are already open read settings at load time and keep writing drafts until they are reloaded. If you need a hard guarantee across tabs, disable drafts at mount time instead. ## Choosing a Storage Backend The `draftStorage` mount option controls where drafts live: | Value | Backend | Survives reload | Survives tab close | Notes | | ------------------- | ---------------- | --------------- | ------------------ | ------------------------------------------------------- | | `'local'` (default) | `localStorage` | ✅ | ✅ | Best crash recovery; clear on logout for shared devices | | `'session'` | `sessionStorage` | ✅ | ❌ | Per-tab; drafts do **not** survive crash-and-reopen | | custom adapter | up to you | — | — | Implement `DraftStorageAdapter` | The resolved backend is captured per mount, so multiple FlowDrop instances on one page can use different backends without interfering. (The standalone `clearAllDrafts()` helper uses the most recent mount's backend unless you pass it an adapter explicitly.) A custom adapter implements four **synchronous** methods. Async backends — IndexedDB, network storage, WebCrypto encryption — cannot implement the interface directly; put a synchronous in-memory cache in front and flush to the async backend separately. Beware that an `async` method will type-check here (a Promise is assignable to `void`), but its errors are silently swallowed. ```typescript theme={null} import type { DraftStorageAdapter } from '@flowdrop/flowdrop/editor'; const memoryDrafts = new Map(); const inMemoryAdapter: DraftStorageAdapter = { getItem: (key) => memoryDrafts.get(key) ?? null, setItem: (key, value) => void memoryDrafts.set(key, value), removeItem: (key) => void memoryDrafts.delete(key), keys: () => [...memoryDrafts.keys()] }; const app = await mountFlowDropApp(container, { draftStorage: inMemoryAdapter }); ``` ### Disabling Auto-Save ```typescript theme={null} features: { autoSaveDraft: false; } ``` ### Faster Auto-Save For critical workflows, save more frequently: ```typescript theme={null} features: { autoSaveDraft: true, autoSaveDraftInterval: 10000 // every 10 seconds } ``` ## Manual Draft Management with Events Use the `onBeforeUnmount` event to save a final draft when the editor is destroyed: ```typescript theme={null} eventHandlers: { onBeforeUnmount: (workflow, isDirty) => { if (isDirty) { localStorage.setItem(`flowdrop-draft-${workflow.id}`, JSON.stringify(workflow)); } }; } ``` ## Clearing Drafts on Logout On the default `'local'` backend, drafts persist until they are explicitly cleared. **FlowDrop has no notion of authentication**, so it cannot clear drafts when the user signs out of your application — you must do this from the host application's logout handler. On a shared browser profile, leftover drafts could otherwise be readable by the next user via DevTools. The mounted FlowDrop instance exposes `clearAllDrafts()` for this purpose. It removes every key beginning with `flowdrop:draft:` — including instance-scoped sub-namespaces like `flowdrop:draft::` — plus the custom `draftStorageKey` you configured at mount time (if any), and returns the number of entries removed. ```typescript theme={null} const app = await mountFlowDropApp(container, { /* ... */ }); async function logout() { app.clearAllDrafts(); await authService.signOut(); } ``` If you need to clear drafts after the editor has already been unmounted, import the standalone helper: ```typescript theme={null} import { clearAllDrafts } from '@flowdrop/flowdrop/editor'; clearAllDrafts(); // clears flowdrop:draft:* keys clearAllDrafts(['my-custom-draft-key']); // also clears explicit custom keys ``` ## Storage Limits Browsers typically limit `localStorage` to **5-10MB**. Large workflows with many nodes and complex configurations could approach this limit. If storage is full: * The draft save fails silently * The editor continues working normally * No data is lost from the active session ## Combining with Backend Save A typical save flow: ```typescript theme={null} eventHandlers: { onDirtyStateChange: (isDirty) => { // Show/hide "unsaved changes" indicator indicator.style.display = isDirty ? 'block' : 'none'; }, onAfterSave: async (workflow) => { // Draft is automatically cleared after successful save showToast('Saved!'); }, onSaveError: async (error, workflow) => { // Draft is preserved — user can retry showToast('Save failed. Your changes are still saved locally.'); } } ``` ## Next Steps * [Event System](/guides/advanced/event-system) — all lifecycle events including save * [Framework Integration](/guides/integration) — mount options including features * [Troubleshooting](/troubleshooting/common-issues#draft-recovery-not-working) — draft recovery issues # Backend: Express.js Source: https://flowdrop.mintlify.app/recipes/backend-express Build a working FlowDrop backend with Express in 15 minutes. This recipe walks you through building a complete FlowDrop backend using Express.js. By the end, you'll have a working API that FlowDrop can talk to. FlowDrop includes a complete example Express server at `apps/example-server-express/` in the repository. This recipe is based on that implementation. ## Prerequisites * Node.js 20+ * A FlowDrop frontend (see [Quick Start](/docs/quickstart)) ## Step 1: Project Setup ```bash theme={null} mkdir flowdrop-backend && cd flowdrop-backend npm init -y npm install express cors npm install -D typescript tsx @types/express @types/cors ``` Create `tsconfig.json`: ```json theme={null} { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "outDir": "dist", "rootDir": "src" } } ``` ## Step 2: Health Endpoint Create `src/index.ts`: ```typescript theme={null} import express from 'express'; import cors from 'cors'; const app = express(); app.use(cors()); app.use(express.json()); const API_BASE = '/api/flowdrop'; // Health check — FlowDrop calls this on mount app.get(`${API_BASE}/health`, (req, res) => { res.json({ status: 'ok', version: '1.0.0' }); }); const PORT = process.env.PORT || 3001; app.listen(PORT, () => { console.log(`FlowDrop backend running on http://localhost:${PORT}`); }); ``` Run it: ```bash theme={null} npx tsx --watch src/index.ts ``` ## Step 3: Node Definitions Create `src/nodes.ts` with your node metadata. Each node describes what appears in FlowDrop's sidebar: ```typescript theme={null} export interface NodeMetadata { node_type_id: string; name: string; description?: string; type: string; category: string; icon?: string; inputs: Array<{ id: string; name: string; type: 'input'; dataType: string; }>; outputs: Array<{ id: string; name: string; type: 'output'; dataType: string; }>; configSchema?: { type: 'object'; properties?: Record; required?: string[]; }; } export const nodes: NodeMetadata[] = [ { node_type_id: 'text_input', name: 'Text Input', description: 'Accepts text from the user', type: 'simple', category: 'inputs', icon: 'mdi:text-box-outline', inputs: [], outputs: [{ id: 'output', name: 'Text', type: 'output', dataType: 'string' }], configSchema: { type: 'object', properties: { placeholder: { type: 'string', title: 'Placeholder', default: 'Enter text...' }, multiline: { type: 'boolean', title: 'Multi-line', default: false } } } }, { node_type_id: 'chat_model', name: 'Chat Model', description: 'Send prompts to an LLM', type: 'workflowNode', category: 'models', icon: 'mdi:robot-outline', inputs: [{ id: 'prompt', name: 'Prompt', type: 'input', dataType: 'string' }], outputs: [{ id: 'response', name: 'Response', type: 'output', dataType: 'string' }], configSchema: { type: 'object', properties: { model: { type: 'string', title: 'Model', oneOf: [ { const: 'gpt-4', title: 'GPT-4' }, { const: 'claude-3-sonnet', title: 'Claude 3 Sonnet' } ], default: 'gpt-4' }, temperature: { type: 'number', title: 'Temperature', minimum: 0, maximum: 2, default: 0.7 } } } }, { node_type_id: 'text_output', name: 'Text Output', description: 'Display text results', type: 'simple', category: 'outputs', icon: 'mdi:text', inputs: [{ id: 'input', name: 'Text', type: 'input', dataType: 'string' }], outputs: [], configSchema: { type: 'object', properties: { format: { type: 'string', title: 'Format', enum: ['plain', 'markdown', 'json'], default: 'plain' } } } } ]; ``` Add the nodes route in `src/index.ts`: ```typescript theme={null} import { nodes } from './nodes.js'; app.get(`${API_BASE}/nodes`, (req, res) => { let result = nodes; // Filter by category if (req.query.category) { result = result.filter((n) => n.category === req.query.category); } // Search if (req.query.search) { const q = (req.query.search as string).toLowerCase(); result = result.filter( (n) => n.name.toLowerCase().includes(q) || n.description?.toLowerCase().includes(q) ); } res.json({ success: true, data: result }); }); app.get(`${API_BASE}/nodes/:id`, (req, res) => { const node = nodes.find((n) => n.id === req.params.id); if (!node) { return res.status(404).json({ success: false, error: 'Node not found' }); } res.json({ success: true, data: node }); }); ``` ## Step 4: Workflow CRUD Create `src/workflows.ts`: ```typescript theme={null} import crypto from 'crypto'; export interface Workflow { id: string; name: string; description?: string; nodes: any[]; edges: any[]; metadata?: { schemaVersion: string; createdAt: string; updatedAt: string; }; } // In-memory storage (use a database in production) const workflows = new Map(); export function getAllWorkflows() { return Array.from(workflows.values()); } export function getWorkflowById(id: string) { return workflows.get(id); } export function createWorkflow(input: Partial): Workflow { const now = new Date().toISOString(); const workflow: Workflow = { id: crypto.randomUUID(), name: input.name || 'Untitled Workflow', description: input.description, nodes: input.nodes || [], edges: input.edges || [], metadata: { schemaVersion: '1.0.0', createdAt: now, updatedAt: now } }; workflows.set(workflow.id, workflow); return workflow; } export function updateWorkflow(id: string, updates: Partial): Workflow | null { const existing = workflows.get(id); if (!existing) return null; const updated = { ...existing, ...updates, id, // prevent ID change metadata: { ...existing.metadata, updatedAt: new Date().toISOString() } }; workflows.set(id, updated as Workflow); return updated as Workflow; } export function deleteWorkflow(id: string): boolean { return workflows.delete(id); } ``` Add the workflow routes in `src/index.ts`: ```typescript theme={null} import { getAllWorkflows, getWorkflowById, createWorkflow, updateWorkflow, deleteWorkflow } from './workflows.js'; app.get(`${API_BASE}/workflows`, (req, res) => { res.json({ success: true, data: getAllWorkflows() }); }); app.get(`${API_BASE}/workflows/:id`, (req, res) => { const workflow = getWorkflowById(req.params.id); if (!workflow) { return res.status(404).json({ success: false, error: 'Workflow not found' }); } res.json({ success: true, data: workflow }); }); app.post(`${API_BASE}/workflows`, (req, res) => { const workflow = createWorkflow(req.body); res.status(201).json({ success: true, data: workflow }); }); app.put(`${API_BASE}/workflows/:id`, (req, res) => { const workflow = updateWorkflow(req.params.id, req.body); if (!workflow) { return res.status(404).json({ success: false, error: 'Workflow not found' }); } res.json({ success: true, data: workflow }); }); app.delete(`${API_BASE}/workflows/:id`, (req, res) => { const deleted = deleteWorkflow(req.params.id); if (!deleted) { return res.status(404).json({ success: false, error: 'Workflow not found' }); } res.json({ success: true, data: { id: req.params.id } }); }); ``` ## Step 5: Categories & Port Config Add these routes for the full sidebar and connection validation experience: ```typescript theme={null} app.get(`${API_BASE}/categories`, (req, res) => { res.json({ success: true, data: [ { id: 'inputs', name: 'Inputs', icon: 'mdi:import', color: 'var(--fd-node-emerald)', weight: 10 }, { id: 'models', name: 'Models', icon: 'mdi:robot', color: 'var(--fd-node-purple)', weight: 20 }, { id: 'outputs', name: 'Outputs', icon: 'mdi:export', color: 'var(--fd-node-blue)', weight: 30 }, { id: 'processing', name: 'Processing', icon: 'mdi:cog', color: 'var(--fd-node-amber)', weight: 40 }, { id: 'logic', name: 'Logic', icon: 'mdi:sitemap', color: 'var(--fd-node-indigo)', weight: 50 } ] }); }); app.get(`${API_BASE}/port-config`, (req, res) => { res.json({ success: true, data: { version: '1.0.0', defaultDataType: 'string', dataTypes: [ { id: 'string', name: 'String', color: '#10b981', category: 'basic' }, { id: 'number', name: 'Number', color: '#3b82f6', category: 'basic' }, { id: 'boolean', name: 'Boolean', color: '#8b5cf6', category: 'basic' }, { id: 'json', name: 'JSON', color: '#f59e0b', category: 'complex' }, { id: 'trigger', name: 'Trigger', color: '#ef4444', category: 'special' } ], compatibilityRules: [ { from: 'string', to: 'json' }, { from: 'number', to: 'string' }, { from: 'json', to: 'string' } ] } }); }); ``` ## Step 6: Connect to FlowDrop In your frontend, point FlowDrop to your backend: ```typescript theme={null} import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; import { createEndpointConfig } from '@flowdrop/flowdrop/core'; const app = await mountFlowDropApp(document.getElementById('editor'), { endpointConfig: createEndpointConfig('http://localhost:3001/api/flowdrop'), eventHandlers: { onAfterSave: async (workflow) => { console.log('Saved:', workflow.id); } } }); ``` Start both servers and you should see nodes in the sidebar, be able to drag them onto the canvas, connect them, and save workflows. ## Production Considerations This recipe uses in-memory storage for simplicity. For production: * **Database**: Replace the `Map` with PostgreSQL, MongoDB, or any persistent store * **Validation**: Add request body validation (zod, joi, etc.) * **Authentication**: Add auth middleware and configure FlowDrop's `authProvider` * **Rate limiting**: Protect endpoints from abuse * **Error handling**: Add proper error middleware ## Next Steps * [Backend Implementation Guide](/guides/integration/backend-implementation) — full endpoint reference * [Framework Integration](/guides/integration) — advanced frontend configuration * [Authentication Patterns](/guides/integration) — secure your API # Conditional branching Source: https://flowdrop.mintlify.app/recipes/conditional-branching Use gateway nodes with branches for if/else and switch/case routing. Gateway nodes let you route workflow execution based on conditions — like if/else or switch/case logic. ## How Gateways Work A **gateway node** has: * Input ports that receive data * A **default** output port * **Branch** output ports that you define Each branch has a label and maps to a separate output port. Your backend decides which branch to activate during execution. ## Defining a Gateway Node ```json theme={null} { "node_type_id": "intent_router", "name": "Intent Router", "type": "gateway", "category": "logic", "icon": "mdi:directions-fork", "inputs": [ { "id": "input", "name": "Input", "type": "input", "dataType": "string" }, { "id": "metadata", "name": "Metadata", "type": "input", "dataType": "json" } ], "outputs": [{ "id": "default", "name": "Default", "type": "output", "dataType": "string" }], "configSchema": { "type": "object", "properties": { "condition_field": { "type": "string", "title": "Condition Field", "description": "JSON path to the field used for routing" } } } } ``` ## Adding Branches in the Editor When a user clicks on a gateway node, the configuration panel shows a **Branches** section: 1. Click **Add Branch** 2. Enter a branch label (e.g., "Yes", "No", or "Question", "Task") 3. Each branch creates a new output port on the gateway node 4. Connect each branch output to the appropriate downstream node ## If/Else Pattern For simple true/false routing: ```text theme={null} ┌─ "Yes" ─▸ [Process] [Check Condition] ──┤ └─ "No" ─▸ [Skip] ``` Define two branches: "Yes" and "No". Your backend evaluates the condition and activates the matching branch. ## Switch/Case Pattern For multi-way routing: ```text theme={null} ┌─ "Email" ─▸ [Send Email] [Detect Channel] ──────┤── "Slack" ─▸ [Post to Slack] ├─ "Webhook" ─▸ [Call Webhook] └─ "Default" ─▸ [Log Event] ``` Add as many branches as you need. The default output handles unmatched cases. ## Backend Implementation Your backend decides which branch to activate. The workflow JSON stores branches in the node's data: ```json theme={null} { "id": "node-3", "type": "gateway", "data": { "label": "Intent Router", "branches": [ { "id": "question", "label": "Question" }, { "id": "task", "label": "Task" } ] } } ``` Edges connect from branch-specific output ports: ```json theme={null} { "source": "node-3", "sourceHandle": "node-3-output-question", "target": "node-4", "targetHandle": "node-4-input-input" } ``` ## Connection Validation Gateway branch outputs use the same data type as the gateway's input. FlowDrop validates that downstream nodes have compatible input ports. ## Next Steps * [Node Types](/guides/node-types) — all 7 built-in node types including gateway * [Edge Structure](/guides/edge-json) — how edges reference branch ports * [AI Agent Workflow](/recipes/ai-agent-workflow) — full example using gateway routing # Undo & redo Source: https://flowdrop.mintlify.app/recipes/undo-redo How FlowDrop's undo/redo system works and how to use it programmatically. FlowDrop provides built-in undo/redo that tracks every workflow change. ## Keyboard Shortcuts | Shortcut | Action | | ---------------------- | ------ | | `Ctrl/Cmd + Z` | Undo | | `Ctrl/Cmd + Shift + Z` | Redo | These work automatically — no configuration needed. ## How It Works FlowDrop's history store takes **snapshots** of the entire workflow state. Each change (node add/remove/move, edge add/remove, config change) pushes a new snapshot onto the undo stack. * **Undo** restores the previous snapshot and pushes the current state onto the redo stack * **Redo** restores the next snapshot from the redo stack * Making a new change after undoing clears the redo stack ## Programmatic Access History lives on the instance — resolve it with `getInstance()` inside the component tree (or use the mount handle's `.instance`): ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); // Check availability — reactive getters on historyBindings const canUndo = fd.historyBindings.canUndo; // boolean const canRedo = fd.historyBindings.canRedo; // boolean // Perform undo/redo (HistoryStore actions are bound — safe to detach) fd.historyBindings.undo(); fd.historyBindings.redo(); // Clear history fd.historyBindings.clear(fd.workflow.current); ``` ## Transactions Group multiple changes into a single undo step: ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); // Start a transaction fd.historyBindings.startTransaction(fd.workflow.current, 'Rearrange layout'); // Make multiple changes — none are recorded individually fd.workflow.actions.updateNode('node-1', { position: { x: 100, y: 200 } }); fd.workflow.actions.updateNode('node-2', { position: { x: 300, y: 200 } }); fd.workflow.actions.updateNode('node-3', { position: { x: 500, y: 200 } }); // Commit — all changes become one undo step fd.historyBindings.commitTransaction(); // Or cancel — all changes revert // fd.historyBindings.cancelTransaction(); ``` ## Building Custom Undo/Redo Buttons Outside the component tree, hold the mount handle and read `.instance`: ```typescript theme={null} const app = await mountFlowDropApp(container, options); const fd = app.instance; const undoBtn = document.getElementById('undo'); const redoBtn = document.getElementById('redo'); undoBtn.addEventListener('click', () => fd.historyBindings.undo()); redoBtn.addEventListener('click', () => fd.historyBindings.redo()); // Update button state (polling — for non-Svelte frameworks) setInterval(() => { undoBtn.disabled = !fd.historyBindings.canUndo; redoBtn.disabled = !fd.historyBindings.canRedo; }, 500); ``` In Svelte, resolve the instance and use reactivity instead of polling: ```svelte theme={null} ``` ## Next Steps * [Store System](/guides/advanced/store-system) — all stores including history * [Event System](/guides/advanced/event-system) — `onWorkflowChange` fires after undo/redo # API overview Source: https://flowdrop.mintlify.app/reference/api-overview Module structure and exports of the FlowDrop library. FlowDrop is organized as tree-shakable sub-modules. Import only what you need to minimize bundle size. ## Module structure ### `@flowdrop/flowdrop/core` Types and utilities with zero heavy dependencies. Safe to import anywhere without pulling in Svelte components or CodeMirror. **Key exports:** | Category | Purpose | Exports | | -------------- | ---------------------------------------------- | ------------------------------------------------------------------------------ | | Core types | The workflow graph data model | `Workflow`, `WorkflowNode`, `WorkflowEdge`, `NodeMetadata`, `NodePort` | | Node types | Built-in node type enums | `BuiltinNodeType`, `NodeCategory` | | Auth providers | Strategies for authenticating backend requests | `AuthProvider`, `StaticAuthProvider`, `CallbackAuthProvider`, `NoAuthProvider` | | Configuration | Library and endpoint setup | `FlowDropConfig`, `EndpointConfig`, `createEndpointConfig` | | Event handlers | Editor event hooks and feature flags | `FlowDropEventHandlers`, `FlowDropFeatures` | | Port system | Port typing and connection compatibility | `PortConfig`, `PortDataTypeConfig`, `PortCompatibilityRule` | | UI Schema | Declarative layout for generated forms | `UISchemaElement`, `UISchemaControl`, `UISchemaGroup` | | Form types | Field-level form schema | `FieldSchema`, `FieldType`, `SchemaFormProps` | | Agent Spec | Import/export interop with Agent Spec | Types and adapters | | Theme | Theme state and switching | `theme`, `resolvedTheme`, `setTheme`, `toggleTheme` | | Utilities | Graph and UI helpers | colors, icons, node types, connections, cycle detection | ### `@flowdrop/flowdrop/editor` Visual workflow editor with `@xyflow/svelte`. **Key exports:** | Category | Purpose | Exports | | --------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Components | Top-level editor UI | `WorkflowEditor`, `App`, `NodeSidebar`, `ConfigForm`, `ConfigPanel` | | Node components | Per-node-type renderers | `WorkflowNodeComponent`, `SimpleNode`, `ToolNode`, `NotesNode`, `GatewayNode`, `SquareNode`, `TerminalNode`, `UniversalNode` | | Mount functions | Imperative mount/unmount entry points | `mountFlowDropApp`, `mountWorkflowEditor`, `unmountFlowDropApp` | | Helpers | Programmatic workflow and styling operations | `WorkflowOperationsHelper`, `NodeOperationsHelper`, `EdgeStylingHelper`, `ConfigurationHelper` | | Store classes | Instance-scoped editor state (resolve with `getInstance()`) | `WorkflowStore`, `HistoryStore`, `HistoryService`, `PortCoordinateStore` | | Instance access | Create and resolve editor instances | `getInstance`, `provideInstance`, `createFlowDropInstance` | | Services | API client, toasts, and node execution | `EnhancedFlowDropApiClient` / `ApiContext`, toast service, node execution | | Registration | Register custom nodes and plugins | `NodeComponentRegistry` (`fd.nodes`), `createPlugin`, `isValidNamespace` | ### `@flowdrop/flowdrop/form` Dynamic form generation from JSON Schema. **Key exports:** | Category | Purpose | Exports | | ----------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Components | Form renderer and field wrappers | `SchemaForm`, `FormField`, `FormFieldWrapper` | | Field types | Built-in field widgets | `FormTextField`, `FormTextarea`, `FormNumberField`, `FormToggle`, `FormSelect`, `FormArray`, `FormCheckboxGroup`, `FormRangeField` | | UISchema | Layout and grouping renderers | `FormFieldset`, `FormUISchemaRenderer` | | Registry | Register custom field types | `FieldComponentRegistry` (`fd.fields`), matchers | ### `@flowdrop/flowdrop/form/code` Code and JSON editor support (adds \~300KB, requires CodeMirror). **Key exports:** | Category | Purpose | Exports | | ------------ | ------------------------- | ------------------------------------------------------------------------------ | | Components | Code and template editors | `FormCodeEditor`, `FormTemplateEditor` | | Registration | Register the field types | `registerCodeEditorField(fd.fields)`, `registerTemplateEditorField(fd.fields)` | ### `@flowdrop/flowdrop/form/markdown` Markdown editor support (requires CodeMirror + `@codemirror/lang-markdown`). **Key exports:** | Category | Purpose | Exports | | ------------ | ----------------------- | ---------------------------------------- | | Component | Markdown editor | `FormMarkdownEditor` | | Registration | Register the field type | `registerMarkdownEditorField(fd.fields)` | ### `@flowdrop/flowdrop/display` Content rendering components. **Key exports:** | Category | Purpose | Exports | | --------- | ----------------------------------------- | ----------------- | | Component | Renders markdown via the `marked` library | `MarkdownDisplay` | ### `@flowdrop/flowdrop/playground` Interactive workflow testing and human-in-the-loop. **Key exports:** | Category | Purpose | Exports | | --------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------- | | Components | Playground UI | `Playground`, `PlaygroundModal`, `ChatPanel`, `SessionManager`, `ExecutionLogs`, `MessageBubble` | | Interrupts | Human-in-the-loop prompt UI | `InterruptBubble`, `ConfirmationPrompt`, `ChoicePrompt`, `TextInputPrompt`, `FormPrompt`, `ReviewPrompt` | | Services | Session and interrupt logic | `PlaygroundService`, `InterruptService` | | Store | Playground and interrupt state | playground state, interrupt management | | Mount functions | Imperative mount/unmount | `mountPlayground`, `unmountPlayground` | | Types | Session and interrupt types | `PlaygroundSession`, `PlaygroundMessage`, `Interrupt`, `InterruptType` | ### `@flowdrop/flowdrop/settings` User preferences with hybrid persistence. **Key exports:** | Category | Purpose | Exports | | ---------- | --------------------- | ------------------------------------------------------------------- | | Components | Settings and theme UI | `ThemeToggle`, `SettingsPanel`, `SettingsModal` | | Store | Settings state | theme, editor, UI, behavior, API categories | | Types | Settings types | `FlowDropSettings`, `ThemeSettings`, `EditorSettings`, `UISettings` | ### `@flowdrop/flowdrop/styles` CSS styling with design tokens. **Exports:** CSS files with `--fd-*` custom properties for theming. ### `@flowdrop/flowdrop` Full bundle — re-exports from all sub-modules for convenience. Use this when bundle size is not a concern. ## REST API FlowDrop expects a backend implementing these endpoint groups. Not all are required — see [Backend Implementation](/guides/integration/backend-implementation) for which tier each belongs to. ### Required (Tier 1 — Minimum Viable Backend) | Method | Path | Purpose | | ------ | ---------------- | ------------------------------ | | `GET` | `/health` | Health check (called on mount) | | `GET` | `/nodes` | List available node types | | `GET` | `/workflows/:id` | Load a workflow | | `POST` | `/workflows` | Create a new workflow | | `PUT` | `/workflows/:id` | Update a workflow | ### Recommended (Tier 2 — Full Editor) | Method | Path | Purpose | | -------- | ---------------- | --------------------------------------- | | `GET` | `/categories` | Category definitions for sidebar groups | | `GET` | `/port-config` | Port data types and compatibility rules | | `GET` | `/nodes/:id` | Get single node metadata | | `GET` | `/workflows` | List all workflows | | `DELETE` | `/workflows/:id` | Delete a workflow | | `GET` | `/system/config` | Runtime configuration | ### Optional (Tier 3 — Advanced Features) | Group | Paths | Purpose | | ---------- | --------------------------------------------------------------------------- | ------------------------ | | Execution | `/workflows/{id}/execute`, `/executions/{id}` | Workflow execution | | Pipelines | `/pipeline/{id}` | Pipeline status & logs | | Playground | `/workflows/{id}/playground/sessions`, `/playground/sessions/{id}/messages` | Interactive testing | | Interrupts | `/interrupts/{id}`, `/interrupts/{id}/cancel` | Human-in-the-loop | | Agent Spec | `/agentspec` | Agent Spec import/export | See the [API reference](/api-reference/introduction) for full endpoint documentation, or follow the [Backend: Express.js](/recipes/backend-express) recipe to get started quickly. # Components Source: https://flowdrop.mintlify.app/reference/components Key Svelte components exported by FlowDrop. Browse every component interactively — props, states, and variants — in the FlowDrop Storybook. ## Editor components ### `App` Full-featured application wrapper with sidebar, editor, navbar, and config panel. ```svelte theme={null} ``` | Prop | Type | Default | Description | | ---------------- | ------------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `workflow` | `Workflow` | — | Initial workflow to load | | `nodes` | `NodeMetadata[]` | — | Available node types (overrides API fetch) | | `endpointConfig` | `EndpointConfig` | — | API endpoint configuration | | `authProvider` | `AuthProvider` | — | Authentication provider | | `height` | `string \| number` | `'100vh'` | Editor height | | `width` | `string \| number` | `'100%'` | Editor width | | `showNavbar` | `boolean` | `false` | Show the top navbar | | `disableSidebar` | `boolean` | `false` | Hide the node sidebar | | `mode` | `'edit' \| 'readonly' \| 'locked'` | `'edit'` | Interaction mode; `'readonly'`/`'locked'` disable all canvas editing | | `showSettings` | `boolean` | `true` | Show settings gear in navbar | | `showStatus` | `boolean` | `true` | Show the "Connected" status indicator in the navbar | | `navbarTitle` | `string` | — | Custom title in navbar | | `navbarActions` | `NavbarAction[]` | — | Custom action buttons in navbar | | `pipelineId` | `string` | — | Pipeline ID for execution status display | | `theme` | `FlowDropTheme \| FlowDropThemeName` | — | Visual theme — named built-in (`'default'`/`'minimal'`) or a custom theme object | | `on*` handlers | see below | — | Flat lifecycle callbacks: `onBeforeSave`, `onAfterSave`, `onSaveError`, `onApiError`, `onWorkflowLoad`, `onBeforeSwap`, `onAfterSwap` | | `features` | `FlowDropFeatures` | — | Feature flags | | `instance` | `FlowDropInstance` | — | Per-instance state container; defaults to context, then the page-default instance | The `` component takes flat `on*` event props. The grouped `eventHandlers` object is the **mount** API only — see [Event Handlers](/reference/event-handlers). ### `WorkflowEditor` Core canvas component using SvelteFlow. Renders nodes and edges with drag-and-drop, zoom, pan, and minimap. It reads its nodes, edges, and dimensions from the instance state container rather than from props. ```svelte theme={null} ``` | Prop | Type | Default | Description | | ------------------- | ---------------------------------- | -------- | --------------------------------------------------------------------------------- | | `endpointConfig` | `EndpointConfig` | — | API configuration | | `mode` | `'edit' \| 'readonly' \| 'locked'` | `'edit'` | Interaction mode; `'readonly'`/`'locked'` disable all canvas editing | | `openConfigSidebar` | `(node: WorkflowNode) => void` | — | Callback invoked when a node is clicked to open its config panel | | `pipelineId` | `string` | — | Pipeline ID for fetching node execution status | | `refreshTrigger` | `number` | `0` | Increment to force a re-fetch of node execution status from the server | | `consoleOpen` | `boolean` | — | Whether the bottom console panel is open | | `onToggleConsole` | `() => void` | — | Callback to toggle the console panel | | `instance` | `FlowDropInstance` | — | Per-instance state container; defaults to context, then the page-default instance | ### `NodeSidebar` Left sidebar displaying available node types organized by category. ```svelte theme={null} ``` | Prop | Type | Default | Description | | ------------------ | ---------------- | ------- | --------------------------------------------- | | `nodes` | `NodeMetadata[]` | `[]` | Node types to display | | `selectedCategory` | `NodeCategory` | — | Pre-select a category filter | | `activeFormat` | `WorkflowFormat` | — | Filter nodes by workflow format compatibility | ### `UniversalNode` Dynamic node wrapper that resolves and renders the correct node component based on type. Automatically injects `NodeStatusOverlay`. Used internally by the editor. ## Node components All node components accept `NodeComponentProps` from the registry: | Component | Type | Description | | ----------------------- | -------------- | ------------------------------------------------ | | `WorkflowNodeComponent` | `workflowNode` | Full-featured node with input/output port lists | | `SimpleNode` | `simple` | Compact layout with header, icon, description | | `SquareNode` | `square` | Minimal icon-only square design | | `ToolNode` | `tool` | Agent tool node with badge indicator | | `GatewayNode` | `gateway` | Conditional branching with multiple output ports | | `TerminalNode` | `terminal` | Circular start/end node | | `NotesNode` | `note` | Sticky note with markdown content | See [Node Types](/guides/node-types) for visual examples. ## Form components ### `SchemaForm` Renders a form from JSON Schema definition. ```svelte theme={null} { /* handle change */ }} /> ``` ### `ConfigForm` Configuration form with support for dynamic schemas, template variables, and external edit links. | Prop | Type | Default | Description | | ------------------ | ------------------------- | ------- | -------------------------------------------- | | `node` | `WorkflowNode` | — | Node to configure (derives schema/values) | | `schema` | `ConfigSchema` | — | Direct JSON Schema (alternative to `node`) | | `uiSchema` | `UISchemaElement` | — | Layout definition for the form | | `values` | `Record` | — | Configuration values | | `showUIExtensions` | `boolean` | `false` | Show UI extension fields | | `workflowId` | `string` | — | For dynamic schema and variable API requests | | `workflowNodes` | `WorkflowNode[]` | — | All nodes (for template variable resolution) | | `workflowEdges` | `WorkflowEdge[]` | — | All edges (for template variable resolution) | | `authProvider` | `AuthProvider` | — | Auth for API requests | | `onChange` | `function` | — | Called on any field change | | `onSave` | `function` | — | Called when form is saved | | `onCancel` | `function` | — | Called when form is cancelled | ### `ConfigPanel` Generic panel wrapper for displaying details and a configuration form. | Prop | Type | Default | Description | | ------------- | -------------- | ------- | --------------------- | | `title` | `string` | — | Panel title | | `id` | `string` | — | Entity identifier | | `description` | `string` | — | Description text | | `details` | `DetailItem[]` | — | Key-value detail rows | | `configTitle` | `string` | — | Config section title | | `onClose` | `function` | — | Close callback | | `children` | `Snippet` | — | Slot for form content | ### Field components | Component | Schema Match | Description | | -------------------- | ------------------------------ | ----------------------------------------------------- | | `FormTextField` | `type: "string"` | Text input | | `FormTextarea` | `format: "multiline"` | Multi-line text | | `FormNumberField` | `type: "number"` / `"integer"` | Number input | | `FormToggle` | `type: "boolean"` | Toggle switch | | `FormSelect` | `enum` or `oneOf` | Select dropdown | | `FormCheckboxGroup` | `enum` + `multiple: true` | Checkbox group | | `FormRangeField` | `format: "range"` | Range slider | | `FormArray` | `type: "array"` | Dynamic array editor | | `FormCodeEditor` | `format: "json"` | JSON/code editor (requires `form/code`) | | `FormTemplateEditor` | `format: "template"` | Template editor with variables (requires `form/code`) | | `FormMarkdownEditor` | `format: "markdown"` | Markdown editor (requires `form/markdown`) | ## Display components ### `MarkdownDisplay` Renders markdown content using the `marked` library. ```svelte theme={null} ``` ## Playground components ### `Playground` Full interactive playground with chat interface and session management. | Prop | Type | Default | Description | | ------------------ | ------------------ | -------------- | --------------------------------------------------------------------------------- | | `workflowId` | `string` | — | Workflow to test (required) | | `workflow` | `Workflow` | — | Pre-loaded workflow data | | `mode` | `PlaygroundMode` | `'standalone'` | `'standalone'` or `'embedded'` | | `initialSessionId` | `string` | — | Resume a previous session | | `endpointConfig` | `EndpointConfig` | — | API configuration | | `config` | `PlaygroundConfig` | — | Playground options | | `onClose` | `function` | — | Close callback (for embedded mode) | | `instance` | `FlowDropInstance` | — | Per-instance state container; defaults to context, then the page-default instance | ### Interrupt components | Component | Interrupt Type | Description | | -------------------- | -------------- | ----------------------------------------- | | `InterruptBubble` | All | Container that renders the correct prompt | | `ConfirmationPrompt` | `confirmation` | Yes/No approval | | `ChoicePrompt` | `choice` | Selection from options | | `TextInputPrompt` | `text_input` | Text entry | | `FormPrompt` | `form` | JSON Schema form | | `ReviewPrompt` | `review` | Field change review with diffs | ## Settings components ### `ThemeToggle` Button to cycle through light/dark/auto themes. ```svelte theme={null} ``` ### `SettingsPanel` Tabbed settings interface for managing user preferences across categories (theme, editor, UI, behavior, API). Can be embedded anywhere in your layout. ```svelte theme={null} { /* handle change */ }} onClose={() => { /* handle close */ }} /> ``` | Prop | Type | Default | Description | | ------------------ | -------------------- | -------------- | ------------------------------------------------------------------------------------ | | `categories` | `SettingsCategory[]` | All categories | Which tabs to display. Options: `"theme"`, `"editor"`, `"ui"`, `"behavior"`, `"api"` | | `showSyncButton` | `boolean` | `true` | Show the "Sync to Cloud" button in the footer | | `showResetButton` | `boolean` | `true` | Show the reset/reset-all buttons in the footer | | `onSettingsChange` | `function` | — | Called when any setting changes with `(category, values)` | | `onClose` | `function` | — | Close callback (also renders a "Close" button in the footer) | | `class` | `string` | — | Custom CSS class | ### `SettingsModal` Modal dialog wrapper around `SettingsPanel`. Provides backdrop, close-on-escape, and open/close animations. ```svelte theme={null} ``` | Prop | Type | Default | Description | | ------------------ | -------------------- | -------------- | ------------------------------------ | | `open` | `boolean` | `false` | Whether the modal is open (bindable) | | `categories` | `SettingsCategory[]` | All categories | Which tabs to display | | `showSyncButton` | `boolean` | `true` | Show the "Sync to Cloud" button | | `showResetButton` | `boolean` | `true` | Show the reset buttons | | `onClose` | `function` | — | Called when the modal is closed | | `onSettingsChange` | `function` | — | Called when any setting changes | | `class` | `string` | — | Custom CSS class for the modal | #### Hiding features To hide the cloud sync button (e.g. for self-hosted deployments): ```svelte theme={null} ``` To show only specific settings categories: ```svelte theme={null} ``` #### Vanilla JS / `mountFlowDropApp` When using the vanilla JS mount API, pass settings modal options via `FlowDropMountOptions`: ```javascript theme={null} import { mountFlowDropApp, createEndpointConfig } from '@flowdrop/flowdrop'; const app = await mountFlowDropApp(document.getElementById('editor'), { endpointConfig: createEndpointConfig('/api/flowdrop'), showSettings: true, // Customize the settings modal settingsCategories: ['theme', 'editor', 'ui'], // hide Behavior & API tabs showSettingsSyncButton: false, // hide "Sync to Cloud" showSettingsResetButton: true // show reset (default) }); ``` | Option | Type | Default | Description | | ------------------------- | -------------------- | -------------- | ------------------------------------------- | | `showSettings` | `boolean` | `true` | Show the settings gear icon in the navbar | | `settingsCategories` | `SettingsCategory[]` | All categories | Which tabs to display in the settings modal | | `showSettingsSyncButton` | `boolean` | `true` | Show the "Sync to Cloud" button | | `showSettingsResetButton` | `boolean` | `true` | Show the reset buttons | ## Status components | Component | Description | | ------------------- | ----------------------------------------------------------------------- | | `NodeStatusOverlay` | Displays execution status on nodes (pending, running, completed, error) | | `StatusIcon` | Color-coded status icon | | `PipelineStatus` | Full pipeline execution view with logs sidebar | ## Component hierarchy ```text theme={null} App ├── Navbar (Logo, workflow name, save/export, custom actions) ├── NodeSidebar (search, category groups, draggable node cards) ├── WorkflowEditor (SvelteFlow canvas) │ └── UniversalNode │ ├── NodeStatusOverlay │ └── [Node Component] (WorkflowNode, SimpleNode, etc.) └── ConfigSidebar └── ConfigForm (JSON Schema form with template variables) ``` # CSS design tokens Source: https://flowdrop.mintlify.app/reference/css-tokens Complete reference for all --fd-* CSS custom properties used to theme FlowDrop. FlowDrop uses CSS custom properties with a `--fd-*` prefix as its theming API. Override these tokens to customize the editor's appearance. See components rendered with these tokens — toggle light/dark and inspect the result live — in the FlowDrop Storybook. ## How theming works FlowDrop's token system has three tiers: | Tier | Purpose | You customize? | | ------------------------------ | ----------------------- | -------------- | | **Internal Palette** (`--_*`) | Raw color values | No | | **Semantic Tokens** (`--fd-*`) | Public theming API | **Yes** | | **Component styles** | Consume semantic tokens | No | Override `--fd-*` tokens and all components update automatically. ```css theme={null} /* Example: Apply a purple theme */ :root { --fd-primary: #8b5cf6; --fd-primary-hover: #7c3aed; --fd-primary-muted: #f5f3ff; --fd-accent: #8b5cf6; --fd-ring: #8b5cf6; } ``` ## Surfaces | Token | Description | | ----------------------- | -------------------------------- | | `--fd-background` | Main background color | | `--fd-foreground` | Main text color | | `--fd-muted` | Muted background (cards, inputs) | | `--fd-muted-foreground` | Muted text color | | `--fd-card` | Card background | | `--fd-card-foreground` | Card text color | ## Borders | Token | Description | | -------------------- | -------------------- | | `--fd-border` | Default border color | | `--fd-border-muted` | Subtle border | | `--fd-border-strong` | Emphasized border | | `--fd-ring` | Focus ring color | ## Primary & accent | Token | Description | | ------------------------- | ------------------------------------- | | `--fd-primary` | Primary action color (buttons, links) | | `--fd-primary-hover` | Primary hover state | | `--fd-primary-foreground` | Text on primary background | | `--fd-primary-muted` | Light primary background | | `--fd-accent` | Accent color | | `--fd-accent-hover` | Accent hover state | ## Status colors Each status color has four variants: | Base Token | `-hover` | `-foreground` | `-muted` | | -------------- | -------------------- | ------------------------- | -------------------- | | `--fd-success` | `--fd-success-hover` | `--fd-success-foreground` | `--fd-success-muted` | | `--fd-warning` | `--fd-warning-hover` | `--fd-warning-foreground` | `--fd-warning-muted` | | `--fd-error` | `--fd-error-hover` | `--fd-error-foreground` | `--fd-error-muted` | | `--fd-info` | `--fd-info-hover` | `--fd-info-foreground` | `--fd-info-muted` | ## Spacing | Token | Default | | ---------------- | ------- | | `--fd-space-3xs` | 4px | | `--fd-space-2xs` | 6px | | `--fd-space-xs` | 8px | | `--fd-space-sm` | 10px | | `--fd-space-md` | 12px | | `--fd-space-lg` | 14px | | `--fd-space-xl` | 16px | | `--fd-space-2xl` | 20px | | `--fd-space-3xl` | 24px | ## Border radius | Token | Default | | ------------------ | ------------------- | | `--fd-radius-sm` | 4px | | `--fd-radius-md` | 6px | | `--fd-radius-lg` | 8px | | `--fd-radius-xl` | 12px | | `--fd-radius-full` | 9999px (pill shape) | ## Typography | Token | Default | | ---------------- | ------- | | `--fd-text-xs` | 12px | | `--fd-text-sm` | 14px | | `--fd-text-base` | 16px | | `--fd-text-lg` | 18px | | `--fd-text-xl` | 20px | ## Layout | Token | Default | Description | | --------------------- | ------- | --------------------- | | `--fd-sidebar-width` | 320px | Node sidebar width | | `--fd-navbar-height` | 60px | Top navbar height | | `--fd-toolbar-height` | 40px | Canvas toolbar height | ## Node dimensions Node dimensions use a **10px grid** to align with the editor's snap grid: | Token | Default | Description | | ------------------------- | ------- | ------------------------------ | | `--fd-node-default-width` | 290px | Standard node width | | `--fd-node-header-height` | 60px | Node header area | | `--fd-node-terminal-size` | 80px | Terminal node size (start/end) | | `--fd-node-square-size` | 80px | Square node size (gateway) | | `--fd-handle-size` | 20px | Port handle hit area | | `--fd-handle-visual-size` | 12px | Port handle visible size | ## Accent HSL knobs For fine-grained accent control, FlowDrop exposes HSL components: | Token | Default | Description | | ------------------------ | --------- | --------------------- | | `--fd-accent-hue` | 17 | Accent hue (0–360) | | `--fd-accent-saturation` | 100% | Accent saturation | | `--fd-accent-lightness` | 34% | Accent lightness | | `--fd-accent-low` | (derived) | Low-intensity accent | | `--fd-accent-high` | (derived) | High-intensity accent | | `--fd-gray-hue` | 210 | Base hue for grays | Quick accent experiments: ```css theme={null} :root { --fd-accent-hue: 174; /* Teal */ --fd-accent-hue: 260; /* Purple */ --fd-accent-hue: 340; /* Pink */ --fd-accent-hue: 220; /* Blue */ --fd-accent-hue: 150; /* Green */ } ``` ## Dark mode FlowDrop auto-switches tokens based on `data-theme`: ```html theme={null} ``` Or programmatically: ```typescript theme={null} import { setTheme, toggleTheme } from '@flowdrop/flowdrop/settings'; setTheme('dark'); toggleTheme(); ``` All semantic tokens have dark-mode equivalents that activate automatically. The accent HSL knobs adjust luminance in dark mode — `--fd-accent-low` becomes darker and `--fd-accent-high` becomes lighter. ## Next steps * [Theming Guide](/guides/theming) — practical theming patterns * [Store API: settingsStore](/reference/stores#settingsstore) — programmatic theme control # Error reference Source: https://flowdrop.mintlify.app/reference/errors Mount API exceptions, API error handling, HTTP status codes, and Agent Spec validation errors in FlowDrop. This page covers all error conditions you may encounter when integrating FlowDrop: mount exceptions, API errors during operation, and Agent Spec validation failures. ## Mount API exceptions `mountFlowDropApp()` throws synchronously if the mount cannot proceed. | Error message | Cause | Fix | | ------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `Container element not found` | The selector or element passed to `container` does not exist in the DOM | Ensure the container element exists before calling `mountFlowDropApp()` | | `Container has no dimensions` | The container element has zero width or height | Apply `width` and `height` (or `min-height`) to the container via CSS before mounting | | `FlowDrop CSS not imported` | The base styles from `@flowdrop/flowdrop/styles` were not loaded | Import `@flowdrop/flowdrop/styles` before mounting | | `Cannot mount in server-side context` | `mountFlowDropApp()` was called during SSR (no `window` object) | Guard the call with `if (typeof window !== 'undefined')` — see [Framework Integration](/guides/integration) | ```javascript theme={null} // Safe mount pattern if (typeof window !== 'undefined') { const app = await mountFlowDropApp(document.getElementById('editor'), { // ...options }); } ``` ## API error handling FlowDrop calls your REST API during normal operation. Use the `onApiError` event handler to intercept these errors. ```javascript theme={null} const app = await mountFlowDropApp(document.getElementById('editor'), { endpointConfig: createEndpointConfig('/api/flowdrop'), eventHandlers: { onApiError: (error, operation) => { // error.message — error description // operation — what FlowDrop was doing when the error occurred if (error.status === 401) { // Redirect to login window.location.href = '/login'; return true; // return true to suppress the default toast notification } // Return false (or nothing) to show the default error toast return false; } } }); ``` ### HTTP status codes | Status | Cause | Recommended action | | ------ | --------------------------------------------- | -------------------------------------------------- | | `0` | Network error — no response received | Check server is running; show connectivity warning | | `401` | Unauthorized — missing or expired credentials | Redirect to login or refresh token | | `403` | Forbidden — authenticated but not allowed | Show permission error; do not redirect | | `404` | Endpoint not found — wrong URL configured | Verify your endpoint configuration | | `422` | Validation error — malformed workflow JSON | Log the response body for details | | `500` | Server error | Log and surface to user; offer retry | ### `operation` values The `operation` argument tells you what FlowDrop was doing when the error occurred: | Value | Trigger | | ----------------- | ------------------------------------------------- | | `loadNodes` | Initial `GET /nodes` request on mount | | `loadWorkflow` | `GET /workflows/:id` on mount | | `saveWorkflow` | `POST /workflows` or `PUT /workflows/:id` on save | | `loadPortConfig` | `GET /port-config` for dynamic port compatibility | | `executeWorkflow` | `POST /execute` (if using the playground) | ## Save errors `onSaveError` is a separate handler called when a save operation fails. It receives the workflow object that failed to save, allowing you to recover or retry. ```javascript theme={null} const app = await mountFlowDropApp(document.getElementById('editor'), { eventHandlers: { onSaveError: async (error, workflow) => { console.error('Save failed:', error); // workflow is the workflow object that failed to persist // You can store it locally or retry: localStorage.setItem('flowdrop-save-fallback', JSON.stringify(workflow)); } } }); ``` ## Agent Spec validation errors When exporting a workflow to Agent Spec format via `WorkflowOperationsHelper.exportAsAgentSpec()`, the result object indicates success or failure: ```javascript theme={null} const result = WorkflowOperationsHelper.exportAsAgentSpec(workflow); // result: { valid: boolean, errors: string[], warnings: string[] } if (!result.valid) { console.error('Export failed:', result.errors); } ``` ### Common validation errors | Error message | Cause | Fix | | -------------------------------- | ----------------------------------------------- | ------------------------------------------------------------ | | `Workflow has no nodes` | The workflow is empty | Add at least one node before exporting | | `Cycle detected` | The workflow graph contains a loop | Remove cyclic connections; Agent Spec requires a DAG | | `Node type not mappable: ` | A custom node type has no Agent Spec equivalent | Map custom types to Agent Spec actions in your export config | | `Disconnected node: ` | A node has no edges | Connect all nodes or remove unused ones | | `Missing required port: ` | A required input port has no incoming edge | Connect the required port before exporting | Warnings (non-fatal) indicate information that may be lost in the export, such as FlowDrop-specific configuration fields that have no Agent Spec equivalent. # Event handlers reference Source: https://flowdrop.mintlify.app/reference/event-handlers Complete reference for all FlowDrop event handlers. FlowDrop exposes the same set of lifecycle callbacks through two surfaces: * **The `` component** takes them as **flat `on*` props** — `onBeforeSave`, `onAfterSave`, `onSaveError`, `onApiError`, `onWorkflowLoad`, `onBeforeSwap`, `onAfterSwap`. * **The mount options bag** (`mountFlowDropApp`, `mountWorkflowEditor`) takes the **grouped `eventHandlers` object**, which additionally wires `onWorkflowChange`, `onDirtyStateChange`, and `onBeforeUnmount`. ```typescript theme={null} const app = await mountFlowDropApp(container, { eventHandlers: { /* handlers below */ } }); ``` Every handler is optional. ## Workflow lifecycle | Handler | Signature | When it fires | | -------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | | `onWorkflowChange` | `(workflow: Workflow, changeType: WorkflowChangeType) => void` | Any modification to nodes, edges, config, or metadata | | `onWorkflowLoad` | `(workflow: Workflow) => void` | After a workflow is loaded and initialized | | `onDirtyStateChange` | `(isDirty: boolean) => void` | When the workflow transitions between saved and unsaved states | ### `WorkflowChangeType` values | Value | Trigger | | ------------- | --------------------------------- | | `node_add` | Node added to canvas | | `node_remove` | Node deleted | | `node_move` | Node dragged to new position | | `node_config` | Node configuration values changed | | `edge_add` | Connection drawn between nodes | | `edge_remove` | Connection deleted | | `metadata` | Workflow metadata changed | | `name` | Workflow name edited | | `description` | Workflow description edited | ## Save lifecycle | Handler | Signature | When it fires | | -------------- | ----------------------------------------------------- | -------------------------------------- | | `onBeforeSave` | `(workflow: Workflow) => Promise` | Before save. Return `false` to cancel. | | `onAfterSave` | `(workflow: Workflow) => Promise` | After successful save | | `onSaveError` | `(error: Error, workflow: Workflow) => Promise` | When save fails | ## Node swap | Handler | Signature | When it fires | | -------------- | ---------------------------------------------------------------------------- | --------------------------------------------- | | `onBeforeSwap` | `(context: SwapEventContext) => boolean \| void \| Promise` | Before a node swap. Return `false` to cancel. | | `onAfterSwap` | `(result: SwapResult, oldNode: WorkflowNode, newNodeId: string) => void` | After a node swap is applied | ## Error & cleanup | Handler | Signature | When it fires | | ----------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `onApiError` | `(error: Error, operation: string) => boolean \| void` | Any API request failure. Return `true` to suppress default toast. `operation` values: `"save"`, `"load"`, `"fetchNodes"`, `"fetchCategories"`, etc. | | `onBeforeUnmount` | `(workflow: Workflow, isDirty: boolean) => void` | Before FlowDrop is destroyed/unmounted | ## Agent Spec execution | Handler | Signature | When it fires | | ------------------------------- | ----------------------------------------------------------------- | ------------------------------------ | | `onAgentSpecExecutionStarted` | `(executionId: string) => void` | Execution begins | | `onAgentSpecExecutionCompleted` | `(executionId: string, results: Record) => void` | Execution succeeds | | `onAgentSpecExecutionFailed` | `(executionId: string, error: Error) => void` | Execution fails | | `onAgentSpecNodeStatusUpdate` | `(nodeId: string, status: NodeExecutionInfo) => void` | Node status changes during execution | ## Complete interface ```typescript theme={null} interface FlowDropEventHandlers { onWorkflowChange?: (workflow: Workflow, changeType: WorkflowChangeType) => void; onDirtyStateChange?: (isDirty: boolean) => void; onBeforeSave?: (workflow: Workflow) => Promise; onAfterSave?: (workflow: Workflow) => Promise; onSaveError?: (error: Error, workflow: Workflow) => Promise; onWorkflowLoad?: (workflow: Workflow) => void; onBeforeUnmount?: (workflow: Workflow, isDirty: boolean) => void; onApiError?: (error: Error, operation: string) => boolean | void; onBeforeSwap?: (context: SwapEventContext) => boolean | void | Promise; onAfterSwap?: (result: SwapResult, oldNode: WorkflowNode, newNodeId: string) => void; onAgentSpecExecutionStarted?: (executionId: string) => void; onAgentSpecExecutionCompleted?: (executionId: string, results: Record) => void; onAgentSpecExecutionFailed?: (executionId: string, error: Error) => void; onAgentSpecNodeStatusUpdate?: (nodeId: string, status: NodeExecutionInfo) => void; } ``` For usage examples and patterns, see the [Event System guide](/guides/advanced/event-system). # Icons Source: https://flowdrop.mintlify.app/reference/icons How FlowDrop uses Iconify for icons across nodes, categories, and UI. FlowDrop uses [Iconify](https://iconify.design/) for all icons throughout the editor — node icons, category icons, status indicators, and toolbar actions. ## How it works Icons are rendered via [`@iconify/svelte`](https://iconify.design/docs/icon-components/svelte/), which is listed as an **optional peer dependency**. When installed, the Iconify component fetches icons on demand from the Iconify API, giving you access to **200,000+ icons** from 150+ open-source sets with zero bundling overhead. Install it alongside FlowDrop: ```bash theme={null} npm install @iconify/svelte ``` ## Icon format All icon fields in FlowDrop use the Iconify string format: ```text theme={null} set:icon-name ``` For example: ```js theme={null} 'mdi:brain'; // Material Design Icons 'heroicons:sparkles'; // Heroicons 'lucide:bot'; // Lucide 'ph:brain'; // Phosphor Icons 'tabler:api'; // Tabler Icons ``` You can browse and search all available icon sets at **[icon-sets.iconify.design](https://icon-sets.iconify.design/)**. ## Where icons are used Icons appear in several places within FlowDrop: | Context | Field | Example | | -------------------- | ---------- | -------------------- | | Node definition | `icon` | `'mdi:text'` | | Category definition | `icon` | `'mdi:import'` | | Status indicators | (internal) | `'mdi:check-circle'` | | Toolbar & UI actions | (internal) | `'mdi:content-save'` | ### Node icons Set the `icon` field on a node definition: ```js theme={null} const myNode = { id: 'text_input', name: 'Text Input', icon: 'mdi:text' // Any Iconify icon // ... }; ``` FlowDrop resolves icons with a fallback chain: 1. The node's own `icon` field 2. The category's icon 3. Default fallback: `mdi:cube` ### Category icons Set the `icon` field on a category definition: ```js theme={null} const myCategory = { id: 'inputs', name: 'Inputs', icon: 'mdi:import', color: '#22c55e' }; ``` ## Built-in default icons FlowDrop includes a set of built-in icon constants used across the editor UI. These are not required for your own nodes — they document what the editor uses internally. ### UI action icons | Key | Icon | Usage | | ---------- | ------------------ | -------------- | | `ADD` | `mdi:plus` | Add buttons | | `REMOVE` | `mdi:minus` | Remove buttons | | `EDIT` | `mdi:pencil` | Edit actions | | `SAVE` | `mdi:content-save` | Save workflow | | `EXPORT` | `mdi:download` | Export actions | | `IMPORT` | `mdi:upload` | Import actions | | `SEARCH` | `mdi:magnify` | Search fields | | `CLOSE` | `mdi:close` | Close buttons | | `SETTINGS` | `mdi:cog` | Settings panel | ### Status icons | Status | Icon | | --------- | -------------------- | | Idle | `mdi:circle-outline` | | Pending | `mdi:clock-outline` | | Running | `mdi:loading` | | Completed | `mdi:check-circle` | | Failed | `mdi:alert-circle` | | Cancelled | `mdi:cancel` | | Skipped | `mdi:skip-next` | ### Built-in category icons When you define categories, you can use any icon you like. For reference, these are the icons FlowDrop uses for common category IDs: | Category ID | Icon | | ------------ | ----------------------- | | `triggers` | `mdi:lightning-bolt` | | `inputs` | `mdi:arrow-down-circle` | | `outputs` | `mdi:arrow-up-circle` | | `prompts` | `mdi:message-text` | | `models` | `mdi:robot` | | `processing` | `mdi:cog` | | `logic` | `mdi:source-branch` | | `data` | `mdi:database` | | `tools` | `mdi:wrench` | | `ai` | `mdi:shimmer` | | `agents` | `mdi:account-cog` | | `memories` | `mdi:brain` | | `interrupts` | `mdi:hand-back-left` | ## Icon validation FlowDrop validates icon strings against the format `set:icon-name` (lowercase letters and hyphens). Invalid icons fall back to `mdi:cube`. ```js theme={null} 'mdi:brain'; // ✓ valid 'heroicons:bolt'; // ✓ valid 'Brain'; // ✗ invalid — falls back to mdi:cube ``` ## Popular icon sets Here are some commonly used icon sets that work well with FlowDrop: | Set | Prefix | Icons | Browse | | --------------------- | ------------ | ------ | ----------------------------------------------------- | | Material Design Icons | `mdi:` | 7,000+ | [Browse](https://icon-sets.iconify.design/mdi/) | | Heroicons | `heroicons:` | 300+ | [Browse](https://icon-sets.iconify.design/heroicons/) | | Lucide | `lucide:` | 1,500+ | [Browse](https://icon-sets.iconify.design/lucide/) | | Phosphor | `ph:` | 7,000+ | [Browse](https://icon-sets.iconify.design/ph/) | | Tabler Icons | `tabler:` | 5,000+ | [Browse](https://icon-sets.iconify.design/tabler/) | | Carbon | `carbon:` | 2,000+ | [Browse](https://icon-sets.iconify.design/carbon/) | The full catalog is at [icon-sets.iconify.design](https://icon-sets.iconify.design/). # Mount API Source: https://flowdrop.mintlify.app/reference/mount-api Complete reference for mountFlowDropApp(), mountWorkflowEditor(), and mountPlayground() — all options, return values, and lifecycle. The mount API lets you embed FlowDrop into any HTML container, regardless of framework. ## `mountFlowDropApp()` Mounts the full FlowDrop application (sidebar, editor, config panel) into a container. ```typescript theme={null} import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; const app = await mountFlowDropApp(container: HTMLElement, options: FlowDropMountOptions); ``` ### Options ```typescript theme={null} interface FlowDropMountOptions { // Data /** Initial workflow to load */ workflow?: Workflow; /** Available node types (overrides API fetch) */ nodes?: NodeMetadata[]; // API /** REST API endpoint configuration */ endpointConfig?: EndpointConfig; /** Port data type configuration (overrides API fetch) */ portConfig?: PortConfig; /** Category definitions (overrides API fetch) */ categories?: CategoryDefinition[]; /** Authentication provider for API requests */ authProvider?: AuthProvider; // Event handlers — the grouped lifecycle events bag eventHandlers?: FlowDropEventHandlers; // Features features?: FlowDropFeatures; // UI options /** Show the top navbar @default true */ showNavbar?: boolean; /** Disable the node sidebar */ disableSidebar?: boolean; /** Editor interaction mode. @default 'edit' */ mode?: 'edit' | 'readonly' | 'locked'; /** Editor height (CSS value or number) */ height?: string | number; /** Editor width (CSS value or number) */ width?: string | number; // Navbar customization /** Custom navbar title */ navbarTitle?: string; /** Custom navbar action buttons */ navbarActions?: NavbarAction[]; /** Show settings gear icon in navbar */ showSettings?: boolean; /** Show the "Connected" status indicator in the navbar @default true */ showStatus?: boolean; // Pipeline mode /** Pipeline ID for execution status display */ pipelineId?: string; // Theme /** Visual theme — named built-in ('default' | 'minimal') or a custom theme object */ theme?: FlowDropTheme | FlowDropThemeName; // Instance /** Identifier for this instance. @default 'default' (auto-generated for extra mounts) */ instanceId?: string; // Settings /** Initial settings overrides (theme, behavior, editor, ui, api) */ settings?: PartialSettings; /** Custom localStorage key for draft auto-save */ draftStorageKey?: string; /** Where workflow drafts are persisted: 'local' (default), 'session', or a custom adapter */ draftStorage?: DraftStorageOption; /** Which settings tabs to show in the modal (defaults to all) */ settingsCategories?: SettingsCategory[]; /** Show the "Sync to Cloud" button in the settings modal */ showSettingsSyncButton?: boolean; /** Show the reset buttons in the settings modal */ showSettingsResetButton?: boolean; // Format adapters /** Custom workflow format adapters */ formatAdapters?: WorkflowFormatAdapter[]; } ``` The `mode` option controls canvas interaction: `'edit'` (the default) allows editing; `'readonly'` and `'locked'` disable all canvas interaction. When you omit `instanceId`, the first mount on the page becomes the default instance (id `default`); additional mounts get auto-generated ids (`fd-1`, `fd-2`, …). Every instance scopes its draft and panel storage keys by id (`flowdrop:draft::…`). Pass an explicit `instanceId` whenever you mount more than one editor with drafts enabled, so the keys stay stable across page loads. ### Feature flags (`FlowDropFeatures`) ```typescript theme={null} interface FlowDropFeatures { /** Auto-save the current workflow to localStorage as a draft. @default true */ autoSaveDraft?: boolean; /** How often to auto-save the draft, in milliseconds. @default 30000 */ autoSaveDraftInterval?: number; /** Show toast notifications for save success, failure, and API errors. * Disable if you want to handle notifications yourself via event handlers. @default true */ showToasts?: boolean; } ``` See [Auto-Save & Drafts](/recipes/auto-save-and-drafts) for practical examples. See [Core Types](/reference/types) for `FlowDropEventHandlers` and `FlowDropFeatures`. ### Return value ```typescript theme={null} interface MountedFlowDropApp { /** This mount's state container — drive the editor programmatically. */ instance: FlowDropInstance; /** Destroy the editor and clean up resources */ destroy(): void; /** Check if there are unsaved changes */ isDirty(): boolean; /** Mark the workflow as saved (clears dirty state) */ markAsSaved(): void; /** Get the current workflow data */ getWorkflow(): Workflow | null; /** Trigger save operation */ save(): Promise; /** Trigger export (downloads JSON file) */ export(): void; /** Clear all workflow drafts from draft storage. Returns the number of entries removed. */ clearAllDrafts(): number; } ``` `instance` is the state container — workflow, history, playground, interrupts, categories, and the rest. Call into it to drive the editor programmatically, e.g. `app.instance.workflow.addNode(...)` or `app.instance.history.undo()`. **Call `clearAllDrafts()` on logout.** Otherwise drafts persist across user sessions on shared devices. It clears the configured draft storage (`localStorage` unless changed via the `draftStorage` option). ## `mountWorkflowEditor()` Mounts just the editor canvas — no navbar, no sidebar. Useful for embedding a minimal editor. ```typescript theme={null} import { mountWorkflowEditor } from '@flowdrop/flowdrop/editor'; const editor = await mountWorkflowEditor(container, { workflow: myWorkflow, endpointConfig: createEndpointConfig('/api/flowdrop'), portConfig: myPortConfig, // optional, overrides API categories: myCategories, // optional, overrides API instanceId: 'editor-b' // optional — scope to a named instance for multi-editor pages }); ``` Returns the same `MountedFlowDropApp` interface as `mountFlowDropApp()`. `instanceId` follows the same default-instance semantics as `mountFlowDropApp()` above. ## `mountPlayground()` Mounts the interactive playground for workflow testing. ```typescript theme={null} import { mountPlayground } from '@flowdrop/flowdrop/playground'; const playground = await mountPlayground(container, { workflowId: 'my-workflow-id', endpointConfig: createEndpointConfig('/api/flowdrop'), mode: 'standalone', // 'standalone' | 'embedded' | 'modal' config: { pollingInterval: 1500, shouldStopPolling: (status) => ['completed', 'failed'].includes(status), isTerminalStatus: (status) => ['completed', 'failed'].includes(status) }, onSessionStatusChange: (newStatus, previousStatus) => { console.log(`${previousStatus} -> ${newStatus}`); }, onClose: () => console.log('Playground closed'), // required for embedded/modal height: '600px', width: '100%', initialSessionId: 'resume-session-id', // optional instanceId: 'playground-b' // optional — scope to a named instance }); ``` `instanceId` follows the same default-instance semantics as `mountFlowDropApp()`. **Live polling is page-global.** Playground state (sessions, messages, interrupts) is isolated per instance, but only one playground can actively poll at a time. Drive a non-polling playground via `pushMessages()` instead. ### Playground return value ```typescript theme={null} interface MountedPlayground { /** Destroy and clean up */ destroy(): void; /** Get the current session */ getCurrentSession(): PlaygroundSession | null; /** Get all sessions */ getSessions(): PlaygroundSession[]; /** Get message count in current session */ getMessageCount(): number; /** Check if currently executing */ isExecuting(): boolean; /** Stop polling for messages */ stopPolling(): void; /** Restart polling */ startPolling(): void; /** Push poll response data */ pushMessages(response: PlaygroundMessagesApiResponse): void; /** Reset playground state */ reset(): void; } ``` ## `unmountFlowDropApp()` / `unmountPlayground()` Clean up a mounted instance. Equivalent to calling `.destroy()` on the returned object. ```typescript theme={null} import { unmountFlowDropApp } from '@flowdrop/flowdrop/editor'; unmountFlowDropApp(container); ``` ## Lifecycle 1. **Mount** — Call `mountFlowDropApp()` with a container element 2. **Interact** — Use the returned API to control the editor programmatically 3. **Destroy** — Call `.destroy()` or the unmount function to clean up Registration of custom nodes and fields can happen **before or after** mounting. The node and field registries are instance-scoped — resolve them via `getInstance()` inside the component tree, or the mount handle's `.instance` outside it. Each registry tracks a version counter that invalidates dependent reactive reads. When you register a node or field after mount, that counter bumps and the editor re-resolves, so late registrations take effect. # OpenAPI spec Source: https://flowdrop.mintlify.app/reference/openapi The full FlowDrop REST API specification. FlowDrop's REST API is documented in full as an OpenAPI specification, rendered as an interactive, endpoint-by-endpoint reference. Browse the full FlowDrop API, generated from the OpenAPI specification. # Store API reference Source: https://flowdrop.mintlify.app/reference/stores Complete API reference for all FlowDrop reactive stores. FlowDrop uses Svelte 5 rune-based stores for state management. There are **no module-level store functions** — every store lives on the per-mount `FlowDropInstance`. **Reaching a store** Resolve the owning instance with `getInstance()` inside a FlowDrop component (it resolves the page-default instance for single-editor embeds), or hold the mount handle's `.instance` outside the component tree. Exports from `@flowdrop/flowdrop/editor`: `createFlowDropInstance`, `getInstance`, `provideInstance`, the `FlowDropInstance` type, and the store classes `WorkflowStore`, `HistoryStore`, `HistoryService`, `PortCoordinateStore` (`PlaygroundStore` and `InterruptStore` export from `@flowdrop/flowdrop/playground`). See the [multiple instances guide](/guides/multiple-instances). ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); ``` ## `fd.workflow` (WorkflowStore) The primary store for workflow state, dirty tracking, and node/edge mutations. ### Reactive getters | Getter | Returns | Description | | ------------------------------ | ----------------------------------------------------- | ------------------------------------------ | | `fd.workflow.current` | `Workflow \| null` | Current workflow object | | `fd.workflow.isDirty` | `boolean` | Whether workflow has unsaved changes | | `fd.workflow.id` | `string \| null` | Current workflow ID | | `fd.workflow.name` | `string` | Workflow display name | | `fd.workflow.nodes` | `WorkflowNode[]` | All nodes | | `fd.workflow.edges` | `WorkflowEdge[]` | All edges | | `fd.workflow.metadata` | `WorkflowMetadata` | Metadata (created, updated, schemaVersion) | | `fd.workflow.format` | `string` | Workflow format identifier | | `fd.workflow.validation` | `{hasNodes, hasEdges, nodeCount, edgeCount, isValid}` | Validation summary | | `fd.workflow.connectedHandles` | `Set` | Set of connected port handle IDs | ### Non-reactive utilities ```typescript theme={null} const dirty = fd.workflow.isDirty; // current value fd.workflow.markAsSaved(); // Clears dirty flag ``` ### Workflow actions ```typescript theme={null} const { actions } = fd.workflow; ``` | Method | Parameters | Description | | ----------------------------- | -------------------------------------------------- | --------------------------------- | | `initialize(workflow)` | `Workflow` | Load a workflow into the store | | `updateWorkflow(workflow)` | `Workflow` | Replace the entire workflow | | `addNode(node)` | `WorkflowNode` | Add a node | | `removeNode(nodeId)` | `string` | Remove a node by ID | | `updateNode(nodeId, updates)` | `string, Partial` | Update node properties | | `addEdge(edge)` | `WorkflowEdge` | Add an edge | | `removeEdge(edgeId)` | `string` | Remove an edge by ID | | `updateNodes(nodes)` | `WorkflowNode[]` | Replace all nodes | | `updateEdges(edges)` | `WorkflowEdge[]` | Replace all edges | | `updateName(name)` | `string` | Change workflow name | | `updateMetadata(metadata)` | `Partial` | Update metadata fields | | `batchUpdate(updates)` | `{nodes?, edges?, name?, description?, metadata?}` | Apply multiple changes atomically | | `clear()` | — | Reset the store | | `pushHistory(description?)` | `string?` | Manually push a history snapshot | ### Change callbacks ```typescript theme={null} fd.workflow.setOnDirtyStateChange((isDirty) => { console.log('Dirty state:', isDirty); }); fd.workflow.setOnWorkflowChange((workflow, changeType) => { console.log('Changed:', changeType); }); ``` *** ## `fd.historyBindings` (HistoryStore) Manages undo/redo with snapshot-based history — the reactive rune wrapper around `fd.history` (the underlying `HistoryService`). ### Reactive getters | Getter | Returns | Description | | ---------------------------- | --------- | ------------------------- | | `fd.historyBindings.canUndo` | `boolean` | Whether undo is available | | `fd.historyBindings.canRedo` | `boolean` | Whether redo is available | ### History actions | Method | Returns | Description | | ------------------------------------------ | --------- | ---------------------------------- | | `undo()` | `boolean` | Undo last change | | `redo()` | `boolean` | Redo last undone change | | `clear(currentWorkflow?)` | `void` | Clear all history | | `startTransaction(workflow, description?)` | `void` | Begin grouping changes | | `commitTransaction()` | `void` | Commit grouped changes as one step | | `cancelTransaction()` | `void` | Revert grouped changes | | `pushState(workflow, options?)` | `void` | Manually push a snapshot | | `initialize(workflow)` | `void` | Initialize with a starting state | ```typescript theme={null} fd.historyBindings.undo(); fd.historyBindings.startTransaction(fd.workflow.current, 'Rearrange'); ``` *** ## settingsStore Manages editor settings with localStorage persistence and optional API sync. Settings are **page-global by design** — not instance-scoped, so they remain module-level functions. ### Reactive getters ```typescript theme={null} import { getSettings, themeSettings, editorSettings, uiSettings, behaviorSettings, apiSettings, theme, resolvedTheme, syncStatusStore } from '@flowdrop/flowdrop/settings'; // theme/resolvedTheme also on /core ``` | Function | Returns | Description | | -------------------- | ------------------------------- | ------------------------------------------ | | `getSettings()` | `FlowDropSettings` | All settings | | `themeSettings()` | `ThemeSettings` | Theme preferences | | `editorSettings()` | `EditorSettings` | Editor behavior settings | | `uiSettings()` | `UISettings` | UI preferences | | `behaviorSettings()` | `BehaviorSettings` | Behavior flags | | `apiSettings()` | `ApiSettings` | API configuration | | `theme()` | `ThemePreference` | `'light' \| 'dark' \| 'auto'` | | `resolvedTheme()` | `ResolvedTheme` | `'light' \| 'dark'` (after resolving auto) | | `syncStatusStore()` | `{status, lastSyncedAt, error}` | API sync state | ### Settings updates ```typescript theme={null} import { updateSettings, resetSettings } from '@flowdrop/flowdrop/settings'; updateSettings({ theme: { preference: 'dark' } }); resetSettings(['theme', 'editor']); // Reset specific categories ``` ### Theme functions ```typescript theme={null} import { setTheme, toggleTheme, cycleTheme } from '@flowdrop/flowdrop/core'; setTheme('dark'); // Set explicit theme toggleTheme(); // Toggle light/dark cycleTheme(); // Cycle light → dark → auto ``` ### Change listener ```typescript theme={null} import { onSettingsChange } from '@flowdrop/flowdrop/settings'; const unsubscribe = onSettingsChange((newSettings, oldSettings) => { console.log('Settings changed'); }); // Later: unsubscribe(); ``` *** ## `fd.playground` (PlaygroundStore) Manages playground sessions, messages, and execution state. ### Reactive getters | Getter | Returns | Description | | ------------------------------ | --------------------------- | ------------------------------- | | `fd.playground.currentSession` | `PlaygroundSession \| null` | Active session | | `fd.playground.sessions` | `PlaygroundSession[]` | All sessions | | `fd.playground.messages` | `PlaygroundMessage[]` | All messages in current session | | `fd.playground.isExecuting` | `boolean` | Whether a workflow is running | | `fd.playground.isLoading` | `boolean` | Whether data is loading | | `fd.playground.error` | `string \| null` | Current error message | | `fd.playground.messageCount` | `number` | Total message count | | `fd.playground.chatMessages` | `PlaygroundMessage[]` | Chat-type messages only | | `fd.playground.logMessages` | `PlaygroundMessage[]` | Log-type messages only | | `fd.playground.inputFields` | `PlaygroundInputField[]` | Available input fields | | `fd.playground.sessionCount` | `number` | Total session count | ### Playground actions ```typescript theme={null} const { actions } = fd.playground; ``` | Method | Description | | ---------------------------------------- | ------------------------------------------------ | | `setCurrentSession(session)` | Set the active session | | `addSession(session)` | Add a new session | | `removeSession(sessionId)` | Remove a session | | `switchSession(sessionId)` | Switch to a different session | | `addMessage(message)` | Add a message | | `addMessages(messages)` | Add multiple messages | | `clearMessages()` | Clear all messages | | `updateSessionStatus(sessionId, status)` | Update a session's status (drives `isExecuting`) | | `setLoading(loading)` | Set loading state | | `setError(error)` | Set error message | | `reset()` | Reset all playground state | *** ## `fd.interrupts` (InterruptStore) Manages human-in-the-loop interrupts with a state machine for each interrupt's lifecycle. ### Query methods | Method | Returns | Description | | ------------------------------------ | --------------------------------- | ----------------------------------------- | | `fd.interrupts.getPending()` | `InterruptWithState[]` | Interrupts awaiting action | | `fd.interrupts.getPendingCount()` | `number` | Count of pending interrupts | | `fd.interrupts.getResolved()` | `InterruptWithState[]` | Completed interrupts | | `fd.interrupts.getIsAnySubmitting()` | `boolean` | Whether any interrupt is being submitted | | `fd.interrupts.getInterrupt(id)` | `InterruptWithState \| undefined` | Get interrupt by ID | | `fd.interrupts.isPending(id)` | `boolean` | Check if specific interrupt is pending | | `fd.interrupts.isSubmitting(id)` | `boolean` | Check if specific interrupt is submitting | ### Interrupt actions ```typescript theme={null} const { actions } = fd.interrupts; ``` | Method | Description | | ----------------------------- | ----------------------------- | | `addInterrupt(interrupt)` | Add an interrupt | | `addInterrupts(interrupts)` | Add multiple interrupts | | `startSubmit(id, value)` | Begin submitting a response | | `submitSuccess(id)` | Mark submission as successful | | `submitFailure(id, error)` | Mark submission as failed | | `startCancel(id)` | Begin cancelling an interrupt | | `retry(id)` | Retry a failed submission | | `resolveInterrupt(id, value)` | Resolve an interrupt | | `cancelInterrupt(id)` | Cancel an interrupt | | `clearInterrupts()` | Clear all interrupts | | `reset()` | Reset the store | ### State machine Each interrupt transitions through these states: ```text theme={null} pending → submitting → resolved → error → submitting (retry) pending → cancelling → cancelled ``` *** ## `fd.categories` (CategoriesStore) Manages node category definitions. | Member | Returns | Description | | --------------------------------------- | --------------------------------- | ------------------------------- | | `fd.categories.categories` | `CategoryDefinition[]` | All categories sorted by weight | | `fd.categories.getLabel(category)` | `string` | Display label for a category | | `fd.categories.getIcon(category)` | `string` | Iconify icon ID for a category | | `fd.categories.getColor(category)` | `string` | CSS color for a category | | `fd.categories.getDefinition(category)` | `CategoryDefinition \| undefined` | Full definition | | `fd.categories.initialize(categories)` | `void` | Load categories from API | *** ## Next steps * [Store System Guide](/guides/advanced/store-system) — patterns and best practices * [Event System](/guides/advanced/event-system) — events that complement store reads * [Undo & Redo](/recipes/undo-redo) — using history in practice # Core types Source: https://flowdrop.mintlify.app/reference/types Key TypeScript types exported by FlowDrop. All types are exported from `@flowdrop/flowdrop/core` (or the main `@flowdrop/flowdrop` entry point). ## Workflow ```typescript theme={null} interface Workflow { id: string; name: string; description?: string; nodes: WorkflowNode[]; edges: WorkflowEdge[]; metadata: { /** Workflow schema format version — identifies the document format, not the workflow's own revision. */ schemaVersion: string; createdAt: string; updatedAt: string; author?: string; tags?: string[]; versionId?: string; updateNumber?: number; /** Workflow format. Determines sidebar filtering and export behavior. */ format?: WorkflowFormat; }; /** Custom workflow-level configuration values, populated via workflowSettingsSchema. */ config?: Record; } ``` `metadata` is required. The format-version field lives at `metadata.schemaVersion` and identifies the document format — it is not the workflow's own revision number. ## WorkflowNode ```typescript theme={null} interface WorkflowNode { id: string; type: string; position: { x: number; y: number }; data: { label: string; config?: Record; metadata?: NodeMetadata; branches?: Branch[]; }; } ``` ## WorkflowEdge ```typescript theme={null} interface WorkflowEdge { id: string; source: string; sourceHandle?: string; target: string; targetHandle?: string; data?: { label?: string; condition?: string; metadata?: { edgeType?: EdgeCategory; sourcePortDataType?: string; }; }; } ``` ## NodeMetadata ```typescript theme={null} interface NodeMetadata { node_type_id: string; name: string; description?: string; type: string; supportedTypes?: string[]; category?: string; version?: string; icon?: string; color?: string; badge?: string; inputs?: NodePort[]; outputs?: NodePort[]; config?: Record; configSchema?: ConfigSchema; uiSchema?: UISchemaElement; configEdit?: ConfigEditOptions; tags?: string[]; extensions?: NodeExtensions; } ``` ## NodePort ```typescript theme={null} interface NodePort { id: string; name: string; type: 'input' | 'output' | 'metadata'; dataType: string; required?: boolean; description?: string; defaultValue?: unknown; schema?: OutputSchema | InputSchema; } ``` ## ConfigSchema Standard JSON Schema with FlowDrop extensions: ```typescript theme={null} interface ConfigSchema { type: 'object'; properties?: Record; required?: string[]; } ``` ## FieldSchema ```typescript theme={null} interface FieldSchema { type: FieldType | string; title?: string; description?: string; default?: unknown; format?: string; enum?: unknown[]; oneOf?: Array<{ const: any; title: string }>; multiple?: boolean; minimum?: number; maximum?: number; minLength?: number; maxLength?: number; pattern?: string; readOnly?: boolean; items?: FieldSchema; properties?: Record; autocomplete?: AutocompleteConfig; variables?: TemplateVariablesConfig; 'x-display-order'?: number; [key: string]: unknown; } ``` ## UISchema types ```typescript theme={null} type UISchemaElement = UISchemaControl | UISchemaVerticalLayout | UISchemaGroup; interface UISchemaControl { type: 'Control'; scope: string; // JSON Pointer: "#/properties/fieldName" label?: string; hidden?: boolean; } interface UISchemaVerticalLayout { type: 'VerticalLayout'; elements: UISchemaElement[]; } interface UISchemaGroup { type: 'Group'; label?: string; description?: string; collapsible?: boolean; defaultOpen?: boolean; elements: UISchemaElement[]; } ``` ## Authentication ```typescript theme={null} interface AuthProvider { getHeaders(): Promise>; onUnauthorized?(): Promise; } class StaticAuthProvider implements AuthProvider { constructor(token: string); } class CallbackAuthProvider implements AuthProvider { constructor(options: { getToken: () => string | Promise; onUnauthorized?: () => void | Promise; }); } class NoAuthProvider implements AuthProvider {} ``` Authentication is supplied through an `AuthProvider` passed as the `authProvider` mount option — not via the endpoint config. ## EndpointConfig ```typescript theme={null} interface EndpointConfig { baseUrl: string; endpoints: { nodes: { list: string; get: string; byCategory: string; metadata: string }; portConfig: string; categories: string; workflows: { list: string; get: string; create: string; update: string; delete: string; validate: string; export: string; import: string; }; executions: { execute: string; status: string; cancel: string; logs: string; history: string }; // ... pipelines, playground, interrupts, chat, templates, users, system }; agentSpec?: AgentSpecEndpointConfig; timeout?: number; /** Transform applied to workflow objects before they are sent to the backend */ transformWorkflowPayload?: (workflow: Record) => Record; } function createEndpointConfig(baseUrlOrConfig: string | Partial): EndpointConfig; ``` `EndpointConfig` carries no authentication field. Use the `authProvider` mount option for credentials. ## WorkflowChangeType ```typescript theme={null} type WorkflowChangeType = | 'node_add' | 'node_remove' | 'node_move' | 'node_config' | 'edge_add' | 'edge_remove' | 'metadata' | 'name' | 'description'; ``` ## Event handlers Event handlers for lifecycle integration. See [Event System](/guides/advanced/event-system) for usage examples. ```typescript theme={null} interface FlowDropEventHandlers { /** Called on any workflow modification */ onWorkflowChange?: (workflow: Workflow, changeType: WorkflowChangeType) => void; /** Called when dirty state changes (saved ↔ unsaved) */ onDirtyStateChange?: (isDirty: boolean) => void; /** Called before save — return false to cancel */ onBeforeSave?: (workflow: Workflow) => Promise; /** Called after successful save */ onAfterSave?: (workflow: Workflow) => Promise; /** Called when save fails */ onSaveError?: (error: Error, workflow: Workflow) => Promise; /** Called after a workflow is loaded */ onWorkflowLoad?: (workflow: Workflow) => void; /** Called before FlowDrop is destroyed */ onBeforeUnmount?: (workflow: Workflow, isDirty: boolean) => void; /** Called on any API error — return true to suppress default toast */ onApiError?: (error: Error, operation: string) => boolean | void; /** Called before a node swap — return false to cancel */ onBeforeSwap?: (context: SwapEventContext) => boolean | void | Promise; /** Called after a node swap is applied */ onAfterSwap?: (result: SwapResult, oldNode: WorkflowNode, newNodeId: string) => void; /** Called when Agent Spec execution starts */ onAgentSpecExecutionStarted?: (executionId: string) => void; /** Called when Agent Spec execution completes */ onAgentSpecExecutionCompleted?: (executionId: string, results: Record) => void; /** Called when Agent Spec execution fails */ onAgentSpecExecutionFailed?: (executionId: string, error: Error) => void; /** Called when a node's execution status updates during Agent Spec execution */ onAgentSpecNodeStatusUpdate?: (nodeId: string, status: NodeExecutionInfo) => void; } ``` ## Features ```typescript theme={null} interface FlowDropFeatures { /** Save drafts to localStorage automatically @default true */ autoSaveDraft?: boolean; /** Auto-save interval in ms @default 30000 */ autoSaveDraftInterval?: number; /** Show toast notifications @default true */ showToasts?: boolean; } ``` ## Port configuration ```typescript theme={null} interface PortConfig { dataTypes: PortDataTypeConfig[]; compatibilityRules: PortCompatibilityRule[]; defaultDataType: string; version?: string; } interface PortDataTypeConfig { id: string; name: string; description?: string; color: string; category?: string; aliases?: string[]; enabled?: boolean; } interface PortCompatibilityRule { from: string; to: string; description?: string; } ``` ## Playground types ```typescript theme={null} interface PlaygroundSession { id: string; workflowId: string; name: string; status: PlaygroundSessionStatus; createdAt: string; updatedAt: string; } interface PlaygroundMessage { id: string; sessionId: string; role: 'user' | 'assistant' | 'system'; content: string; status?: string; sequenceNumber?: number; metadata?: Record; timestamp: string; } ``` ## Interrupt types ```typescript theme={null} interface Interrupt { id: string; type: InterruptType; status: InterruptStatus; config: InterruptConfig; responseValue?: unknown; } type InterruptType = 'confirmation' | 'choice' | 'text_input' | 'form' | 'review'; type InterruptStatus = 'pending' | 'resolved' | 'cancelled' | 'expired'; ``` # Drupal Source: https://flowdrop.mintlify.app/server-implementations/drupal A full FlowDrop backend shipped as a Drupal module — node definitions, workflow storage, execution, and triggers. The **Drupal server implementation** is a complete FlowDrop backend packaged as a Drupal module. It serves node definitions, stores workflows as configuration entities, runs executions, and authenticates requests — everything the [frontend–backend contract](/concepts/what-is-a-workflow#the-frontend-backend-contract) expects, integrated with the entities, users, and content you already have. The official module page — releases, downloads, and the issue queue. Installation, node reference, execution modes, triggers, and developer guides on the canonical Drupal docs site. ## How it maps to FlowDrop Everything you learned in [Concepts](/concepts/what-is-a-workflow) carries over — the Drupal module is the backend that gives those workflows meaning: | FlowDrop concept | In the Drupal server | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | Node definitions (`GET /nodes`) | 25+ built-in node processors (data, control flow, entity operations, HTTP, AI), defined as PHP plugins | | Workflow storage | Workflows are Drupal **configuration entities** — exportable to YAML, versionable in git, deployable with `drush cex/cim` | | Execution | Runs every workflow the editor produces, recording results per node | | Authentication | Standard Drupal permissions and session/token auth behind an `AuthProvider` | ## What it adds on top Beyond the core contract, the Drupal server brings backend-side capabilities the editor surfaces but does not itself implement: * **Execution modes** — Synchronous (in-request), Asynchronous (queue-based background), and StateGraph (checkpointed, resumable). * **Triggers** — start workflows automatically on entity changes, user events, or cron. * **Human-in-the-loop** — pause execution for confirmations, choices, or free-text input. * **Pipelines and jobs** — every run produces a pipeline record with one job per node for monitoring and debugging. * **Node Types** — pre-set node variants site builders configure without code. ## Get started The Drupal docs site is the source of truth for setup and reference: * [Get the module on Drupal.org →](https://www.drupal.org/project/flowdrop) * [Install FlowDrop on Drupal →](https://project.pages.drupalcode.org/flowdrop/getting-started/installation/) * [Node reference →](https://project.pages.drupalcode.org/flowdrop/guide/nodes/) * [Execution modes →](https://project.pages.drupalcode.org/flowdrop/guide/execution-modes/) * [Developer guide (custom node processors, triggers, orchestrators) →](https://project.pages.drupalcode.org/flowdrop/development/flowdrop-node-processor/) # Example server (Express) Source: https://flowdrop.mintlify.app/server-implementations/example-server-express A reference Express server implementing the full FlowDrop REST API — the recommended backend to develop a client against. The **example Express server** is the fastest way to get a working backend, and the recommended one while you're building a FlowDrop client. It's a reference implementation of the full FlowDrop REST API — node types, categories, port config, and full workflow CRUD — with CORS enabled and seed data built in, so the editor has real nodes to place the moment you connect. **In-memory only — not for production.** The example server keeps everything **in memory**, so workflows you create are lost when it restarts. It's built for local development. ## Run it The server lives in the FlowDrop repo at `apps/example-server-express`: ```bash theme={null} cd apps/example-server-express npm install npm run dev ``` It starts on **`http://localhost:7104`**, serving the API under `http://localhost:7104/api/flowdrop`. Open the root URL in a browser for a browsable index of every endpoint. Set a different port with the `PORT` environment variable: ```bash theme={null} PORT=8080 npm run dev ``` ## Connect your editor Point your client at the API base — no authentication required: ```javascript theme={null} import { createEndpointConfig } from '@flowdrop/flowdrop/core'; const endpointConfig = createEndpointConfig('http://localhost:7104/api/flowdrop'); ``` That's all the wiring the editor needs — see the [Quick start](/docs/quickstart) for mounting it in your framework. ## Endpoints The server implements the full FlowDrop contract. All paths are relative to the `/api/flowdrop` base. | Method | Path | Description | | -------- | ---------------- | ------------------------------------------------------------- | | `GET` | `/health` | Server health and uptime | | `GET` | `/system/config` | Client bootstrap config | | `GET` | `/nodes` | List node types (`?category`, `?search`, `?limit`, `?offset`) | | `GET` | `/nodes/:id` | Get a single node type | | `GET` | `/categories` | List node categories | | `GET` | `/port-config` | Port data types and compatibility rules | | `GET` | `/workflows` | List workflows (`?search`, `?tags`, `?sort`, `?order`) | | `POST` | `/workflows` | Create a workflow | | `GET` | `/workflows/:id` | Get a workflow | | `PUT` | `/workflows/:id` | Update a workflow | | `DELETE` | `/workflows/:id` | Delete a workflow | ## Next steps Install FlowDrop and mount the editor against this server. Implement the same REST contract on your own stack for production. # Overview Source: https://flowdrop.mintlify.app/server-implementations/overview Backends that speak the FlowDrop API — ready-made servers you can run instead of building your own. FlowDrop is a frontend editor: it needs a backend that serves node definitions, stores workflows, and runs executions (see [the frontend–backend contract](/concepts/what-is-a-workflow#the-frontend-backend-contract)). You can build that backend yourself — the [Backend Implementation guide](/guides/integration/backend-implementation) covers the exact REST endpoints — or you can run one of the **ready-made server implementations** documented here. Just need a backend to build a client against? Run the [example Express server](/server-implementations/example-server-express) — the recommended backend for local development. ## Available implementations A full FlowDrop backend as a Drupal module: node definitions, workflow storage, execution, triggers, and authentication. A standalone Rust server implementation of the FlowDrop API. **Planned — documentation to follow.** ## Build your own instead If none of these fit, any backend that implements the FlowDrop REST contract works. Start here: * [Backend Implementation](/guides/integration/backend-implementation) — the endpoints FlowDrop calls * [Authentication Patterns](/guides/integration/authentication-patterns) — wiring an `AuthProvider` * [API Reference](/api-reference/introduction) — the full OpenAPI specification # Common issues & FAQ Source: https://flowdrop.mintlify.app/troubleshooting/common-issues Solutions to frequently encountered problems when integrating FlowDrop. ## Nodes don't appear in the sidebar **Symptoms:** The sidebar is empty or shows "No nodes available." **Causes & fixes:** 1. **Endpoint config is wrong.** Verify your `endpointConfig` base URL is correct and your backend is running: ```typescript theme={null} const endpointConfig = createEndpointConfig('http://localhost:3001/api/flowdrop'); ``` Open `http://localhost:3001/api/flowdrop/nodes` in your browser — you should see JSON. 2. **CORS is blocking requests.** Check the browser console for CORS errors. Your backend must allow the frontend's origin: ```typescript theme={null} app.use(cors({ origin: 'http://localhost:5173' })); ``` 3. **Node metadata is malformed.** Each node needs at minimum `id`, `name`, and `type`: ```json theme={null} { "id": "my-node", "name": "My Node", "type": "simple" } ``` 4. **Response format is wrong.** FlowDrop expects `{ success: true, data: [...] }`, not a bare array. ## Editor is blank (white screen) **Symptoms:** The container is mounted but nothing renders. **Causes & fixes:** 1. **CSS is not imported.** You must import FlowDrop styles: ```typescript theme={null} import '@flowdrop/flowdrop/styles'; ``` 2. **Container has no height.** The editor needs a container with explicit dimensions: ```css theme={null} #editor { width: 100%; height: 100vh; } ``` 3. **Mount failed silently.** Wrap in try/catch and check the console: ```typescript theme={null} try { const app = await mountFlowDropApp(container, options); } catch (error) { console.error('Mount failed:', error); } ``` ## Connections won't snap **Symptoms:** Dragging from an output port to an input port doesn't create a connection. **Causes & fixes:** 1. **Port data types are incompatible.** FlowDrop enforces type-safe connections. A `trigger` port cannot connect to a `string` port unless you define a compatibility rule. Check your [port config](/guides/port-system). 2. **Port IDs are missing.** Both source and target ports must have `id` fields in the node metadata: ```json theme={null} { "outputs": [{ "id": "output", "name": "Result", "type": "output", "dataType": "string" }] } ``` 3. **No port-config endpoint.** If you don't serve `/port-config`, FlowDrop uses defaults which may not match your data types. ## CodeMirror fields don't render **Symptoms:** Config fields with `format: "json"` or `format: "template"` show as plain text inputs. **Fix:** You must explicitly register CodeMirror fields against the instance's `fd.fields` registry. They're in a separate module to avoid the \~300KB bundle cost: ```typescript theme={null} import { getInstance } from '@flowdrop/flowdrop/editor'; const fd = getInstance(); // or app.instance outside the component tree import { registerCodeEditorField } from '@flowdrop/flowdrop/form/code'; registerCodeEditorField(fd.fields); // For template fields with variable autocomplete: import { registerTemplateEditorField } from '@flowdrop/flowdrop/form/code'; registerTemplateEditorField(fd.fields); // For markdown: import { registerMarkdownEditorField } from '@flowdrop/flowdrop/form/markdown'; registerMarkdownEditorField(fd.fields); ``` Each installer takes the target field registry as its first argument and re-checks registration after its dynamic import resolves, so calling it after mount (once `fd.fields` is available) is the supported flow. ## Multiple editors share state **Symptoms:** Two FlowDrop editors on the same page appear to share workflow or history state. Multiple editors per page are supported natively — each mount gets its own isolated `FlowDropInstance`. If two editors still appear to share state: 1. **Both mounts omitted `instanceId`.** The first mount without an `instanceId` becomes the page-default instance; a second un-keyed mount can collide with it. Pass an explicit `instanceId` to each additional editor to scope its draft storage (`flowdrop:draft::`), and resolve state via `getInstance()` / the mount handle's `.instance` to target a specific editor. Instances are the API — there are no module-level store APIs. Note that theme and settings (including UI toggles) are **intentionally shared** across all editors on a page. Registries (`fd.nodes`/`fd.fields`), the API context (`fd.api`), and port compatibility (`fd.portCompatibility`) are instance-scoped. See the [multiple instances guide](/guides/multiple-instances). ## Save fails silently **Symptoms:** Clicking Save does nothing visible, or changes are lost. **Causes & fixes:** 1. **No save endpoint.** FlowDrop needs `POST /workflows` (create) and `PUT /workflows/:id` (update) endpoints. 2. **No error handler.** Add `onSaveError` and `onApiError` to see what's happening: ```typescript theme={null} eventHandlers: { onSaveError: async (error, workflow) => { console.error('Save failed:', error); }, onApiError: (error, operation) => { console.error(`API error during ${operation}:`, error); } } ``` 3. **Response format is wrong.** The save endpoint must return the saved workflow with an `id` field. If creating a new workflow, the response must include the server-generated ID. ## Draft recovery not working **Symptoms:** Auto-saved drafts don't appear when reopening the editor. **Causes & fixes:** 1. **Feature is disabled.** Auto-save drafts are enabled by default, but verify: ```typescript theme={null} features: { autoSaveDraft: true, autoSaveDraftInterval: 30000 } ``` 2. **localStorage is full.** Browsers limit localStorage to \~5-10MB. Check `localStorage` usage in DevTools. 3. **Different storage key.** Drafts are keyed by workflow ID. If the workflow ID changes between sessions, the draft won't match. ## Agent Spec import drops data **Symptoms:** Importing an Agent Spec document loses some nodes or connections. **Cause:** Not all Agent Spec features have FlowDrop equivalents. The adapter does best-effort conversion. **Fix:** Check the console for warnings during import. Review the `AgentSpecAdapter` conversion for specific limitations. ## "Cannot read properties of undefined" **Symptoms:** Runtime error in the console when interacting with nodes. **Common cause:** Node metadata is missing required fields. Ensure your nodes have: ```json theme={null} { "id": "unique-id", "name": "Display Name", "type": "simple", "inputs": [], "outputs": [] } ``` The `inputs` and `outputs` arrays must always be present, even if empty. ## Toast notifications are annoying **Fix:** Disable them via features: ```typescript theme={null} features: { showToasts: false; } ``` Or handle errors yourself via `onApiError` (return `true` to suppress the toast for that error). ## FAQ ### Can I use FlowDrop without a backend? Yes, for prototyping. Pass `nodes` directly and omit `endpointConfig`: ```typescript theme={null} const app = await mountFlowDropApp(container, { nodes: [{ id: 'my-node', name: 'My Node', type: 'simple', inputs: [], outputs: [] }] }); ``` Saving won't work without a backend, but you can use `app.getWorkflow()` to extract the JSON. ### Can I have multiple editors on one page? Yes — supported natively. Each mount gets its own isolated `FlowDropInstance`. Pass an `instanceId` to each editor to scope its draft storage. Theme/settings and port config remain page-global. See the [multiple instances guide](/guides/multiple-instances). ### What browsers are supported? FlowDrop targets modern evergreen browsers (Chrome, Firefox, Safari, Edge). It requires ES2020+ support. ### How do I update the node palette after mounting? Currently, FlowDrop fetches nodes on mount. To refresh the palette, destroy and remount the editor. ### Can I use FlowDrop with React/Vue/Angular? Yes, via the [Mount API](/reference/mount-api). FlowDrop mounts into any HTML container element, regardless of your framework. ## Getting help If none of the above resolves your issue: * **[GitHub Issues](https://github.com/flowdrop-io/flowdrop/issues)** — Bug reports and reproducible problems When reporting a bug, include: 1. FlowDrop version (`npm list @flowdrop/flowdrop`) 2. Browser and version 3. The error message from the browser console 4. Minimal reproduction: your node metadata JSON and mount options # Embedding the editor Source: https://flowdrop.mintlify.app/tutorial/01-embedding-the-editor Mount the FlowDrop visual workflow editor in any web page. **What you'll learn** How to install FlowDrop and mount a blank workflow editor canvas in your application. The most minimal FlowDrop setup is just the editor itself: an empty canvas with no nodes in the sidebar and no workflow loaded. Even empty, you can zoom (scroll wheel) and pan (click and drag the background). ## Set up a project FlowDrop's UI is built with Svelte 5, so your bundler needs the Svelte plugin to compile it — even though you won't write any Svelte yourself. Here's a complete, working [Vite](https://vite.dev/) setup; it's exactly what the runnable example (`apps/tutorials/01-embedding-the-editor` in the FlowDrop repo) uses. ```bash npm theme={null} npm install @flowdrop/flowdrop @iconify/svelte @xyflow/svelte npm install -D vite @sveltejs/vite-plugin-svelte svelte ``` ```bash pnpm theme={null} pnpm add @flowdrop/flowdrop @iconify/svelte @xyflow/svelte pnpm add -D vite @sveltejs/vite-plugin-svelte svelte ``` ```js vite.config.js theme={null} import { defineConfig } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; export default defineConfig({ // FlowDrop ships Svelte components — the plugin compiles them for you. plugins: [svelte()], // The mount call uses top-level await, which needs a modern target. build: { target: 'es2022' } }); ``` ```html index.html theme={null}
```
**SvelteKit users can skip the Vite steps.** Using **SvelteKit** or another Svelte toolchain? The Svelte plugin is already configured. **React** and **Vue** apps need the plugin just like vanilla JS does. ## Mount the editor With the project set up, two imports in your entry file (`src/main.js`) are all you need: ```js theme={null} import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; import '@flowdrop/flowdrop/styles'; ``` Then mount it into any container element: ```js theme={null} const app = await mountFlowDropApp(document.getElementById('editor'), { height: '100vh' }); ``` That's it. The editor renders a pannable, zoomable canvas with a grid background. ### What `mountFlowDropApp` returns The mount function returns a controller object you can use later: ```js theme={null} const app = await mountFlowDropApp(container, options); // Check if the user has unsaved changes app.isDirty(); // Get the current workflow data app.getWorkflow(); // Clean up when done app.destroy(); ``` ## The complete project Once you've followed the steps above, your project has just four files. This is the whole thing — the runnable [`apps/tutorials/01-embedding-the-editor`](https://github.com/flowdrop-io/flowdrop) example mirrors it exactly: Run `npm run dev` (or `pnpm dev`) and open the printed URL. You'll see the empty editor canvas: An empty FlowDrop editor canvas with a grid background, no nodes in the sidebar and no workflow loaded. ## Using with Svelte If you're building a Svelte application, you can use the `WorkflowEditor` component directly: ```svelte theme={null}
``` ## What's next The editor is running, but there's nothing to build with yet. Before adding nodes, you need to tell FlowDrop where your backend API lives. *** **Tutorial — Step 1 of 5** [Next: Configuring endpoints →](/tutorial/02-configuring-endpoints) # Configuring endpoints Source: https://flowdrop.mintlify.app/tutorial/02-configuring-endpoints Tell FlowDrop where your backend API lives so it can load nodes, save workflows, and more. **What you'll learn** How to configure the API endpoint so FlowDrop knows where to send requests for loading nodes, saving workflows, and executing pipelines. In Step 1 the canvas was empty because nothing was serving it data. Here you'll point the editor at a backend — and run a real one — so it can load nodes into the sidebar and save your work. This is the step that turns a blank canvas into a working editor. ## Why endpoints matter FlowDrop is a **frontend editor** that talks to a **backend API** for things like: * Loading available node types and categories * Saving and loading workflows * Executing workflows and pipelines * Managing playground sessions Without an endpoint configuration, the editor can render a canvas, but it can't load nodes into the sidebar or persist any data. This is why Step 1 showed an empty canvas — there was no API to fetch nodes from. ## The `createEndpointConfig` helper FlowDrop provides a helper that generates a full endpoint configuration from a single base URL: ```js highlight={6} theme={null} import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; import { createEndpointConfig } from '@flowdrop/flowdrop/core'; import '@flowdrop/flowdrop/styles'; const app = await mountFlowDropApp(document.getElementById('editor'), { endpointConfig: createEndpointConfig('/api/flowdrop'), height: '100vh' }); ``` The `createEndpointConfig('/api/flowdrop')` call sets up paths for all API operations under that base URL. | Operation | Endpoint | | ---------------- | ------------------------------------------- | | List nodes | `GET /api/flowdrop/nodes` | | List categories | `GET /api/flowdrop/categories` | | Port config | `GET /api/flowdrop/port-config` | | Save workflow | `PUT /api/flowdrop/workflows/{id}` | | Create workflow | `POST /api/flowdrop/workflows` | | Execute workflow | `POST /api/flowdrop/workflows/{id}/execute` | | Health check | `GET /api/flowdrop/health` | It also configures sensible defaults: a 30-second timeout and automatic retries with exponential backoff. ## Run a backend A config that points at nothing won't load nodes or save anything. The fastest way to get a **real** endpoint is the reference Express server that ships with FlowDrop (`apps/example-server-express` in the repo). It implements the full API — nodes, categories, port config, and workflow CRUD — backed by in-memory demo data, so you have something live to develop against in seconds: ```bash theme={null} cd apps/example-server-express pnpm install pnpm dev ``` It starts on **`http://localhost:7104`** and serves the API under `http://localhost:7104/api/flowdrop`. Open that root URL in a browser to see every endpoint it exposes. ## Point the editor at it Your editor (say, Vite's `http://localhost:5173`) and the API (`http://localhost:7104`) are different origins. The cleanest fix — and the one that mirrors production, where the two are usually same-origin — is a dev proxy, so the relative `/api/flowdrop` from `createEndpointConfig` just works: ```js vite.config.js highlight={8,9} theme={null} import { defineConfig } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; export default defineConfig({ plugins: [svelte()], build: { target: 'es2022' }, server: { // Forward API calls to the example server. proxy: { '/api': 'http://localhost:7104' } } }); ``` With both servers running, reload the editor — the sidebar now fills with the node types served by your backend. The endpoint is live, and **Save** persists to it. The FlowDrop editor with an endpoint configured, showing node types loaded into the sidebar from the backend API. Don't want a proxy? Point `createEndpointConfig` straight at the absolute URL instead — the example server enables CORS: ```js theme={null} createEndpointConfig('http://localhost:7104/api/flowdrop') ``` ## Customizing endpoints You can override individual settings by passing a second argument: ```js theme={null} const endpointConfig = createEndpointConfig('/api/v2/flowdrop', { timeout: 60000, retry: { enabled: true, maxAttempts: 5, delay: 2000, backoff: 'exponential' } }); ``` **A backend is what makes the editor useful.** You *can* mount it without one — it renders the canvas and only calls the API when an action (load, save, execute) fires. But until an endpoint is reachable it has no nodes to place and nothing to save, so start the example server above to see FlowDrop actually do something. ## What's next Now that the editor knows where the API lives, it's time to define your first node and see it appear in the sidebar. *** **Tutorial — Step 2 of 5** [← Embedding the editor](/tutorial/01-embedding-the-editor) · [Next: Your first node →](/tutorial/03-your-first-node) # Your first node Source: https://flowdrop.mintlify.app/tutorial/03-your-first-node Define a node type and see it appear in the editor sidebar. **What you'll learn** How to define a node, group it into a category, and pass both to the editor so it appears in the sidebar. With a single node defined, the sidebar shows a **Text Input** node under the **Inputs** category. Users can drag it onto the canvas, then click it to see its configuration form. ## Core concepts FlowDrop needs two things to populate the sidebar: 1. **Nodes** — definitions of the building blocks users can drag onto the canvas. 2. **Categories** — groups that organize nodes in the sidebar. ### Defining a node A node is a plain object describing its identity, appearance, and ports: ```js theme={null} const textInput = { id: 'text_input', name: 'Text Input', type: 'simple', // Visual style: simple, tool, gateway, terminal description: 'Simple text input for user data', category: 'inputs', // Must match a category id icon: 'mdi:text', // Any Iconify icon color: '#22c55e', version: '1.0.0', inputs: [], // No input ports outputs: [ { id: 'text', name: 'text', type: 'output', dataType: 'string', description: 'The input text value' } ], configSchema: { // JSON Schema for the config form type: 'object', properties: { placeholder: { type: 'string', title: 'Placeholder', default: 'Enter text...' } } } }; ``` Key fields: | Field | Purpose | | -------------------- | --------------------------------------------------- | | `type` | Controls the visual style of the node on the canvas | | `category` | Determines which sidebar group the node appears in | | `inputs` / `outputs` | Define the connection ports on the node | | `configSchema` | JSON Schema that generates the configuration form | ### Icons The `icon` field accepts any [Iconify](https://iconify.design/) icon identifier in `set:name` format. FlowDrop uses [`@iconify/svelte`](https://iconify.design/docs/icon-components/svelte/) to render icons, which loads them on demand from the Iconify API — so you have access to **200,000+ icons** from 150+ open-source icon sets without bundling anything extra. ```js theme={null} icon: 'mdi:text'; // Material Design Icons icon: 'heroicons:sparkles'; // Heroicons icon: 'lucide:bot'; // Lucide icon: 'ph:brain'; // Phosphor Icons ``` Browse all available icons at [icon-sets.iconify.design](https://icon-sets.iconify.design/). **Install `@iconify/svelte` to render icons.** It's an optional peer dependency of FlowDrop; if it's not installed, icons will not render. See the [Icons reference](/reference/icons) for full details, including built-in defaults and fallback behavior. ### The `configSchema` and JSON Schema The `configSchema` field uses [JSON Schema](https://json-schema.org/) — an open standard for describing the structure of JSON data. FlowDrop reads this schema and **automatically generates a configuration form** for each node, so you never need to build form UI by hand. In the example above, the schema defines a single `placeholder` property of type `string`: ```json theme={null} { "type": "object", "properties": { "placeholder": { "type": "string", "title": "Placeholder", "default": "Enter text..." } } } ``` This produces a text input labeled "Placeholder" with a default value. You can use any standard JSON Schema features to build richer forms: | JSON Schema feature | What it generates | | ------------------------- | ------------------------- | | `type: "string"` | Text input | | `type: "number"` | Number input | | `type: "boolean"` | Toggle / checkbox | | `type: "string"` + `enum` | Dropdown select | | `title` | Field label | | `description` | Help text below the field | | `default` | Pre-filled value | **Learn more about JSON Schema.** The full specification is at [json-schema.org](https://json-schema.org/). The [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) guide is an excellent starting point if you're new to it. ### Defining a category A category groups related nodes in the sidebar: ```js theme={null} const inputsCategory = { id: 'inputs', name: 'Inputs', icon: 'mdi:import', color: '#22c55e' }; ``` The `id` must match the `category` field in your node definitions. ## Mounting with nodes Pass the `nodes` and `categories` arrays when mounting, along with the `endpointConfig` [from the previous step](/tutorial/02-configuring-endpoints): ```js theme={null} import { mountFlowDropApp } from '@flowdrop/flowdrop/editor'; import { createEndpointConfig } from '@flowdrop/flowdrop/core'; import '@flowdrop/flowdrop/styles'; const app = await mountFlowDropApp(document.getElementById('editor'), { nodes: [textInput], categories: [inputsCategory], endpointConfig: createEndpointConfig('/api/flowdrop'), height: '100vh', showNavbar: true }); ``` ## Try it In your editor: 1. Open the sidebar and drag **Text Input** onto the canvas. 2. Click the node to open its configuration panel. 3. Change the **Placeholder** value — this is generated from the `configSchema`. A Text Input node placed on the canvas with its config panel open, showing the editable Placeholder field generated from configSchema. ## What's next One node is a good start, but workflows need variety. Next, you'll add multiple nodes across several categories to build a full editing experience. *** **Tutorial — Step 3 of 5** [← Configuring endpoints](/tutorial/02-configuring-endpoints) · [Next: Nodes & categories →](/tutorial/04-multiple-nodes-and-categories) # Multiple nodes & categories Source: https://flowdrop.mintlify.app/tutorial/04-multiple-nodes-and-categories Build a fully-functional editor with multiple node types and categories. **What you'll learn** How to define multiple nodes across categories, understand node types and visual styles, and connect nodes together. With several nodes defined, the sidebar shows them grouped across categories. Users can build a workflow by dragging nodes onto the canvas and connecting their ports — dragging from an output to an input. ## Expanding the node palette Adding more nodes follows the same pattern from the previous step. Here's how to define nodes across different categories: ```js theme={null} const nodes = [ // Inputs { id: 'text_input', name: 'Text Input', type: 'simple', category: 'inputs', icon: 'mdi:text', color: '#22c55e' // ...ports and config }, // Outputs { id: 'text_output', name: 'Text Output', type: 'simple', category: 'outputs', icon: 'mdi:text-box', color: '#ef4444' // ...ports and config }, // AI & ML { id: 'ai_content_analyzer', name: 'AI Content Analyzer', type: 'tool', category: 'ai', icon: 'mdi:brain', color: '#9C27B0' // ...ports and config }, // Processing { id: 'json_transformer', name: 'JSON Transformer', type: 'tool', category: 'processing' // ... }, // Logic { id: 'gateway', name: 'Gateway', type: 'gateway', category: 'logic' // ... }, // Helpers { id: 'notes', name: 'Notes', type: 'idea', category: 'helpers' // ... } ]; 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' }, { id: 'processing', name: 'Processing', icon: 'mdi:cog', color: '#3b82f6' }, { id: 'logic', name: 'Logic', icon: 'mdi:source-branch', color: '#f59e0b' }, { id: 'helpers', name: 'Helpers', icon: 'mdi:wrench', color: '#fbbf24' } ]; ``` ## Node types (visual styles) The `type` field controls how the node renders on the canvas: | Type | Appearance | Use case | | ---------- | ----------------------------------------- | ----------------------------------- | | `simple` | Compact rounded rectangle | Inputs, outputs, basic operations | | `tool` | Rectangle with tool badge and extra ports | API calls, integrations, processing | | `gateway` | Diamond shape | Conditional branching and routing | | `terminal` | Rounded end-cap shape | Start/end points of a workflow | | `idea` | Sticky-note style | Documentation and comments | | `default` | Standard rectangle | General-purpose nodes | A node can support multiple types via `supportedTypes`, letting users switch between visual styles from the config panel. ## Connecting nodes Nodes connect through **ports** — the small circles on the edges of each node: * **Output ports** (right side) send data out of a node * **Input ports** (left side) receive data into a node To create a connection, drag from an output port to a compatible input port. Compatibility is determined by the `dataType` field: | Data Type | Compatible With | | --------- | ------------------- | | `string` | string, mixed | | `number` | number, mixed | | `json` | json, mixed, string | | `array` | array, mixed | | `mixed` | all data types | | `tool` | tool only | | `trigger` | trigger only | FlowDrop validates connections in real-time and only allows compatible port types to connect. ## Try it Build a small workflow in your editor: 1. Drag **Text Input**, **AI Content Analyzer**, and **Text Output** onto the canvas. 2. Connect the `text` output of Text Input to the `Content to Analyze` input of AI Content Analyzer. 3. Connect the `analyzed_content` output of AI Content Analyzer to the `Text Input` port of Text Output. 4. Try adding a **Gateway** node to see the diamond shape, or a **Notes** node for sticky-note documentation. ## What's next You now have a fully functional editor where users can build workflows. In the next step, you'll learn how to save those workflows and understand the data structure behind them. *** **Tutorial — Step 4 of 5** [← Your first node](/tutorial/03-your-first-node) · [Next: Saving workflows →](/tutorial/05-saving-workflows) # Saving workflows Source: https://flowdrop.mintlify.app/tutorial/05-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: ```json theme={null} { "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: ```json theme={null} { "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: ```js theme={null} 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: ```js theme={null} // 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: ```js theme={null} 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: * [Node Types](/guides/node-types) — deep dive into all built-in node types and custom nodes * [Configuration Forms](/guides/config-schema) — advanced JSON Schema forms with UI schema layouts * [Framework Integration](/guides/integration) — use FlowDrop with React, Vue, Angular, or vanilla JS * [Theming](/guides/theming) — customize colors, fonts, and dark mode with CSS tokens * [Interactive Playground](/guides/playground) — add a chat-based testing interface to your editor *** **Tutorial — Step 5 of 5 · Complete!** [← Nodes & categories](/tutorial/04-multiple-nodes-and-categories)