A pull request template is a Markdown file whose contents pre-fill the description box when someone opens a pull request.
That is the whole mechanism. Its value is entirely in what you choose to put there, and the difference between a template that improves review and one everyone deletes is almost always length.
Where templates live
Section titled “Where templates live”A single default template goes in any of:
.github/pull_request_template.mdpull_request_template.mddocs/pull_request_template.mdMultiple templates go in a directory:
.github/PULL_REQUEST_TEMPLATE/├── feature.md├── bugfix.md└── release.mdMultiple templates are not offered in a chooser the way Issue templates are. They are selected by a query parameter on the pull request URL:
https://github.com/OWNER/REPO/compare/main...my-branch?template=bugfix.mdThat is a meaningful usability difference. Because nobody types that URL by hand, multiple templates only work in practice when something generates the link — a contributing guide, a bot, or a shell alias. For most repositories, one good default template beats three that nobody selects.
What belongs in a template
Section titled “What belongs in a template”The template exists to answer the questions a reviewer would otherwise have to ask. In practice that is three:
- What does this change? — because the diff shows how, not what for.
- Why? — the reasoning that the code cannot contain.
- How do I know it works? — how the author verified it, so the reviewer can too.
Everything else is optional and should justify its presence.
A template that does this and nothing more:
## What
<!-- One or two sentences. What does this change do? -->
## Why
<!-- What problem does it solve? Link the issue if there is one. -->
## How to verify
<!-- What did you run or check? What should a reviewer do to confirm it? -->
## Notes for reviewers
<!-- Anything worth flagging: trade-offs, areas you are unsure about, follow-ups. -->The last section earns its place more than it looks. “I am not sure about the error handling in
retry()” directs a reviewer’s attention where it is most valuable, and gives authors permission to
express uncertainty rather than presenting everything as finished.
Checklists, used sparingly
Section titled “Checklists, used sparingly”Checklists are where templates go wrong. The instinct is to encode every process requirement as a checkbox, and the result is a twenty-item list that authors tick without reading.
A checklist item is worth including only if all three are true:
- It is genuinely sometimes forgotten. Not “I wrote code” — something people actually miss.
- The author is the right person to confirm it. Not something CI can check.
- Failing to do it has a real cost.
Anything a machine can verify should be a required status check, not a checkbox. A checkbox saying “tests pass” is strictly worse than a check that runs the tests: it is unverified, and it trains people to tick without looking.
A defensible short list:
## Checklist
- [ ] I have tested this beyond the automated checks- [ ] Documentation is updated, or no documentation change is needed- [ ] Breaking changes are called out aboveThree items that require judgement. Nothing a linter could have caught.
Linking issues
Section titled “Linking issues”Templates are a good place to prompt for issue linkage, because it is easy to forget and valuable later:
Closes #Leaving the number blank is deliberate — a reviewer sees an unfilled field immediately, where a missing line is invisible. Closing keywords and their behaviour are covered in Issues.
Security prompts
Section titled “Security prompts”For repositories where it matters, one targeted question outperforms a generic security checklist:
## Security
<!-- Does this change touch authentication, authorisation, input validation, secrets handling, or CI workflow files? If so, say how. -->An open question makes an author think. A checkbox saying “this change is secure” produces a tick and no thought.
Templates and the CLI
Section titled “Templates and the CLI”gh pr create picks up the template automatically when opening an editor. In scripted use, the
template is bypassed entirely — which is worth knowing if your team relies on it:
gh pr create --fillgh pr create --body-file .github/pull_request_template.mdgh pr create --template bugfix.md--fill populates the title and body from your commits, which replaces the template rather than
merging with it. If your process depends on template sections being present, prefer --body-file or
open the editor.
Rollout
Section titled “Rollout”Introducing a template to an existing repository goes better with a light touch.
Start smaller than you think. Three sections. Add more only when a specific recurring gap justifies it.
Announce why, once. “Reviewers were spending time asking what changes were for” gives people a reason to fill it in.
Do not enforce it mechanically at first. A bot rejecting pull requests with unfilled sections on day one produces resentment and copy-pasted filler. See whether the template is used before deciding it needs enforcement.
Revisit after a month. Sections nobody fills in are either unclear or unnecessary. Delete them.
Anti-patterns
Section titled “Anti-patterns”The compliance checklist. Twenty checkboxes covering every process requirement. Ticked without reading, universally.
Duplicating CI. Checkboxes asserting things the pipeline already verifies.
Mandatory sections that rarely apply. “Performance impact” on a documentation change produces “N/A” and noise.
Asking for the diff in prose. “List the files changed” — GitHub already shows that.
A template that is a policy document. Link to the contributing guide; do not inline it.
Multiple templates with no way to choose them. Without generated links, only the default is used.
Multiple templates, made usable
Section titled “Multiple templates, made usable”Multiple templates are selected by query parameter, which nobody types. They become usable only when something generates the link.
Three ways to do that:
A link in CONTRIBUTING.md:
- [Report a bug fix](https://github.com/OWNER/REPO/compare/main...HEAD?template=bugfix.md)- [Propose a feature](https://github.com/OWNER/REPO/compare/main...HEAD?template=feature.md)- [Cut a release](https://github.com/OWNER/REPO/compare/main...HEAD?template=release.md)A shell alias, for templates you use often:
gh alias set --shell prbug 'gh pr create --template bugfix.md --title "$1"'gh alias set --shell prfeat 'gh pr create --template feature.md --title "$1"'A CLI flag directly:
gh pr create --template release.mdWithout one of these, the default template is the only one anyone uses — and three unused templates are worse than one used, because the process appears more considered than it is.
Query parameters beyond templates
Section titled “Query parameters beyond templates”The pull request creation URL accepts more than template, which is useful for tooling that opens a
pre-filled form:
https://github.com/OWNER/REPO/compare/main...my-branch?expand=1&title=Fix%20retry%20loop&body=Closes%20%2342&labels=bug&assignees=alice| Parameter | Effect |
|---|---|
expand=1 | Opens the form directly rather than the comparison view |
title | Pre-fills the title |
body | Pre-fills the description |
labels | Comma-separated labels |
assignees | Comma-separated assignees |
template | Selects a template |
All values need URL encoding. Note that body and template conflict — supplying both means the
body wins, which is occasionally what you want and usually a mistake in a generated link.
This is how a CI job, a bot or an internal tool can produce a “file a pull request for this” link with everything already filled in.
Evolving a template
Section titled “Evolving a template”A template is not finished when you write it. Two habits keep it honest.
Review it quarterly. Read the last twenty merged pull requests and check which sections were filled in meaningfully, which were left empty, and which contained “N/A”. A section that is always “N/A” is asking the wrong question and should be deleted.
Add sections in response to specific incidents. “We had an outage because nobody mentioned the migration” is a reason to add a section. “It seems like good practice” is how templates grow to twenty items.
Deleting is harder than adding and matters more. Every section costs every author on every pull request, and a template people delete wholesale is worse than none — it makes documented process visibly optional.
Common mistakes
Section titled “Common mistakes”Making it too long. The single biggest cause of templates being deleted.
Forgetting --fill bypasses it. Scripted pull requests silently lose the structure.
Checkboxes for machine-verifiable facts. Should be status checks.
No prompt for reviewer guidance. The most useful section, and the most often missing.
Never revisiting it. Templates accumulate sections and never lose them.
Exercise
Section titled “Exercise”- Add
.github/pull_request_template.mdwith the four-section template above. - Open a pull request in the web interface and confirm the description is pre-filled.
- Open one with
gh pr create --filland note the template was not applied. - Open one with
gh pr create --body-file .github/pull_request_template.mdand compare. - Add a second template under
.github/PULL_REQUEST_TEMPLATE/and select it with the?template=query parameter.
Step 3 is worth doing deliberately — it is the behaviour most likely to surprise a team that adopts both a template and CLI-driven workflows.
Templates and automation
Section titled “Templates and automation”Because the description is structured text, automation can read it — which is what makes a template more than a courtesy.
# Does this pull request link an issue?gh pr view 128 --json body --jq '.body | test("(Closes|Fixes|Resolves) #[0-9]+"; "i")'
# Which pull requests merged this week did not?gh pr list --state merged --limit 100 --json number,title,body,mergedAt \ --jq --arg since "$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)" \ '.[] | select(.mergedAt > $since) | select(.body // "" | test("(Closes|Fixes|Resolves) #[0-9]+"; "i") | not) | [.number, .title] | @tsv'That second query is a gentle way to encourage linking: a weekly report rather than a bot rejecting pull requests. It surfaces the gap without adding friction to every change, and teams generally respond to a visible list better than to a blocked merge.
The same idea covers other conventions — an unfilled section, a missing breaking-change note, an empty verification step. Report first; enforce only if reporting does not work.
When not to use a template
Section titled “When not to use a template”Very small teams. Three people who talk daily do not need a form to communicate. The overhead is real and the benefit is mostly for readers who lack context.
Repositories where every change is the same shape. A repository containing only translations, or only configuration, has nothing variable enough to prompt for.
When it would be the only documented process. A template is a poor place to put your contribution
guidelines. Link to CONTRIBUTING.md instead — a template people must scroll past is a template they
delete.
The test: does filling this in tell a reviewer something they could not get from the diff? If not, it is ceremony.
What you learned
Section titled “What you learned”- A template pre-fills the description; its value is entirely in what you choose to ask.
- What, why and how to verify are the three questions worth asking; most else is optional.
- Multiple templates are selected by query parameter, so they only work with generated links.
- Checklist items should require judgement — anything machine-verifiable belongs in a status check.
- HTML comments give the author guidance without polluting the rendered description.
--fillbypasses the template entirely.- Length is the main determinant of whether a template is used or deleted.
A minimal template that works
Section titled “A minimal template that works”If nothing else here fits, this is a reasonable default for almost any repository:
## What
<!-- One or two sentences. -->
## Why
<!-- The problem this solves. Link the issue. -->
## How to verify
<!-- What you ran, and what a reviewer should check. -->Three sections, no checklist, no ceremony. It answers the questions a reviewer would otherwise ask and takes under a minute to fill in — which is the only reason people actually fill it in.
Add to it when a specific recurring gap justifies it, and delete anything that has been answered “N/A” five times running.