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.
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',];
Option
Default
Description
enabled
true
Enable 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, 'highest' to use the highest tax rate found among cart items, or a callable (e.g., [App\Shipping\TaxRateCalculator::class, 'calculate']) for custom logic. Callables receive the Lunar\Shipping\Models\ShippingRate and the cart, and should return a Lunar\Models\TaxClass.
The add-on registers a shipping:manage permission and enforces it on the Shipping Methods, Shipping Zones, and Shipping Exclusion Lists resources in the admin panel. Staff members need this permission (either directly or via a role) to view or manage shipping configuration.The permission is created automatically by a migration when the add-on is installed. See Access Control for guidance on assigning permissions to roles and staff.
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.
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.
Field
Type
Description
id
bigIncrements
Primary key
name
string
description
textnullable
code
stringnullable
Unique identifier used as the shipping option identifier
enabled
boolean
Whether the method is active
stock_available
boolean
When enabled, the method is only available if all cart items are in stock
min_weight
decimalnullable
Minimum cart weight required for the method to be offered
max_weight
decimalnullable
Maximum cart weight allowed for the method to be offered
weight_unit
stringnullable
Unit used by min_weight and max_weight (e.g., kg, g)
data
jsonnullable
Driver-specific configuration (cast to AsArrayObject)
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.
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.
Shipping methods use an availability schedule to define when the method can be selected. The schedule replaces the older single daily cutoff time and supports finer-grained rules (for example, different cutoffs per day of the week, or unavailable on specific dates). The schedule is managed through the Shipping Method page in the admin panel.When the cart is evaluated, the shipping rate resolver checks the schedule for each method and only returns rates from methods that are currently available.
min_weight and max_weight on the shipping method allow restricting the method to carts within a given weight range. Both fields are optional; leaving them empty means no lower or upper bound is applied. weight_unit defines the unit used by both values.The total cart weight is calculated from each cart line’s purchasable weight multiplied by its quantity. Methods whose constraints are not satisfied are excluded from the resolver’s results.
Driver identifier:ship-byThe 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:
Key
Values
Description
charge_by
cart_total (default), weight
Determines 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.
Driver identifier:flat-rateCharges 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.
Driver identifier:free-shippingOffers free shipping when the cart meets a minimum spend requirement.Data configuration:
Key
Values
Description
minimum_spend
array or number
Minimum 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_amount
boolean
When true, discount amounts are subtracted from the cart subtotal before checking the minimum spend.
Driver identifier:collectionAllows 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.
Shipping rates store their base price per currency, not just for the store’s default currency. Each currency enabled on the store can be assigned its own base price through the Shipping Rates page in the admin panel. Tiered pricing (used by ship-by and flat-rate) continues to be defined alongside, using Lunar’s core pricing system.
The add-on registers a ShippingDiscount discount type that targets the shipping portion of the cart. It is configured through the Discounts area of the admin panel.
Lunar\Shipping\DiscountTypes\ShippingDiscount
A shipping discount holds one or more rules. Each rule may target a specific shipping method (by shipping_method_id) or act as a catch-all (no method selected), and offers one of two adjustments:
Percentage — reduces the matched shipping option price by a percentage.
Fixed per currency — replaces the matched shipping option price with a fixed value per currency code. A value of 0 produces free shipping.
When the discount is applied, the resolver matches each item in the cart’s shipping breakdown against the configured rules and rewrites the price accordingly. Discount conditions (minimum spend, customer groups, etc.) apply as with any other discount type.
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.
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));
Postcode formats vary considerably between countries, so the add-on splits a raw postcode into a set of “parts” that are matched against ShippingZonePostcode records. The default Lunar\Shipping\Resolvers\PostcodeResolver works well for UK-style postcodes (and any country that follows a similar prefix structure), but custom resolvers can be registered for stores operating in regions where a different breakdown is needed.
A resolver implements Lunar\Shipping\Interfaces\PostcodeResolverInterface:
namespace Lunar\Shipping\Interfaces;use Illuminate\Support\Collection;use Lunar\Models\Contracts\Country as CountryContract;interface PostcodeResolverInterface{ public function supportsCountry(CountryContract $country): bool; public function getParts(string $postcode, CountryContract $country): Collection;}
supportsCountry declares which countries the resolver handles. Returning true for every country makes the resolver a catch-all.
getParts returns the postcode permutations to match against the zone’s stored postcodes. Wildcards (*) are honored by the zone resolver.
Resolvers are registered through the Lunar\Shipping\Facades\Postcode facade, typically in the boot method of a service provider:
use Lunar\Shipping\Facades\Postcode;use App\Shipping\UsZipResolver;Postcode::addResolver(UsZipResolver::class);
Resolvers are evaluated in reverse registration order — the last-registered resolver that supports the country wins. Register country-specific resolvers after the default, more general ones.
use Lunar\Shipping\Facades\Postcode;// Match the resolver for the cart's shipping country.$resolver = Postcode::country($country);$parts = $resolver->getParts('SW1A 1AA', $country);
If no registered resolver supports the cart’s country, a Lunar\Shipping\Exceptions\NoPostcodeResolverException is thrown. The default resolver ships unrestricted, so this only applies when it has been removed.
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.