Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Shopping Cart Laravel Package

ibrand/laravel-shopping-cart

A shopping cart package for Laravel with session and database storage, multiple cart instances, item conditions, quantity updates, and totals calculation. Designed to be simple to install, extend, and integrate into any Laravel storefront or e-commerce app.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm the package boots before writing any cart code.

composer require ibrand/laravel-shopping-cart
php artisan vendor:publish --provider="iBrand\Shoppingcart\ShoppingcartServiceProvider"

On Laravel 5.5+ the service provider and Cart facade alias are auto-discovered. On anything older — or if discovery fails — register them manually in config/app.php. The API is a near 1:1 clone of Crinsane/LaravelShoppingcart, so the README you find will look familiar; just confirm the facade namespace against src/Facades/ in the vendor directory (iBrand\Shoppingcart\Facades\Cart is the expected path) rather than trusting a blog snippet.

Session first, always. This cart lives in the session. Set SESSION_DRIVER=redis or file in .env — the cookie driver has a ~4KB payload ceiling that a real cart will blow past, and array loses everything between requests. Clear config cache after touching the published config: php artisan config:cache.

Where to look first, in order: the README, vendor/ibrand/laravel-shopping-cart/src/Cart.php (the entire business logic is in one class), the service provider (to see what's bound and what's publishable), then config/shoppingcart.php for tax/format settings.

First use case — a guest add-to-cart and a header badge:

use iBrand\Shoppingcart\Facades\Cart;

public function add(Request $request, Product $product)
{
    $qty = $request->validate(['qty' => 'required|integer|min:1'])['qty'];

    Cart::add($product->id, $product->name, $qty, $product->price, [
        'sku'   => $product->sku,
        'image' => $product->cover,
    ]);

    return back()->with('status', 'Added to cart');
}
<a href="{{ route('cart.index') }}">Cart ({{ Cart::count() }})</a>

Then Cart::content() for the cart page, Cart::subtotal() / Cart::total() for the summary, and Cart::destroy() on order completion.

Implementation Patterns

Wrap the facade in your own service. Given the last release was January 2018, don't let Cart:: calls leak into 40 controllers. Create an App\Services\CartService that exposes addItem, updateItem, removeItem, contents, totals, and swap the internals when you outgrow the package. This is the single highest-leverage decision you'll make with this library.

Live header count without touching every controller. Bind a view composer to your layout:

View::composer('layouts.app', function ($view) {
    $view->with('cartCount', Cart::count());
});

Row IDs, not product IDs. Every mutation after add() keys off the generated rowId:

Route::patch('/cart/{rowId}', fn ($rowId, Request $request) =>
    Cart::update($rowId, $request->integer('qty'))
);
Route::delete('/cart/{rowId}', fn ($rowId) => Cart::remove($rowId));

Re-price at checkout — never trust the snapshot. The cart stores the price you passed at add() time. At checkout, iterate Cart::content(), load each product fresh from the database, compare prices, and rebuild totals from your own server-side calculation inside a DB transaction. Then Cart::destroy().

Re-associate models on every request. associate() only holds the Eloquent model in memory for that request; the model is not serialized into the session. So on the cart page:

$cart = Cart::content()->map(function ($row) {
    return $row->associate(Product::find($row->id));
});

Guest cart → user cart. On login, snapshot the guest cart (most Crinsane-derived versions expose Cart::store($identifier) / Cart::restore($identifier)), merge it into the user's stored cart, then Cart::destroy() on logout. If those methods aren't present, persist the raw session('cart') payload yourself in a carts table keyed by user id.

Keep option keys stable. options participates in the row hash. Adding ['gift_wrap' => true] to an item that already had no options creates a second row for the same product — which is sometimes what you want (variants), and sometimes a bug report.

Gotchas and Tips

Laravel version drift is the real risk. A 2018 package reaches for helpers that were removed in Laravel 6 (array_get, str_slug, camel_case) and may use Collection methods that changed. Before adopting, run the test suite against your target Laravel and PHP version, and expect to fork the repo into packages/ or a VCS path repository with a one-line patch. Pin the version in composer.json and lock it.

Floats everywhere. All prices are PHP floats with no rounding contract. Do not compute order totals from Cart::total() and hand them to a payment gateway — recompute in integer minor units at checkout and treat the cart as display-only.

No stock enforcement. Nothing stops a user from updating quantity to 999. Validate against inventory in your update endpoint, and again at checkout. Same for product status — deleted or unpublished products will sit happily in the cart.

Silent session size failures. With the file/cookie drivers, large carts plus base64 image data in options will truncate or throw at the session layer, and the failure looks like "cart randomly empties." Store an image URL, never a data URI, and keep options lean.

rowId changes on update in some forks. If you're seeing duplicated rows after quantity updates, check whether update() re-hashes on options change; the fix is normally to pass only the quantity.

Multi-instance carts (Cart::instance('wishlist')) may not exist in this release. It was added to the upstream Crinsane package in v2. Test it in php artisan tinker before designing a wishlist around it.

Debugging. dd(Cart::content()->toArray()) gives you the full row structure; session('cart') shows the raw persisted payload and is the fastest way to tell a facade-state bug from a session-serialization bug. In feature tests, $this->withSession([...]) seeds a cart without going through the UI.

Extension points are thin. There's no repository interface or event system to hook — the practical extension points are (a) a facade macro or your own wrapper service, (b) subclassing Cart and rebinding it in a service provider, and (c) listening to your own domain events around the service. Given zero dependents and no commits since 2018, plan for ownership, not for upstream fixes.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console