Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Maintaining Laravel Package Compatibility Across Framework Releases

A Laravel package runs as part of a Laravel application, so compatibility means more than being able to install its PHP code. Composer must find dependency versions that work together, Laravel must load the package, and the package’s features must work with every framework release it claims to support. A Composer version constraint is the rule that tells Composer which dependency versions it may select.

Package maintainers should back each compatibility claim with tests. They should state which Laravel versions the package supports and test it against every Laravel major version in that range. Major releases can introduce breaking changes. Laravel can also deprecate APIs, change defaults, or change framework behavior in ways that affect a package even when it installs successfully. The practices below show how to define a support range, test it, and respond when Laravel changes an integration point.

Declare only tested Laravel versions

Use Composer constraints to describe the Laravel releases the package supports. For example, a package tested on Laravel 11, 12, and 13 could declare:

{
  "require": {
    "laravel/framework": "^11.0 || ^12.0 || ^13.0"
  }
}

The caret (^) lets Composer choose compatible updates within each listed major version, but excludes the next major version. Laravel recommends bounded, major-specific constraints because major releases can contain breaking changes. Composer also warns that an unbounded constraint such as * can let users install a later major that the package has not been tested against.

A package that uses only specific Laravel components can require those components instead of the full laravel/framework package. For example, a package that extends Laravel’s service-provider base class may depend on illuminate/support. Choose the dependency based on the APIs the package actually uses, then verify that Composer can resolve it for every supported Laravel release. Laravel describes package service providers and component dependencies in its package development documentation.

A constraint does not prove that the package works. It only tells Composer which versions it may install. Once a package release has been tagged, maintainers cannot change that release’s dependency requirements retroactively. If the published range claims compatibility that the package does not have, maintainers need a new release with corrected constraints and, where necessary, code changes.

Test the supported combinations

Run package tests against every Laravel major listed in composer.json. Laravel packages can use Orchestra Testbench, which runs a Laravel environment inside the package’s test suite. The Testbench Core compatibility table maps Laravel 11, 12, and 13 to Testbench Core 9, 10, and 11, respectively. That mapping helps select the test environment; it does not establish that the package itself passes on those versions. Check the Testbench Core compatibility table when updating the matrix.

Each Laravel version must be paired with a PHP version that Laravel supports. Laravel’s release table lists PHP 8.2–8.5 for Laravel 12 and PHP 8.3–8.5 for Laravel 13. A package’s own PHP constraint can narrow those combinations further. Select combinations that reflect the package’s actual support policy instead of testing every possible pairing.

GitHub Actions matrices let maintainers run separate jobs for specified combinations. For example, a matrix can explicitly pair each Laravel version with compatible PHP and Testbench versions:

strategy:
  matrix:
    include:
      - laravel: 12
        php: "8.2"
        testbench: "^10.0"
      - laravel: 12
        php: "8.5"
        testbench: "^10.0"
      - laravel: 13
        php: "8.3"
        testbench: "^11.0"
      - laravel: 13
        php: "8.5"
        testbench: "^11.0"

This example checks the ends of each listed PHP range, not every PHP version. Add combinations that matter to the package’s stated policy, and exclude combinations that Laravel or the package does not support. GitHub documents how matrix jobs create and filter combinations.

Test the points where package code meets Laravel, not just isolated classes. For a package that registers routes, verify that Laravel loads those routes. For one that publishes configuration or migrations, check that the published files and database behavior match expectations. Tests should also cover relevant container bindings, commands, events, queues, views, or other integrations the package provides. Laravel’s package documentation describes these integration points, and its database testing guidance explains options for keeping database tests isolated.

Run quick unit tests and static checks for every change. Run the Laravel compatibility matrix when a change affects framework integration and before each release. The precise schedule depends on the package, but the release check should confirm that the declared Laravel range matches the combinations that passed.

Replace deprecated APIs before they disappear

A deprecation means the framework discourages an API and directs developers toward a replacement. It gives package maintainers a reason to update references before a later release removes or changes the old path.

Laravel 13’s upgrade guide says the deprecated VerifyCsrfToken and ValidateCsrfToken aliases remain available after the middleware rename to PreventRequestForgery, and recommends updating direct references. Laravel 10’s guide documents a different outcome: deprecated Bus::dispatchNow and dispatch_now methods were removed, with dispatchSync and dispatch_sync as replacements. Maintainers should review each upgrade guide because deprecation periods vary. See the Laravel 13 upgrade guide and the Laravel 10 upgrade guide.

Search package code and tests for deprecated classes and methods. Check properties and behavior for deprecations too. When Laravel provides a replacement that works across the package’s minimum supported version, migrate to it and test the affected feature. If older supported releases require the old API, isolate the compatibility branch and exercise both paths in the matrix. Remove the fallback only when the package’s version policy permits dropping the older release, and describe the change in release notes.

Investigate behavior changes as well as errors

A framework upgrade can break a package without removing a class or method. Laravel’s upgrade guides document changes to framework contracts, which specify methods that package classes must provide. The guides also cover event-property changes and changed defaults. They explain when Laravel registers features, too. Laravel 13, for example, added methods to some framework contracts and changed behaviors involving container resolution and scheduling.

Prefer documented APIs. Private methods sit outside those APIs. Assumptions about class internals rely on details the API does not document. Reflection reads framework implementation details directly. Documented APIs still require testing: a package that implements a framework contract must update its implementation when Laravel adds a required method. Add tests that check the contract integration and the resulting behavior, such as which service the container resolves or when a scheduled task registers.

Be cautious when you call Laravel methods with PHP named arguments, which pass values by parameter name rather than by position. Laravel’s backwards-compatibility guidelines do not cover parameter names, which means a framework change to a parameter name can break a named-argument call even when the method still exists. Positional arguments avoid depending on those names; if package code uses named arguments, test those calls across the supported Laravel versions. Laravel’s release notes describe this compatibility boundary.

When an upgrade fails, reproduce it with the exact Laravel, PHP, and Testbench versions from the failing job. Then identify whether the failure comes from dependency resolution, package bootstrapping, a contract change, a changed default, or runtime behavior. Compare the relevant upgrade guides and release notes, write a regression test for the package behavior, and change the code or support range to match the result. Laravel says its upgrade guides aim to document breaking changes, while acknowledging that some relevant changes may appear elsewhere. The guides help narrow the investigation, but the package’s tests must verify the fix.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Email updates

Usually a new article and a few links I found interesting.

No spam. Unsubscribe with one click.

Leave a Reply

Your email address will not be published. Required fields are marked *