Writing GitHub Issues and PRs with Markdown — A Practical Guide

The difference between an issue that gets fixed in a day and one that sits untouched for months is usually not the bug — it is the write-up. GitHub issues and pull requests are rendered with GitHub Flavored Markdown, and a handful of its features (fenced code blocks, task lists, collapsible sections, tables, and auto-linking) can turn a wall of text into something a maintainer can act on in minutes. This guide covers each technique with copy-paste examples, plus a before/after of a real bug report.

The Minimal Repro in a Fenced Code Block

The single highest-value part of any bug report is a minimal reproducible example: the smallest snippet that triggers the problem. Put it in a fenced block with a language tag so it gets syntax highlighting, and pair it with expected vs actual behaviour:

## Steps to reproduce

```js
const cache = new LRUCache({ max: 2 });
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);   // evicts 'a'
console.log(cache.get('b')); // expected 2, got undefined
```

**Expected:** `cache.get('b')` returns `2`
**Actual:** returns `undefined` — 'b' was evicted instead of 'a'

Strip everything not needed to trigger the bug: no framework boilerplate, no real business data, no 400-line file. If the maintainer can paste your snippet into a REPL and see the failure, you have done most of their debugging for them.

Task Lists for Acceptance Criteria

GitHub renders - [ ] as a real checkbox that anyone with write access can tick without editing the text. Use them for acceptance criteria in feature issues and for reviewer checklists in PRs — GitHub even shows the completion count ("3 of 5 tasks") in issue lists:

## Acceptance criteria

- [x] Export button visible on the reports page
- [x] CSV includes all filtered rows, not just the current page
- [ ] Dates exported in ISO 8601 format
- [ ] Export of 50k rows completes in under 10 seconds
- [ ] Unit tests cover empty-result export

This turns a vague request into a definition of done. In a PR description, the same pattern works as a self-review checklist: tests added, docs updated, migration included, changelog entry written.

Collapsible Sections for Logs and Long Output

Full stack traces and verbose logs are essential evidence but terrible reading. Wrap them in a details/summary block — GitHub renders it collapsed by default. Note the blank line after the summary tag; without it, Markdown inside will not render:

<details>
<summary>Full stack trace (142 lines)</summary>

```text
TypeError: Cannot read properties of undefined (reading 'id')
    at UserService.resolve (src/services/user.js:88:31)
    at async Router.handle (src/router.js:52:12)
    ...
```

</details>

The issue stays scannable — summary, repro, environment — while the full detail is one click away for whoever needs it.

Linking, Tables, and Templates

Auto-linking. GitHub links references for you: #142 becomes a link to issue or PR 142, a pasted commit SHA becomes a commit link, and owner/repo#57 links across repositories. In a PR description, closing keywords wire the whole workflow together:

Fixes #142
Related to #98, follow-up planned in #150
Regression introduced in a1b2c3d

When the PR merges, issue #142 closes automatically and both threads are permanently cross-referenced.

Tables for environment info. A small GFM table beats a prose paragraph for version details:

| Field       | Value            |
|-------------|------------------|
| OS          | Ubuntu 24.04     |
| Node        | 22.11.0          |
| Package     | lru-cache 11.0.2 |
| Reproduced  | 10/10 runs       |

Issue templates. To make this structure the default for your whole repo, add templates under .github/ISSUE_TEMPLATE/ (Markdown or YAML forms) and a .github/PULL_REQUEST_TEMPLATE.md. Contributors then start from your headings — Steps to reproduce, Expected, Actual, Environment — instead of a blank box, and the quality floor of your issue tracker rises overnight.

Before and After: A Bad Issue vs a Great One

Before — technically a bug report, practically useless:

Title: cache broken

it doesnt work. i set values and get undefined back sometimes.
using latest version. please fix asap this is blocking us

After — same bug, five minutes more effort:

Title: Wrong entry evicted when max=2 and get() precedes set()

## Steps to reproduce
```js
const cache = new LRUCache({ max: 2 });
cache.set('a', 1); cache.set('b', 2);
cache.get('a');       // touch 'a'
cache.set('c', 3);    // should evict 'b'
cache.get('a');       // returns undefined ❌
```

**Expected:** 'b' is evicted (least recently used)
**Actual:** 'a' is evicted

## Environment
| Field   | Value            |
|---------|------------------|
| Node    | 22.11.0          |
| Package | lru-cache 11.0.2 |

Possibly related to #131 (recency not updated on get).

The second version has a searchable title, a two-minute repro, a clear expected/actual contrast, exact versions, and a lead for the maintainer to follow. That is the issue that gets fixed first.

Frequently Asked Questions

How do I make a collapsible section in a GitHub issue?

Use the HTML details and summary tags, which GitHub renders as a collapsible block. Put your long logs or stack traces inside, with a blank line after the summary tag so Markdown inside still renders. This keeps issues scannable while preserving full detail.

How do I link a commit or another issue in GitHub Markdown?

Type # followed by the issue or PR number (for example #142) and GitHub auto-links it. Paste a full commit SHA or its first 7 characters and GitHub links the commit. Keywords like "Fixes #142" in a PR description automatically close the issue when the PR merges.

What should a good bug report contain?

A good bug report has a specific title, a minimal reproducible example in a fenced code block, expected versus actual behavior, an environment table (OS, runtime version, package version), and any relevant logs in a collapsible section. If a maintainer can reproduce the bug in under two minutes, it gets fixed much faster.

Try the Free Markdown Previewer

Draft your issue or PR description and preview the rendered GFM — tables, task lists, and collapsible sections — before you post it. No signup, no cost.

Related articles