v23 -> v24
The v24 addon manifest: what every addon maintainer has to change to keep their addon loading on v24.
Light Store v24 replaces addon discovery-by-reflection with a manifest. Every addon now
declares itself in its own composer.json, and the store wires the addon into Laravel by
convention instead of making each addon ship a service provider that does it by hand.
This is a breaking change
An addon built for v23 or earlier will not be discovered at all on v24. Every addon needs the migration below.
Why it changed
Discovery used to scan every top-level .php file in an addon folder, reflect on each class, and
look for a StoreAddon subclass. The identifier, name and version lived as protected properties on
that class, so the store had to load and instantiate PHP before it could tell you what an addon
even was. A broken addon took the panel down with it, a disabled addon could not name itself, and
nothing could be validated before install.
The manifest is read as plain JSON, so the store now knows an addon's identity, its version requirements and its dependencies without executing any of its code.
v23 vs v24
| v23 | v24 |
|---|---|
Identity in protected string $identifier etc. | Identity in composer.json under extra.lightstore |
| Main class found by reflection over top-level files | Main class named by extra.lightstore.addon |
Classes autoloaded from a hardcoded Addon\{Folder} guess | Classes autoloaded from autoload.psr-4 |
| Each addon ships a provider that loads routes/views/lang | Loaded by convention, provider optional |
Routes registered inside the provider's boot() | routes/web.php, grouped under web for you |
Name and description translated with __() | Plain strings in the manifest, no longer translatable |
config('settings.general.site_name') in Blade | $generalSettings->site_name |
Install by unzipping, then php artisan migrate by hand | Admin panel, Addons then Install addon |
Migrating an addon
Add a composer.json
Create it at the root of your addon folder, next to the main class. This file is now the single source of truth for your addon's identity.
{
"name": "nortexdev/lightstore-points",
"description": "Reward users with loyalty points on every purchase",
"version": "2.0.0",
"type": "library",
"license": "MIT",
"homepage": "https://lightstore.nortex.dev",
"authors": [{ "name": "NorteX" }],
"require": {
"php": ">=8.4.0",
"nortexdev/lightstore": ">=24.0"
},
"autoload": {
"psr-4": {
"Addon\\PointsAddon\\": ""
}
},
"extra": {
"laravel": {
"providers": ["Addon\\PointsAddon\\Providers\\PointsServiceProvider"]
},
"lightstore": {
"identifier": "points",
"name": "Points",
"addon": "Addon\\PointsAddon\\PointsAddon"
}
}
}The identifier is a data key
extra.lightstore.identifier is the primary key in the addon_settings table, and the namespace for your views,
translations and config. Use exactly the string your v23 addon returned from getIdentifier(). Changing it orphans
every stored setting, so treat it as a data migration rather than a rename.
Strip the metadata off the main class
All six identity properties are gone from StoreAddon. Delete them, along with any getName() or
getDescription() override. Those values now come from the manifest.
class PointsAddon extends StoreAddon
{
protected string $identifier = "points";
protected string $version = "1.1.0";
protected string $author = "NorteX";
public function getName(): string
{
return __("points::addon.Points");
}
public function getDescription(): string
{
return __("points::addon.Reward users with loyalty points on every purchase");
}
public function getConfig(array $values = []): array { /* ... */ }
}class PointsAddon extends StoreAddon
{
public function getConfig(array $values = []): array { /* ... */ }
}The properties that no longer exist are $identifier, $name, $version, $description,
$author and $url.
The getters themselves stay and keep working. getIdentifier(), getName(), getVersion(),
getDescription(), getAuthor() and getUrl() all read through to the manifest. Three members
are new: getManifest(), getPath() for the absolute path to your addon folder, and
setManifest(), which the store calls for you right after it constructs your class.
Every hook method is unchanged. getConfig(), boot(), enabled(), disabled(), the
Filament getters, the cart and product view hooks, getProfileTabs(), getDataExport() and
getAccountDeletionSummary() all keep their exact v23 signatures.
Delete the service provider boilerplate
The store now loads routes, views, translations, migrations and config for you. For most addons the
whole service provider goes away. Drop it, and leave extra.laravel.providers out of the manifest
entirely.
class KnowledgebaseServiceProvider extends ServiceProvider
{
public function boot(): void
{
$routesPath = __DIR__ . "/../routes/web.php";
if (file_exists($routesPath)) {
Route::middleware("web")->group(fn() => require $routesPath);
}
$viewsPath = __DIR__ . "/../resources/views";
if (is_dir($viewsPath)) {
$this->loadViewsFrom($viewsPath, "knowledgebase");
}
$langPath = __DIR__ . "/../lang";
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, "knowledgebase");
}
$migrationsPath = __DIR__ . "/../database/migrations";
if (is_dir($migrationsPath)) {
$this->loadMigrationsFrom($migrationsPath);
}
}
}Keep a provider only for the things the conventions do not cover, such as event listeners, container bindings, macros and policies. Once the boilerplate is gone, it usually shrinks to just that:
class PointsServiceProvider extends ServiceProvider
{
public function register(): void {}
public function boot(): void
{
Event::listen(ItemPurchased::class, GrantPointsListener::class);
}
}Move routes into routes/web.php
Routes defined inside a provider's boot() need to move to a routes/web.php file in your addon
folder. The store groups that file under the web middleware itself.
<?php
declare(strict_types=1);
use Addon\PointsAddon\Http\Controllers\PointsController;
use Illuminate\Support\Facades\Route;
Route::middleware("auth")->group(function () {
Route::get("/profile/points", [PointsController::class, "show"])->name("points.profile");
});Do not re-apply `web`
The store already wraps the file in Route::middleware("web"). Listing web again in your own group runs the
session, cookie and CSRF middleware twice.
Drop the name and description translation keys
Addon names and descriptions are no longer translatable. They are plain strings, read from
extra.lightstore.name and the manifest's top-level description. Remove those two keys from
every lang/{locale}/addon.php. Everything else in your language files is untouched.
return [
"Points" => "Points",
"Reward users with loyalty points on every purchase" => "...",
// Settings
"Earn Multiplier" => "Earn Multiplier",
];Keep a "Points" style key if your own views, navigation links or getConfig() labels still use
it. Only the two entries the admin panel used to read are obsolete.
Check your Blade against the settings change
Separately from the manifest work, v24 removed the settings-into-config() injection. Any addon
view still calling config('settings.*') now reads null.
@section('title', __('points::addon.Points') . ' - ' . config('settings.general.site_name')) {/* [!code --] */}
@section('title', __('points::addon.Points') . ' - ' . $generalSettings->site_name) {/* [!code ++] */}Every settings group is shared with all views as a variable named after its class:
$generalSettings, $captchaSettings, $footerSettings, $integrationsSettings,
$oauth2Settings, $socialSettings and $statsSettings. Outside Blade, resolve the class
directly with app(GeneralSettings::class).
After migrating
The conventional layout, the full manifest reference, packaging and install errors are documented in Addon Development, which always describes the current release. Continue with v24 -> v25.