afterExecution Tasks in Kestra – Post-Run Actions
Run tasks after a flow execution completes.
afterExecution tasks – post-run actions
afterExecution tasks run once a flow has finished, allowing you to act on the final execution status.
afterExecution property
afterExecution is a block of tasks that run after the flow ends. You can use it to run conditional tasks based on the final state, such as SUCCESS or FAILED. This is especially useful for custom notifications and alerts. For example, you can combine afterExecution with the runIf property to send different Slack messages depending on the execution state.
id: alerts_demonamespace: company.team
tasks: - id: fail type: io.kestra.plugin.core.execution.Fail
afterExecution: - id: onSuccess runIf: "{{execution.state == 'SUCCESS'}}" type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook url: https://hooks.slack.com/services/xxxxx messageText: "{{flow.namespace}}.{{flow.id}} finished successfully!"
- id: onFailure runIf: "{{execution.state == 'FAILED'}}" type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook url: https://hooks.slack.com/services/xxxxx messageText: "Oh no, {{flow.namespace}}.{{flow.id}} failed!!!"afterExecution vs errors
Both constructs are useful for notifications and follow-up actions, but they run at different moments.
errorsruns when a task or flow errors and is primarily for failure handling.afterExecutionruns only after the execution reaches its final state.
For failure-specific handling, including local handlers inside flowable tasks, see the errors documentation.
Choose afterExecution when you need to branch on the final status of the whole execution, for example to send one message for SUCCESS, another for FAILED, and a third for WARNING.
Choose errors when you only care about failure handling or when you need local error handling inside a specific flowable task.
Pros of afterExecution:
- It works naturally with final states such as
SUCCESS,FAILED, andWARNING. - It keeps all post-run outcome logic in one place.
- It is well suited for final notifications, reporting, and auditing tasks.
Cons of afterExecution:
- It cannot be scoped locally to a flowable task the way
errorscan. - Errors inside
afterExecutiondo not change the final execution state.
Any errors in the afterExecution block will not change the state of the flow from SUCCESS to FAILED, and they will not trigger a flow that relies on ExecutionStatus = FAILED. You can force a state change by using a Sequential flowable task with an errors block, as in the example below:
afterExecution: - id: t2 type: io.kestra.plugin.core.flow.Sequential tasks: - id: t2-t1 type: io.kestra.plugin.core.flow.Sleep duration: "PT5S" - id: t2-t2 type: io.kestra.plugin.core.execution.Fail errors: - id: sendAlert type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook url: https://hooks.slack.com/services/xxxxx messageText: "Flow {{ flow.namespace }}.{{ flow.id }} with execution ID {{ execution.id }} failed."afterExecution vs finally
afterExecution and finally are both end-of-flow constructs, but they serve different purposes.
The afterExecution property differs from the finally property because:
finallyruns tasks at the end of the flow while the execution is still in aRUNNINGstate.afterExecutionruns tasks after the execution finishes in a terminal state like SUCCESS or FAILED.
Use finally for cleanup operations that should always run, regardless of the outcome. See the finally documentation for examples. When follow-up actions depend on the final state, use afterExecution to capture the result.
To demonstrate, take the following flow that uses both finally and afterExecution:
id: state_demonamespace: company.team
tasks: - id: run type: io.kestra.plugin.core.log.Log message: Execution {{ execution.state }} # Will show RUNNING
- id: fail type: io.kestra.plugin.core.execution.Fail
finally: - id: finally type: io.kestra.plugin.core.log.Log message: Execution {{ execution.state }} # Will show RUNNING
afterExecution: - id: afterExecution type: io.kestra.plugin.core.log.Log message: Execution {{ execution.state }} # Will show FAILEDAfter running the example above, the finally task appears with a RUNNING state while the afterExecution task shows FAILED.

Best practice: Use afterExecution when you need to act on the final state of an execution. Use finally when you need to ensure cleanup happens regardless of state.
Was this page helpful?