skip to content

In GitHub Actions, what does adding workflow_dispatch to on: enable?

level: juniorimportance: should knowfreq 58%

answer

  1. The manual trigger
  2. A button, an API call, and a CLI command
  3. It must be visible from one particular branch
  4. Typed values the operator fills in
  5. Default branch controls whether it appears

basics

~20 s

It makes the workflow runnable on demand — from the Actions tab, the REST API, or gh workflow run — against any branch or tag you choose, with optional typed inputs you declare in the workflow file.

solid answer

~40 s

`workflow_dispatch` is the manual trigger. Declaring it under `on:` adds a **Run workflow** button in the Actions tab, and lets you start the same workflow with `gh workflow run` or a `POST` to the workflow's `dispatches` REST endpoint. You pick which ref to run — any branch or tag — but the button only appears once a copy of the workflow file containing `workflow_dispatch` exists on the repository's **default branch**, which is the single most common reason people think it is broken. You can declare `inputs` with a `description`, `required`, `default` and a `type` of `string`, `choice`, `boolean`, `number` or `environment`; inside the workflow you read them as `${{ inputs.name }}`. It composes with other triggers, so a workflow can run on push and still be re-runnable by hand.

code

yaml · 24 lines
yaml
name: Deploy
run-name: Deploy ${{ inputs.environment }} by @${{ github.actor }}

on:
  workflow_dispatch:
    inputs:
      environment:
        description: Target environment
        type: choice
        options: [staging, production]
        required: true
        default: staging
      dry_run:
        description: Plan only, skip the apply step
        type: boolean
        default: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo "target=${{ inputs.environment }}"
      - if: inputs.dry_run == false
        run: ./scripts/apply.sh "${{ inputs.environment }}"

go deeper

for a junior

Recall that workflow_dispatch is the manual trigger, that it can carry inputs, and that the Run workflow button comes from the default branch. Be able to write the three-line on: block from memory.

for a middle

Explain the mechanics: the default-branch visibility rule versus the selected-ref execution rule, the input type values, and why inputs.x and github.event.inputs.x differ for booleans and numbers.

for a senior

Show operational judgment — dispatch as the escape hatch for scheduled jobs, run-name for auditability, and who holds the actions: write access that lets someone fire a production deploy by hand.

for a principal

Own the tradeoff between one heavily parameterised dispatchable workflow (10-input cap, sprawling conditionals, weak audit trail) and several narrow ones, plus how manual dispatch fits an approvals and change-management story.

## What workflow_dispatch is Every GitHub Actions workflow declares the events that start it under the `on:` key. Most events are automatic — someone pushes, opens a pull request, or a schedule fires. `workflow_dispatch` is the exception: it is the *manual* event, meaning "a human or a script asked for this run explicitly". It is what you use for deploys, one-off maintenance jobs, backfills, release cuts, and anything you do not want tied to a commit. ``` on: workflow_dispatch: ``` That is the whole minimum declaration. The colon with nothing after it is valid YAML — the event takes no required configuration. ## Where the Run workflow button comes from GitHub scans the workflow files on the repository's **default branch** to decide which workflows can be dispatched. If you add `workflow_dispatch` on a feature branch and push, no button appears anywhere, because GitHub has never seen a dispatchable version of that file on the default branch. Merge it to the default branch once, and from then on the UI offers a branch/tag picker so you can dispatch it against *any* ref — including that feature branch, whose version of the file is the one that actually executes. This split trips up almost everyone the first time: the *visibility* of the trigger comes from the default branch, the *content* of the run comes from the ref you select. ## Declaring inputs Inputs let the person triggering the run parameterise it: ``` on: workflow_dispatch: inputs: environment: description: Target environment type: choice options: [staging, production] required: true default: staging dry_run: type: boolean default: true ``` Supported `type` values are `string`, `choice` (which requires `options`), `boolean`, `number` and `environment` (which renders a picker of the repository's configured environments). `description` is what the UI shows next to the field, `required` decides whether it can be left blank, and `default` prefills it. GitHub caps a `workflow_dispatch` event at 10 top-level inputs, which is a real constraint on "one workflow to run everything" designs. ## Reading inputs Inside the workflow, use the `inputs` context: `${{ inputs.environment }}`, `${{ inputs.dry_run }}`. The older spelling `${{ github.event.inputs.environment }}` still resolves, but it delivers **everything as a string** — a `boolean` input read that way is the string `'true'`, so `if: github.event.inputs.dry_run` is truthy even when the box was unchecked. The `inputs` context preserves the declared type, so `if: inputs.dry_run` behaves the way you expect. Prefer `inputs`. ## Triggering without the UI Two non-UI paths exist. The CLI: ``` gh workflow run deploy.yml --ref main -f environment=production -f dry_run=false ``` and the REST API, `POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches` with a JSON body carrying `ref` and `inputs`. Both require a token with `actions: write` (or the equivalent repository write access). Note that a dispatch started by another workflow using the built-in `GITHUB_TOKEN` will not, by design, trigger further workflows — GitHub blocks that recursion, and you need a separate token or a GitHub App to chain dispatches. ## Naming the run A dispatched run is far more useful when the run list says what it did. `run-name` is a top-level key that accepts expressions and can read the `github` and `inputs` contexts: ``` run-name: Deploy ${{ inputs.environment }} by @${{ github.actor }} ``` Without it, ten manual deploys all display the workflow name and are indistinguishable in the history. ## Combining with other triggers `on:` takes a map of events, so `workflow_dispatch` sits happily alongside others: ``` on: schedule: - cron: '0 3 * * *' workflow_dispatch: ``` This is a standard pattern for scheduled jobs — the cron drives the routine run, and the manual trigger lets you reproduce a failure immediately instead of waiting for tomorrow. When several declared events could fire, whichever one occurs starts an independent run; they do not merge. ## Common failure modes No button: the file with `workflow_dispatch` is not on the default branch, or the workflow is disabled in the Actions tab. Inputs empty: the dispatch was aimed at a ref whose copy of the file declares different inputs — GitHub validates inputs against the *selected ref's* file. Boolean logic inverted: you read `github.event.inputs.*` instead of `inputs.*`. And a workflow that has both `push` and `workflow_dispatch` will happily run twice if you dispatch a ref you just pushed — that is two runs, not one.

  • You added workflow_dispatch on a feature branch and the Run workflow button never appeared. Why?
    GitHub decides which workflows are dispatchable by reading the files on the repository's default branch. Until a copy declaring `workflow_dispatch` is merged there, the button does not exist. Once it is merged, you can dispatch the workflow against any ref, including that feature branch — and the selected ref's version of the file is what runs.
  • Why does an if: on a boolean workflow_dispatch input sometimes behave backwards?
    Because `github.event.inputs.<name>` delivers every value as a string, and the non-empty string `'false'` is truthy in a GitHub Actions expression. Read the value through the `inputs` context instead — `if: inputs.dry_run` — which preserves the declared `boolean` type. This is the standard fix for a dry-run flag that never actually skips anything.
  • How do you make manually dispatched runs distinguishable in the Actions run list?
    Set the top-level `run-name` key, which accepts expressions over the `github` and `inputs` contexts — for example `run-name: Deploy ${{ inputs.environment }} by @${{ github.actor }}`. Without it every dispatch shows the same workflow name, and you cannot tell a staging smoke test from a production release without opening each run.

saying these in an interview costs you the question

  • Says the button appears as soon as any branch has the trigger
  • Claims workflow_dispatch can only run on the default branch
  • Thinks inputs are free-form only, with no types
  • Reads booleans via github.event.inputs and trusts the type
  • Believes a dispatch can only be started from the web UI

context