Skip to main content

Workflow input reference

Every workflow run starts with an input: a JSON object that the caller hands to the workflow engine. The shape of that object is not fixed — it depends entirely on where the workflow is triggered from (a request plugin, a scheduled job, a data exporter, an authentication module, etc.). This page documents, for each launch context, exactly which fields you can expect in the input.

How the input is exposed inside a workflow​

When the engine runs a workflow, the input object is written into the workflow memory under two keys, workflow_input and input:

wfRun.memory.set("workflow_input", input)
wfRun.memory.set("input", input)

Inside any node you can therefore reference input fields with expressions using either key:

{
"kind": "call",
"function": "core.hello",
"args": {
"name": "${workflow_input.request.headers.x-user}"
},
"result": "res"
}

${workflow_input.<path>} and ${input.<path>} are equivalent. workflow_input is the recommended form because it is stable: some nodes may rewrite the input memory slot during execution, whereas workflow_input always reflects the original input.

note

The tables below list the top-level keys of the input object for each context. Nested objects that are shared across several contexts (request, the typed body, route…) are described once in Shared structures.

Summary​

Launch contextPlugin / componentTop-level input keys
Route backendWorkflowBackendsnowflake, backend, apikey, user, raw_request, request, route, config, global_config, attrs
Request transformWorkflowRequestTransformersnowflake, raw_request, otoroshi_request, apikey, user, request, route, config, global_config, attrs
Response transformWorkflowResponseTransformersnowflake, raw_response, otoroshi_response, apikey, user, request, route, config, global_config, attrs
Access controlWorkflowAccessValidatorsnowflake, apikey, user, request, route, config, global_config, attrs
WebSocket messageWorkflowWebsocketTransformersnowflake, idx, request, otoroshi_request, target, route, config, attrs, action, message
Resume a paused workflowWorkflowResumeBackend / resume APIquery, body (stored under the resume_data memory key)
Scheduled jobWorkflowJobuser-defined (the job config object)
Data exporterWorkflowCallSettings exporterevents, config
Exporter custom filterdata exporter customFilter (kind workflow)event, config
Exporter custom transformdata exporter customTransform (kind workflow)event, config
AuthenticationWorkflowAuthModulephase, input
Nested callcore.workflow_call functionuser-defined (the input argument)
Test / debugadmin UI editor & test endpointuser-defined

Shared structures​

Several contexts embed the same nested objects. They are described once here and referenced from each context below.

The request object​

Produced by JsonHelpers.requestToJson from the incoming client request. Present in every HTTP-triggered context.

{
"id": "1516789…",
"method": "GET",
"headers": { "Host": "api.foo.bar", "Accept": "application/json" },
"cookies": [
{
"name": "session",
"value": "…",
"path": "/",
"domain": "foo.bar",
"http_only": true,
"max_age": 3600,
"secure": true,
"same_site": "Lax"
}
],
"tls": true,
"uri": "/api/users?page=1",
"query": { "page": ["1"] },
"path": "/api/users",
"version": "HTTP/1.1",
"has_body": false,
"remote": "10.0.0.4",
"client_cert_chain": null,
"path_params": { "id": "42" }
}
FieldTypeDescription
idstringInternal request id
methodstringHTTP method
headersobjectHeader name → value (simple map)
cookiesarrayRequest cookies
tlsbooleantrue when the connection is secured and trusted
uristringFull request URI (path + query)
queryobjectQuery parameter name → array of values
pathstringRequest path
versionstringHTTP protocol version
has_bodybooleanWhether the request carries a body
remotestringClient remote address
client_cert_chainarray | nullClient certificate chain (mTLS), null if none
path_paramsobjectPath parameters extracted by the matched route

Body handling (body_json / body_str / body_bytes)​

For contexts that read the request/response body (the backend, request and response transformers), the body is decoded according to its Content-Type and merged into the relevant object (request for the backend and request transformer, otoroshi_response for the response transformer). Exactly one of these keys is added:

