Flows
CairnCMS’s automation surface comes in two system collections: flows define automated processes and operations are the steps inside a flow. Flows handle both inbound HTTP requests (Webhook trigger) and outbound HTTP calls (Webhook / Request URL operation), so the standalone webhook collection from earlier Directus versions is not present. The data model and the operator UX are documented in Flows; this page covers the API surface.
Each collection has the standard CRUD shape documented in Items, and directus_flows adds one bespoke endpoint that exposes a flow as an HTTP-callable handler.
Flows (/flows)
Section titled “Flows (/flows)”A flow defines a chain of operations triggered by an event. The platform supports five trigger types:
- Event hook — fires on item create / update / delete events.
- Schedule — fires on a cron expression.
- Webhook — fires on an inbound HTTP request to
/flows/trigger/<flow-id>. - Manual — fires from the admin app (a button on a collection or item view), also reachable via
/flows/trigger/<flow-id>. - Operation — fires when another flow’s operation calls into this flow.
The first two run on internal events; the next two are HTTP-callable; operation-triggered flows are reached only through other flows. The endpoint shape is the same regardless of trigger type for create/read/update/delete, but only flows configured with a webhook or manual trigger respond to /flows/trigger/<flow-id>.
Endpoints
Section titled “Endpoints”| Method | Path | Purpose |
|---|---|---|
GET | /flows | List flows. |
SEARCH | /flows | Read flows with the request body. |
GET | /flows/<id> | Read a single flow. |
POST | /flows | Create one or many flows. |
PATCH | /flows | Update many flows. |
PATCH | /flows/<id> | Update a single flow. |
DELETE | /flows | Delete many flows. |
DELETE | /flows/<id> | Delete a single flow. |
GET | /flows/trigger/<id> | Trigger a webhook flow configured for GET. |
POST | /flows/trigger/<id> | Trigger a webhook flow configured for POST or any manual flow. |
Flow record fields
Section titled “Flow record fields”id(UUID) — primary key.name,icon,color,description— display metadata.status—activeorinactive. Inactive flows ignore their triggers and the trigger endpoint returns403.trigger— one ofevent,schedule,webhook,manual,operation.accountability— controls how the flow run is recorded. Operations always execute under whatever accountability the trigger passes through (the requesting user for webhook and manual flows, the user whose action triggered the event for event hooks, no accountability for schedule and operation triggers); this field does not change that. The values are:allwrites an activity row for the run and a revision row containing the full step-by-step execution trace,activitywrites the activity row without the revision, andnullskips both. The naming is historical and a little misleading; treat it as the run-logging policy, not as a permission switch.options— per-trigger configuration (the cron expression for schedule, the HTTP method and async flag for webhook, the allowed collections for manual, and so on).operation— UUID of the first operation. Subsequent operations chain throughresolveandrejectreferences on each operation row.operations— alias field listing all operations belonging to this flow.date_created,user_created— accountability.
Revision redaction
Section titled “Revision redaction”When accountability is all, the persisted revision row contains the full keyed state of the flow run: trigger payload, accountability snapshot, environment, and every operation’s output keyed by its operation key. Before this state is written to directus_revisions, the platform redacts values associated with a known set of secret-bearing keys (authorization headers, tokens, password fields, OAuth/API credentials, and similar). Values that originate from a secret-bearing input and are subsequently interpolated into a later operation’s options are also redacted at the interpolated site, even when the surrounding key is not itself sensitive. Redacted values appear as --redact-- in the revision.
The redaction targets secret-bearing keys and values that propagate from them. It does not automatically redact arbitrary personally identifiable information. Operators who need PII suppression in flow revisions should remove the data before it enters the flow rather than expecting the redaction layer to catch it.
/flows/trigger/<id>
Section titled “/flows/trigger/<id>”Two flow trigger types translate to HTTP routes: webhook and manual.
Webhook flows are configured with a method option (GET or POST); only requests using that method against the matching flow ID resolve. The handler receives the request’s path, query, body, method, and headers as the operation chain’s input data.
POST /flows/trigger/<flow-id>Content-Type: application/json
{ "anything": "the flow operations expect" }The response body is the result of the last operation in the chain unless the flow’s options.return is set to a different operation key. If the flow’s options.async is true, the request returns immediately and the operations run in the background; otherwise the request waits for the chain to complete.
options.cacheEnabled defaults to true and only takes effect when method is GET; setting it to false prevents new responses for that flow from being stored in the response cache. It does not invalidate entries that are already cached. With CACHE_AUTO_PURGE=true (the value the bootstrap installer writes for new projects) updating the flow clears the response cache as a side effect, so the new setting applies on the next request. With CACHE_AUTO_PURGE=false (the in-code default) a previously cached response continues to serve until its TTL expires or the cache is cleared with POST /utils/cache/clear.
Manual flows also resolve through /flows/trigger/<flow-id> but require POST and a body that names the target collection:
POST /flows/trigger/<flow-id>Content-Type: application/json
{ "collection": "articles", "keys": ["<id-1>", "<id-2>"]}collection is required. keys is conventional for collection-page manual flows that operate on selected items, but it is not strictly required: a manual flow with requireSelection: false (configured through the admin app) can run against a collection without specific item IDs, and the body in that case is just { "collection": "articles" } plus any operator-supplied confirmation values.
The flow’s options.collections lists which collections it can run against. Requests targeting a collection outside that list return 403 FORBIDDEN. The flow operations receive the full request body plus path, query, method, and headers as input data.
A request to /flows/trigger/<id> for a flow whose trigger type is anything other than webhook or manual (or for a flow that does not exist) returns 403 FORBIDDEN. The response code is intentional: distinguishing “wrong trigger type” from “no such flow” would leak which flow IDs exist.
Authorization for manual triggers requires: an authenticated caller, the target collection in the flow’s options.collections list, and read permission on the target items (the keys array, when supplied) or read permission on the target collection (when requireSelection: false). The caller does not need read permission on directus_flows itself. The ability to fetch the flow’s record through /flows is independent of the ability to trigger it. UI discovery and backend execution are separate capabilities.
Operations (/operations)
Section titled “Operations (/operations)”An operation is one step in a flow. Each operation has a type (the operation extension that runs it), a key (used to reference its output downstream), and resolve / reject pointers to the next operations on success and failure.
Endpoints
Section titled “Endpoints”| Method | Path | Purpose |
|---|---|---|
GET | /operations | List operations. |
SEARCH | /operations | Read operations with the request body. |
GET | /operations/<id> | Read a single operation. |
POST | /operations | Create one or many operations. |
PATCH | /operations | Update many operations. |
PATCH | /operations/<id> | Update a single operation. |
DELETE | /operations | Delete many operations. |
DELETE | /operations/<id> | Delete a single operation. |
There is no HTTP endpoint to invoke an operation directly. Operations run only as part of a flow chain; to invoke an operation from outside, build a flow with a webhook or manual trigger that has the operation as its first step.
Operation record fields
Section titled “Operation record fields”id(UUID) — primary key.name— display name.key— short identifier used to reference this operation’s output downstream. References take the form{{ <key> }}in subsequent operationoptionspayloads.type— the operation type (the operation extension that runs the step). Built-in types includecondition,exec,item-create,item-read,item-update,item-delete,mail,notification,request,transform,trigger,log, andsleep. Custom operation extensions add to this list.position_x,position_y— coordinates in the flow editor’s grid.options— per-type configuration. Shape varies bytype.resolve— UUID of the operation to run next on success. Null at the end of a chain.reject— UUID of the operation to run on failure. Null skips error handling, in which case the flow halts.flow— UUID of the flow this operation belongs to.date_created,user_created— accountability.
The platform stores operations independently of their parent flow. The flow’s chain is built at runtime by traversing from flow.operation through resolve/reject references, so operations that are not reachable from the entry point still exist in the database but never run, and broken resolve/reject references only surface when the chain is constructed. There is no API-side enforcement that the chain is well-formed. Editing operations directly through /operations rather than the admin app is supported for migration and tooling, but the admin app is where the chain shape is most easily kept consistent.
Permission semantics
Section titled “Permission semantics”Both collections are admin-only by default. Granting non-admin write access to either is a privilege escalation surface: a role that can edit flows or operations can configure them to bypass accountability with accountability: null. Treat any non-admin grant as a deliberate decision.
Read access is sometimes granted to non-admins for inspection and reporting, but the operation options payloads can contain credentials passed through to external services; consider field-level filtering when granting any read access.
GraphQL
Section titled “GraphQL”Both collections are exposed on /graphql/system with the standard generated CRUD shape (flows, operations for queries; create_flows_item, update_operations_item, and so on for mutations). Filter, sort, and pagination arguments work the same way as on user collections; see Filters and queries / GraphQL.
The bespoke /flows/trigger/<id> endpoint is REST-only. There is no GraphQL equivalent because the request shape varies per flow (the body becomes the operations’ input data) and GraphQL’s typed interface does not fit that shape.
Where to go next
Section titled “Where to go next”- Flows — operator-facing reference for designing flows, the trigger types, and the operation catalog.
- Operations — building custom operations as extensions.
- Hooks — the lower-level event surface that flows are built on top of.
- Filters and queries — the query DSL for filtering flow and operation lists.