Skip to main content
Locations represent physical places where you store inventory and ship orders from: warehouses, retail stores, fulfillment centers, or even your apartment. Each location has its own address and can track stock independently, so a product can have 50 units in your Paris warehouse and 20 in your New York store. When Shopper is first installed, the setup wizard creates a default location using your store’s address. All stock mutations target this location unless you specify another one. If your business ships from multiple places, you can add more locations and assign stock to each one.

Model

The model used is Shopper\Core\Models\Inventory. It implements Shopper\Core\Models\Contracts\Inventory and is configurable via config/shopper/models.php.

Extending the Model

To add custom behavior, extend the model and update your configuration:
namespace App\Models;

use Shopper\Core\Models\Inventory as BaseInventory;

class Inventory extends BaseInventory
{
}
Update config/shopper/models.php:
return [
    'inventory' => \App\Models\Inventory::class,
];

Database Schema

ColumnTypeNullableDefaultDescription
idbigintnoautoPrimary key
namestringno-Location name
codestringno-Unique identifier code
descriptiontextyesnullLocation description
emailstringno-Location contact email (unique)
street_addressstringno-Street address
street_address_plusstringyesnullAdditional address line
postal_codestringno-Postal/ZIP code
citystringno-City
statestringyesnullState/province/region
phone_numberstringyesnullPhone number
priorityintegerno0Fulfillment priority order
latitudedecimal(11,5)yesnullGPS latitude
longitudedecimal(10,5)yesnullGPS longitude
is_defaultbooleannofalseDefault location for stock
country_idbigintno-FK to countries table
created_attimestampyesnullCreation timestamp
updated_attimestampyesnullLast update timestamp

Relationships

Country

Each location belongs to a country:
$location->country; // Country model

Inventory Histories

Every stock mutation (increase, decrease, set) on any product or variant creates an InventoryHistory record tied to a specific location. This is how Shopper tracks stock per location.
$location->histories; // Collection of InventoryHistory records

Query Scopes

The default scope returns the location marked as the default inventory. This is the location used by InitialQuantityInventory when setting stock during product creation.
use Shopper\Core\Models\Inventory;

$defaultLocation = Inventory::query()->default()->first();

How Locations Connect to Stock

When you call mutateStock(), decreaseStock(), or setStock() on a product or variant, you pass an $inventoryId. This ties the stock mutation to a specific location:
$product->mutateStock(
    inventoryId: $warehouse->id,
    quantity: 100,
    event: 'Shipment received',
);
To get stock for a product at a specific location:
$product->stockInventory($warehouse->id);
To batch-load stock for a specific location across multiple products (avoiding N+1):
use Shopper\Models\Product;

$products = Product::query()->get();

Product::loadStockForInventory($products, $warehouse->id);

Creating Locations

To create a new warehouse location:
use Shopper\Core\Models\Inventory;
use Shopper\Core\Models\Country;

$france = Country::query()->where('cca2', 'FR')->first();

$warehouse = Inventory::query()->create([
    'name' => 'Paris Warehouse',
    'code' => 'WH-PARIS',
    'email' => 'paris@mystore.com',
    'street_address' => '15 Rue du Commerce',
    'city' => 'Paris',
    'postal_code' => '75015',
    'country_id' => $france->id,
    'priority' => 1,
    'latitude' => 48.8466,
    'longitude' => 2.2958,
]);

Retrieving Locations

To get the default location:
$default = Inventory::query()->default()->first();
To get all locations ordered by priority:
$locations = Inventory::query()
    ->with('country')
    ->orderBy('priority')
    ->get();
To find the nearest location to a customer (if latitude/longitude are set):
$locations = Inventory::query()
    ->whereNotNull('latitude')
    ->whereNotNull('longitude')
    ->get()
    ->sortBy(function ($location) use ($customerLat, $customerLng) {
        return sqrt(
            pow($location->latitude - $customerLat, 2)
            + pow($location->longitude - $customerLng, 2)
        );
    });

$nearest = $locations->first();

Default Location

The default location is used as the origin address for shipping calculations and as the fallback when stock operations do not specify a location. Only one location can be the default.
Deleting a location removes all inventory history records tied to it. This affects stock levels for every product that had stock in that location. Proceed with caution.

Components

To customize the admin UI for location management:
php artisan shopper:component:publish setting
Location-related components in config/shopper/components/setting.php:
use Shopper\Livewire;

return [
    'pages' => [
        'location-index' => Livewire\Pages\Settings\Locations\Index::class,
        'location-create' => Livewire\Pages\Settings\Locations\Create::class,
        'location-edit' => Livewire\Pages\Settings\Locations\Edit::class,
    ],
    'components' => [
        'settings.locations.form' => Livewire\Components\Settings\Locations\InventoryForm::class,
    ],
];