0x55aa
โ† Back to Blog

The Art of Writing PRs That Maintainers Actually Merge ๐ŸŽฏ๐Ÿš€

โ€ข12 min read

The Art of Writing PRs That Maintainers Actually Merge ๐ŸŽฏ๐Ÿš€

Confession time: My first open source pull request got closed in 4 minutes.

Not 4 days. Not 4 hours. 4 MINUTES. โฑ๏ธ

I'd spent an entire Saturday refactoring a popular Laravel package โ€” improving readability, adding clever abstractions, making the code "cleaner." Proud of myself, I opened the PR with the confidence of someone who definitely knows what they're doing.

The response? "Thanks, but this changes the public API. We don't accept breaking changes. Closing."

Reader, I wanted to delete my GitHub account. ๐Ÿ˜ญ

But here's the thing: That wasn't bad luck. I broke basically every unwritten rule of open source contribution. And once I figured out those rules? My merge rate went from 0% to consistently getting PRs merged into projects with thousands of stars.

Let me save you the Saturday (and the dignity).

Why 80% of PRs Never Get Merged ๐Ÿ’€

As a full-time developer who contributes to open source, I've been on BOTH sides of this. I've had PRs rejected, and as a co-maintainer on a few smaller projects, I've had to reject PRs from others.

Here's what the graveyard looks like:

โŒ "Refactored everything for consistency"
   โ†’ Maintainer: "We didn't ask for this"

โŒ "Added feature X!" (no issue discussion first)
   โ†’ Maintainer: "We're not planning to support X"

โŒ 4,000 line PR touching 87 files
   โ†’ Maintainer: *closes browser tab*

โŒ No tests included
   โ†’ Maintainer: "Please add tests"
   You: *never comes back*

โŒ Breaks existing functionality
   โ†’ Maintainer: "This is a regression"

The uncomfortable truth?

Most rejected PRs aren't rejected because the code was bad. They're rejected because the process was wrong. ๐Ÿคฆ

The Golden Rule Nobody Talks About ๐Ÿฅ‡

Open an issue BEFORE writing code.

I cannot stress this enough. I learned this the hard way (see: my 4-minute PR graveyard above).

The workflow that WORKS:

1. Found a bug or want a feature?
   โ†’ Search existing issues first

2. Not found?
   โ†’ Open an issue BEFORE coding

3. Describe your idea
   โ†’ Get maintainer feedback

4. Maintainer says "yes, please!"
   โ†’ NOW write the code

5. Link PR to the issue
   โ†’ Context is built in! ๐ŸŽ‰

Real story: I wanted to add Redis support to a PHP authentication library. Instead of just doing it, I opened an issue: "Would you consider adding Redis as a session backend? I'd love to contribute this."

The maintainer replied in 3 hours: "Yes! But let's use an interface so users can plug in any backend." That feedback changed my entire implementation design. The PR sailed through review in TWO days. ๐Ÿš€

Without that issue? I'd have shipped a Redis-only solution that the maintainer would have rejected. Classic me.

Anatomy of a PR That Gets Merged โœจ

Let me show you the exact structure I use now.

The Title ๐Ÿ“Œ

Bad:

Fix bug
Add feature
Update code

Good:

fix: prevent null pointer exception when user session is empty
feat: add Redis session backend with configurable TTL
docs: update installation guide for PHP 8.2+

The pattern: Use Conventional Commits format. Most maintainers know it. It signals that you're experienced and thoughtful.

type: short description where type is fix, feat, docs, test, refactor, chore.

The Description ๐Ÿ“

The template I actually use:

## What does this PR do?
[2-3 sentences max. What problem does it solve?]

## Why is this needed?
[Link to the issue, or explain the motivation]

## How does it work?
[Brief explanation of the approach]

## Testing
- [ ] Existing tests pass
- [ ] Added tests for new behavior
- [ ] Manually tested on [environment]

## Breaking changes?
- [ ] No breaking changes
- OR: [List what breaks and migration path]

Closes #[issue number]

Why this matters: Maintainers review dozens of PRs. A clear description means they spend 30 seconds understanding your change instead of 30 minutes. Guess which PRs get reviewed first? ๐Ÿค”

The Size ๐Ÿ“

The single biggest mistake I see:

Files changed: 87
Additions: +4,232
Deletions: -1,891

Nobody is reviewing that. Not today. Probably not this year.

The sweet spot:

Files changed: 3-8
Additions: +50-200
Deletions: varies

How to keep PRs small:

  1. One concern per PR. Bug fix? That's it. Don't also refactor while you're there.
  2. Feature too big? Break it into a series of PRs.
  3. Refactoring needed before feature? Separate PR first.