Content-TypeKey addedType
application/jsonbody_jsonparsed JSON
application/xml, text/*body_strstring
anything else (binary)body_bytesarray of byte values

If the body is empty, none of these keys is added.

raw_request vs request vs otoroshi_request​

  • raw_request — the original client request, untouched (request object shape above).
  • otoroshi_request — the request as Otoroshi will forward it to the backend (after previous plugins ran): url, method, headers, query, version, cookies, client_cert_chain, backend.
  • otoroshi_response — the response as Otoroshi will return it: status, headers, cookies (+ the decoded body in the response transformer).

Other common keys​

  • route — the full JSON of the NgRoute the workflow runs for.
  • apikey — a light JSON view of the matched API key, or null.
  • user — a light JSON view of the connected user, or null.
  • config — the configuration of the plugin instance that triggered the workflow (for the workflow plugins this mostly contains ref and async).
  • global_config — the Otoroshi global configuration JSON.
  • attrs — the JSON view of the per-request context attributes (TypedMap).
  • snowflake — the unique id of the current request.

HTTP traffic contexts​

These workflows are attached to a route through a plugin and run while a request is being processed.

Route backend — WorkflowBackend​

The workflow is the backend: its returned value becomes the HTTP response (status, headers, body). Input built from NgbBackendCallContext.jsonWithTypedBody:

{
"snowflake": "1516…",
"backend": { /* selected target */ },
"apikey": null,
"user": null,
"raw_request": { /* request object */ },
"request": {
"url": "https://backend.svc/api/users",
"method": "GET",
"headers": { /* … */ },
"query": { /* … */ },
"version": "HTTP/1.1",
"cookies": [],
"client_cert_chain": null,
"backend": { /* … */ },
"body_json": { /* decoded request body, if any */ }
},
"route": { /* NgRoute JSON */ },
"config": { "ref": "workflow_xxx", "async": false },
"global_config": { /* … */ },
"attrs": { /* … */ }
}

Here request is the outgoing request (NgPluginHttpRequest) with the decoded body merged in; raw_request is the original client request.

Request transformer — WorkflowRequestTransformer​

Runs before the call to the backend and rewrites the outgoing request (method, url, headers, cookies, body) from its returned value. Input from NgTransformerRequestContext.jsonWithTypedBody:

{
"snowflake": "1516…",
"raw_request": { /* original request, NgPluginHttpRequest JSON */ },
"otoroshi_request": { /* request to be forwarded */ },
"apikey": null,
"user": null,
"request": {
/* request object (client request) … */
"body_json": { /* decoded body, if any */ }
},
"route": { /* NgRoute JSON */ },
"config": { "ref": "workflow_xxx" },
"global_config": { /* … */ },
"attrs": { /* … */ }
}

The decoded body is merged into the request key.

Response transformer — WorkflowResponseTransformer​

Runs after the backend answered and rewrites the response (status, headers, cookies, body) from its returned value. Input from NgTransformerResponseContext.jsonWithTypedBody:

{
"snowflake": "1516…",
"raw_response": { /* original response */ },
"otoroshi_response": {
"status": 200,
"headers": { /* … */ },
"cookies": [],
"body_json": { /* decoded response body, if any */ }
},
"apikey": null,
"user": null,
"request": { /* request object */ },
"route": { /* NgRoute JSON */ },
"config": { "ref": "workflow_xxx" },
"global_config": { /* … */ },
"attrs": { /* … */ }
}

For this context the decoded body is merged into otoroshi_response (not request).

Access control — WorkflowAccessValidator​

Decides whether the request is allowed. It runs before the body is read, so no body fields are present. The workflow must return an object like { "result": true } to allow, or { "result": false, "error": { "message": "…", "status": 403 } } to deny. Input from NgAccessContext.wasmJson:

{
"snowflake": "1516…",
"apikey": null,
"user": null,
"request": { /* request object */ },
"route": { /* NgRoute JSON */ },
"config": { "ref": "workflow_xxx" },
"global_config": { /* … */ },
"attrs": { /* … */ }
}

WebSocket message — WorkflowWebsocketTransformer​

Runs for each WebSocket frame, in both directions (a separate workflow can be configured for the incoming and the outgoing direction). Input is NgWebsocketPluginContext.wasmJson augmented with action and message:

{
"snowflake": "1516…",
"idx": 0,
"request": { /* request object */ },
"otoroshi_request": { /* forwarded request */ },
"target": { /* selected target */ },
"route": { /* NgRoute JSON */ },
"config": { /* … */ },
"attrs": { /* … */ },
"action": "incoming_message",
"message": {
"kind": "text",
"payload": "the frame content"
}
}
FieldDescription
actiondirection of the frame: "incoming_message" or "outgoing_message"
message.kind"text" or "binary"
message.payloadthe frame content: a string for text, an array of bytes for binary

