How Do I Run a Workflow Manually?
Add a workflow_dispatch trigger with typed inputs, then start the run from the Actions tab or with one API call. Branch rules and a working example below.
Answer
Add a workflow_dispatch trigger to the workflow file, declare the inputs it needs with types, and the run starts either from the Run workflow button on the Actions tab or from a single API call. The trigger is documented on the events that trigger workflows reference (checked on 2026-08-13), and the one rule that catches most people is that the definition has to exist on the branch you run it from.
name: release
run-name: release ${{ inputs.version }} to ${{ inputs.environment }}
on:
workflow_dispatch:
inputs:
environment:
description: Target environment
type: choice
required: true
default: staging
options:
- staging
- production
version:
description: Tag to release, for example v2.4.1
type: string
required: true
runner:
description: Runner label for the build job
type: choice
required: true
default: warp-ubuntu-latest-x64-8x
options:
- warp-ubuntu-latest-x64-4x
- warp-ubuntu-latest-x64-8x
- warp-ubuntu-latest-x64-16x
dry_run:
description: Build and test without publishing
type: boolean
default: true
jobs:
build:
runs-on: ${{ inputs.runner }}
steps:
- uses: actions/checkout@v5
with:
ref: ${{ inputs.version }}
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- run: npm test
publish:
needs: build
if: ${{ !inputs.dry_run }}
runs-on: warp-ubuntu-latest-x64-4x
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v5
- run: ./scripts/publish.sh ${{ inputs.version }}Merge that to the default branch and the Actions tab shows a Run workflow button with a branch picker and one form field per input.
Detail
The five input types and how the two contexts differ
workflow_dispatch accepts up to 10 top level inputs, and each one declares a type. Every row below comes from the workflow syntax reference and the contexts reference, checked on 2026-08-13.
| Type | Form control | inputs.<name> returns | github.event.inputs.<name> returns |
|---|---|---|---|
string | Text box | String | String |
choice | Dropdown built from options | String | String |
boolean | Checkbox | Boolean | "true" or "false" as a string |
number | Text box | Number | String |
environment | Dropdown of repository environments | String | String |
That last column is the reason the example uses if: ${{ !inputs.dry_run }} rather than the github.event.inputs form. Under github.event.inputs, an unchecked box arrives as the string "false", which is truthy, so the publish job would run on every dispatch. Read booleans through the inputs context and the condition behaves the way the checkbox looks.
Two more constraints worth knowing before you design the form. A choice input needs an options list, and its default has to be one of those options or the run fails validation. Inputs marked required: true block the form until they are filled, while the API rejects the call outright when one is missing.
Starting the run from the API
The REST endpoint takes the workflow file name in the path and the branch or tag in the body (workflows REST API, checked on 2026-08-13):
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/OWNER/REPO/actions/workflows/release.yml/dispatches \
-d '{"ref":"main","inputs":{"environment":"production","version":"v2.4.1","runner":"warp-ubuntu-latest-x64-16x","dry_run":"false"}}'Four practical notes. The token needs Actions write access. Input values travel as strings and GitHub coerces them to the declared type. A successful call returns 204 No Content and no run id, so the run has to be located afterwards. And a dispatch sent with the built in GITHUB_TOKEN does create a run, because workflow_dispatch and repository_dispatch are the two exceptions to the recursion guard described in the automatic token authentication documentation.
The same call from the GitHub CLI, which is easier to keep in a release script:
gh workflow run release.yml --ref main \
-f environment=production \
-f version=v2.4.1 \
-f runner=warp-ubuntu-latest-x64-16x \
-f dry_run=false
gh run list --workflow release.yml --event workflow_dispatch \
--branch main --limit 1 --json databaseId,displayTitle,statusThe run-name line in the workflow is what makes that second command useful. Each dispatch gets a title containing its inputs, so the list distinguishes a staging dry run from a production release without opening either.
The branch rule
The trigger has to exist in the workflow file on the ref you are dispatching, and the Actions tab reads its workflow list from the default branch (events that trigger workflows reference, checked on 2026-08-13). Three consequences follow.
- A
workflow_dispatchtrigger that lives only on a feature branch produces no Run workflow button anywhere. Merge it to the default branch first. - Once the button exists, the branch dropdown covers any branch or tag, and the run executes that ref's copy of the file. Editing steps on a branch changes what a dispatch of that branch does.
- Dispatching a ref whose copy of the file has no
workflow_dispatchtrigger returns HTTP 422 with the message that the workflow does not have aworkflow_dispatchtrigger (workflows REST API). The fix is a merge, not a retry.
The workflow_dispatch glossary entry covers the event itself, and running jobs on a schedule covers the schedule trigger, which carries the same default branch rule.
What a manual run costs
Manual runs are usually the long ones: releases, migrations, load tests, full matrix reruns. Making the runner label an input prices them one dispatch at a time, since the same workflow can take a small machine for a routine release and a large one when a fix is waiting. Rates below come from the cloud runners catalog and the pricing page, and the GitHub rate from the GitHub Actions billing reference, all checked on 2026-08-13.
| Label chosen at dispatch time | Shape | Per minute | 20 minute release run | 12 runs a month |
|---|---|---|---|---|
| warp-ubuntu-latest-x64-4x | 4 vCPU, 16 GB | $0.008 | $0.16 | $1.92 |
| warp-ubuntu-latest-x64-8x | 8 vCPU, 32 GB | $0.016 | $0.32 | $3.84 |
| warp-ubuntu-latest-x64-16x | 16 vCPU, 64 GB | $0.032 | $0.64 | $7.68 |
| the 8-core Linux larger runner, GitHub-hosted | 8 vCPU, 32 GB | $0.022 | $0.44 | $5.28 |
At the 8 vCPU shape, warp-ubuntu-latest-x64-8x costs $0.016 per minute against $0.022 per minute for the 8-core Linux larger runner: 27 percent lower list price, GitHub list price checked on 2026-08-13.
A manual release that builds a Mac artifact and a Linux artifact can put both labels in the same dispatch form. Setup is the account, the GitHub bot from the dashboard, and the label (quick start documentation).
Related Questions
Why is the Run workflow button missing from the Actions tab?
The workflow file carrying the trigger is not on the default branch. GitHub builds the Actions tab list from the default branch (events that trigger workflows reference), so a workflow_dispatch block added on a feature branch stays invisible until it merges. After the merge, the branch dropdown on the button lets you dispatch the feature branch and run that branch's version of the file.
Can I start a run from a script or another workflow?
Yes. POST to /repos/OWNER/REPO/actions/workflows/release.yml/dispatches with a ref and an inputs object, using a token with Actions write access (workflows REST API). The WarpBuild automation API exposes runner-image and custom-runner operations, so a script can prepare the runner and then dispatch the workflow that exercises it (automation documentation).
Can a manual run choose its own runner size?
Yes. Declare a choice input listing the labels you allow and set runs-on to that input, as in the example above. The dropdown becomes the size control, which keeps a routine release on warp-ubuntu-latest-x64-4x at $0.008 per minute and lets a release under time pressure take warp-ubuntu-latest-x64-16x at $0.032 per minute (pricing page, checked on 2026-08-13). More ways to shorten those runs are in speeding up GitHub Actions.
How do I find the run the API just started?
The dispatch response is 204 No Content with no body, so nothing comes back to poll on directly. Put the inputs into run-name, then list runs filtered by event=workflow_dispatch and the branch you dispatched and take the newest databaseId (workflow runs REST API). Scheduled equivalents of the same job are covered in nightly builds in GitHub Actions.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.