Balancing work and open source taught me this: I get maybe 90 minutes on weekends for OSS. A 50-line PR I can review in 10 minutes. A 3,000-line PR requires 3+ focused hours. I simply don't have that. Most maintainers don't either. โฐ

The Test Question ๐Ÿงช

Here's a filter that separates good contributors from great ones:

Did you write tests?

# The mindset shift
Before: "I'll add tests if they ask"
After: "No tests = no PR"

What good test coverage looks like:

// You fixed a bug where empty input crashed the parser
// BEFORE (just fixing the bug):
public function parse(string $input): array
{
    if (empty($input)) {
        return [];  // your fix
    }
    // ...
}

// AFTER (with tests - what maintainers LOVE):
public function testParseReturnsEmptyArrayForEmptyInput(): void
{
    $parser = new Parser();
    $this->assertSame([], $parser->parse(''));
}

public function testParseReturnsEmptyArrayForWhitespace(): void
{
    $parser = new Parser();
    $this->assertSame([], $parser->parse('   '));
}

In my Laravel contributions, I always check if there's an existing test file for the component I'm touching. If there is, I add tests right alongside the existing ones. If there isn't, I create one. Maintainers notice this. It tells them "this person cares about quality." ๐Ÿ™

The Code Style Trap โš ๏ธ

Scenario that plays out constantly:

You:       Use 4-space indentation everywhere
Project:   Uses 2-space indentation

Result:    Every line shows as modified
           PR diff is unreadable
           Maintainer is annoyed before reading a word

Before writing a single line of code:

# Read the contribution guide
cat CONTRIBUTING.md

# Check if there's a linter config
ls .eslintrc .phpcs.xml .rubocop.yml .editorconfig

# Run the project's linter on your changes
npm run lint
composer run-script phpcs

# Check existing tests pass
npm test
./vendor/bin/phpunit

Pro tip: Install EditorConfig in your editor. Most projects have an .editorconfig file that auto-formats to the project's standards. Zero effort, huge impact. ๐ŸŽฏ

In the security community, style consistency isn't just aesthetics โ€” inconsistent formatting can hide malicious code changes in noisy diffs. Maintainers of security tools are especially sensitive to this. Keep your diffs clean.

The Branch Name Nobody Cares About (But You Should) ๐ŸŒฟ

Bad:

fix
my-changes
patch-1
test123

Good:

fix/null-pointer-in-session-parser
feat/redis-session-backend
docs/php82-installation-guide

Why? Maintainers often work on multiple open PRs simultaneously. A descriptive branch name means when they see fix/null-pointer-in-session-parser in their branch list, they immediately know what they're looking at. Small thing. Big signal.

Handling Review Feedback Like a Pro ๐ŸŽญ

Getting your first round of review comments feels like this:

Maintainer: "The approach here is different from what we discussed"
Internal monologue: "WHY WON'T YOU JUST MERGE IT"

What actually matters:

When they request changes โœ…

