Laravel Badge

Guide

How to Contribute to an Open Source Laravel Package

By Peter Fox

Open source runs on contributions. Every bug fix, test addition, and documentation improvement from someone other than the original author is what turns a single person's side project into something the wider community can trust.

But starting out as a contributor is harder than it looks. Most guides tell you to "just open a pull request." They skip the part where you figure out which issue to pick, how to set up the project so you can actually run the tests, what makes a maintainer want to merge your change, and how to handle the review that asks for three rounds of changes.

This guide covers all of that — including two things most contribution guides don't address: how to decide whether a contribution is actually worth making, and how to use AI tools without turning your PR into a burden for the people reviewing it. By the end you'll know how to move from "I want to contribute to this package" to "my pull request is merged."


Choosing what to contribute

Not all contributions are wanted. This is the thing most guides skip, and it's why a lot of well-intentioned PRs get closed without merging.

A maintainer's time is finite and largely unpaid. Every PR they receive — whether it's merged or not — costs them time to read, understand, and respond to. A contribution that doesn't align with the project's direction, doesn't fix a real problem, or adds complexity the maintainer doesn't want creates a burden rather than a benefit. Respect that by thinking carefully before you start.

Contributions that are almost always welcome

Confirmed bugs — If an issue exists with a clear reproduction case and the maintainer has acknowledged it's a bug, fixing it is the clearest possible contribution. The problem is defined, the expected behaviour is agreed on, and the value is obvious.

Missing test coverage — Packages frequently ship with gaps in their test suite. Adding tests for edge cases that aren't covered yet is low-controversy and high value. Check whether the project tracks coverage — uncovered paths in a code coverage report are a reliable map of where tests are needed.

Documentation for confusing behaviour — If the README or docblocks don't explain something clearly and you spent time figuring it out, that's a documentation contribution waiting to happen. The fact that you were confused is evidence that others will be too.

Reproducible issue reports — If you can take a vague bug report and produce a minimal failing test case, that's a real contribution even without a code fix. Maintainers often have more issues than time to reproduce them. A clear reproduction moves something from "unclear" to "actionable."

Contributions that are often unwanted

Unsolicited feature additions — Just because you want a feature doesn't mean the package should have it. A focused package that does one thing well doesn't need to become a Swiss Army knife. If acme/laravel-gravatar generates Gravatar URLs, the maintainer probably doesn't want it to also support Libravatar, generate initials avatars, and cache to S3. Open an issue and discuss first. The response tells you whether to proceed.

Style rewrites — Refactoring the whole codebase to your preferred patterns, renaming variables to suit your taste, or "modernising" code that works perfectly well is almost never welcome as an unsolicited PR. The maintainer wrote the code the way they wrote it. A style rewrite that touches fifty files creates a large diff, introduces merge conflicts for in-flight work, and provides no functional improvement.

Abstractions the project didn't ask for — "I noticed you do this pattern in three places, so I extracted it into a base class" sounds helpful. It usually isn't. Abstractions have a cost: they add indirection, they create coupling, and they can make the code harder to change later. Three similar lines are often fine. Introduce an abstraction when the maintainer agrees one is needed, not unilaterally.

Changes that reflect your preferences, not the project's — Switching from array_map to a collection chain, changing how exceptions are thrown, rewriting conditionals in a different style — these are aesthetic choices, not improvements. A PR description that says "I prefer this style" is a fast path to a closed PR.

The question to ask before you start

Before picking up any contribution, ask: is there evidence this project wants this change?

That evidence might be an open issue where the maintainer acknowledged the problem. It might be a help wanted label. It might be a roadmap or a discussion thread. If you can point to something the maintainer said, you're on solid ground. If your only evidence is that you personally want the change, open a discussion first and wait for a response before writing any code.


Before writing a single line of code

Most contributions fail before any code is written. The work is wasted because the contributor skipped steps that would have told them the contribution was unwanted, already in progress, or solving the wrong problem.

Read the contributing guide

