Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions src/contents/docs/05.workflow-components/05.inputs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ Here is the list of supported data types:
- `URI`: Must be a valid URI and will be kept as a string.
- `SECRET`: Encrypted string stored in the database. It is decrypted at runtime and can be used in all tasks. The value of a `SECRET` input is masked in the UI and in the execution context. Note that you need to set the [encryption key](../../configuration/05.security-and-secrets/index.md) in your [Kestra configuration](../../configuration/index.mdx) before using it.
- `ARRAY`: Must be a valid JSON array or a YAML list. The `itemType` property is required to ensure validation of the type of the array items.
- `FORM`: Groups related inputs under a shared `displayName` and `description`. When a flow contains at least one FORM input, the Execute modal renders a multi-step wizard — one step per FORM group plus any ungrouped inputs, then a recap. Children are referenced as `{{ inputs.<form_id>.<child_id> }}`. FORM inputs cannot be nested and do not support `defaults` or `prefill`.

All `FILE` inputs are automatically uploaded to Kestra's [internal storage](../../08.architecture/data-components/index.md#internal-storage) and accessible to all tasks. After the upload, the input variable will contain a fully qualified URL of the form `kestra:///.../.../` that will be automatically managed by Kestra and can be used as-is within any task.

Expand Down Expand Up @@ -306,6 +307,114 @@ tasks:

You can access the first input value using `{{ inputs.nested.string }}`. This provides type validation for nested inputs without resorting to raw JSON (JSON inputs are passed as strings).

## FORM inputs

Use a `FORM` input to group related inputs under a shared label and description. When a flow has at least one FORM input, the Execute modal renders a multi-step wizard: one step per FORM group, a step for any ungrouped inputs, then a recap. Apps using `CreateExecutionForm` render the same wizard automatically.

```yaml
id: provision_environment
namespace: company.team

inputs:
- id: requester
type: STRING
required: true
description: Name or team submitting this request.

- id: environment
type: FORM
displayName: Environment setup
description: Where the environment runs and what size it needs.
inputs:
- id: region
type: SELECT
required: true
defaults: eu-central-1
values:
- eu-central-1
- eu-west-1
- us-east-1

- id: instance_type
type: SELECT
required: true
defaults: t3.medium
values:
- t3.medium
- t3.large
- t3.xlarge

- id: notifications
type: FORM
displayName: Notifications
description: Where to send status updates.
inputs:
- id: slack_channel
type: STRING
defaults: "#platform-ops"

- id: notify_on_failure
type: BOOL
defaults: true

tasks:
- id: log_request
type: io.kestra.plugin.core.log.Log
message: |
Requested by: {{ inputs.requester }}
Region: {{ inputs.environment.region }}
Instance: {{ inputs.environment.instance_type }}
Slack: {{ inputs.notifications.slack_channel }}
```

Children are accessed via `{{ inputs.<form_id>.<child_id> }}`. In the example above, `region` inside the `environment` FORM is `{{ inputs.environment.region }}`.

### dependsOn across FORM children

To make one FORM child depend on another, use the full dotted path in `dependsOn`:

```yaml
inputs:
- id: cloud
type: FORM
displayName: Cloud configuration
inputs:
- id: provider
type: SELECT
values: [AWS, GCP, Azure]

- id: region
type: SELECT
dependsOn:
inputs:
- cloud.provider
condition: "{{ inputs.cloud.provider == 'AWS' }}"
values:
- us-east-1
- eu-west-1
```

### Constraints

:::alert{type="warning"}
- A FORM cannot contain another FORM — grouping is limited to one level.
- A FORM cannot have `defaults` or `prefill` — those properties belong on the individual child inputs.
:::

### API submission

When triggering a flow with FORM inputs via the API, use flat dotted field names in the multipart form data. Kestra maps them to the nested execution context automatically.

