A workflow is useless if it does not fire at the right moment. The on key
defines the events that start a workflow: a push, a pull request, a
scheduled time, a manual click. Setting it well avoids useless runs, and missing
ones.
What you will learn
- Trigger a workflow on
pushandpull_request - Filter runs by branch, tag and file path
- Schedule a workflow with
scheduleand cron syntax - Start a workflow by hand with
workflow_dispatchand itsinputs - Chain workflows with
workflow_run - Combine several triggers in one file
The on key: what starts a workflow
Every workflow file begins with an on key listing the triggering
events. GitHub watches the repository continuously: as soon as a listed event
happens, it creates a run of the workflow. Without on, the workflow never
fires.
A workflow can listen to a single event or several at once, each with its own filters.
push and pull_request: the everyday triggers
push and pull_request cover the vast majority of CI needs: validating
the code on every push and on every proposed merge.
Reacting to pushes
The push event fires the workflow when commits land on the repository. The
branches filter narrows it to the branches you care about:
on: push: branches: - mainWithout a branches filter, the workflow runs on every branch, including
short-lived working branches, which burns minutes for nothing.
Reacting to pull requests
The pull_request event fires the workflow on pull requests targeting your
repository. The types filter states which activities count:
on: pull_request: types: [opened, synchronize, reopened]By default, pull_request already reacts to opened, synchronize and
reopened; there is no need to repeat them unless you want to add or
remove one. The most useful types beyond the default are labeled, closed
and ready_for_review.
Filtering: branches, tags and paths
Filters avoid useless runs. There are three axes: the branch, the tag, and the file path that changed.
on: push: branches: - main - 'release/**' paths: - 'src/**' - 'package.json'The paths filter is valuable: it only starts the workflow when a
relevant file has changed. There is no point rerunning the whole CI for a
typo in a Markdown file. The inverse form, paths-ignore, excludes instead
of including:
on: push: paths-ignore: - 'docs/**' - '**.md'To react to the publication of a version, filter on tags:
on: push: tags: - 'v*'schedule: running a workflow periodically
The schedule event fires a workflow at regular intervals, through a
cron expression. It is the tool for recurring tasks: a nightly security
audit, a cleanup, a weekly report.
on: schedule: - cron: '0 6 * * 1' # Every Monday at 06:00 UTCThe five cron fields are minute, hour, day of month, month, and day of week. Two traps come up often:
- the time is always in UTC, not your local timezone;
- a scheduled workflow only runs from the repository's default branch.
GitHub does not run schedule events to the second: under load, the trigger can
drift by several minutes.
workflow_dispatch: the manual trigger
The workflow_dispatch event adds a "Run workflow" button to the
Actions tab. It is the trigger for deliberate operations: a deployment, a
restore, an on-demand generation.
It accepts inputs, parameters the operator fills in at launch:
name: Manual deployment
on: workflow_dispatch: inputs: environment: description: 'Target environment' required: true type: choice options: - staging - production version: description: 'Version to deploy (tag)' required: true type: string
permissions: {}
jobs: deploy: runs-on: ubuntu-24.04 permissions: contents: read steps: - name: Check out the code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Deploy env: ENVIRONMENT: ${{ inputs.environment }} VERSION: ${{ inputs.version }} run: ./deploy.sh "$ENVIRONMENT" "$VERSION"The type: choice enforces a closed list of values, so the operator cannot
get it wrong. Inputs are read through the inputs context, passed here
through an env: block: interpolating ${{ }} directly into a run: would
open a command injection.
workflow_run: chaining workflows
The workflow_run event fires a workflow after another one finishes. It
separates responsibilities, for example a build workflow, then a deployment
workflow that consumes only the artifact produced.
on: workflow_run: workflows: ["CI"] types: [completed] branches: [main]The workflows field references the other workflow by its name, not by
its file name. The triggered workflow always runs from the default branch,
and reaches the first one's result through the github.event.workflow_run
context.
That decoupling is also a security pattern: it lets you handle untrusted code in a first workflow without secrets, then deploy in a second one. The detail is covered in Securing pull_request_target.
pull_request_target: the trigger to handle with care
The pull_request_target event looks like pull_request, but it runs in
the context of the target repository, with access to the secrets, even
for a pull request coming from a fork.
While you are getting started, stay on pull_request: it is safe by
default, since it does not expose secrets to forks.
Combining several triggers
One workflow can listen to several events. That is common for a CI pipeline that has to run on pushes, on pull requests, and also on demand:
on: push: branches: [main] pull_request: schedule: - cron: '0 6 * * 1' workflow_dispatch:Inside the workflow, the github.event_name context says which event
triggered the run. That is useful to adapt the behaviour, a subject covered in
Conditions and if.
Key points
- The
onkey defines the events that start a workflow; without it, nothing fires. pushandpull_requestcover everyday CI; filterpushbybranchesto avoid useless runs.- The
paths,branchesandtagsfilters target the runs; never mix a form with its inverse (paths/paths-ignore). scheduleplans in UTC cron, and only from the default branch.workflow_dispatchadds a manual trigger with typedinputs(choice,string,boolean).workflow_runchains workflows;pull_request_targetis powerful but dangerous, to be used knowingly.
Next steps
- Matrix strategy: multiplying one trigger across several systems and versions.
- Reusable workflows: the
workflow_calltrigger, to call one pipeline from another. - Securing pull_request_target: the detail of the most dangerous trigger in the catalogue.