Overview
Workflows enable automation of actions based on triggers. A workflow consists of:- Trigger: What starts the workflow (e.g., an API request, a new log event, or a new autodiscovered resource)
- Actions: What the workflow does when triggered (e.g., send a Slack message, call the Formal API)
Triggers vs Actions
| Component | Purpose | When it runs |
|---|---|---|
| Trigger | Defines what starts the workflow | Once, when the triggering event occurs |
| Actions | Defines what the workflow does | After the trigger fires, in sequence based on depends_on |
depends_on and conditionally executed using if.
Workflow Structure
depends_on - Action Chaining
The depends_on field controls the execution order of actions. Actions with empty depends_on run immediately after the trigger. Subsequent actions run after their dependencies complete.
Referencing Previous Trigger and Action Outputs
Actions can reference previous triggers and actions outputs via the following syntax in CEL expressions:trigger.<output_field>: reference any output field of the triggeractions.<action_name>.<output_field>: reference any output field of an action by its name
if or the api-request allow argument, take CEL expressions by default. For arguments that take strings
instead, embed CEL expressions via the ${{}} syntax.
if - Conditional Execution
The if field is a CEL expression that determines whether a subsequent action should execute based on the previous actions or trigger.
Trigger Types
api-request
Triggered when a user calls the CreateWorkflowTrigger API endpoint. Useful for manually triggered workflows or integrations.
Args:
| Arg | Type | Required | Description |
|---|---|---|---|
allow | string | No | CEL expression to authorize the request. If omitted, all authenticated users can trigger. |
| Output | Type | Description |
|---|---|---|
user | object | The user who triggered the workflow |
payload | object | Custom payload passed in the API request |
new-log
Triggered when a new log event matches a specified condition. Useful for reacting to specific database queries, policy violations, or access patterns.
Args:
| Arg | Type | Required | Description |
|---|---|---|---|
if | string | Yes | CEL expression to filter logs. Only logs matching this condition trigger the workflow. |
| Output | Type | Description |
|---|---|---|
log | object | The full log event that matched the condition |
new-autodiscovered-resource
Triggered when a new resource is autodiscovered via cloud integration (AWS, GCP, etc.). Useful for automatically onboarding new databases.
Args: None
Outputs (available in actions):
| Output | Type | Description |
|---|---|---|
resource | object | The autodiscovered resource object |
form-submission
Triggered when a user submits a Formal form via Slack. Useful for approval workflows, access requests, and other structured data collection scenarios.
Args:
| Arg | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The ID of the form to listen for submissions on |
trigger.form_submission):
| Output | Type | Description |
|---|---|---|
form_id | string | The ID of the submitted form |
form_name | string | The name of the submitted form |
submitter_email | string | Email of the user who submitted the form |
submission | object | Map of field IDs to submitted values |
See the Forms documentation for details on creating forms and the full Slack integration.
Action Types
send-slack-message
Sends a direct message to a Slack user or channel. Requires a Slack integration to be configured.
Args:
| Arg | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Message text to send (supports Slack markdown) |
recipient_email | string | One of | Email address of the Slack user (mutually exclusive with recipient_channel) |
recipient_channel | string | One of | Slack channel name without # (mutually exclusive with recipient_email) |
| Output | Type | Description |
|---|---|---|
recipient_email | string | Email of the recipient (if email was used) |
recipient_channel | string | Name of the channel (if channel was used) |
message_sent | boolean | Whether the message was successfully sent |
ask-in-chat
Sends an interactive message with Yes/No buttons. The workflow pauses until the user responds.
Args:
| Arg | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Message text to display |
integration | string | Yes | Must be "slack" |
recipient_email | string | One of | Email of the Slack user (mutually exclusive with recipient_channel) |
recipient_channel | string | One of | Slack channel name without # (mutually exclusive with recipient_email) |
action-response.
Example:
formal-app-command
Calls a Formal API endpoint using a machine user’s credentials.
Args:
| Arg | Type | Required | Description |
|---|---|---|---|
app | string | Yes | Service name (e.g., Resource, User, Policy) |
command.name | string | Yes | Method name without prefix (e.g., Resource for CreateResource) |
command.type | string | Yes | Method prefix (e.g., Create, Update, Delete, Get) |
input | object | Yes | Request payload for the API call |
machine_user_id | string | Yes | ID of the machine user to authenticate as |
| Output | Type | Description |
|---|---|---|
status_code | int | HTTP status code of the response |
body | object | Parsed JSON response body |