Overview
Workflows are directed graphs stored as JSON. A workflow starts from an event, passes data between elements, and ends after the connected elements have run. Each workflow should perform one clear task in the business logic.
File anatomy
Section titled “File anatomy”A workflow file is usually stored in systems/<system-name>/workflows/<Identifier>.workflow.json.
{ "identifier": "CreateProjectTask", "title": "Create project task", "async": false, "entry": "createProjectTask", "elements": [], "events": []}| Property | Purpose |
|---|---|
identifier | Stable workflow identifier. Usually matches the file name without .workflow.json. |
title | Human-readable title for editors, logs, and notifications. |
description | Optional description of the workflow intent. |
async | true queues the workflow for background execution; false runs it immediately. |
queue | Optional queue name for asynchronous work. |
debug | Enables extra debugging behavior where supported. |
entry | Name of the element that starts the workflow. |
elements | Array of workflow nodes. |
events | Event registrations that make the workflow callable from Brezel. |
Elements
Section titled “Elements”Elements are the nodes of a workflow graph. Every element has a name, a type, optional options, and optional connection or data-binding properties.
{ "name": "loadProject", "type": "source/entities", "options": { "module": "projects", "first": true }, "set": { "project": "default:" }, "to": { "default": { "createTask": ["default"] } }}Element types use the format <category>/<type>.
| Category | Typical purpose |
|---|---|
event/* | Start or resume a workflow from a Brezel event. |
source/* | Load or create data. |
op/* | Transform, select, merge, or calculate data. |
query/* | Add query constraints, often for policies and scopes. |
flow/* | Branch, loop, or control execution. |
action/* | Persist data, respond to the client, send mail, export, or run another workflow. |
api/* | Communicate with external APIs. |
cast/* | Send live updates such as progress notifications. |
See Element Types for the broader inventory.
Ports and connections
Section titled “Ports and connections”Elements communicate through ports. Most elements expose a default output port, and many expose additional ports such as else, empty, error, confirm, or event.
Connections live in the to property:
{ "name": "hasProject", "type": "flow/if", "options": { "condition": "$project != null" }, "to": { "default": { "createTask": ["default"] }, "else": { "notifyMissingProject": ["default"] } }}The first key under to is the output port of the current element. The nested object maps target element names to their input ports.
Data bindings
Section titled “Data bindings”Use set to store element output in the workflow context.
{ "name": "openTaskModal", "type": "action/modal", "options": { "type": "confirm", "layout": "tasks.modal", "checkpoint": true }, "set": { "taskData": "confirm:" }}The binding "taskData": "confirm:" stores the data emitted from the confirm port in $taskData.
Use in to pass a specific context value into an element input:
{ "name": "saveTask", "type": "action/save", "in": { "default": "$task" }}See Data Flow for more detail on context values, selectors, and recipes.
Events
Section titled “Events”Events make a workflow callable by the UI, module lifecycle, cron scheduler, or policy system.
{ "entry": "createProjectTask", "events": [ { "identifier": "createProjectTask", "type": "webhook", "module": "projects" } ], "elements": [ { "name": "createProjectTask", "type": "event/webhook", "options": { "module": "projects", "refresh_prototype": true }, "set": { "project": "default:", "data": "input:" } } ]}The event registration and entry element usually share the same identifier. See Events and Triggers for common event types.
Sync and async
Section titled “Sync and async”Synchronous workflows run in the current request. Use async: false when the workflow must return a response to the browser, open a modal, set client fields, redirect, or block a save operation.
Asynchronous workflows are queued and handled in the background. Use async: true for long-running work, exports, mail batches, imports, API polling, build jobs, and other tasks that should not keep the request open.
Good defaults:
- Use sync for UI interactions and immediate validation.
- Use async for work that may take more than a few seconds.
- Use
cast/progressfor background jobs that should stay visible to the user. - Use a small sync workflow to validate and enqueue a larger async workflow with
action/run.
Naming and formatting
Section titled “Naming and formatting”- Use
PascalCasefor workflow identifiers and file names, for exampleCreateProjectTask.workflow.json. - Use readable
camelCaseelement names such asloadProject,createTask, andsaveTask. - Prefer business names over generated names when editing a workflow manually.
- Keep JSON formatted with 4 spaces and no trailing commas.
- Keep each workflow focused on one business outcome.
- Give branches and variables neutral, explicit names:
$project,$taskData,$selectedCustomer.