```bash
curl -X POST "http://localhost:8080/api/v1/main/executions/company.team/provision_environment" \
-H "Content-Type: multipart/form-data" \
-F "requester=platform-team" \
-F "environment.region=eu-central-1" \
-F "environment.instance_type=t3.large" \
-F "notifications.slack_channel=#ops" \
-F "notifications.notify_on_failure=true"
```

## Array inputs

Array inputs are used to pass a list of values to a flow. The `itemType` property is required to ensure validation of the type of the array items.
Expand Down Expand Up @@ -624,6 +733,19 @@ tasks:
When using `http()` inside an `expression` with secrets in headers (e.g., an authenticated API request), use named arguments and string concatenation ([Pebble Literals](https://pebbletemplates.io/wiki/guide/basic-usage/#literals)). The key to the syntax is to use string interpolation with `~`.
:::

### Dynamic inputs from a subflow

For cases that require complex logic — running a script, calling a CLI command, or executing multi-step tasks — use the `subflow()` Pebble function in the `expression:` property. `subflow()` runs a flow synchronously at form render time and populates the dropdown from its outputs:

```yaml
inputs:
- id: region
type: SELECT
expression: "{{ subflow(namespace='company.ops', id='fetch_regions').outputs.region_list }}"
```

See [Populate a dropdown from a subflow](../../15.how-to-guides/dynamic-inputs/index.md#populate-a-dropdown-from-a-subflow) for a full example and constraints.

## Conditional inputs for interactive workflows

You can set up inputs that depend on other inputs, letting further inputs be conditionally displayed based on user choices. This is useful for use cases such as approval workflows or dynamic resource provisioning.
Expand Down
4 changes: 4 additions & 0 deletions src/contents/docs/07.enterprise/04.scalability/apps/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ By combining different blocks, you can create a custom UI that guides users thro
| `Markdown` | OPEN, CREATED, RUNNING, PAUSE, RESUME, SUCCESS, FAILURE, FALLBACK | - `content` | `- type: io.kestra.plugin.ee.apps.core.blocks.Markdown`<br> &nbsp;&nbsp;&nbsp;&nbsp;`content: "## Please validate the request. Inspect the logs and outputs below. Then, approve or reject the request."` |
| `RedirectTo` | OPEN, CREATED, RUNNING, PAUSE, RESUME, SUCCESS, FAILURE, ERROR, FALLBACK | - `url`: redirect URL <br> - `delay`: delay in seconds | `- type: io.kestra.plugin.ee.apps.core.blocks.RedirectTo`<br> &nbsp;&nbsp;&nbsp;&nbsp;`url: "https://kestra.io/docs"`<br> &nbsp;&nbsp;&nbsp;&nbsp;`delay: "PT60S"` |
| `CreateExecutionForm` | OPEN | None | `- type: io.kestra.plugin.ee.apps.execution.blocks.CreateExecutionForm` |

:::alert{type="info"}
When the flow uses [`FORM` inputs](../../../05.workflow-components/05.inputs/index.md#form-inputs), `CreateExecutionForm` renders a multi-step Next/Back wizard — one step per FORM group, a step for ungrouped inputs, then a recap. No additional App configuration is required; the wizard is driven entirely by the flow's input definition.
:::
| `ResumeExecutionForm` | PAUSE | None | `- type: io.kestra.plugin.ee.apps.execution.blocks.ResumeExecutionForm` |
| `CreateExecutionButton` | OPEN | - `text` <br> - `style`: DEFAULT, SUCCESS, DANGER, INFO <br> - `size`: SMALL, MEDIUM, LARGE | `- type: io.kestra.plugin.ee.apps.execution.blocks.CreateExecutionButton`<br> &nbsp;&nbsp;&nbsp;&nbsp;`text: "Submit"`<br> &nbsp;&nbsp;&nbsp;&nbsp;`style: "SUCCESS"`<br> &nbsp;&nbsp;&nbsp;&nbsp;`size: "MEDIUM"` |
| `CancelExecutionButton` | CREATED, RUNNING, PAUSE | - `text` <br> - `style`: DEFAULT, SUCCESS, DANGER, INFO <br> - `size`: SMALL, MEDIUM, LARGE | `- type: io.kestra.plugin.ee.apps.execution.blocks.CancelExecutionButton`<br> &nbsp;&nbsp;&nbsp;&nbsp;`text: "Reject"`<br> &nbsp;&nbsp;&nbsp;&nbsp;`style: "DANGER"`<br> &nbsp;&nbsp;&nbsp;&nbsp;`size: "SMALL"` |
Expand Down
74 changes: 74 additions & 0 deletions src/contents/docs/15.how-to-guides/dynamic-inputs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,77 @@ tasks:
:::alert{type="info"}
When using `http()` inside an `expression` with secrets in headers (e.g., an authenticated API request), use named arguments and string concatenation ([Pebble Literals](https://pebbletemplates.io/wiki/guide/basic-usage/#literals)). The key to the syntax is to use string interpolation with `~`.
:::

## Populate a dropdown from a subflow

When `kv()` and `http()` are not enough — for example, when you need to run a script task, call a CLI command (`aws ec2 describe-instances`, `gcloud projects list`), or execute complex multi-step logic — use the `subflow()` Pebble function.

`subflow()` runs a subflow synchronously at form render time and exposes its flow-level outputs as the dropdown values. The main flow does not start until the subflow finishes and the form is submitted.

**Step 1 — Create the data-fetching subflow.** This flow queries your infrastructure and returns a list as a flow-level output:

```yaml
id: fetch_aws_regions
namespace: company.ops

tasks:
- id: get_regions
type: io.kestra.plugin.scripts.shell.Commands
taskRunner:
type: io.kestra.plugin.core.runner.Process
commands:
- |
regions=$(aws ec2 describe-regions --query 'Regions[].RegionName' --output json)
echo "::$(printf '{"outputs":{"regions":%s}}' "$regions")::"

outputs:
- id: regions
type: JSON
value: "{{ outputs.get_regions.vars.regions }}"
```

The `::{"outputs":{"key":"value"}}::` line is Kestra's [script output format](../../16.scripts/06.outputs-metrics/index.md) — it's how `shell.Commands` tasks publish named values that downstream expressions can reference via `outputs.<task_id>.vars.<key>`.

**Step 2 — Reference it from a SELECT input in your main flow:**

```yaml
id: deploy_to_region
namespace: company.ops

inputs:
- id: region
type: SELECT
displayName: AWS Region
expression: "{{ subflow(namespace='company.ops', id='fetch_aws_regions').outputs.regions }}"

tasks:
- id: deploy
type: io.kestra.plugin.core.log.Log
message: "Deploying to {{ inputs.region }}"
```

When a user opens the Execute form, Kestra runs `fetch_aws_regions` synchronously and populates the dropdown from its output.

### Chaining dropdowns with `dependsOn`

You can chain dropdowns so the second list depends on the first selection:

```yaml
inputs:
- id: environment
type: SELECT
expression: "{{ subflow(namespace='company.ops', id='fetch_environments').outputs.envs }}"

- id: cluster
type: SELECT
dependsOn:
inputs:
- environment
expression: "{{ subflow(namespace='company.ops', id='fetch_clusters', inputs={'env': inputs.environment}).outputs.clusters }}"
```

**Constraints to be aware of:**

- `subflow()` is only valid in the `expression:` property of a `SELECT` or `MULTISELECT` input. It throws if used in a task or trigger property.
- The subflow must complete within the timeout (default `PT1M`, max `PT5M`). Keep data-fetching subflows fast.
- Recursion is capped at depth 3.
21 changes: 21 additions & 0 deletions src/contents/docs/configuration/04.plugins-and-execution/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,27 @@ Relevant runtime-wide settings include:

Those settings are documented in more detail on [Runtime and Storage](../02.runtime-and-storage/index.md), since they affect the whole instance and not just plugin behavior.

### Subflow function configuration

The `subflow()` Pebble function, used to populate `SELECT` and `MULTISELECT` input dropdowns at form render time, has three configurable limits. All three accept ISO 8601 duration strings or integers.

```yaml
kestra:
pebble:
subflow-function:
default-timeout: PT1M # timeout when the caller omits the timeout argument
max-timeout: PT5M # hard cap — larger values are rejected at runtime
max-depth: 3 # maximum nesting depth of subflow() calls on one render thread
```

| Key | Default | Description |
|---|---|---|
| `kestra.pebble.subflow-function.default-timeout` | `PT1M` | Applied when the `timeout` argument is not passed. Keep this short — the call blocks the Execute form render. |
| `kestra.pebble.subflow-function.max-timeout` | `PT5M` | Hard cap. A `timeout` argument larger than this value is rejected at runtime with an error. |
| `kestra.pebble.subflow-function.max-depth` | `3` | Guards against runaway recursion. A subflow whose own inputs also call `subflow()` counts against this limit. |

Increase `max-timeout` only if your data-fetching subflows genuinely need longer — long form renders degrade user experience. Increase `max-depth` only if you have intentionally nested multi-level dependent dropdowns.

## Related docs

- Flow-level plugin defaults: [Plugin Defaults](../../05.workflow-components/09.plugin-defaults/index.md)
Expand Down
47 changes: 47 additions & 0 deletions src/contents/docs/expressions/04.functions/04.workflow/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,53 @@ Retrieves the output of a parent task. The optional `index` argument specifies w
{{ parentOutput(1) }}
```

## `subflow()`

Synchronously runs a subflow and returns its terminal execution result, so you can read the subflow's outputs, state, or labels from within an expression.

```twig
{{ subflow(namespace='company.team', id='my_subflow', inputs={'key': 'value'}).outputs.my_output }}
```

**Arguments:**

| Argument | Required | Description |
|---|---|---|
| `namespace` | Yes | Namespace of the subflow to run |
| `id` | Yes | Flow ID of the subflow to run |
| `inputs` | No | Map of inputs to pass to the subflow |
| `revision` | No | Specific revision to run; defaults to latest |
| `labels` | No | Labels to attach to the subflow execution |
| `timeout` | No | ISO 8601 duration; defaults to `PT1M`, hard cap `PT5M` |

**Return value** — an object with four fields:

| Field | Type | Description |
|---|---|---|
| `.id` | string | Execution ID of the subflow run |
| `.state` | string | Terminal state name, e.g. `SUCCESS`, `FAILED` |
| `.outputs.<key>` | any | Flow-level outputs declared in the subflow's `outputs:` block |
| `.labels.<key>` | string | Execution labels as a key → value map |

**Primary use case** — populating a `SELECT` or `MULTISELECT` input's `expression:` at form render time:

```yaml
inputs:
- id: datacenter
type: SELECT
expression: "{{ subflow(namespace='company.ops', id='fetch_datacenters').outputs.datacenter_list }}"
```

When a user opens the Execute form, Kestra runs the subflow synchronously, reads its output, and populates the dropdown before the main flow begins.

**Important constraints:**

- Only valid in an input `expression:` context. Using `subflow()` inside a task or trigger property throws an error, because blocking a worker thread while waiting for a child execution can deadlock a worker under load.
- Only available on `WEBSERVER` and `STANDALONE` server types. The function is not registered on other server types.
- The default timeout is `PT1M`. The hard cap is `PT5M` — passing a larger `timeout` value is rejected at runtime. Both limits are configurable; see [configuration reference](../../../configuration/04.plugins-and-execution/index.md#subflow-function-configuration).
- Subflow recursion depth is capped at 3. A subflow whose own inputs call `subflow()` counts against this limit.
- Executions triggered by `subflow()` carry a `system.from: subflow` label automatically.

## `appLink()`

Enterprise Edition's `appLink()` builds links back to Kestra Apps:
Expand Down
Loading