.agents/features/flows.md
Flows are the core automation primitive in Activepieces. Each flow is a versioned directed graph of trigger and action steps stored as a JSONB tree. The module handles the full lifecycle: draft editing via a single-endpoint operation dispatch, publishing (locking a version and registering the trigger source), enabling/disabling, folder organization, sample data capture for testing, human-input forms/chat interfaces, and the visual builder frontend powered by XYFlow. All 26 flow modification types are dispatched through one endpoint (POST /v1/flows/:id) with a discriminated-union body.
packages/server/api/src/app/flows/flow/flow.service.ts — core service (operations, publish, enable/disable)packages/server/api/src/app/flows/flow/flow.controller.ts — REST controllerpackages/server/api/src/app/flows/folder/ — folder CRUDpackages/server/api/src/app/flows/step-run/ — sample data capture and test-step executionpackages/server/api/src/app/flows/flow/human-input/ — form and chat public endpointspackages/shared/src/lib/automation/flows/flow.ts — Flow, PopulatedFlow typespackages/shared/src/lib/automation/flows/flow-version.ts — FlowVersion, FlowVersionStatepackages/shared/src/lib/automation/flows/operations/ — FlowOperationRequest union and all 26 op typespackages/shared/src/lib/automation/flows/actions/action.ts — FlowAction discriminated unionpackages/shared/src/lib/automation/flows/triggers/trigger.ts — FlowTrigger discriminated unionpackages/web/src/features/flows/api/flows-api.tsx — flowsApi (list, create, update, get, versions, delete, count)packages/web/src/features/flows/hooks/flow-hooks.tsx — flowHooks (status change, export, import, test, version management)packages/web/src/features/flows/components/ — FlowStatusToggle, ImportFlowDialog, ShareTemplateDialog, ChangeOwnerDialog, FlowCreatedByBadgepackages/web/src/features/flows/utils/flows-utils.tsx — download, zip, template parsing helperspackages/web/src/app/builder/index.tsx — visual flow builder entry pointpackages/web/src/app/builder/flow-canvas/ — XYFlow canvas (nodes, edges, drag layer, context menu)packages/web/src/app/builder/state/ — Zustand-based builder state (flow, run, canvas, notes, step form, piece selector)packages/web/src/app/builder/step-settings/ — step configuration panel and split/drawer layout for the step data panelpackages/web/src/app/builder/step-data/ — step data panel UI (step-data-panel-host.tsx / StepDataPanelHost, step-data-panel-header.tsx, step-data-panel-view-toggle.tsx)packages/web/src/app/builder/test-step/ — test execution UI (action/trigger sections, sample-data viewer, CTA buttons); test-runner-context.tsx hoists useTestAction + the webhook-return dialog so the bottom CTA can fire the test in-treepackages/web/src/app/builder/data-display/ — failed-step error UI: friendly-error-view.tsx (the friendly error card), copy-ai-prompt.tsx ("Copy Error for AI" button), explanation-prompt.ts (sanitized AI prompt builder), and build-step-properties-snapshot.ts (step-properties snapshot helper). Used by both the test panel and the run-details output view.packages/web/src/app/builder/pieces-selector/ — piece/action browserpackages/web/src/app/routes/automations/index.tsx — flows list pagePOST /v1/flows/:id.LOCK_AND_PUBLISH snapshots it to LOCKED and optionally enables the flow.IMPORT_FLOW operations)./form/:flowId) or chat interface (/chat/:flowId) as their trigger UI.FlowCreator: { type: 'MCP' | 'AGENT', id }). Null for human-created flows. Server-set only — surfaced in the UI as the "AI" badge (FlowCreatedByBadge). Distinct from ownerId, which is the current owning user.Flow: id, projectId, folderId (nullable), status (ENABLED/DISABLED), externalId, publishedVersionId (nullable, unique FK), metadata (JSONB), operationStatus (NONE/DELETING/ENABLING/DISABLING), timeSavedPerRun, ownerId, templateId, createdBy (nullable JSONB — FlowCreator). Relations: project, folder, owner, publishedVersion (one-to-one), versions (one-to-many), runs, events, tableWebhooks.
FlowVersion: id, flowId, displayName, schemaVersion, trigger (JSONB — full flow graph), connectionIds[], agentIds[], updatedBy, valid, state (DRAFT/LOCKED), backupFiles (JSONB), notes[] (JSONB). Relations: flow, updatedByUser.
Folder: id, projectId, displayName. Used to organize flows and tables. Case-insensitive uniqueness.
All flow modifications go through POST /v1/flows/:id with a FlowOperationRequest discriminated union. 26 operation types:
flow.publishedVersionIdWhen LOCK_AND_PUBLISH or CHANGE_STATUS to ENABLED:
When CHANGE_STATUS to DISABLED:
human-input/)GET /form/:flowId): Public endpoint returning form UI config from flow definition. Supports ?useDraft=true.GET /chat/:flowId): Public endpoint returning chat UI config. Supports sessionId, message, file attachments.step-run/)The visual builder (packages/web/src/app/builder/) uses XYFlow for the canvas. State is split into focused Zustand slices, composed by builder-state-provider.tsx:
flow-state.ts — current flow and version, pending operationsrun-state.ts — active test run, step results, focused/failed step (used by the run-info widget's "See error" affordance); setRun resets userManuallySelectedStepDuringRun whenever a new run id arrivescanvas-state.ts — viewport, selected node, drag state, plus the userManuallySelectedStepDuringRun flag and resumeLiveFollow action that gate auto-follow. The auto-focus effect lives in useFocusOnStep (flow-canvas/hooks.tsx): it calls selectStepByName(step, { fromAutoFocus: true }) to pan the canvas to the latest engine step, and short-circuits whenever userManuallySelectedStepDuringRun is set. The flag flips to true when the user picks a different step mid-run (any selectStepByName call without fromAutoFocus) and clears via resumeLiveFollow or when setRun receives a new run id. Also owns the step-data-panel layout state: stepDataPanelView (StepDataPanelView = 'split' | 'drawer', persisted via localStorage) and isStepDataPanelOpenstep-form-state.ts — open/focused step configurationpiece-selector-state.ts — piece browser visibility and searchnotes-state.tsx — sticky notes overlaychat-state.ts — embedded chat drawer state for testing chat_submission-trigger flows from the builderflowHooks.useChangeFlowStatus handles both publish and enable/disable, surfaces TRIGGER_UPDATE_STATUS errors via an ApErrorDialog, and maps gateway timeout errors to a user-readable message. flowHooks.importFlowsFromTemplates replaces externalId references across a multi-flow template import to maintain cross-flow links. flowHooks.useExportFlows downloads a single flow as JSON or zips multiple, and reports failures via a toast (onError). Bulk export from the automations list (handleBulkExport) resolves selected flows against the in-memory treeItems (which holds every loaded flow, including those inside expanded folders) rather than the root-scoped rootFlows list — flows inside folders are absent from rootFlows, so filtering it would silently drop them. No per-flow fetch is issued: the selected flows are already loaded, so they are read straight from treeItems. To keep that safe, the selection is cleared on every view change that can remove loaded items (filtering, paging, collapsing a folder — wired in automations/index.tsx), so the selection is always a subset of treeItems and the in-memory lookup can never miss a selected flow.
The step-settings sidebar hosts a step data panel with two layouts, switched via stepDataPanelView in canvas state:
drawer — slides up from the bottom of the sidebar, occupies 60% height, sits at z-50 over the settings form. Dismisses on outside-click (pointerdown listener in step-data/step-data-panel-host.tsx, ignoring Radix poppers, role="dialog", and resizable handles).split — non-resizable 50/50 horizontal split between settings form and step data panel inside the sidebar.step-settings/index.tsx composes the two layouts via StepSettingsLayout, rendering StepDataPanelHost for the active view. The bottom CTA (TestStepCTAButton) shows under the settings form when the drawer is closed; clicking it opens the drawer and auto-fires the test by calling useActionTestRunner().fireTest() or useTriggerTestRunner().fireTest() from context. ActionTestRunnerProvider owns the action mutation and the return-response webhook dialog; TriggerTestRunnerProvider owns the trigger piece lookup, the three trigger mutations (simulate, poll, saveMock), the MCP-tool testing dialog, and a fireTest() dispatcher that picks the right one based on triggerEventUtils.getTestType. Both providers are wired with the Zustand-backed selectedStep (not the RHF form values) so step.valid stays in sync with the resolver-computed validity that applyOperation writes to canvas state — RHF never writes the resolver's valid back into its own form store, so reading from form.getValues() / form.formState.isValid would observe a stale value for freshly-added steps.
builder/index.tsx drives the right-sidebar pixel size imperatively: a useLayoutEffect on react-resizable-panels' PanelImperativeHandle.resize() targets 1000px (initial split open), 850px (subsequent split open), or 25% (drawer). A separate useEffect attaches a ResizeObserver while the user drags the handle and auto-collapses split → drawer once the sidebar drops under 700px. The old useAnimateSidebar hook has been removed in favour of this approach.