Most maintained packages ship a CONTRIBUTING.md. Read it. It tells you:

  • How to set up the development environment
  • The branching strategy (submit against main? against 2.x?)
  • Whether you need to open an issue before a pull request
  • Code style requirements and how to run the formatter
  • Whether the project is accepting contributions at all (some packages in maintenance mode explicitly aren't)

Skipping this and submitting a PR that follows the wrong process is a fast way to get your contribution closed without review.

Find the right issue

The open issues list is where contribution opportunities live. A few categories are worth knowing about:

good first issue — Most packages tag issues that are straightforward and well-scoped. These exist because maintainers want new contributors to have a good experience. They're not dumbed down; they're defined clearly enough that you can attempt them without deep context about the whole codebase.

help wanted — Issues the maintainer wants fixed but doesn't have time to get to. Often more complex than good first issues, but explicitly open to outside contributions.

Bug reports with a reproducible case — If an issue includes a failing test case or a minimal reproduction script, the hardest part of the contribution is already done for you. You just need to make it pass.

Finding these issues one repository at a time is tedious. findapr.io aggregates good first issue and help wanted labels across hundreds of Laravel repositories and packages in one searchable list — a faster way to find a well-scoped opportunity than browsing each project's issue tracker individually.

Check for an existing PR

Before you start work, search open (and recently closed) pull requests for the same issue. Finding out you duplicated someone else's open PR after you've spent two hours on it is demoralising. If a PR exists but has been stalled for weeks, comment to ask whether it's still being pursued — sometimes the original author has moved on and a maintainer will welcome a fresh attempt.

Communicate intent before investing time

For anything beyond a trivial fix, leave a comment on the issue saying you're planning to work on it. This does several things:

  1. The maintainer may give you direction that saves hours of work in the wrong direction
  2. It prevents two contributors from doing the same work in parallel
  3. It opens a conversation about the right approach before you're invested in a particular implementation

Suppose you've found issue #47 on acme/laravel-gravatar: gravatar(0) silently returns an avatar at the default size of 80 instead of passing 0 through. You might post:

I'd like to take this one. Looking at HasGravatar::gravatar(), the $size ?: 80 fallback treats 0 as falsy and substitutes the default — which looks like the bug. My plan is to pass $size straight through to GravatarUrl and add a test covering the zero case. Does that match your read of it?

A comment like this is not asking for permission. It's demonstrating that you've already read the relevant code and thought about the fix, and giving the maintainer a chance to steer you before you've written anything.


Setting up for development

The goal is to run the existing test suite successfully before making any changes. If you can't get the tests green on the unmodified codebase, your changes will produce noise you can't interpret.

Fork and clone

Fork the repository on GitHub, then clone your fork. Don't work on a branch of the original repository — you don't have push access, and working on a fork is the expected model:

git clone git@github.com:yourname/laravel-gravatar.git
cd laravel-gravatar

Add the original repository as a remote called upstream. You'll use this later to keep your fork in sync:

git remote add upstream git@github.com:acme/laravel-gravatar.git

Install dependencies

Package repositories almost never commit their composer.lock. Without a lockfile, composer install and composer update behave identically. Either works:

composer install

If the package requires a specific PHP version you don't have locally, consider using a Docker-based PHP environment or checking whether a .phpenv or .php-version file hints at the expected version.

Run the test suite

./vendor/bin/phpunit

All tests should pass. If they don't, look at the README.md or CONTRIBUTING.md for setup instructions you may have missed — sometimes a database connection or a service key is required. If tests fail with no obvious explanation, open an issue noting the failure. Broken test suites on the default branch are a bug worth reporting.

Create a feature branch

Never work on main directly. Create a descriptively named branch:

git checkout -b fix/gravatar-zero-size

Use the prefixes the project asks for (fix/, feat/, docs/), or check recent merged PRs to see what naming convention the maintainer uses. Matching the convention shows you've paid attention.


Making the change

Understand before you edit

Before touching any files, read the code around the thing you're changing. How is it called? What does it depend on? Are there other methods with similar patterns you should follow?

For a bug fix, find the exact line causing the problem and trace through why it behaves incorrectly before writing any fix. A fix that addresses the symptom rather than the cause will come back as a regression later.

Picking up issue #47 from earlier: gravatar() ignores its $size argument whenever the value is 0.

// HasGravatar.php — current implementation
public function gravatar(int $size = 80): string
{
    return (new GravatarUrl($this->email))
        ->size($size ?: 80)  // bug: 0 is falsy, falls back to 80
        ->build();
}

The fix is a one-liner, but reading the surrounding code first means you see the GravatarUrl class, understand that size() sets the s= query param, and know you won't need to change anything else.

Write the test first

If the issue includes a failing test case, run it and confirm it fails before making any changes:

./vendor/bin/phpunit --filter test_gravatar_passes_through_an_explicit_zero_size

If there's no test, write one. Start with a test that fails for the reason described in the issue, then make it pass:

// tests/Unit/HasGravatarTest.php
public function test_gravatar_passes_through_an_explicit_zero_size(): void
{
    $user = new User(['email' => 'test@example.com']);
    $url = $user->gravatar(0);

    $this->assertStringContainsString('s=0', $url);
}

The test fails. Now fix the code:

// HasGravatar.php — corrected
public function gravatar(int $size = 80): string
{
    return (new GravatarUrl($this->email))
        ->size($size)
        ->build();
}

The test passes. Run the full suite to check you haven't broken anything:

./vendor/bin/phpunit

All green. The change is two lines — one removing a falsy fallback, and one line of test. That's a complete, correct contribution.

Match the code style

Most packages use Laravel Pint or PHP CS Fixer for code style. If the CONTRIBUTING.md says to run Pint before submitting, run it:

./vendor/bin/pint

Some repositories configure a pre-commit hook that formats automatically. Others enforce style via CI. Either way, a PR that includes unrelated whitespace diffs or style violations creates noise in the review and signals that you didn't read the contributing guide.


Using AI tools as a contributor

AI coding assistants are now part of many developers' workflows. Used well, they can help you contribute faster and with less friction. Used poorly, they produce a new category of low-quality PR that wastes everyone's time — including yours.

Check the project's policy first

Some packages have explicit policies about AI-generated code. Check the CONTRIBUTING.md before using any AI tooling on a contribution. Policies range from outright prohibition to full acceptance to "AI-assisted is fine, AI-generated without review is not." Whatever the policy says, follow it — even if you disagree with it. Submitting AI-generated code to a project that prohibits it is not a grey area, and it damages trust in a way that's hard to recover from.

If the project has no stated policy and you're uncertain, a single comment on the issue — "I'm planning to use AI assistance for parts of this, is that acceptable?" — takes thirty seconds and avoids an awkward conversation later.

Where AI assistance genuinely helps

Understanding unfamiliar code — A well-prompted AI can explain what a method does, trace a call chain through the codebase, and surface the relevant context faster than reading every file yourself. This is probably the highest-value use for contributors: accelerating comprehension, not replacing it.

Generating test scaffolding — AI is good at producing boilerplate. A prompt like "write a PHPUnit test for a method that does X" gives you a starting structure to fill in correctly, rather than a blank file. You still need to verify that the test actually fails before the fix and passes after it.

Drafting commit messages and PR descriptions — Turning a diff into a coherent description is a task AI handles reasonably well. A generated draft is a starting point — edit it to reflect exactly what you changed and why.

Where AI assistance goes wrong

The failure modes are consistent enough to be worth naming directly.

Hallucinated APIs — AI tools confidently generate code that calls methods that don't exist in the version of Laravel or the package you're working with. The code looks plausible, passes a syntax check, and fails at runtime. Every method call in AI-generated code needs to be verified against the actual codebase.

Tests that test nothing — AI-generated tests frequently produce assertions that will pass regardless of whether the code is correct. An assertion that just checks the return value isn't null, or asserts true against a boolean that's always true, provides no coverage. Read every assertion in an AI-generated test and ask what it would take to make it fail.

Changes that don't address the root cause — When you describe a bug symptom to an AI, it tends to patch the symptom. For acme/laravel-gravatar's size-zero bug, an AI might add if ($size === 0) { $size = null; } before the call — technically making the test pass, but burying the actual fix. You need to understand the root cause yourself before you can judge whether the generated fix is correct.

Scope creep — AI tools presented with a codebase tend to suggest improvements beyond the task at hand. Refactoring nearby code, adding exception handling, extracting classes. These suggestions may be technically reasonable and are still out of scope. A contribution that mixes a bug fix with a refactor suggested by an AI is harder to review and easier to reject.

The ownership test

Before submitting any PR, apply this test: can you explain every changed line, in your own words, to a reviewer who asks about it?

If the answer is no for any part of the diff, don't submit yet. Either work through the code until you understand it, or remove the parts you can't explain. A PR review will surface exactly the lines you don't understand — in public, on the permanent record of a project you were hoping to be welcomed into.

AI assistance is legitimate. Being unable to explain your own PR is not. The contribution is yours; own it completely.


Writing a good pull request

Keep it small and focused

The most important property of a contribution is focus. A pull request that fixes one bug, adds one feature, or improves one thing is easy to review, easy to revert if it causes a problem, and easy to understand six months later.

Avoid the temptation to clean up nearby code while you're in the file. That's a separate change that deserves a separate PR. Mixing it in forces the reviewer to untangle what's relevant to the fix from what's incidental cleanup.

If you notice other things that need fixing while working, open issues for them. They're valuable observations — just not part of this PR.

Write a useful description

The PR title should complete the sentence "If merged, this PR will…":

  • Fix gravatar() ignoring size=0 — good
  • Fix bug — not good
  • Various improvements — avoid

The body should answer three questions:

What does this change? One or two sentences. Describe the change in human terms, not code terms.

Why? Link to the issue. If there's no issue, explain what was wrong and why the current behaviour is a bug or missing feature.

How did you verify it? Describe what you tested. This is usually just "I added a test case — see tests/Unit/HasGravatarTest.php." For more complex changes it might include manual testing steps.

Here's what a complete PR description looks like for our example:

Fix gravatar() ignoring size=0

Fixes #47

gravatar(0) was falling back to size 80 because $size ?: 80 treats 0 as falsy. This was unintentional — a size of 0 is valid and tells Gravatar to use its own default.

Fixed by removing the falsy fallback and passing $size directly to GravatarUrl::size().

Added a test case in HasGravatarTest that asserts gravatar(0) produces a URL with s=0.

That's the entire description. Short, complete, useful.


Navigating the review

Expect feedback

A maintainer reviewing your PR is not criticising you personally. They're applying context you don't have: performance characteristics you didn't know about, API choices made three years ago for a reason not documented anywhere, plans for a v2 that makes your approach the wrong direction.

When you get review comments, read each one charitably. If something is unclear, ask for clarification. "I'm not sure I understand — could you expand on why the current approach would cause problems?" is a perfectly reasonable response.

Make changes in new commits during review

While review is ongoing, push additional commits rather than force-pushing rewrites of earlier commits. This makes it easy for the reviewer to see what changed since they last looked:

# After making the requested changes
git add src/HasGravatar.php
git commit -m "Address review: use explicit null check instead of ternary"
git push origin fix/gravatar-zero-size

Once the PR is approved and ready to merge, the maintainer will typically squash or merge the commits themselves. Don't worry about a messy commit history during review — the history that matters is in the final merge, not the PR branch.

Handle disagreements carefully

If a reviewer requests a change you disagree with, explain your reasoning once, clearly and without defensiveness. Share any relevant evidence: test results, documentation, a similar pattern elsewhere in the codebase.

If the maintainer still wants the change their way after hearing your reasoning, make the change. They have final authority over their own project. A contribution that gets merged with compromises is more valuable than a standoff that closes without merging.

If you believe the maintainer is mistaken about something factual — a bug in their reasoning, a misread of the test output — point it out directly but without escalating. Most disagreements resolve when both sides are looking at the same information.


After the merge

Delete your branch

Once the PR is merged, delete the remote branch. GitHub offers a button for this in the PR view. Clean up the local branch too:

git checkout main
git branch -d fix/gravatar-zero-size

Sync your fork

Your fork's main branch is now behind the upstream. Sync it:

git fetch upstream
git merge upstream/main
git push origin main

This keeps your fork clean for the next contribution.

Stay engaged

Watch the repository for a week or two after your fix is merged. If the change introduced a regression that shows up in CI or in someone else's issue, you'll know the context and can help quickly. Maintainers notice contributors who stay engaged after a merge — it's a quality signal.


Building a track record

A single merged contribution is a start. A pattern of reliable contributions over time is how you become someone a maintainer trusts with more responsibility: triaging issues, reviewing other contributors' PRs, and eventually holding write access to the repository itself.

The path there is consistent behaviour: small focused PRs, thorough tests, clear descriptions, responsive review handling, and no dropped threads. The technical bar for most contributions is not high. The bar that most people don't clear is the process bar.

Most package maintenance happens in spare evenings, for no pay, on top of a day job. A contributor who makes that load lighter — by submitting things that are ready to merge, by asking questions before investing a week in the wrong direction, by treating the maintainer's time as valuable — will always be welcome back.

That's the whole model. Everything else is just code.