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

Laracart Laravel Package

lukepolo/laracart

Laracart is a flexible shopping cart package for Laravel. It offers a clean API for managing cart items, quantities, prices, attributes, conditions, taxes, and coupons, with support for multiple cart instances, making it easy to add ecommerce cart functionality to any Laravel application.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require lukepolo/laracart
php artisan vendor:publish --provider="LukePOLO\LaraCart\LaraCartServiceProvider"

Laravel auto-discovery registers the service provider and the LaraCart facade (LukePOLO\LaraCart\Facades\LaraCart, alias laracart), so there is nothing to wire up by hand. Publishing drops config/cart.php, which is the file to read first — it decides storage backend, tax behaviour, event dispatch, and the session identifier used to scope a cart.

Decide storage immediately

The default is session storage. If you already know you need carts to survive a logout, a device switch, or an abandoned-cart dashboard, flip it now rather than retrofitting later:

// config/cart.php
'storage' => 'database',

Then publish and run the migration:

php artisan vendor:publish --provider="LukePOLO\LaraCart\LaraCartServiceProvider" --tag=migrations
php artisan migrate

Make one model Buyable

The package only knows how to price things it can talk to. Implement the contract on your Product:

use LukePOLO\LaraCart\Contracts\BuyableContract;

class Product extends Model implements BuyableContract
{
    public function getBuyableID()          { return $this->id; }
    public function getBuyableName()        { return $this->name; }
    public function getBuyableDescription() { return $this->description; }
    public function getBuyablePrice($qty = 1, $options = []) { return $this->price; }
}

First use case: add-to-cart route

Route::post('/cart/{product}', function (Product $product) {
    LaraCart::add($product, request('qty', 1), request('options', []));
    return back()->with('status', 'Added to cart');
})->name('cart.add');

Then a cart page calls LaraCart::getItems(), LaraCart::subTotal(), LaraCart::tax(), LaraCart::total() and LaraCart::getItemCount(). That is the entire happy path — everything else is refinement.

Implementation Patterns

Wrap the facade in your own cart service. The facade resolves a session-bound singleton. Injecting LaraCart directly into ten controllers makes it painful to add policies (per-user carts), caching, or a second cart (wishlist, saved-for-later). One CartService class that composes the facade gives you a seam for all of it.

Treat options as part of the line-item identity. add($product, 2, ['size' => 'L']) and add($product, 1, ['size' => 'M']) are two distinct line items; adding the same product with the same options increments quantity instead of duplicating. Use that deliberately — if you'd rather merge variants into one row, aggregate the options into a single string and display them separately.

Mutation endpoints should redirect, not render. Cart mutations are POST/DELETE, and the cart lives in the session. Returning back() with a flash keeps the browser refresh-safe and avoids double-add on reload. Pair with a @if(session('status')) banner.

Model fees, not totals. Shipping, gift wrap, payment surcharges are fees — LaraCart::addFee('shipping', 599) — and should be computed before you render. Doing total() + 599 in Blade means the number on the cart page and the number on the checkout page will eventually disagree. Discounts go through the discount API, so total() is a single source of truth everywhere.

Checkout is a copy, not a reference. At order placement, snapshot line items into your own orders/order_items tables with the price as charged, then destroy the cart. Never let an order page read from the live cart, or a product price change next month will silently rewrite historical orders.

Validate stock at two points: when adding, and again inside the checkout transaction. The cart is a client-editable wish list; it is not a reservation.

Guest-to-user merge is your code. On login, read the guest cart, add each item to the authenticated cart, clear the guest one. Put it in a listener on Login and guard against double-merging on refresh.

Test against a clean cart every time.

protected function setUp(): void
{
    parent::setUp();
    $this->app['laracart']->destroy();
}

Use $this->post(route('cart.add', $product)) and assert on LaraCart::getItems()->count() — testing the HTTP surface catches session and middleware issues that unit-testing the facade will not.

Gotchas and Tips

Silent cart loss from the session driver. Under array (the default in many test/CI setups) or misconfigured cookie sessions, items vanish between requests with no error. If a cart "randomly empties," check config('session.driver') before you debug the package.

destroy_on_logout surprises people. The config flag wipes the cart when the user logs out. If you want a cart that survives logout (common with database storage), turn it off and let the storage layer decide lifetime.

Long-running workers and Octane. The cart is a singleton bound to the session for the request lifecycle. Under Octane or a queue worker that resolves it once, state can bleed across requests. Resolve the cart fresh per request (or forgetInstance('laracart') in a middleware) rather than caching it on a property.

The BuyableContract price signature must match. If your getBuyablePrice() takes no arguments while the package calls it with ($qty, $options), you get a fatal error — and if you return a stale $this->price after adding quantity discounts, the cart silently ignores your volume pricing. Price everything inside that method.

Money is float math. Sub-totals, tax, and fees are summed as floats. Do not add cents-precision comparisons in tests; assert with a delta, and format only at render time via formatMoney(). For anything regulated, compute tax server-side from stored integers and treat the package's tax figure as a preview.

getItem($id) returns null, not an exception. Item lookups by hash miss silently when an option key changes — e.g. renaming color to colour orphans every existing cart row. Namespace option keys from day one (variant.color).

Events can double-fire your inventory sync. With cart events enabled, add, update, and remove all dispatch. An update from qty 1 to 3 is one event, but a "delete then re-add" UI flow is two — netting your stock counter to the wrong place. Prefer a single saved/order-placed reconciliation over per-item listeners, or make listeners idempotent keyed on the cart item hash.

Database storage needs an identifier that actually changes. Guest carts are scoped by session ID; if your middleware regenerates the session on every request, each guest gets a new empty cart. Regenerate only at login, and merge before you regenerate.

removeItem doesn't guarantee an empty cart. Fees and discounts added manually persist independently of line items — call destroy() for a true reset (e.g. "empty cart" button, or post-checkout), otherwise a re-used cart can still carry a shipping fee with zero products.

Extend via the config and events before forking. The package is small and lightly maintained; the fastest path for custom tax rules, currency formatting, or storage is usually a config value or an event listener, not a fork. Wrap rather than patch, so composer update stays boring.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager