Skip to main content
This page covers the tooling and steps for moving a Lunar v1.x application to v2.x.

Overview

Lunar v2 is a wholesale breaking release. The core package moves to the Lunar\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.
The upgrade package’s data migrations are one-way. They have no down() method, so there is no automated rollback. Take a full database backup before running php artisan lunar:upgrade, and treat “restore from backup” as the only way back if something goes wrong partway through.

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).
Upgrade the application’s PHP and Laravel versions before starting the Lunar upgrade if it is not already on these versions.

Step-by-step

1. Require the upgrade package

2. Run the upgrade command

The command runs four steps in order, each tied to the spec that introduced it:
  1. composer-require-rewrite — rewrites the application’s composer.json, swapping lunarphp/lunar for lunarphp/admin in require / require-dev, and adding an explicit lunarphp/core requirement if it was previously only pulled in transitively. Run composer update afterwards to refresh the lockfile.
  2. rector — reports how many class references the bundled Rector rules would rewrite across the configured paths.
  3. 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).
  4. ledger-rewrite — rewrites the application’s migrations table 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.
Useful options:
The command aborts before making any changes if the configured connection has no v1.x migration rows (nothing to upgrade) or already has v2 baseline rows recorded (already upgraded).
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:
This covers the class, method, property, and money-attribute rewrites described below. Run it before or after the 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\AmountOff has been split into Lunar\Core\DiscountTypes\PercentageOff and Lunar\Core\DiscountTypes\FixedAmountOff. Stored discount records are converted automatically (based on their data.fixed_value flag), but source code referencing AmountOff needs to be updated by hand to whichever type it meant, since a rename rule cannot infer that. The discount’s data.fixed_values key is renamed to data.amounts, and the data.fixed_value flag is removed.

4. Publish config and clear caches

Review the application’s 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/core moves from Lunar\… to Lunar\Core\… (for example Lunar\Models\Product becomes Lunar\Core\Models\Product, Lunar\Facades\CartSession becomes Lunar\Core\Facades\CartSession). Filament support classes extracted into the new lunarphp/filament bridge package move from Lunar\Admin\Support\… to Lunar\Filament\….
  • Renamed methods and properties, including ProductType::mappedAttributes()attributeMapping(), Price::$compare_price$list_price, and ProductVariant::$purchasable$selling_policy (class-scoped, so the unrelated CartLine/OrderLine purchasable morph relation and the customer_group_product.purchasable pivot boolean are left untouched).
  • Money-attribute access. v1’s per-attribute PriceDataType value object is gone; casts now return raw integers with formatting on the model. Rector rewrites $order->total->value to $order->total, $order->total->formatted() to $order->format('total'), and $price->price->unitDecimal() / unitFormatted() on the catalogue Price model to $price->unitDecimal('price') / $price->unitFormat('price').
  • Model::modelClass() calls rewrite to Model::class, since model class substitution has been removed (see Model extending below).
  • Order::refund() call sites rewrite from three positional arguments to a Lunar\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 to app(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') for name and description, now dedicated translatable columns instead of attribute_data entries.
  • Custom price formatter signatures. A consumer’s own PriceFormatterInterface implementation has its $formatterStyle parameter retyped from string to int on formatted(), unitFormatted(), and formatValue(), matching the NumberFormatter::* 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 the data-migrations step:
  • Class-string rewrite — updates persisted class strings (discount conditions, purchasable morphs, and similar) from Lunar\… to Lunar\Core\….
  • compare_pricelist_price — renames the column on prices.
  • Catalogue name / description columns — adds dedicated translatable columns and migrates the equivalent attribute_data values onto them.
  • Attribute data keys to IDs — converts the attribute system’s handle-keyed storage to id-keyed storage.
  • Order line requires_fulfilment backfill.
  • Stock backfill — populates the new per-location StockLevel / StockMovement records from the v1 flat stock column.
  • Default region seed and backfill — v1 has no region concept; this seeds a catch-all default Region from the v1 default channel, currency, and language, and backfills region_id on existing carts and orders.
  • Shipping line purchasable morph nulling — shipping order lines no longer store a placeholder morph.
  • purchasableselling_policy — renames the column on product_variants, reconciles stored values onto the SellingPolicy enum’s canonical set (correcting the in_stock_or_backorder typo some v1 test fixtures used), and maps any unrecognised value to always so the enum cast cannot throw after upgrade.
  • public_id backfill — mints a ULID onto every addressable model.
  • Brand handle and status columns.
  • Product type columns.
  • Product variant enabled column.
  • 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.status headline with the derived payment_status / fulfilment_status rollups and the closed_at / cancelled_at archive columns, and materializes a whole-order Fulfilment record for each historically shipped order. See Order status mapping.
  • AmountOff discount type split — converts stored AmountOff discount rows into PercentageOff or FixedAmountOff based on the discount’s data.fixed_value flag (see the manual action above).
  • Product association sort column backfill.

Order status mapping

v1’s orders.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:
The defaults cover the stock v1 statuses. Add any custom status strings the store uses to the appropriate list before running 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.
Model class substitution is removed. v1 let an application swap a Lunar model for its own subclass via ModelManifest::replace() and have Lunar hand back instances of that subclass everywhere. v2 removes HasModelExtending, the ModelManifest substitution API, Model::modelClass(), and the Models\Contracts\* model interfaces entirely. Lunar models are now a single concrete class.Rector rewrites Model::modelClass() calls to Model::class, but a subclass relying on substitution needs re-implementing through native Laravel mechanisms:
  • Untyped ->purchasable accesses. The RenamePropertyRector rule that renames ProductVariant::$purchasable to $selling_policy is class-scoped, so it only rewrites expressions Rector can type as ProductVariant. A fully untyped $model->purchasable is not rewritten and needs manual review — though most untyped occurrences are the unrelated CartLine/OrderLine purchasable morph or the customer_group_product.purchasable pivot boolean, which must not be renamed.
  • AmountOff discount type references. As noted above, source code referencing Lunar\DiscountTypes\AmountOff needs a manual decision between PercentageOff and FixedAmountOff.
  • ProductVariant::getTotalInventory() for the Always selling policy. v1 added the backorder allowance on top of stock_available for variants set to always sell. v2 returns the honest physical stock_available figure instead (the Always policy 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 as Order. Calls already using named arguments, a different argument count, or an untyped receiver need a manual check against the new RefundRequest signature.
  • 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. The lunar.admin.order_count_statuses key is removed with no replacement (the order navigation badge now counts open orders directly); override OrderResource::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 a configureTable(Table $table): Table hook targeting the bridge table class (for example Lunar\Filament\Tables\Currency\CurrencyTable) instead of the resource. A list-page extension’s relationManagers() is replaced by getRelations() 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.

Reference