2.1.2. Manual Triggers: workflow_dispatch Inputs
💡 First Principle: workflow_dispatch turns a workflow into a form — a button with typed, validated parameters — which is how you get deliberate, auditable human-in-the-loop operations like "deploy version X to staging" without giving anyone shell access to production.
The inputs are declared in the trigger itself, and their types are what make the form useful rather than a free-text footgun:
| Input type | Renders as | Notes |
|---|---|---|
string | Free text box | Default type if type: is omitted |
number | Free text box | Parsed as a number in the inputs context |
boolean | Checkbox | Arrives as a real boolean in the inputs context |
choice | Dropdown | Requires an options: list — the way to constrain environments |
environment | Dropdown of repo environments | Auto-populated from configured environments |
A dispatch trigger looks like this, and note the 25-input maximum GitHub enforces:
on:
workflow_dispatch:
inputs:
target:
description: 'Deployment target'
type: choice
options: [staging, production]
required: true
version:
description: 'Release tag to deploy'
type: string
required: true
dry_run:
description: 'Log actions without applying them'
type: boolean
default: true
Inside the workflow, read them with the inputs context: ${{ inputs.target }}. (The older github.event.inputs.* form still works but always yields strings — with inputs.dry_run a boolean input is genuinely boolean, which matters for if: conditions.) You can dispatch from the Actions UI, the REST API, or gh workflow run; either way the run records who triggered it, which is exactly the audit trail a manual-deploy scenario is asking about.
One structural requirement trips people up: like scheduled workflows, a workflow_dispatch workflow must exist on the default branch for the "Run workflow" button to appear at all — though once it does, you may select any branch to run against.
⚠️ Exam Trap: required: true does not mean "GitHub will reject an empty submission" in every path — when a default: is supplied, the field is prefilled, and API dispatches that omit an input receive the default. If a question hinges on a workflow behaving unexpectedly after an API-triggered run, check for a default silently supplying a value nobody chose.
Reflection Question: Why is a choice input with options: [staging, production] meaningfully safer than a string input the operator types the environment into — beyond just convenience?