Overview
Lunar v2 is a wholesale breaking release. The core package moves to theLunar\Core\… namespace, the admin panel splits into a lunarphp/filament bridge package, migrations are flattened into a single v2 baseline, and a long list of renamed columns, renamed classes, and reshaped data (pricing, selling policy, order status, regions, fulfilment) ship alongside it. See What’s new in Lunar v2 for the full picture of what changed and why.
A dedicated package, lunarphp/upgrade, automates as much of the v1 to v2 transition as possible:
- Rector rules rewrite class references, renamed methods, renamed properties, and money-attribute access patterns across application code.
- Data migrations transform the existing v1 database in place (column renames, enum reconciliation, backfilling new v2 concepts such as regions and fulfilments) rather than requiring a fresh install.
- A guided CLI,
php artisan lunar:upgrade, runs the composer, data-migration, and migrations-ledger steps in order, with a dry run mode and a manual-actions report at the end.
lunarphp/upgrade is a dev dependency of the application being upgraded. It is not a runtime dependency of Lunar v2 itself, and it is not published as part of a fresh v2 install.
Before upgrading
- Back up the database. This is the only recovery path if the upgrade needs to be undone.
- Commit or stash any uncommitted application changes.
- Make sure the application is fully up to date on the latest Lunar v1.x release and all its migrations have run. The upgrade command refuses to proceed otherwise.
- Run the upgrade against a staging copy of the database first, given the size of the breaking surface between v1 and v2.
Coming from GetCandy (pre-v1.x)? The
lunar:migrate:getcandy command that handled that transition exists only on Lunar v1.x and has been removed in v2. Reach v1.x first, run that command there, then continue with this guide.Requirements
Lunar v2 raises the minimum platform requirements:- PHP 8.4 or later.
- Laravel 12 or 13.
- Filament v5, if using the Filament-based admin panel (
lunarphp/filament/lunarphp/admin).
Step-by-step
1. Require the upgrade package
2. Run the upgrade command
composer-require-rewrite— rewrites the application’scomposer.json, swappinglunarphp/lunarforlunarphp/admininrequire/require-dev, and adding an explicitlunarphp/corerequirement if it was previously only pulled in transitively. Runcomposer updateafterwards to refresh the lockfile.rector— reports how many class references the bundled Rector rules would rewrite across the configured paths.data-migrations— runs the package’s data migrations against the existing v1 schema: class-string rewrites, column renames, enum value reconciliation, and backfilling new v2 concepts (regions, fulfilments, stock,public_id).ledger-rewrite— rewrites the application’smigrationstable so v1 Lunar migration rows are removed and the v2 flat baseline is recorded as already run, so future v2.x migrations layer on cleanly.
The This covers the class, method, property, and money-attribute rewrites described below. Run it before or after the
rector step in lunar:upgrade currently reports the rewrite plan rather than invoking Rector directly. Run Rector against the application separately, using the rule set the upgrade package ships:lunar:upgrade data migrations — the two operate on code and data respectively and do not depend on each other’s order.3. Review the manual-actions report
After the data migrations run,lunar:upgrade prints a report of what happened per step, followed by any manual actions that need attention. Currently this includes:
Lunar\DiscountTypes\AmountOffhas been split intoLunar\Core\DiscountTypes\PercentageOffandLunar\Core\DiscountTypes\FixedAmountOff. Stored discount records are converted automatically (based on theirdata.fixed_valueflag), but source code referencingAmountOffneeds to be updated by hand to whichever type it meant, since a rename rule cannot infer that. The discount’sdata.fixed_valueskey is renamed todata.amounts, and thedata.fixed_valueflag is removed.
4. Publish config and clear caches
lunar.upgrade config (published from lunarphp/upgrade) before running the data migrations if the store uses non-default order statuses — see Order status mapping below.
What Rector automates
The bundled Rector configuration (vendor/lunarphp/upgrade/config/rector.php) applies several categories of rewrite across app/, config/, and database/ by default:
- Namespace moves. Every class shipped by
lunarphp/coremoves fromLunar\…toLunar\Core\…(for exampleLunar\Models\ProductbecomesLunar\Core\Models\Product,Lunar\Facades\CartSessionbecomesLunar\Core\Facades\CartSession). Filament support classes extracted into the newlunarphp/filamentbridge package move fromLunar\Admin\Support\…toLunar\Filament\…. - Renamed methods and properties, including
ProductType::mappedAttributes()→attributeMapping(),Price::$compare_price→$list_price, andProductVariant::$purchasable→$selling_policy(class-scoped, so the unrelatedCartLine/OrderLinepurchasablemorph relation and thecustomer_group_product.purchasablepivot boolean are left untouched). - Money-attribute access. v1’s per-attribute
PriceDataTypevalue object is gone; casts now return raw integers with formatting on the model. Rector rewrites$order->total->valueto$order->total,$order->total->formatted()to$order->format('total'), and$price->price->unitDecimal()/unitFormatted()on the cataloguePricemodel to$price->unitDecimal('price')/$price->unitFormat('price'). Model::modelClass()calls rewrite toModel::class, since model class substitution has been removed (see Model extending below).Order::refund()call sites rewrite from three positional arguments to aLunar\Core\DataObjects\RefundRequest:$order->refund($transactionId, $amount, $notes)becomes$order->refund(new RefundRequest(transactionId: $transactionId, adjustment: $amount, notes: $notes)).- Action call sites.
SomeAction::run(...$args)rewrites toapp(SomeContract::class)->execute(...$args), following the move to constructor-injected action classes bound to a contract. - Translated catalogue field access.
$product->translateAttribute('name')/$product->attr('name')rewrite to$product->translate('name')fornameanddescription, now dedicated translatable columns instead ofattribute_dataentries. - Custom price formatter signatures. A consumer’s own
PriceFormatterInterfaceimplementation has its$formatterStyleparameter retyped fromstringtointonformatted(),unitFormatted(), andformatValue(), matching theNumberFormatter::*constants it receives.
What the data migrations do
The package ships one data migration per v1 to v2 schema change, run in dependency order by thedata-migrations step:
- Class-string rewrite — updates persisted class strings (discount conditions, purchasable morphs, and similar) from
Lunar\…toLunar\Core\…. compare_price→list_price— renames the column onprices.- Catalogue
name/descriptioncolumns — adds dedicated translatable columns and migrates the equivalentattribute_datavalues onto them. - Attribute data keys to IDs — converts the attribute system’s handle-keyed storage to id-keyed storage.
- Order line
requires_fulfilmentbackfill. - Stock backfill — populates the new per-location
StockLevel/StockMovementrecords from the v1 flatstockcolumn. - Default region seed and backfill — v1 has no region concept; this seeds a catch-all default
Regionfrom the v1 default channel, currency, and language, and backfillsregion_idon existing carts and orders. - Shipping line purchasable morph nulling — shipping order lines no longer store a placeholder morph.
purchasable→selling_policy— renames the column onproduct_variants, reconciles stored values onto theSellingPolicyenum’s canonical set (correcting thein_stock_or_backordertypo some v1 test fixtures used), and maps any unrecognised value toalwaysso the enum cast cannot throw after upgrade.public_idbackfill — mints a ULID onto every addressable model.- Brand handle and status columns.
- Product type columns.
- Product variant
enabledcolumn. - Collection and channel status columns.
- Soft-delete reconciliation — v2 drops soft deletes from several models; this migration reconciles previously soft-deleted rows.
- Order status and fulfilment backfill — replaces the hand-set v1
orders.statusheadline with the derivedpayment_status/fulfilment_statusrollups and theclosed_at/cancelled_atarchive columns, and materializes a whole-orderFulfilmentrecord for each historically shipped order. See Order status mapping. AmountOffdiscount type split — converts storedAmountOffdiscount rows intoPercentageOfforFixedAmountOffbased on the discount’sdata.fixed_valueflag (see the manual action above).- Product association
sortcolumn backfill.
Order status mapping
v1’sorders.status is a free-form string set by hand per store. v2 replaces it with two rollups derived from the order’s transactions and fulfilments. The order-status data migration needs to know which of a store’s v1 statuses map to which v2 concept, configured in config/lunar/upgrade.php:
php artisan lunar:upgrade.
Manual checklist
Rector and the data migrations cover most of the mechanical work. The following need a manual pass because they cannot be inferred automatically or depend on how the application used the feature.- Untyped
->purchasableaccesses. TheRenamePropertyRectorrule that renamesProductVariant::$purchasableto$selling_policyis class-scoped, so it only rewrites expressions Rector can type asProductVariant. A fully untyped$model->purchasableis not rewritten and needs manual review — though most untyped occurrences are the unrelatedCartLine/OrderLinepurchasable morph or thecustomer_group_product.purchasablepivot boolean, which must not be renamed. AmountOffdiscount type references. As noted above, source code referencingLunar\DiscountTypes\AmountOffneeds a manual decision betweenPercentageOffandFixedAmountOff.ProductVariant::getTotalInventory()for theAlwaysselling policy. v1 added thebackorderallowance on top ofstock_availablefor variants set to always sell. v2 returns the honest physicalstock_availablefigure instead (theAlwayspolicy already short-circuits availability checks, so the old inflated number was never load-bearing for sell/no-sell decisions, but any code displaying or exporting the figure directly will see a lower number).Order::refund()call sites Rector could not rewrite. The refund rewrite only fires for exactly two or three positional, unnamed arguments on a receiver typed asOrder. Calls already using named arguments, a different argument count, or an untyped receiver need a manual check against the newRefundRequestsignature.- Removed config keys and Filament v3/v4 customizations. Any published
config/lunar/*files, Filament resources, or Blade views customized against v1 need reviewing against the v2 shape of the equivalent file — the upgrade package does not rewrite published assets. Thelunar.admin.order_count_statuseskey is removed with no replacement (the order navigation badge now counts open orders directly); overrideOrderResource::getNavigationBadge()for a different count. - Replaced extension hooks. Two v1 admin extension hooks are removed rather than renamed, so Rector cannot rewrite them. A resource extension’s
extendTable()is replaced by aconfigureTable(Table $table): Tablehook targeting the bridge table class (for exampleLunar\Filament\Tables\Currency\CurrencyTable) instead of the resource. A list-page extension’srelationManagers()is replaced bygetRelations()on an extension targeting the resource itself.extendTable()still works when targeting a relation manager or relation page. lunar:migrate:getcandy. Removed in v2 with no replacement. Applications still on GetCandy-era data must run it on v1.x before upgrading further.