Flow Studio
Full Checklist
Everything required to ship a new node type end-to-end: database record, Atlas Form, frontend renderer, canvas registration, and backend executor. All five layers must be complete before the node is usable in production.
The Five Layers
| # | Layer | What to Build | TypeCode Role |
|---|---|---|---|
| 1 | Database | NodeType record in the NodeTypes table | Defines the canonical typeCode |
| 2 | Configuration Form | Atlas Form for node settings (formId in NodeType) | Linked by formId, not typeCode |
| 3 | Frontend Renderer | React class extending BaseNode | Renderer file (no typeCode coupling) |
| 4 | Canvas Registration | Entry in WorkflowCanvas.nodeTypes | Key must equal typeCode exactly |
| 5 | Backend Executor | C# class implementing IProcessElementExecution | Executor.TypeCode must equal typeCode |
Complete Checklist
1 — Database: NodeType Record
- NodeType row inserted with a unique
typeCode(kebab-case, e.g. "send-slack-message") displayNameset (shown in the node palette)categoryset (groups nodes in the sidebar)shapeset: "rectangle" | "circle" | "diamond" (controls built-in renderer selection)iconset (Font Awesome class, e.g. "fa-solid fa-envelope")colorset (hex string, used in node header background)portsarray defined — each port has portKey, portType, isMainPort, isErrorPort, isRequired, isMultipledefaultConfigJSON set with default values for all configuration fieldsformIdset (references the Atlas Form for this node's config panel) — set to 0 if no config neededisDeprecated: falseconfirmed- NodeType record seeded via migration or seed script (not inserted manually)
2 — Atlas Form: Configuration UI
- Atlas Form created in the Forms system matching
NodeType.formId - All configuration fields defined (required/optional/default values)
- Credential fields use the
credentialIdfield type (never raw secret strings) - Form field keys exactly match the properties read by the backend executor via
GetConfig<T>() - Form validated in Atlas Form preview mode before linking
3 — Frontend: Custom Renderer (only if custom shape needed)
- New file created:
packages/flow-studio-designer/src/components/Nodes/MyCustomNode.tsx - Class extends
BaseNode(import from../Base/BaseNode) renderBody()implemented — notrender()- At least one
CustomHandle(target) included - At least one
CustomHandle(source) included for non-terminal nodes - Handle
idprops exactly match theportKeyvalues in the NodeType ports array - Error output handle uses
isErrorPort={true}if node has error routing - Execution status overlay implemented (reads
this.designerMode.nodeStatuses) - Component exported with
withNodeHooks:export const MyCustomNode = withNodeHooks(MyCustomNodeClass) - No
withNodeHookscall duplicated inWorkflowCanvas.tsx(pre-wrapped at export)
4 — Frontend: Canvas Registration
- Renderer imported into
WorkflowCanvas.tsx - Entry added to the
nodeTypesobject: key = typeCode, value = imported renderer nodeTypesobject is declared outside the component function (avoids re-mount bug)- typeCode key matches the DB NodeType.typeCode character-for-character
5 — Backend: Executor
- C# class created implementing
IProcessElementExecution TypeCodeproperty returns the exact same string as the DB NodeType.typeCode- Settings class created (inherits
BaseNodeExecutorSettings) with properties matching Atlas Form field keys GetConfig<MySettings>(context)called to deserialise node configuration- All secrets accessed via
ICredentialResolver.GetPasswordAsync(credentialId)— never stored raw - Happy path returns
NodeExecutionResult.Success(outputData) - Error path returns
NodeExecutionResult.Failure(errorMessage)or throws for error-port routing - Executor registered in the DI container (Transient) in the appropriate executor project
- Unit tests written for the executor covering success and error paths
TypeCode Must Match in All Three Places
// 1. NodeType DB record
typeCode: "send-slack-message"
// 2. WorkflowCanvas.tsx — nodeTypes key
const nodeTypes = {
'send-slack-message': SlackMessageNode, // <-- must match
}
// 3. Backend executor
public string TypeCode => "send-slack-message"; // <-- must match
TypeCode Is Immutable After Deploy
Changing a typeCode after workflows have been saved will break every existing workflow that uses the node. The typeCode is stored in the ProcessElement records in the database. Treat it as a permanent identifier — plan carefully before committing.
Skipping the Custom Renderer
If your node fits the rectangle, circle, or diamond shape exactly and needs no special visual behaviour, you can skip steps 3 and 4 entirely. Set
shape in the NodeType DB record and the canvas will use the built-in renderer automatically. You still need the DB record, Atlas Form, and backend executor.