Skip to content

How to Write Effective GitHub Issue Templates

A pull request template improves the quality of proposed changes, but it only helps after someone has already made it to the solution stage. GitHub issue forms solve the earlier problem: they shape the information you collect when someone reports a bug, asks for an enhancement, or suggests a documentation fix. In that sense, they're the natural companion to a pull request template, and for many repositories they do even more to reduce maintainer back-and-forth.

While my previous post, How to Write an Effective GitHub Pull Request Template, was about improving review, this post is about improving intake.

  • Pull request templates help contributors explain a proposed change.
  • Issue template help contributors explain a problem, an idea, or a gap before any code has been written.

That distinction matters, because most maintainer time is lost much earlier in the process: missing reproduction steps, missing environment details, vague enhancement requests, and documentation issues with no concrete suggestion.

GitHub supports both legacy Markdown issue templates and the newer issue forms, but this post is specifically about issue forms. That's the model I use. They're YAML-based, support required fields, structured inputs, dropdowns, markdown instructions, uploads, and per-field validation. Most importantly, they let you ask only for the information that a specific type of issue actually needs.

Start With Three Forms

The files live in .github/ISSUE_TEMPLATE/ and are read from the default branch. In most repositories, I generally start with three forms:

Form Use Case
bug.yml Something is broken and needs to be reproduced and fixed.
enhancement.yml Something is missing and needs a clearer problem statement or use case.
docs.yml Something needs to be clarified, corrected, or expanded in the documentation.

Those three cover the majority of contributor traffic cleanly.

That's the shape I come back to over and over. It's small enough to maintain, but broad enough to cover the kinds of issues most repositories actually receive.

I also pair those forms with a chooser configuration:

.github/ISSUE_TEMPLATE/config.yml
blank_issues_enabled: false

In this repository, the file is intentionally minimal: blank issues are disabled. That forces contributors to choose one of the defined forms instead of bypassing the structure entirely.

Note

I covered this file in detail in Configuring the GitHub Issue Template Chooser. If you want to add contact_links for Discussions, upstream documentation, or support resources, that post walks through the full config.yml schema.

The Shape of an Issue Form

A GitHub issue form isn't a large schema, but it helps to know where the moving parts actually are. These are the top-level keys I reach for most often.

Key What It Does Example
name The display name shown in the issue template chooser. name: Bug
description Short helper text shown under the template name in the chooser. description: Report a bug or unexpected behavior in the library.
title Pre-populates the issue title field. title: "[Bug]: "
labels Applies one or more labels automatically when the issue is created. labels: ["bug"] or labels: ["bug", "triage"]
assignees Assigns one or more GitHub usernames automatically. assignees: ["tenthirtyam"]
projects Adds the issue to one or more GitHub Projects. projects: ["tenthirtyam/67"]
type Sets the issue type when your repository uses GitHub issue types. type: bug
body Defines the actual form fields contributors will fill out. body:

Tip

Some practical notes on those keys:

  • name and description are what contributors see first, so write them for humans, not for maintainers reading the YAML later.
  • title is helpful when you want a consistent prefix such as [Bug]: or [Docs]:, but leave enough room for contributors to summarize the issue clearly.
  • labels is one of the highest-value keys in the file. Applying bug, enhancement, documentation, or triage automatically means new issues arrive pre-categorized.
  • assignees is best used when a repository has a very small maintainer group. In larger repositories, automatic assignment can create noise or bottlenecks. This key expects GitHub usernames, not team slugs.
  • projects is useful when your issue tracker and your planning workflow are tightly connected. If you don't actively use GitHub Projects, skip it.
  • type is useful, but only if you're already using GitHub's issue types consistently. If not, labels are usually the more portable way to categorize work.

For the keys that commonly take arrays, this is what single-value and multi-value usage looks like:

labels: ["bug"]
assignees: ["tenthirtyam"]
projects: ["tenthirtyam/67"]
labels: ["bug", "triage"]
assignees: ["tenthirtyam", "octocat"]
projects: ["tenthirtyam/67", "tenthirtyam/88"]

