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

Adding Features to Laravel Packages Without Breaking Stability

When you maintain a Laravel package, a new feature can affect more than the PHP method that implements it. Users rely on package configuration and service-provider behavior. They also rely on routes, commands, events, published files, and documented side effects. Changing any of these can require users to make upgrade changes, even if the existing method signatures stay the same.

Treat these behaviors and interfaces as part of the package’s public contract, which means users can rely on them. Semantic Versioning, or SemVer, uses version numbers to show the impact of changes. Define the contract, add features without silently changing existing behavior, and give users clear steps to update their code or configuration when a change affects them.

Define what users can rely on

Before changing anything users rely on, list the package interfaces you support. Include documented PHP classes and methods, along with extension points. List configuration keys and defaults. Include container bindings, which tell Laravel how to find or create services. Include Laravel integrations such as routes and commands. Include events too. Also consider published files and lasting effects, such as database changes or cache formats, if users rely on them.

A Laravel service provider connects a package to the framework, for example by registering services or loading package resources. Laravel’s package development documentation describes these integration points, including provider registration and package discovery. If users install your package through automatic discovery or depend on a route your provider registers, changes to that behavior belong in your compatibility review.

Make the boundary explicit in documentation. Identify supported classes and configuration options, and mark internal code as internal. If you document a class as an extension point or encourage users to override it, treat it as supported even if you intended it as an implementation detail.

Configuration deserves particular care because users may publish a package’s configuration file and maintain their own copy. Adding an option with a backward-compatible default is generally less disruptive than renaming a key, changing its type, or changing what happens by default. Laravel’s configuration documentation also explains that mergeConfigFrom merges only the first level of an array. When you add settings inside a nested configuration array, check whether existing published files still provide the values your package expects.

Add features without changing old paths

Prefer adding a new method, option, driver, or integration over changing the meaning of an existing one. For example, a package that adds a new way to select a service can expose a new configuration option while preserving the current default. Users who want the new behavior can opt in, while existing installations continue along the same path.

Review side effects as well as method signatures. If a feature starts emitting an event, users are affected even when their calls to the package stay the same. The same is true when it registers a route or writes data in a new format. If the feature changes the default behavior, say so and decide whether users must enable it themselves or update their existing configuration.

Test the feature in the Laravel context where users will run it. Isolated unit tests check a method or class. Package integration tests check how Laravel loads the provider and finds services. They also check how it reads configuration and handles routes or commands. Laravel’s package documentation points package authors to Orchestral Testbench for tests that run as if the package were installed in a Laravel application. Laravel’s testing documentation also distinguishes isolated unit tests from feature tests that exercise interactions among objects or a full request.

Keep tests for existing documented usage alongside tests for the new feature. If your package claims compatibility with multiple PHP or Laravel versions, test the versions you claim to support. Your composer.json declares dependency constraints, but those constraints do not replace tests that verify the package works across the declared range.

Deprecate before removing

A deprecation notice tells users that you plan to retire a supported API or behavior. It gives them time to switch; the old option or behavior still works during this period. Introduce the replacement first, keep the old path working, and name the replacement wherever users will see the notice.

Name the exact method, option, or behavior being deprecated, show what users should use instead, and state whether the old path still works. If you have a removal target, name the planned version. If you have not scheduled removal, say so rather than implying a deadline.

SemVer requires a minor-version increment when you deprecate public functionality, and a major-version increment when you remove public functionality in a backward-incompatible way. It does not require a fixed waiting period or number of releases between the two. Keep a Changelog recommends making deprecations and removals visible and identifying the version in which removal is expected. For a package below 1.0.0, SemVer treats the public API as unstable, so publish your own expectations clearly rather than assuming users know how much change to expect.

Match releases to compatibility impact

For a package using SemVer after 1.0.0, classify each release by how it affects the declared public contract:

Change Release increment
Backward-compatible bug fix Patch
Backward-compatible new feature Minor
Deprecation of public functionality Minor
Backward-incompatible change or removal Major

These rules come from SemVer 2.0.0. A changed default or new side effect needs judgment: classify it by its real effect on supported usage, not only by whether it changes a method signature.

Accurate release classification helps package users choose compatible updates through Composer constraints. Composer’s caret constraint ^1.2.3, for example, allows compatible updates below 2.0.0 but excludes the next major version. Composer recommends the caret operator for library constraints in its version and constraint guidance. Users rely on these constraints when upgrading, so publish a major release when a change breaks the compatibility you promised.

Keep published releases immutable. If a tagged release contains a breaking mistake, publish a corrected version instead of rewriting the existing tag or package contents. SemVer requires released versions to remain unchanged; the new release becomes the correction users can install.

Laravel’s framework release policy offers an example of a compatibility promise, but it does not automatically govern independent packages. Laravel states that its framework and first-party packages follow SemVer and describes which changes belong in major releases. Your package should publish its own supported PHP and Laravel versions, versioning policy, and support expectations.

Explain the impact in release notes

Write release notes that help users decide whether to upgrade and what to change. Each entry should say what changed, which users or configurations it affects, whether the change breaks compatibility, and what users should do. For a configuration change, name the affected key and say whether users with a published configuration file need to edit it. For a deprecated method, name its replacement.

A changelog should summarize user-visible changes rather than reproduce commit messages. Group entries under clear labels such as Added, Changed, and Deprecated, and call out removals and breaking changes plainly. When a change affects many installations or needs several migration steps, publish an upgrade note or announcement as well as the changelog entry. Laravel’s upgrade guide shows how to describe scope and identify which users are affected by a behavior change.

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 *