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.