How to write great PR descriptions
Have you ever reviewed a pull request (PR) with a diff of a few hundred lines of code, for which the PR description merely said something like: “Add CSV export for messages”?
I believe that a good PR description serves two main purposes: it documents a change for current and future reference, and it facilitates a swift and frictionless review process.
What I find important to appreciate is that there is an asymmetry between creator and reader of a PR. By the time a developer opens a PR, they have usually spent hours if not days deep in the problem: they have built up a solid understanding of the context, considered other implementation approaches, and weighed trade-offs and shortcomings. The reader of a PR knows none of that – they only see a code diff and are asked to make sense of it.
The PR description aims to bridge that gap. It acts as a handover point and externalises all relevant context and information that the mere diff wouldn’t give explicitly.
When creating PRs, this is the guide line that I usually try to follow for putting together the description:
- Include all relevant references, e.g. links to tickets or other related PRs.
- Briefly summarise the changes at a high level.
- Outline the implementation approach and the thought process behind it – e.g.: did I consider alternative implementations? Are there known caveats with the proposed solution?
- If applicable, provide some starting points to facilitate a smooth review process. I basically think of this as service to the reviewer: what information would help them to quickly get their head around the changes, or what hints could they benefit from for testing the branch locally (think, for instance, when specific steps are required to reproduce a certain application state).
That being said, a good PR description could look like this:
Resolves #7162.
This PR introduces an additional export option that allows users to download their private messages in CSV format from the message inbox.
The exported CSV data contains the following fields: message id, message text, timestamp, a char flag to indicate whether the message was outgoing/“sent” (
S) or incoming/“received” (R), and the other user’s name. As discussed in the ticket, we don’t want to include attachments or other metadata for now.For the implementation, I’ve reused the existing
MessageExporterutility, to make the new CSV export consistent with the existing JSON and XML export options. However, I realised that we can simplify the internal control flows and make the code structure less repetitive. I’ve started to prepare a follow-up PR for that, see #7189 (still work in progress, though).Friendly reminder for testing: exporting private messages is disabled by default unless an admin account activates it in the system settings: log in as admin, then go to “Settings” → “Features”, and toggle on the “Allow data export” option. This makes an “Export” dropdown appear in the toolbar when navigating to the message inbox.
Of course, a PR description doesn’t have to be that verbose every time. As a rule of thumb, the comprehensiveness of the PR description should correspond to the complexity of the changes.
I also think it usually isn’t worthwhile to generate PR descriptions via LLM, or to enforce a certain structure via a template: LLM-generated change summaries are often unnecessarily wordy, and they lack valuable process-related insights. PR templates, on the other hand, can feel monotonous and formalistic, which makes the review process dull and inflexible.
In the end, a good PR description isn’t about ceremony or ticking boxes – it’s a piece of information provided by humans for humans. A few minutes of writing on your side can save your reviewer half an hour of guesswork, and help whoever revisits your change in six months to understand what you were thinking. So whatever you end up writing, that’s the spirit you should follow.