Skip to main content

Overview

The Table Rate Shipping add-on provides a flexible, zone-based shipping system for Lunar. It integrates with the core shipping modifier pipeline, allowing storefronts to offer multiple shipping methods with pricing that varies by zone, cart total, weight, or other criteria. Key concepts:
  • Shipping Zones define geographic regions (unrestricted, by country, state, or postcode).
  • Shipping Methods represent the types of delivery available (e.g., standard, express, collection).
  • Shipping Rates link a method to a zone with tiered pricing.
  • Shipping Exclusion Lists prevent specific products from being shipped to certain zones.

Installation

Install via Composer:
composer require lunarphp/table-rate-shipping
Then register the admin panel plugin in a service provider:
use Filament\Panel;
use Lunar\Admin\Support\Facades\LunarPanel;
use Lunar\Shipping\ShippingPlugin;

public function register(): void
{
    LunarPanel::panel(function (Panel $panel) {
        return $panel->plugin(new ShippingPlugin());
    })->register();
}

Configuration

The configuration file is published at config/lunar/shipping-tables.php.
return [
    'enabled' => env('LUNAR_SHIPPING_TABLES_ENABLED', true),

    // 'default' uses the system-wide default tax class.
    // 'highest' selects the highest tax rate from items in the cart.
    'shipping_rate_tax_calculation' => 'default',
];
OptionDefaultDescription
enabledtrueEnable or disable the shipping tables add-on.
shipping_rate_tax_calculation'default'Tax class strategy for shipping rates. Use 'default' for the system default tax class, or 'highest' to use the highest tax rate found among cart items.

Models

ShippingZone

Lunar\Shipping\Models\ShippingZone
A shipping zone represents a geographic area that shipping rates apply to. Zones can be unrestricted (apply everywhere) or limited to specific countries, states, or postcodes.
FieldTypeDescription
idbigIncrementsPrimary key
namestring
typestringunrestricted, countries, states, or postcodes
created_attimestamp
updated_attimestamp

Relationships

RelationshipTypeRelated ModelDescription
ratesHasManyShippingRateShipping rates for this zone
shippingMethodsHasManyShippingMethodShipping methods available in this zone
countriesBelongsToManyLunar\Models\CountryCountries included in this zone
statesBelongsToManyLunar\Models\StateStates included in this zone
postcodesHasManyShippingZonePostcodePostcodes included in this zone
ordersBelongsToManyLunar\Models\OrderOrders assigned to this zone
shippingExclusionsBelongsToManyShippingExclusionListProduct exclusion lists associated with this zone

ShippingMethod

Lunar\Shipping\Models\ShippingMethod
A shipping method represents a type of delivery, such as flat rate, weight-based, free shipping, or in-store collection. Each method uses a driver that determines how pricing is resolved.
FieldTypeDescription
idbigIncrementsPrimary key
namestring
descriptiontext nullable
codestring nullableUnique identifier used as the shipping option identifier
enabledbooleanWhether the method is active
stock_availablebooleanWhen enabled, the method is only available if all cart items are in stock
cutofftime nullableDaily order cutoff time; orders placed after this time will not see this method
datajson nullableDriver-specific configuration (cast to AsArrayObject)
driverstringDriver identifier (e.g., ship-by, flat-rate, free-shipping, collection)
created_attimestamp
updated_attimestamp

Relationships

RelationshipTypeRelated ModelDescription
shippingRatesHasManyShippingRateRates associated with this method
customerGroupsBelongsToManyLunar\Models\CustomerGroupCustomer groups that can see and use this method

ShippingRate

Lunar\Shipping\Models\ShippingRate
A shipping rate connects a shipping method to a shipping zone and holds the pricing. It implements the Lunar\Models\Contracts\Purchasable interface and uses the HasPrices trait, meaning prices are stored in Lunar’s core prices table with support for tiered pricing and multiple currencies.
FieldTypeDescription
idbigIncrementsPrimary key
shipping_method_idforeignIdReferences the shipping method
shipping_zone_idforeignIdReferences the shipping zone
enabledbooleanWhether the rate is active
created_attimestamp
updated_attimestamp

Relationships

RelationshipTypeRelated ModelDescription
shippingMethodBelongsToShippingMethodThe parent shipping method
shippingZoneBelongsToShippingZoneThe parent shipping zone
pricesMorphManyLunar\Models\PriceTiered pricing via the HasPrices trait

ShippingZonePostcode

Lunar\Shipping\Models\ShippingZonePostcode
FieldTypeDescription
idbigIncrementsPrimary key
shipping_zone_idforeignIdReferences the shipping zone
postcodestringIndexed; spaces are automatically stripped on save
created_attimestamp
updated_attimestamp

Relationships

RelationshipTypeRelated ModelDescription
shippingZoneBelongsToShippingZoneThe parent shipping zone

ShippingExclusionList

Lunar\Shipping\Models\ShippingExclusionList
An exclusion list groups products that should not be shipped to certain zones. If a cart contains any product on an exclusion list associated with a zone, shipping options for that zone are not available.
FieldTypeDescription
idbigIncrementsPrimary key
namestringUnique name for the list
created_attimestamp
updated_attimestamp

