Triggers & Events
The on: key decides when your workflow runs — and choosing well is the
difference between CI that guards every change, and a noise machine burning minutes on runs
nobody asked for. Four triggers cover nearly everything.
💡 The Bottom Line
push guards branches, pull_request guards merges,
schedule runs on a cron timetable, and workflow_dispatch adds a
manual "Run workflow" button. Branch and path filters keep each one scoped to changes that
actually matter.
The big four
on:
push:
branches: [main] # commits landing on main
pull_request:
branches: [main] # PRs *targeting* main
schedule:
- cron: "0 6 * * 1" # Mondays 06:00 UTC
workflow_dispatch: # manual button (+ optional inputs)
push— a commit reached a branch. The classic "keep main green" trigger.pull_request— a PR opened or updated. Crucially, it builds the merge preview — your changes as they would look merged into the target — which is exactly what a merge gate should test. With branch protection, this check is what physically blocks a red PR from merging.schedule— cron syntax, UTC. Nightly full test suites, link checkers, dependency audits. (Cron expressions have their own five-field language — the cron track on this site covers it, and the tool linked on this topic's roadmap decodes them.)workflow_dispatch— a human presses a button in the Actions tab, optionally filling in inputs (e.g. which environment to deploy). This is how "continuous delivery with a human on the button" from the CI/CD track literally looks.
Filters: run only when it matters
on:
push:
branches: [main, "release/**"]
paths:
- "src/**"
- "package.json"
branches limits which branch's pushes count (glob patterns allowed);
paths skips the run entirely when only unrelated files changed — a
docs-only commit doesn't need the full test suite. In a monorepo, paths filters
per-project workflows down to the project that actually changed; combined with the pipeline
speed concerns from the CI/CD track, these two filters are your minute budget's best
friends.
🙋 There Are No Dumb Questions
Q: Push and pull_request both fire on my PR branch — am I paying for everything twice?
A: Quite possibly, and it's the most common accidental double-spend. A push to a PR branch
fires push (if unfiltered) and updates the PR (firing
pull_request). Standard fix: filter push to
branches: [main] only, and let pull_request own everything
pre-merge — each commit then triggers exactly one run, in the right context.
⚠️ Watch Out
Two schedule traps: times are UTC (your 6 a.m. cron may
fire at your 11:30 a.m. — or during your maintenance window), and scheduled workflows run
on the default branch's copy of the file — your cron edits on a feature
branch do nothing until merged. Also, GitHub disables schedules on repos with no activity
for 60 days — a surprise for that quarterly job.
🔧 Try It Yourself
Add a manual trigger with an input to your scratch workflow:
on:
workflow_dispatch:
inputs:
who:
description: "Greet whom?"
default: "world"
jobs:
greet:
runs-on: ubuntu-latest
steps:
- run: echo "Hello, ${{ github.event.inputs.who }}!"
Push to the default branch, open Actions → your workflow → Run workflow,
type a name, run it. That ${{ ... }} syntax is the expression language — the
same one that reads secrets in the next lesson.
✅ Quick Check
What does the pull_request trigger test that plain push doesn't?
Next up: your workflow needs credentials to do anything interesting — secrets and variables, done safely.