Introduction
By day 59, you've mastered commits, branches, and pull requests. Now it's time to organize the work itself. Issue templates and labels are the scaffolding that turns a GitHub repository into a predictable, self-documenting workspace where every team member knows what to expect and what's expected of them. In this guide, we'll explore issue templates and labels: patterns, pitfalls and fixes—the unglamorous but essential infrastructure that separates chaotic backlogs from managed ones.
Why issue templates matter
A well-designed issue template is like a contract between the reporter and the team. It ensures that bugs come with reproducible steps, features arrive with clear acceptance criteria, and nothing gets lost in translation.
Without templates, you'll see issues like:
- "The thing is broken"
- "Can we make it faster?"
- "Check this out" with a screenshot but no context
With templates, contributors fill in structured fields before they even write their title. Let's look at a practical bug template:
name: Bug Report
description: Report a reproducible defect
title: "[Bug] "
labels: ["bug", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting. Please fill in all fields below.
- type: input
id: environment
attributes:
label: Environment
description: Node version, OS, browser if relevant
placeholder: "Node 18.12.0, Ubuntu 22.04"
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: Exact sequence to trigger the issue
placeholder: |
1. Run `npm install`
2. Call POST /api/users with body {...}
3. Observe the error
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual behavior
description: What happened?
placeholder: "Returns 500 with no message"
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: What should happen?
placeholder: "Returns 400 with validation error details"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs or error output
description: Paste full stack trace or relevant logs
render: shellThis template enforces rigor. A reporter must think through their issue before submitting. When your team triages the issue later, you have everything you need.
Feature request templates
Feature requests are different from bugs. They need context, motivation, and success criteria—not reproduction steps.
name: Feature Request
description: Propose a new capability
title: "[Feature] "
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: Problem or use case
description: What problem does this solve? Who needs it?
placeholder: "Users in the EU need GDPR-compliant data export"
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed solution
description: How should it work?
placeholder: "Add a /api/users/:id/export endpoint returning JSON"
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: What other approaches did you think of?
placeholder: "CSV export, but JSON is more programmatic"
- type: textarea
id: acceptance
attributes:
label: Acceptance criteria
description: How do we know it's done?
placeholder: |
- Endpoint accepts user ID and format parameter
- Returns all user data in requested format
- Includes audit log entry
- type: dropdown
id: impact
attributes:
label: Estimated impact
description: How many users benefit?
options:
- "Many (100s or 1000s)"
- "Some (10s-100s)"
- "Few (1-10)"
validations:
required: trueThe feature template asks for why before how. This prevents the team from building the wrong thing.
Labels for type, priority, and area
Labels are the vocabulary of your backlog. Without them, finding "all critical frontend bugs" requires scrolling and memory. With them, a single filter reveals exactly what you need.
A sensible label strategy includes three dimensions:
Type labels
bug— defect in production behaviorenhancement— new feature or improvementdocumentation— docs, README, commentschore— maintenance, refactoring, no user impactquestion— clarification needed, not a real issue
Priority labels
p0-critical— production outage, data loss risk, security breachp1-high— significant user impact, broken featurep2-medium— noticeable but workaround existsp3-low— nice-to-have, cosmetic, future consideration
Area labels
area/autharea/apiarea/uiarea/databasearea/devops
You'll also want operational labels:
needs-triage— new, not yet reviewedblocked— waiting on another issue or external eventduplicate— same as another issuehelp-wanted— good for first contributorsstale— inactive for 90+ days, candidate for closure
The key is restraint. Fifteen labels per repository is a good range. Beyond that, labeling becomes tedious and inconsistent.
Milestones for releases and planning
Milestones let you group issues by release or sprint. They're simpler than labels but equally powerful for planning.
Create a milestone named v2.5.0 or Sprint 12. Add issues to it. GitHub shows you a progress bar: "18 of 24 issues closed." You can see what's on track and what's slipping.
A practical pattern: create a milestone per quarter or release cycle, and add issues during sprint planning. When you close an issue, you check off progress automatically.
| Milestone | Purpose | Typical timeline |
|---|---|---|
| v2.5.0 (Open) | Current sprint or release in progress | 2–4 weeks |
| v2.6.0 (Open) | Next planned release | 4–8 weeks out |
| Backlog (Open) | Important but unscheduled work | No deadline |
| v2.4.0 (Closed) | Historical releases, for reference | Archived |
Don't create too many open milestones. Two to three active ones keep the roadmap clear. Older milestones can be closed as their releases ship.
Closing stale issues politely
Over time, issues accumulate. Some are genuinely abandoned. Others are fixed but never closed. Many are waiting for feedback that never comes.
A stale issue is a tax on morale. It signals that the team doesn't care, or that the process has broken down.
GitHub Actions can automate this. Here's a workflow that marks issues as stale after 90 days of inactivity, then closes them 14 days later:
name: Close stale issues
on:
schedule:
- cron: '0 0 * * 0' # Weekly on Sunday
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/stale@v9
with:
days-before-stale: 90
days-before-close: 14
stale-issue-label: 'stale'
stale-issue-message: |
This issue has been inactive for 90 days.
If you'd like us to reopen it, please comment or react with a thumbs-up.
It will be closed in 14 days unless activity resumes.
close-issue-message: |
Closing due to inactivity. Feel free to reopen if this is still relevant.
exempt-issue-labels: 'pinned,blocked'
operations-per-run: 100The tone matters. Be respectful. Explain what's happening and how to get the issue reopened. Many reporters will re-engage if you ask politely.
Common mistakes
Over-labeling: Some teams create fifty labels with overlapping semantics. No one remembers which is which, and consistency suffers. Start with ten and add only when you feel real pain.
Empty templates: A template that's too permissive becomes useless. If every field is optional, people skip them. Make required fields non-negotiable, but don't ask for information you won't use.
Ignoring the backlog: Milestones and labels only work if someone triages incoming issues within 24 hours. Without triage, your backlog becomes a junk drawer. Assign rotation responsibility.
Closing without context: Automated stale-issue closure can feel cold. Add a human-readable message explaining why and how to reopen. Even better, let a human review before closing.
Orphaned milestones: Old milestones collect issues but are never closed. Clean them up quarterly. Move active work to new milestones, and archive the old ones.
Key takeaways
- Issue templates enforce structure and reduce triage time. Create separate templates for bugs and features, with required fields that capture what you actually need.
- Labels should span three dimensions: type (bug, enhancement, docs), priority (p0–p3), and area (auth, api, ui). Keep the total count under twenty.
- Milestones group issues by release or sprint. Maintain two to three active milestones and close old ones when their release ships.
- Stale issues are momentum killers. Use GitHub Actions to flag inactive issues after 90 days and close them after 14 more, but always include a polite message and a path to reopen.
- Triage is a daily habit, not a one-time event. Assign responsibility and protect time for it.






Comments
Loading comments…