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 Cart Laravel Package

isapp/laravel-cart

Laravel Cart is a customizable shopping cart package for Laravel with persistent database or session storage, guest and authenticated user support, high-precision price calculations, model associations, method chaining, and exception handling.

View on GitHub
Deep Wiki
Context7

Getting Started

Install, publish, migrate — in that order.

composer require isapp/laravel-cart

php artisan vendor:publish --provider="Isapp\LaravelCart\CartServiceProvider" --tag="config"
php artisan vendor:publish --provider="Isapp\LaravelCart\CartServiceProvider" --tag="migrations"

php artisan migrate

Verify the resolved version before you commit the lockfile. The only release note in the repo (1.0.1) pins illuminate/support to ^13; there is no published compatibility matrix, so on Laravel 11/12 you should pin an earlier tag explicitly rather than letting Composer resolve to whatever satisfies the constraint.

Read these four things before writing feature code. The README is a method reference, not a tutorial, and it omits the two files that actually determine behaviour:

Where Why it matters
config/laravel-cart.php Driver (database vs session), default guard, table names. Nearly every "cart is empty / cart is duplicated" bug lives here.
Isapp\LaravelCart\Contracts\Driver The complete public surface — small enough to read end to end in five minutes. This is your source of truth when the README drifts.
Isapp\LaravelCart\Contracts\CartItemProduct The interface your Product model must implement. This is the one non-obvious integration step and the package will not guess it for you.
database/migrations/* Confirms the user_id column type. If your users table uses UUIDs/ULIDs, edit the published migration before running migrate.

Wire your product model. Prices must be numeric strings, not floats — the whole package is built on BCMath and returning strings to preserve precision:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Isapp\LaravelCart\Contracts\CartItemProduct;

class Product extends Model implements CartItemProduct
{
    // Implement exactly what the contract asks for (typically price + tax).
    // Return numeric strings; do not cast price to float/int/ Money here.
}

First use case — a single add-to-cart action. Note that storeItem increments quantity when the item already exists, so this one endpoint is safe as the backing action for every add-to-cart button. You do not need an "already in cart?" branch:

use Isapp\LaravelCart\Facades\Cart;
use Isapp\LaravelCart\Models\CartItem;

class AddToCartController extends Controller
{
    public function __invoke(Product $product)
    {
        $item = new CartItem();
        $item->itemable()->associate($product);

        Cart::storeItem($item);

        return redirect()->route('cart.show');
    }
}

Then render with Cart::get(), iterate $cart->items, and use getItemPricePerUnit() / getItemPrice() / getTotalPrice(). That is the whole happy path — roughly an afternoon to a working cart page.


Implementation Patterns

Don't call the facade from controllers and Blade

The facade is convenient but it is global mutable state (you can setUser and setGuard on it, mid-request, and the effect leaks). Wrap it once and inject the wrapper:

namespace App\Services;

use Isapp\LaravelCart\Facades\Cart;
use Isapp\LaravelCart\Models\CartItem;

class CartService
{
    public function add(CartItemProduct $product, int $qty = 1): void
    {
        $item = new CartItem();
        $item->itemable()->associate($product);
        Cart::storeItem($item);

        if ($qty > 1) {
            Cart::increaseQuantity($item, $qty - 1);
        }
    }

    public function contents(): array
    {
        $cart = Cart::get();

        return [
            'items' => $cart->items,
            'total' => Cart::getTotalPrice(),
        ];
    }
}

This gives you one seam to add stock checks, events, logging, or a driver swap later. Given the package's adoption profile (0 stars, 0 dependents, single maintainer), keeping the facade out of your domain code is the cheapest exit strategy you can buy.

Treat the package as a data layer, not a checkout

It stores items, does quantity arithmetic, and totals prices. It does not do stock validation, cart merging, events, order conversion, or guest-cart cleanup — all four are still unchecked on the README ToDo list. Build those yourself around the wrapper:

  • Login merge: hook Login / Authenticated and copy guest session items onto the user's cart before setUser() takes effect. There is no merge() in the API.
  • Stock: validate in a FormRequest before Cart::storeItem, and re-validate at checkout.
  • Checkout: read out Cart::get()->items, snapshot into your own orders / order_lines tables, then Cart::emptyCart(). Never point the order at the cart row.

Guest → authenticated user

Guest carts resolve by session_id; user carts by user_id; both through the same Cart::get(). Keep the guard explicit whenever you operate on behalf of someone else:

Cart::setGuard('admin')->setUser($user)->storeItem($item);

Remember setUser and setGuard mutate the shared facade instance — reset, or use it at the top of a request and nowhere else.

Quantity controls

increaseQuantity / decreaseQuantity throw NotFoundException when the item isn't in the cart. Map exceptions to HTTP in one place rather than try/catching in every controller action:

try {
    Cart::increaseQuantity($item, $qty);
} catch (NotFoundException $e) {
    abort(404, 'Item not in cart');
} catch (ItemAssociatedWithDifferentCartException $e) {
    abort(409, 'Item belongs to another cart');
} catch (NotImplementedException $e) {
    abort(400, 'Invalid itemable');
}

Decreasing to zero does not appear to remove the line — call removeItem explicitly when the user hits zero.

Format at the edge, never in the middle

Everything returns strings on purpose. Keep them as strings through the view layer and format once:

$total = Cart::getTotalPrice();          // "149.97"
$excl  = Cart::getTotalPrice(false);     // without taxes
$tax   = bcsub($total, $excl, 2);

If you need a Money object or currency formatting, convert at the very last step. A single (float) cast anywhere in the chain defeats the package's main selling point.

Test with a driver swap

Cart::extend() lets you register a named driver from a service provider — the documented use case is a custom driver, but the practical one is an in-memory fake so cart tests don't need migrations:

Cart::extend('array', fn () => new ArrayCartDriver());

Combine with RefreshDatabase + actingAs for the authenticated path, and a plain test for the guest/session path.


Gotchas and Tips

Cart::get() has a side effect. It finds or creates the cart row. Calling it in a middleware, a Blade partial, or a ShowCart page will persist an empty cart for every anonymous visitor, inflating your table and skewing any "carts created" metric. Call it once per request in the service layer.

storeItem silently increments. Great for idempotent add-to-cart buttons, terrible if you expected "replace quantity". Always go through increaseQuantity for explicit quantity edits so the two paths stay distinguishable.

No events are fired. The ToDo list confirms ItemAdded / ItemRemoved are unimplemented. Dispatch your own from the wrapper — that's where abandoned-cart emails, analytics, and inventory reservations hang off.

Guest carts never expire. There is no cleanup command. Add a scheduled job that deletes carts with user_id = null older than N days, otherwise the table grows unbounded.

Item-by-ID and exists checks exist but aren't in the README. The ToDo list marks "find item by ID" and "check if item exists" as done, yet the methods aren't documented. Read Contracts\Driver for the real names before writing your own lookup.

Reusing a CartItem model instance across carts throws. If you hydrate an item from one cart and try to store it against another, you get ItemAssociatedWithDifferentCartException. Construct a fresh CartItem and associate() the product each time.

NotImplementedException means your model, not the package. It is thrown when the itemable doesn't implement CartItemProduct — usually a missing implements clause or a wrong return type on the price accessor. Check the contract's exact signatures before debugging anything else.

Tax handling is an opt-in boolean, not config. getItemPrice and getTotalPrice default to $incTaxes = true. Mixing the two in the same view (e.g. per-line with tax, total without) produces totals that don't add up — settle on one convention per screen.

Guard mismatch looks like data loss. If setGuard('admin') or the config default doesn't match the guard the request authenticated under, getUser() returns null and you silently fall through to the session driver. Symptom: an authenticated user's cart "disappears" on a different route group.

Version pin is aggressive. 1.0.1 requires illuminate/support ^13. On older frameworks, pin deliberately and re-check composer show isapp/laravel-cart after every composer update.

Extend, then own it. Cart::extend() in a service provider is the documented escape hatch. If the database driver's query patterns or missing features become a problem, implementing Contracts\Driver against your own tables is a bounded job — the contract is small — and it's far cheaper to do that after a wrapper exists than to unpick facades sprinkled through fifty Blade templates.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings