Last month I opened a pull request from a teammate that had a single line in the description: “fixed stuff.” That was it. No context, no link to the ticket, no explanation of what changed or why. I had to read every file diff, and message them on Teams just to understand what I was reviewing.
The week before that, someone filed a bug report with “it’s broken” as the entire issue body. No environment info, no reproduction steps, no error messages.
These aren’t bad engineers. They’re busy engineers who didn’t have a structure to follow. The fix is dead simple: templates.
Why Templates Matter
Templates solve the “blank text box” problem. When someone opens a new PR or issue and sees an empty description field, the path of least resistance is to type something quick and move on. But when they see a structured form with specific prompts, they fill it in. Every time.
For infrastructure and DevOps teams, this is especially important. A Terraform PR without a plan output summary is a review bottleneck. A bug report without the environment and provider versions is a guessing game. Templates turn tribal knowledge into repeatable process.
What you get from good templates:
- Consistent, reviewable PRs that include the context reviewers need
- Bug reports with reproduction steps from day one
- Feature requests that capture the “why” alongside the “what”
- Faster onboarding — new engineers see exactly what’s expected
The .github Directory
Everything lives in the .github/ directory at the root of your repository. Here’s what a well-organized setup looks like:
.github/
├── PULL_REQUEST_TEMPLATE.md # Default PR template
├── PULL_REQUEST_TEMPLATE/ # Multiple PR templates (optional)
│ ├── infrastructure.md
│ └── hotfix.md
├── ISSUE_TEMPLATE/
│ ├── bug-report.yml # YAML form (structured)
│ ├── feature-request.yml # YAML form (structured)
│ ├── documentation.md # Markdown template
│ └── config.yml # Template chooser config
└── workflows/
└── ... # GitHub Actions (separate topic)
This directory must exist on your default branch (usually main). GitHub reads templates from there when someone opens a new PR or issue.
Pull Request Templates
The Default Template
Create a file at .github/PULL_REQUEST_TEMPLATE.md and its content automatically populates the description field for every new PR. No configuration needed.
Here’s a solid general-purpose template:
## Summary
<!-- What does this PR do? Why is it needed? -->
## Related Issues
<!-- Link to relevant issues: Fixes #123, Relates to #456 -->
## Changes
- [ ] Change 1
- [ ] Change 2
## Testing
<!-- How did you verify this works? -->
- [ ] Unit tests pass
- [ ] Manual testing completed
- [ ] CI pipeline passes
## Checklist
- [ ] Code follows project conventions
- [ ] Documentation updated (if applicable)
- [ ] No secrets or credentials committed
An Infrastructure-Focused PR Template
For DevOps and infrastructure repos, you need more. Here’s what I use for Terraform projects:
## Summary
<!-- Describe the infrastructure change and business reason -->
## Terraform Plan Output
<details>
<summary>Click to expand plan output</summary>
Paste terraform plan output here
</details>
## Change Type
- [ ] New resource(s)
- [ ] Modified resource(s)
- [ ] Destroyed resource(s)
- [ ] Module update
- [ ] Provider update
## Blast Radius
<!-- What systems/environments are affected? -->
- **Environment:** <!-- dev / staging / production -->
- **Resources affected:** <!-- count from plan -->
- **Downtime expected:** <!-- yes/no, duration -->
## Security Review
- [ ] No IAM policy changes (or reviewed and justified below)
- [ ] No security group / firewall rule changes (or reviewed below)
- [ ] No public endpoints exposed
- [ ] Secrets managed through vault/key store (not hardcoded)
- [ ] `checkov` / `tfsec` scan clean
## Rollback Plan
<!-- How do we undo this if it goes wrong? -->
## Checklist
- [ ] `terraform fmt` applied
- [ ] `terraform validate` passes
- [ ] Plan reviewed -- no unexpected destroys
- [ ] State file not modified manually
That <details> block is a lifesaver. Terraform plans can be hundreds of lines — collapsing them keeps the PR readable while keeping the full output available.
Multiple PR Templates
GitHub supports multiple templates through the .github/PULL_REQUEST_TEMPLATE/ directory. Place your templates there and reference them with a URL query parameter:
https://github.com/OWNER/REPO/compare/main...feature-branch?template=infrastructure.md
The ?template=infrastructure.md parameter tells GitHub which template to load. This is useful for monorepos where application code and infrastructure live side by side and need different review checklists.
One important note: if you use the PULL_REQUEST_TEMPLATE/ subdirectory, GitHub won’t auto-populate any template by default. You lose the automatic behavior of the single-file approach. Most teams are better off with one good default template unless they have a genuine need for multiple.
Issue Templates: Markdown vs YAML Forms
GitHub supports two formats for issue templates, and the difference matters.
| Feature | Markdown (.md) | YAML Forms (.yml) |
|---|---|---|
| Pre-fills issue body | Yes | No — renders as a web form |
| Required fields | No enforcement | Yes, with validation |
| Dropdowns / checkboxes | Manual text only | Native form elements |
| Auto-assign labels | Via frontmatter | Via frontmatter |
| User experience | Text editor | Structured form |
| Supported on | GitHub, GitHub Enterprise | GitHub, GitHub Enterprise |
My recommendation: use YAML forms for bug reports and feature requests (where you need structured data), and markdown templates for things like documentation requests or discussion-style issues.
YAML Issue Forms
YAML forms turn your issue template into a proper web form with dropdowns, text fields, checkboxes, and required field validation. No more “it’s broken” issues.
Bug Report Form
.github/ISSUE_TEMPLATE/bug-report.yml:
name: Bug Report
description: Report a bug or unexpected behavior
title: "[Bug]: "
labels: ["bug", "triage"]
assignees:
- rjbennett5
body:
- type: markdown
attributes:
value: |
Thanks for reporting a bug. Please fill out the sections below
so we can reproduce and fix the issue quickly.
- type: input
id: version
attributes:
label: Version
description: What version are you running?
placeholder: "e.g., v1.2.3 or commit SHA"
validations:
required: true
- type: dropdown
id: environment
attributes:
label: Environment
description: Where did this happen?
options:
- Development
- Staging
- Production
validations:
required: true
- type: textarea
id: description
attributes:
label: What happened?
description: Describe the bug clearly.
placeholder: "When I run terraform apply, the module fails with..."
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: What did you expect to happen instead?
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: Walk us through how to trigger this bug.
value: |
1.
2.
3.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant logs or error output
description: Paste any relevant log output here.
render: shell
- type: checkboxes
id: checklist
attributes:
label: Before submitting
options:
- label: I searched existing issues and this is not a duplicate
required: true
- label: I am using the latest version
When a user opens a new issue and selects “Bug Report,” they see a clean web form instead of a blank text area. The render: shell attribute on the logs field automatically wraps the input in a code block — no more forgetting to use triple backticks.
Feature Request Form
.github/ISSUE_TEMPLATE/feature-request.yml:
name: Feature Request
description: Suggest a new feature or improvement
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: Problem statement
description: What problem does this feature solve?
placeholder: "I frequently need to... but currently..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed solution
description: How would you like this to work?
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: What workarounds or alternatives have you tried?
- type: dropdown
id: priority
attributes:
label: Priority
options:
- Nice to have
- Important
- Critical -- blocking work
validations:
required: true
The Template Chooser and config.yml
When you have multiple issue templates, GitHub shows a “template chooser” page instead of dumping users straight into a blank issue. You can customize this with .github/ISSUE_TEMPLATE/config.yml:
blank_issues_enabled: false
contact_links:
- name: Security Vulnerability
url: https://example.com/security
about: Please report security issues privately, not as a public issue.
- name: General Questions
url: https://github.com/OWNER/REPO/discussions
about: Ask questions in Discussions instead of opening an issue.
Setting blank_issues_enabled: false forces users to pick a template. No more “it’s broken” drive-bys. The contact_links section adds external links to the chooser page, which is perfect for redirecting security reports or general questions to the right place.
Markdown Issue Templates
For less structured issues, a classic markdown template still works well. It pre-fills the issue body with your section prompts, and the user edits from there.
.github/ISSUE_TEMPLATE/documentation.md:
---
name: Documentation Improvement
about: Suggest an update or fix for documentation
title: "[Docs]: "
labels: documentation
assignees: ""
---
## What page or section needs updating?
## What's wrong or missing?
## Suggested improvement
The YAML frontmatter block (between the --- lines) is required for markdown issue templates. It sets the template name, description, default title prefix, labels, and assignees that appear in the template chooser. Without this frontmatter, GitHub won’t recognize the file as a template.
Hands-On Lab: Add Templates to Your Repo
Let’s set up a complete template system from scratch. You can follow along in any existing repository.
Step 1: Create the directory structure
mkdir -p .github/ISSUE_TEMPLATE
Step 2: Add a PR template
Create .github/PULL_REQUEST_TEMPLATE.md:
## Summary
<!-- What does this PR do and why? -->
## Related Issues
<!-- Fixes #, Relates to # -->
## Changes
-
## Testing
- [ ] Tests pass locally
- [ ] CI pipeline passes
## Checklist
- [ ] Code follows project conventions
- [ ] No secrets committed
- [ ] Documentation updated (if needed)
Step 3: Add a bug report issue form
Create .github/ISSUE_TEMPLATE/bug-report.yml:
name: Bug Report
description: Report a bug
title: "[Bug]: "
labels: ["bug", "triage"]
body:
- type: input
id: version
attributes:
label: Version
placeholder: "v1.0.0"
validations:
required: true
- type: textarea
id: description
attributes:
label: What happened?
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
value: |
1.
2.
3.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs or error output
render: shell
Step 4: Add a feature request form
Create .github/ISSUE_TEMPLATE/feature-request.yml:
name: Feature Request
description: Suggest an improvement
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: Problem statement
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed solution
validations:
required: true
Step 5: Configure the template chooser
Create .github/ISSUE_TEMPLATE/config.yml:
blank_issues_enabled: false
contact_links:
- name: Questions & Discussion
url: https://github.com/OWNER/REPO/discussions
about: Use Discussions for questions, not Issues.
Step 6: Commit and push
git add .github/
git commit -m "feat: add PR and issue templates"
git push
Once this hits your default branch, every new PR and issue will use your templates automatically. Open a test PR or navigate to the “New Issue” page to verify everything renders correctly.
Troubleshooting Guide
| Problem | Cause | Fix |
|---|---|---|
| PR template not loading | File not on default branch | Merge to main first |
| PR template not loading | Wrong filename | Must be exactly PULL_REQUEST_TEMPLATE.md (case insensitive) |
| Issue form validation error | Duplicate dropdown options | Each option in a dropdown must be unique |
| Issue form not appearing | File extension issue | Use .yml — both .yml and .yaml work, but stay consistent |
| Template chooser not showing | Only one template exists | Chooser appears with 2+ templates or a config.yml |
| Labels not applied | Labels don’t exist in repo | Create the labels in your repo first — templates reference existing labels |
| Blank issues still possible | config.yml missing | Add blank_issues_enabled: false to config.yml |
| Multiple PR templates not working | Query param missing | Use ?template=filename.md in the PR URL |
Frequently Asked Questions
Can I use YAML forms for PR templates? No. YAML forms are only supported for issue templates. PR templates must be markdown files.
Do templates work on private repos? Yes. Templates work on all GitHub repository types — public, private, and internal.
Can I set a default assignee on PR templates? Not through the template file itself. Use a GitHub Action or a CODEOWNERS file for automatic PR assignment instead.
What happens if someone removes the template text? Nothing stops them. Templates are a nudge, not a gate. If you need enforcement, use a GitHub Action that checks the PR body against required sections and blocks the merge if they’re missing.
Do templates work with the GitHub CLI?
Yes. When you create a PR with gh pr create, it opens your editor with the default template pre-filled. For issues, gh issue create --template "Bug Report" selects a specific template.
Can I combine a single default PR template with a PULL_REQUEST_TEMPLATE/ directory?
No. If the directory exists, GitHub ignores the single file. Pick one approach.
Quick Reference
# Directory structure
.github/PULL_REQUEST_TEMPLATE.md # Single default PR template
.github/PULL_REQUEST_TEMPLATE/name.md # Multiple PR templates
.github/ISSUE_TEMPLATE/name.yml # YAML issue form
.github/ISSUE_TEMPLATE/name.md # Markdown issue template
.github/ISSUE_TEMPLATE/config.yml # Template chooser config
# Use a specific PR template via URL
?template=hotfix.md
# Create issue with specific template via CLI
gh issue create --template "Bug Report"
# Create PR (auto-loads default template)
gh pr create
What’s Next
Next post: Git Tags and Release Management. We’ll cover how to tag releases properly, the difference between lightweight and annotated tags, semantic versioning workflows, and how to automate release notes from your commit history.
Happy automating!