Resuming a paused workflow — WorkflowResumeBackend / resume API​

A workflow can pause and be resumed later (see pause & resume). On resume, the original workflow_input is preserved, and the resume payload is written to a separate memory key, resume_data:

{
"query": { "approved": "true" },
"body": { /* parsed JSON body of the resume call, if any */ }
}

body is present only when the resume call carries a body. Reference it from your workflow with ${resume_data.body…} / ${resume_data.query…}.


Scheduled job — WorkflowJob​

When a workflow is attached to a WorkflowJob (cron or interval), the input is the job's own config object (rawConfig), exactly as you fill it in the job configuration. There is no request, route or HTTP context.

{
// whatever you put in the job "config" field
"environment": "prod",
"batch_size": 100
}

If the job has no configuration, the input is an empty object {}.


Data exporter contexts​

A workflow can be plugged into the data exporters pipeline in three different roles.

Workflow exporter — WorkflowCallSettings​

The exporter forwards batches of events to a workflow. Input:

{
"events": [ { /* event 1 */ }, { /* event 2 */ } ],
"config": { /* the DataExporterConfig JSON (id, name, enabled, tags, metadata, …) */ }
}
FieldTypeDescription
eventsarrayThe batch of analytics/audit events to export
configobjectThe data exporter configuration

Exporter custom filter (kind workflow)​

When a data exporter uses a workflow as its customFilter, the workflow is called once per event and must return a boolean (true to keep the event, false to drop it), either directly or as { "result": true }. Input:

{
"event": { /* the single event being filtered */ },
"config": { /* the customFilter config */ }
}

Exporter custom transform (kind workflow)​

When a data exporter uses a workflow as its customTransform, the workflow is called once per event and must return the transformed event (the returned event field, or the whole returned object). Input:

{
"event": { /* the single event being transformed */ },
"config": { /* the customTransform config */ }
}
note

For the custom filter / transform, config is the configuration of the filter/transform itself, not the whole exporter configuration (which is what the Workflow exporter receives).


Authentication module — WorkflowAuthModule​

A workflow can implement a custom authentication provider. It is invoked at several phases of the login/logout flow, and the input always has the same envelope:

{
"phase": "pa_callback",
"input": { /* phase-specific payload (see below) */ }
}

The phase value tells the workflow which step of the flow is being handled. The nested input object depends on the phase:

phaseTriggered forinput keys
pa_login_pageprivate apps — render the login pagerequest, global_config, service, route, is_route
pa_logoutprivate apps — logoutrequest, global_config, service, route, user
pa_callbackprivate apps — auth callbackrequest, global_config, service, route
bo_login_pageback-office — render the login pagerequest, global_config
bo_logoutback-office — logoutrequest, global_config, user
bo_callbackback-office — auth callbackrequest, global_config

Example for the pa_login_page phase:

{
"phase": "pa_login_page",
"input": {
"request": { /* request object */ },
"global_config": { /* … */ },
"service": { /* legacy ServiceDescriptor JSON */ },
"route": { /* NgRoute JSON */ },
"is_route": true
}
}

Depending on the phase, the workflow is expected to return a rendered response, a logout_url, or a user object (PrivateAppsUser / BackOfficeUser shape) on the callback phases.


Nested call — core.workflow_call function​

A workflow can call another workflow with the core.workflow_call function. The callee's input is whatever you pass in the input argument (an empty object if omitted):

{
"kind": "call",
"function": "core.workflow_call",
"args": {
"workflow_id": "other_workflow",
"input": {
"foo": "bar"
}
},
"result": "sub_res"
}

In other_workflow, ${workflow_input.foo} resolves to "bar".


Test & debug — admin UI editor and test endpoint​

When you run a workflow from the workflows editor (or through the POST /extensions/workflows/_test back-office endpoint), the input is the input field you provide in the test payload — an arbitrary JSON object you control:

{
"workflow_id": "my_workflow",
"workflow": { /* the workflow definition */ },
"functions": { /* optional custom functions */ },
"input": "{ \"name\": \"foo\" }"
}

The input field is sent as a JSON-encoded string and parsed into the object handed to the workflow. This is the place to reproduce, by hand, any of the input shapes documented above when designing or debugging a workflow.