Skip to content

Pull Request Templates: Structure Without Bureaucracy

Lesson 10 of 12Beginner → Intermediate8 min readGitHub Engineering · Pull RequestsVerified: GitHub.com, August 2026

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.

A single default template goes in any of:

.github/pull_request_template.md
pull_request_template.md
docs/pull_request_template.md

Multiple templates go in a directory:

.github/PULL_REQUEST_TEMPLATE/
├── feature.md
├── bugfix.md
└── release.md

Multiple 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.md

That 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.

The template exists to answer the questions a reviewer would otherwise have to ask. In practice that is three:

  1. What does this change? — because the diff shows how, not what for.
  2. Why? — the reasoning that the code cannot contain.
  3. 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 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 above

Three items that require judgement. Nothing a linter could have caught.

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.

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.

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:

Terminal window
gh pr create --fill
gh pr create --body-file .github/pull_request_template.md
gh 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.

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.

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 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:

Terminal window
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:

Terminal window
gh pr create --template release.md

Without 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.

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
ParameterEffect
expand=1Opens the form directly rather than the comparison view
titlePre-fills the title
bodyPre-fills the description
labelsComma-separated labels
assigneesComma-separated assignees
templateSelects 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.

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.

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.

  1. Add .github/pull_request_template.md with the four-section template above.
  2. Open a pull request in the web interface and confirm the description is pre-filled.
  3. Open one with gh pr create --fill and note the template was not applied.
  4. Open one with gh pr create --body-file .github/pull_request_template.md and compare.
  5. 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.

Because the description is structured text, automation can read it — which is what makes a template more than a courtesy.

Terminal window
# 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.

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.

  • 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.
  • --fill bypasses the template entirely.
  • Length is the main determinant of whether a template is used or deleted.

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.

Professional ToolkitThe pull request and issue templates are in the Professional Toolkit.