Skip to main content

Overview

Forms enable structured data collection that can trigger workflows. A form defines a set of typed fields that users fill out via Slack. When submitted, the form data is available to workflow actions via CEL expressions. Forms are useful for Access requests: Enable users to request access to perform specific action on resources with time-bound parameters for policy suspensions.

Form Structure

A form consists of:
  • Name: A unique name within your organization
  • Description: Optional description shown to users when filling out the form
  • Fields: One or more typed fields for data collection

Field Types

Text and numeric inputs:
  • string — Plain text input
  • number — Numeric input
  • email — Email address input
  • url — URL input
Date and time inputs:
  • date — Date picker
  • time — Time picker
  • timestamp — Combined date and time picker
Selection inputs (require options configuration — see below):
  • select — Single-select dropdown
  • multi_select — Multi-select dropdown
  • checkbox — Checkbox group
  • radio — Radio button group

Field IDs

Each field has an id that must match the pattern field_[_a-zA-Z0-9]+. This ID is used to reference field values in workflow actions via trigger.form_submission.submission.field_<id>.

Selection Field Options

The select, multi_select, checkbox, and radio field types all require an options configuration. All four types support static options. Only select and multi_select also support a dynamic options source. A field must have one or the other, not both.

Static Options

Provide a fixed list of options directly in the field configuration. Each option has a label (displayed to the user) and a value (submitted with the form).

Dynamic Options

Dynamic sources allow you to populate select and multi_select inputs by calling a List endpoint on Formal’s API. This is useful when the set of options changes over time (e.g., listing resources, users, or datastores). checkbox and radio fields do not support dynamic sources and must use static options. A dynamic source has the following configuration:
ParameterDescription
AppThe service name (e.g., core.v1.ResourceService)
Command NameThe method name without the List prefix (e.g., Resources for the ListResources method)
Machine User IDThe machine user used to authenticate options retrieval
InputThe JSON request body. Use ${{ query }} to interpolate the user’s search text into the request. You can also pass searchFields to control which fields are searched
TransformA CEL expression evaluated against the response body (available via the body variable) when the response is a 200. Should return a list of objects with value and text fields
The searchFields parameter in the input controls which fields the search term is matched against. For example, "searchFields": ["name", "technology", "hostname"] restricts matching to those three fields on the target resource.

Slack Integration

Forms are filled out via Slack using the “Fill out form” shortcut. A Slack integration needs to be linked to your Formal organization.

Slash Command

Type /formal form to open a modal where you can select and fill out a form.

Submission Flow

  1. User opens the form shortcut in Slack
  2. User selects a form from the searchable dropdown
  3. User fills out the fields and submits
  4. A confirmation message with the submitted values is sent to the user
  5. Any workflows with a matching form-submission trigger are executed

Using Forms in Workflows

Forms connect to workflows via the form-submission trigger type. See the workflows documentation for trigger details. Form data will not be saved in the Formal Control Plane unless there is at least one workflow with the corresponding form-submission trigger type.

Example: Access Request with Approval

trigger:
  type: form-submission
  name: access_request
  args:
    id: form_abc123

actions:
  - type: ask-in-chat
    name: ask_approval
    args:
      message: "${{ trigger.form_submission.submitter_email }} is requesting access to ${{ trigger.form_submission.submission.field_resource }}. Reason: ${{ trigger.form_submission.submission.field_reason }}. Approve?"
      recipient_channel: "security-approvals"
      integration: slack

Referencing Form Data in CEL

Form submission data is available under trigger.form_submission:
ExpressionDescription
trigger.form_submission.form_idThe ID of the submitted form
trigger.form_submission.form_nameThe name of the submitted form
trigger.form_submission.submitter_emailEmail of the user in Slack who submitted the form
trigger.form_submission.submission.field_<id>Value of a specific field by its ID

Terraform Example

resource "formal_form" "access_request" {
  name        = "access-request-form"
  description = "Form for requesting database access"

  field {
    id   = "field_reason"
    name = "Reason"
    type = "string"
  }

  field {
    id   = "field_resource"
    name = "Resource"
    type = "select"

    config {
      option {
        label = "Production"
        value = "production"
      }
      option {
        label = "Staging"
        value = "staging"
      }
    }
  }

  field {
    id   = "field_duration"
    name = "Duration (hours)"
    type = "number"
  }
}
For a dynamic options source (e.g., fetching resources from Formal):
resource "formal_form" "dynamic_access_request" {
  name        = "dynamic-access-request"
  description = "Access request with dynamic resource selection"

  field {
    id   = "field_resource"
    name = "Resource"
    type = "select"

    config {
      options_source {
        app             = "core.v1.ResourceService"
        machine_user_id = formal_user.machine.id
        transform       = "body.resources.map(r, {\"value\": r.id, \"text\": r.name})"

        command {
          name = "Resources"
        }

        input = {
          search       = "${{ query }}"
          limit        = "25"
          searchFields = jsonencode(["name", "technology", "hostname"])
        }
      }
    }
  }

  field {
    id   = "field_reason"
    name = "Reason"
    type = "string"
  }
}
See the formal_form resource documentation for the full schema reference.

Next Steps

Workflows

Learn about workflow triggers, actions, and chaining

Slack Integration

Configure your Slack integration for forms