The body key is where issue forms stop being metadata and start becoming a workflow. Each entry in body: is a field or instruction block. The field type determines what GitHub renders.

Field Type Use It For Notes
markdown Instructions, search-before-filing reminders, links, and context. Great for pointing to docs, existing issues, or your Code of Conduct.
input Short single-line values. Good for versions, operating system releases, URLs, and usernames.
textarea Longer free-form responses. Best for descriptions, reproduction steps, logs, code samples, and expected vs actual behavior.
dropdown Selecting from a controlled list of options. Useful when you want clean, normalized input for OS, version families, or feature areas.
checkboxes One or more acknowledgments or multi-select choices. Ideal for Code of Conduct agreement and contributor confirmations.
upload Attaching screenshots or other files. Useful for UI bugs, diagrams, logs, and supporting artifacts.

For most repositories, those six field types are enough.

The other two keys you'll use constantly inside body items are attributes and validations.

  • attributes controls the field's visible behavior: the label, helper text, placeholder, available options, and rendering hint.
  • validations controls whether GitHub requires the field before the issue can be submitted.

For example:

- type: textarea
  id: error-output
  attributes:
    label: Error Output
    description: Provide the complete error output or stack trace.
    render: text
  validations:
    required: false

That split is worth understanding:

  • type: textarea defines the shape of the input.
  • id: error-output gives the field a stable internal identifier.
  • attributes defines how the field is presented to the contributor.
  • validations defines whether GitHub will allow the form to be submitted without it.

Once you see that pattern, most of the schema becomes fairly intuitive.

Keep Simple Forms Simple

Not every issue type needs a long form. A common mistake is to treat every issue like a production outage and ask for far more information than the reporter can reasonably provide. That increases abandonment without improving the quality of the issue tracker.

For lighter-weight issue types, I prefer short forms with a small number of high-value fields.

Documentation Issues

Documentation issues are often even smaller. Sometimes the most useful report is simply: "this section is unclear" and "here is what I expected instead."

A compact form lowers the barrier to that kind of feedback.

Example:

.github/ISSUE_TEMPLATE/docs.yml
---
name: Documentation
description: Found a typo or something that needs clarification?
labels:
  - documentation
body:
  - type: markdown
    attributes:
      value: >
        When filing a documentation issue, please include the following information.
  - type: textarea
    id: motivation
    attributes:
      label: Motivation
      description: Why should we update our docs or examples?
    validations:
      required: false
  - type: textarea
    id: suggestion
    attributes:
      label: Suggestion
      description: What should we do instead?
    validations:
      required: false

Enhancement Requests

The enhancement form asks for three things: description, use cases, and references. That's enough structure to move the conversation from "I want a thing" to "here is the problem this would solve."

In practice, that's the pattern: the smaller and less urgent the issue type, the smaller the form should be.

Example:

.github/ISSUE_TEMPLATE/enhancement.yml
---
name: Enhancement
description: Is something critical missing?
labels:
  - enhancement
body:
  - type: markdown
    attributes:
      value: >
        When filing an enhancement request, please include the following information.
  - type: textarea
    id: description
    attributes:
      label: Description
      description: Provide an overview of the enhancement.
    validations:
      required: true
  - type: textarea
    id: use-case
    attributes:
      label: Use Case(s)
      description: Provide any relevant use-cases.
    validations:
      required: true
  - type: textarea
    id: references
    attributes:
      label: References
      description: Provide any references.
    validations:
      required: false

Build the Bug Form Carefully

Bug reports are where structure pays off the most. A vague enhancement request can still turn into a useful conversation. A vague bug report usually turns into a stalled issue.

Example:

.github/ISSUE_TEMPLATE/bug.yml
---
name: Bug
description: Is something not working as expected?
labels:
  - bug