Relationships

RelationshipTypeRelated ModelDescription
exclusionsHasManyShippingExclusionProducts in this list
shippingZonesBelongsToManyShippingZoneZones this list applies to

ShippingExclusion

Lunar\Shipping\Models\ShippingExclusion
FieldTypeDescription
idbigIncrementsPrimary key
shipping_exclusion_list_idforeignIdReferences the exclusion list
purchasable_typestringPolymorphic type
purchasable_idunsignedBigIntegerPolymorphic ID
created_attimestamp
updated_attimestamp

Relationships

RelationshipTypeRelated ModelDescription
listBelongsToShippingExclusionListThe parent exclusion list
purchasableMorphToThe excluded purchasable (e.g., a Product)

Drivers

Each shipping method uses a driver to determine how pricing is resolved. The add-on ships with four built-in drivers.

Ship By

Driver identifier: ship-by The most flexible driver. Pricing is based on either the cart total or the total weight of items in the cart. Prices are stored as tiered rates on the shipping rate, where each tier’s min_quantity represents the threshold (cart subtotal in minor units, or total weight). Data configuration:
KeyValuesDescription
charge_bycart_total (default), weightDetermines what value is used to match against price tiers. When set to weight, the total is calculated as purchasable.weight_value * quantity for each cart line.

Flat Rate

Driver identifier: flat-rate Charges a flat rate per order, with optional tiered pricing based on the cart subtotal. Prices are stored on the shipping rate using Lunar’s pricing system with quantity tiers representing cart subtotal thresholds.

Free Shipping

Driver identifier: free-shipping Offers free shipping when the cart meets a minimum spend requirement. Data configuration:
KeyValuesDescription
minimum_spendarray or numberMinimum cart subtotal required. Can be a single value or an array keyed by currency code (e.g., {"GBP": 5000, "USD": 6000}). Values are in minor units.
use_discount_amountbooleanWhen true, discount amounts are subtracted from the cart subtotal before checking the minimum spend.

Collection

Driver identifier: collection Allows customers to collect their order in store. Returns a shipping option with a price of zero and sets collect: true on the shipping option, which can be used to identify collection orders in the storefront.

Storefront Usage

This add-on registers a ShippingModifier with Lunar’s shipping modifier pipeline. Shipping options are automatically resolved and available through the ShippingManifest:
use Lunar\Facades\ShippingManifest;

$options = ShippingManifest::getOptions($cart);
The modifier resolves shipping zones based on the cart’s shipping address, finds applicable rates, and converts them into ShippingOption instances that are added to the manifest.
If an existing storefront already uses the ShippingManifest to retrieve shipping options, no changes are needed. The add-on integrates automatically.

Advanced Usage

Retrieving Supported Drivers

use Lunar\Shipping\Facades\Shipping;

$drivers = Shipping::getSupportedDrivers();

Resolving a Driver Directly

use Lunar\Shipping\Facades\Shipping;
use Lunar\Shipping\DataTransferObjects\ShippingOptionRequest;

$option = Shipping::driver('ship-by')
    ->on($shippingRate)
    ->resolve(
        new ShippingOptionRequest(
            shippingRate: $shippingRate,
            cart: $cart,
        )
    );

Resolving Shipping Zones

The zone resolver finds matching zones based on address criteria. Each method is optional; combining them makes the lookup more specific.
use Lunar\Shipping\Facades\Shipping;
use Lunar\Shipping\DataTransferObjects\PostcodeLookup;

$zones = Shipping::zones()
    ->country($country)
    ->state($state)
    ->postcode(
        new PostcodeLookup(
            country: $country,
            postcode: 'NW1'
        )
    )
    ->get();
The resolver always includes zones with type unrestricted, plus any zones matching the specified country, state, or postcode criteria.

Resolving Shipping Rates

The rate resolver finds applicable shipping rates for a cart, filtering by customer group, cutoff time, and stock availability.
use Lunar\Shipping\Facades\Shipping;

$rates = Shipping::shippingRates($cart)->get();

Resolving Shipping Options

The option resolver takes shipping rates and resolves them into priced ShippingOption instances using each rate’s driver.
use Lunar\Shipping\Facades\Shipping;
use Lunar\Shipping\DataTransferObjects\ShippingOptionLookup;

$rates = Shipping::shippingRates($cart)->get();

$options = Shipping::shippingOptions($cart)->get(
    new ShippingOptionLookup(shippingRates: $rates)
);

Events

ShippingOptionResolvedEvent

Lunar\Shipping\Events\ShippingOptionResolvedEvent
Dispatched each time a shipping option is successfully resolved from a shipping rate.
PropertyTypeDescription
cartLunar\Models\Contracts\CartThe cart being resolved
shippingRateShippingRateThe shipping rate that was resolved
shippingOptionLunar\DataTypes\ShippingOptionThe resulting shipping option

Order Observer

When an order is created or updated, the add-on automatically resolves the shipping zone from the order’s shipping address and attaches it to the order via the order_shipping_zone pivot table. The zone name is also stored in the order’s meta for search indexing.