Section 1: Workflow fundamentals
Triggers and filters
Choosing the right triggers keeps workflows from running when they do not need to, and makes sure they run when they do.
Common events
on:
pull_request:
branches: [main]
paths:
- "src/**"
- "package*.json"
push:
branches: [main]
tags: ["v*"]
workflow_dispatch:
inputs:
environment:
type: choice
options: [staging, production]
schedule:
- cron: "30 4 * * 1-5"
pull_requestruns for pull requests targetingmain, here only when source or dependency files change.pushtomainruns after merges; pushing a tag starting withvcan trigger a release.workflow_dispatchadds a Run workflow button with typed inputs.scheduleuses cron syntax in UTC. Scheduled runs can be delayed during busy periods, so do not rely on exact timing.
Path filters and required checks
Path filters are great for monorepos, but be careful with required status checks. If a required workflow is skipped because no matching files changed, the pull request can wait forever for a check that never reports. Common solutions are a small always running job that reports success, or filtering inside the workflow with a changes detection step instead of at the trigger.
Pull requests from forks
For public repositories, pull_request workflows from forks run without access to secrets and with a read only token. This is a deliberate safety measure. The pull_request_target event runs in the context of the base repository with secrets. Combining pull_request_target with checking out and running the fork's code is one of the most common ways CI systems get compromised. Avoid it unless you fully understand the risk.
Conditions on jobs and steps
jobs:
deploy:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
if expressions use the ${{ }} expression syntax, which can be omitted in if. You can reference contexts like github, env, matrix, needs and steps.
Concurrency
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
When you push three commits quickly to the same branch, only the latest run continues. This saves minutes and gets you feedback faster. For deployments, set cancel-in-progress: false so a running deploy is never interrupted halfway.