DO:
- Reply to each comment (even just "Fixed!" or "Good catch, done!")
- Explain your reasoning if you disagree
- Ask clarifying questions respectfully
- Push fixes promptly (don't let it sit for weeks)
- Say "Ready for re-review" when you're done

DON'T:
- Argue aggressively
- Push fixes silently with no response
- Disappear for 3 weeks
- Mark every conversation as "Resolved" without engaging

The magic phrase when you disagree: "I see your point. My concern is [X]. Would [alternative approach] work, or would you prefer I go with your suggestion?"

That single sentence has saved 3 of my PRs from being closed. It signals that you're collaborative, not defensive. ๐Ÿ’ช

When they close it without merging โœ…

First โ€” don't panic. This happens to everyone, including experienced contributors.

Read the reason carefully:

"Not aligned with project direction"
โ†’ Next time: open an issue first, get buy-in

"Too large to review"
โ†’ Next time: break into smaller PRs

"Missing tests"
โ†’ Next time: write tests first

"API breaking change"
โ†’ Next time: check CHANGELOG for stability guarantees

The move: Thank them for their time. Seriously. Maintainers are usually volunteers. A genuine "Thanks for taking the time to review this โ€” I'll keep these points in mind for future contributions" goes a long way. And it puts your name in their memory as "that person who was cool about rejection." Future PRs get extra attention. ๐Ÿค

The Secret Weapon: The Draft PR ๐Ÿ”’

This changed everything for me.

Open a draft PR early โ€” even when you're just starting out. This signals:

  • "I'm working on this, please don't duplicate effort"
  • "I'd love early feedback before I go too far"
  • "I'm committed to finishing this"
# How to use draft PRs
1. Start working on your change
2. Push a WIP commit ("wip: initial redis backend sketch")
3. Open PR as Draft
4. Tag relevant maintainers: "@maintainer โ€” early draft for feedback"
5. Continue developing
6. When ready: click "Ready for Review"

Real example: I was implementing a complex caching layer for a Node.js library. I opened a draft after 2 hours of work with just the interface skeleton. The maintainer spotted that I was duplicating logic that already existed in a different module โ€” something I'd have never found browsing the docs alone. Saved me 6 hours of wasted work. ๐Ÿ˜…

Projects Where My PRs Consistently Get Merged ๐ŸŒŸ

These communities are exceptionally welcoming to contributors and have clear contribution guides:

PHP/Laravel ecosystem:

  • laravel/framework - Solid CONTRIBUTING.md, responsive maintainers
  • spatie/* (any Spatie package) - Stellar contributor experience, great documentation
  • nunomaduro/collision - Nuno is an amazing maintainer who gives detailed feedback

Security tools (my other home):

  • phpstan/phpstan - Great for static analysis contributions
  • enlightn/security-checker - Security-focused Laravel tooling

Developer tools:

  • charmbracelet/* - Beautiful terminal tools with fantastic community
  • sharkdp/bat, sharkdp/fd - Sharkdp's repos are models of maintainer friendliness

For first-time contributors: Look for repos with "good first issue" or "help wanted" labels. Filter GitHub search by label:"good first issue" language:php (or your language). These issues are specifically kept for new contributors. ๐ŸŽ

The Checklist I Use Before Every PR ๐Ÿ“‹

Pre-PR Checklist:

Research phase:
[ ] Searched for existing issues on this topic
[ ] Got maintainer agreement to accept this change
[ ] Read CONTRIBUTING.md fully
[ ] Checked open PRs for duplicates

Development phase:
[ ] Branched from main/master (not an old feature branch)
[ ] Branch name describes the change
[ ] Small, focused change (one concern)
[ ] Followed existing code style
[ ] Ran linter โ€” zero new warnings
[ ] Ran tests โ€” all passing
[ ] Added tests for new behavior

PR phase:
[ ] Descriptive title (Conventional Commits format)
[ ] Description explains what, why, how
[ ] Linked to relevant issue
[ ] Draft PR โ†’ early feedback โ†’ Ready for review
[ ] Responsive to review comments within 48 hours

Print this. Laminate it. Stick it next to your monitor.

The Bottom Line ๐Ÿ’ก

Open source contribution is a skill. The code is only half of it.

What you learned today:

  1. Issue first, code second โ€” always
  2. Small PRs beat big PRs every time
  3. Tests aren't optional
  4. Match the project's code style exactly
  5. Draft PRs for early feedback
  6. Respond to review quickly and graciously
  7. Rejection is data, not failure

The truth about merge rates:

Before knowing these rules:  ~5% merge rate
After following these rules: ~75%+ merge rate

That jump didn't come from writing better code. My code quality didn't magically improve overnight. It came from understanding that contributing to open source is a collaboration, not a submission.

Balancing work and open source taught me that maintainers have limited bandwidth. The contributors who thrive are the ones who make reviewing their PRs easy. Write the PR you'd want to review.

Your Action Plan ๐Ÿš€

This week:

  1. Find one project you use daily and read its CONTRIBUTING.md
  2. Browse the issues, find a "good first issue" label
  3. Leave a comment: "I'd like to work on this โ€” any guidance?"
  4. Wait for the green light, THEN write code

This month:

  1. Open your first (or next) draft PR
  2. Get early feedback before investing too many hours
  3. Iterate based on reviewer input
  4. Celebrate your first merged PR ๐ŸŽ‰

Ongoing:

  1. Review other contributors' PRs (you learn FAST by reviewing)
  2. Help triage issues
  3. Improve documentation (docs PRs merge VERY easily)
  4. Build relationships with maintainers

Have a PR story โ€” the good, the bad, or the hilariously bad? I'm on LinkedIn and would love to hear it.

Want to see what a real contribution workflow looks like? Check my GitHub โ€” you'll see the issues, draft PRs, and review conversations in action.

Now go open that issue before you write a single line of code! ๐ŸŽฏ


P.S. The 4-minute PR rejection? I went back to that project six months later. Filed an issue first, had a great discussion, wrote a focused fix. Got merged in two days. Redemption arcs are real. ๐ŸŒ…

P.P.S. If you're a maintainer: templates for PR descriptions help SO much. A filled-out template is 10x easier to review than a wall of unstructured text. Your contributors will thank you. Your reviewer sanity will thank you. ๐Ÿ™