body:
  - type: markdown
    attributes:
      value: >
        When filing a bug report, please include the following information.
  - type: textarea
    id: environment
    attributes:
      label: Environment Details
      description: Please add any information you can provide about the environment.
    validations:
      required: false
  - type: textarea
    id: description
    attributes:
      label: Description
      description: Please provide a clear and concise description of the issue you are experiencing.
    validations:
      required: true
  - type: textarea
    id: debug
    attributes:
      label: Error or Debug Output
      description: Please provide a link to a [GitHub Gist](https://gist.github.com/) containing the complete error or debug output.
      placeholder: Link to a GitHub Gist. Please do not paste the debug output in the issue.
    validations:
      required: true
  - type: textarea
    id: expected-behavior
    attributes:
      label: Expected Behavior
      description: >
        What is it you expected to happen?

        This should be a description of how the functionality you tried to use is supposed to work.
    validations:
      required: true
  - type: textarea
    id: actual-behavior
    attributes:
      label: Actual Behavior
      description: What actually happened that's different from the expected behavior?
    validations:
      required: true
  - type: textarea
    id: steps-to-reproduce
    attributes:
      label: Steps to Reproduce
      description: Please provide the steps to reproduce the issue.
    validations:
      required: true
  - type: textarea
    id: logs
    attributes:
      label: Log Fragments and Files
      description: >
        Please include appropriate redacted log fragments. If the log is longer than a
        few dozen lines, please include the URL to the
        [Gist](https://gist.github.com/) of the log or use the [GitHub detailed
        format](https://gist.github.com/ericclemmons/b146fe5da72ca1f706b2ef72a20ac39d)
        instead of posting it directly in the issue.
    validations:
      required: false
  - type: textarea
    id: screenshot
    attributes:
      label: Screenshots
      description: Screenshots of the issue, if applicable.
    validations:
      required: false
  - type: textarea
    id: references
    attributes:
      label: References
      description: |
        Please provide any related GitHub issues or pull requests (open or closed) or documentation.
        Learn about [Referencing Github Issues](https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests).
      placeholder: |
        #GH-0000

Every field exists for a reason:

Field Why It Matters
Environment Details Captures context that often determines whether the issue is real, reproducible, or already fixed elsewhere.
Description Forces the reporter to explain the problem in plain language before dropping logs or stack traces into the form.
Error or Debug Output Asks for a Gist link instead of a pasted wall of output, which keeps the issue readable.
Expected Behavior and Actual Behavior Separates "what should happen" from "what did happen", which is one of the fastest ways to make a bug report actionable.
Steps to Reproduce Turns the report into something another human can test.
Log Fragments and Files, Screenshots, and References Provides optional supporting material without making the form feel punitive.

That balance is the goal. The form should be structured enough to make the issue useful, but not so burdensome that contributors give up halfway through.

GitHub Gists

Use a Gist link when the output is long enough that pasting it directly would make the issue hard to read. A short stack trace or a few lines of relevant log output can live in the issue itself. Full debug logs, large plan output, verbose traces, and multi-file examples are usually better in a Gist, with only the most relevant excerpt kept in the issue body.

Make Formatting the Default

One of the best GitHub issue form features is render: on textarea fields. It lets you tell GitHub how to wrap the submitted content automatically. This is the easiest way to improve the readability of configuration fragments, shell output, stack traces, and sample code.

For example:

- type: textarea
  id: config
  attributes:
    label: Relevant Configuration
    description: Provide the minimal configuration required to reproduce the issue.
    render: yaml
  validations:
    required: true

- type: textarea
  id: error-output
  attributes:
    label: Error Output
    description: Provide the complete error output or stack trace.
    render: text
  validations:
    required: false

With that in place, contributors don't need to remember Markdown fences or language identifiers. The form handles it for them. If your project routinely receives issues with YAML, JSON, HCL, shell output, or stack traces, this is worth adding immediately.

I covered the formatting side of this in more detail in Please Format Your Code Blocks: GitHub Issue Etiquette.

Issue forms let maintainers turn that etiquette into a default.

When You Need More

If you want to go beyond a minimal form, GitHub issue forms support much more than a few textareas. You can add checkboxes, dropdowns, project routing, labels, assignees, uploads, and markdown instructions directly in the YAML.

This is also where I like including a Code of Conduct acknowledgment. It's a small thing, but it does useful work. It sets expectations at the start of the interaction, reminds contributors that the issue tracker is a shared community space, and gives maintainers a clear reference point when they need to enforce standards of behavior later.

Here's a more complete example that shows what GitHub issue forms can do:

.github/ISSUE_TEMPLATE/bug.yml
name: Bug
description: Report a bug or unexpected behavior in the library.
labels: ["bug", "triage"]
assignees: ["tenthirtyam"]
projects: ["tenthirtyam/67"]
type: bug
body:
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/tenthirtyam/example/blob/main/CODE_OF_CONDUCT.md).
      options:
        - label: I agree to follow this project's Code of Conduct.
          required: true
  - type: markdown
    attributes:
      value: |
        Before filing an issue, please [search the existing issues](https://github.com/tenthirtyam/example/issues?q=is%3Aissue+is%3Aopen+label%3Abug) (open or closed), and use the [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) feature to add up-votes to existing issues.

        When filing an issue, please include the following information.
  - type: input
    id: version
    attributes:
      label: Library Version
      description: |
        Please provide the library version.
        We recommend testing with [the latest version](https://github.com/tenthirtyam/example/releases/latest).
      placeholder: e.g. 1.0.0
    validations:
      required: true
  - type: input
    id: go-version
    attributes:
      label: Go Version
      description: What version of Go are you using?
      placeholder: e.g. x.y.z
    validations:
      required: true
  - type: dropdown
    id: os
    attributes:
      label: Operating System
      description: Select the operating system where the issue occurs.
      options:
        - Linux
        - macOS
        - Windows
        - Other
    validations:
      required: true
  - type: textarea
    id: description
    attributes:
      label: Description
      description: Please provide a clear and concise description of the issue you are experiencing.
    validations:
      required: true
  - type: textarea
    id: code-sample
    attributes:
      label: Code Sample
      description: |
        - Please provide a minimal, reproducible code sample that demonstrates the issue.
        - Please ensure all sensitive information (passwords, hostnames, etc.) is removed.
      render: go
    validations:
      required: true
  - type: textarea
    id: error-output
    attributes:
      label: Error Output
      description: |
        Please provide the complete error output or stack trace.
        For large outputs, please use a [GitHub Gist](https://gist.github.com/).
      render: text
    validations:
      required: false
  - type: textarea
    id: expected-behavior
    attributes:
      label: Expected Behavior
      description: |
        What is it you expected to happen?
        This should be a description of how the functionality you tried to use is supposed to work.
    validations:
      required: true
  - type: textarea
    id: actual-behavior
    attributes:
      label: Actual Behavior
      description: What actually happened that's different from the expected behavior?
    validations:
      required: true
  - type: textarea
    id: steps-to-reproduce
    attributes:
      label: Steps to Reproduce
      description: Please provide the steps to reproduce the issue.
    validations:
      required: true
  - type: upload
    id: screenshots
    attributes:
      label: Upload Screenshots
      description: Please upload any relevant screenshots or images that can help illustrate the issue.
    validations:
      required: false
  - type: textarea
    id: references
    attributes:
      label: References
      description: |
        Please provide any related GitHub issues or pull requests (open or closed) or documentation.
        Learn about [Referencing GitHub Issues and Pull Requests](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#referencing-issues-and-pull-requests).
      placeholder: |
        #GH-0000
    validations:
      required: false

I wouldn't use this exact form in every repository. It's intentionally fuller than the minimal earlier examples. The point isn't to say every bug form should look like this.

The point is to show the range of what issue forms support, and how you can combine structure, guidance, and formatting defaults in one place.

Templates don't just make forms prettier. They encode the standards of the project at the exact moment someone needs them.

Resources