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

Shoppingcart Laravel Package

anayarojo/shoppingcart

A flexible shopping cart package for Laravel with a simple API for adding, updating, and removing items, applying conditions and attributes, and persisting carts to session or database storage. Well suited for building e-commerce checkout flows.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a maintained fork of gloudemans/shoppingcart (Crinsane), so the upstream README and every tutorial you find for Cart::add() / Cart::content() apply almost verbatim — just swap the namespace to Anayarojo\ShoppingCart.

As of 4.2.5 the package's dependency constraints allow Laravel 13 (a laravel-shift dependency bump). That release is dependency-only — no src/ changes, no API changes — so all usage below is unchanged. On Laravel 13 you need ^4.2.5; older 4.2.x pins will refuse to install.

  1. Install and let Laravel auto-discover the provider:
composer require anayarojo/shoppingcart
php artisan vendor:publish --provider="Anayarojo\ShoppingCart\ShoppingCartServiceProvider"

(Publish creates the config file — config/cart.php in upstream forks, sometimes config/shoppingcart.php. The publish tag is listed in the provider's boot() method; check there if --tag fails.)

  1. Verify the Cart facade alias exists in config/app.php. Older forks don't auto-alias it; add 'Cart' => Anayarojo\ShoppingCart\Facades\Cart::class if Cart:: throws "class not found."

  2. Where to look first, in order:

    • src/ root class (Cart.php / ShoppingCart.php) — the entire public API is 300–500 lines and is the real documentation.
    • src/CartItem.php and src/CartItemOptions.php — tells you exactly what a row looks like.
    • tests/ — the fastest source of truth for method signatures, especially update() and store()/restore().
    • config/cart.php — tax rate, number formatting, session key.
  3. First use case — add to cart from a controller and render it:

use Anayarojo\ShoppingCart\Facades\Cart;

public function add(Product $product)
{
    Cart::add(
        $product->id,
        $product->name,
        1,
        $product->price,          // float, see Gotchas
        ['size' => 'L', 'image' => $product->image_url]
    );

    return back()->with('status', 'Added to cart');
}
@foreach (Cart::content() as $row)
    <tr>
        <td>{{ $row->name }}</td>
        <td>{{ $row->qty }}</td>
        <td>{{ $row->price }}</td>
        <td>{{ $row->subtotal }}</td>
        <td>
            <form method="POST" action="{{ route('cart.remove', $row->rowId) }}">
                @csrf @method('DELETE')
                <button>Remove</button>
            </form>
        </td>
    </tr>
@endforeach
<p>Subtotal: {{ Cart::subtotal() }} • Tax: {{ Cart::tax() }} • Total: {{ Cart::total() }}</p>

You now have a working session cart. Everything else is controller glue plus a checkout routine.

Implementation Patterns

Wrap the facade in one service class. Don't sprinkle Cart:: across twenty controllers — you'll want to swap it, fake it in tests, and add cross-cutting logic (price revalidation, logging, discount rules) in one place.

class CartService
{
    public function add(Product $p, int $qty = 1, array $options = []): string
    {
        return Cart::add($p->id, $p->name, $qty, $p->price, $options);
    }

    public function update(string $rowId, int $qty): void
    {
        Cart::update($rowId, ['qty' => $qty]);
    }

    public function items(): Collection { return Cart::content(); }
    public function count(): int { return Cart::count(); }
    public function clear(): void { Cart::destroy(); }
}

Row identity is id + options, not id alone. The rowId is a hash of the product id and the serialized options. This is the single most useful fact about the package:

  • Same product, different size → two rows, both survive.
  • Same product, same options → quantities merge on add().
  • Changing options for an existing row requires remove + re-add (or update($rowId, ['options' => ...]) if the fork supports it).

Always address lines by rowId from content(), never by product id.

Standard CRUD controller shape. Add / update / remove / clear, each redirecting back with a flash message so the cart page and the mini-cart in the header both refresh. For a quantity input, loop the POSTed items[rowId => qty] array and skip zero-quantity rows (or remove() them).

Multi-cart via instances. Use Cart::instance('wishlist') for a wishlist alongside the main cart, or for admin-side order entry. Instances have independent contents and independent store()/restore() keys — a common trick is one instance per product bundle or per vendor in a marketplace.

Persist across sessions for logged-in users. Cart::store($user->id) writes the cart to the session under that key; Cart::restore($user->id) brings it back. Call store() after each mutation (or in a middleware/terminating callback) and restore() on login. This is how you survive logout and switching devices within the same session cookie — it is not a database cart, so it does not survive a different browser.

Checkout is where the package stops. A reliable order-creation flow:

DB::transaction(function () {
    $order = Order::create(['user_id' => auth()->id(), 'total' => 0]);

    foreach (Cart::content() as $row) {
        $product = Product::findOrFail($row->id);   // re-verify, don't trust session
        abort_if($product->price != $row->price, 409, 'Price changed');

        $order->items()->create([
            'product_id' => $product->id,
            'name'       => $row->name,
            'price'      => $product->price,
            'qty'        => $row->qty,
            'options'    => $row->options->toArray(),
        ]);
    }

    $order->update(['total' => Cart::total(2, '.', '')]);
    Cart::destroy();
});

Snapshot name, price, and options into order_items — product rows change, order history must not.

Header cart count without touching every controller. A view composer or a Blade component that calls Cart::count()/Cart::total() keeps the "3 items — $59.90" badge consistent and centralizes the formatting.

Discounts and shipping are yours to compute. The package only knows subtotal + a flat config tax + total. Apply coupons by iterating content(), computing a discount, and storing it in the session alongside the cart — then subtract at display and checkout time. Don't try to mutate price on rows; you lose the original for receipt rendering.

Events. Newer forks dispatch add/update/remove events. If yours does, hooking a listener for abandoned-cart tracking or inventory reservation is cleaner than post-controller code; grep event( in src/ to confirm before relying on it.

Upgrade path for the 4.2.x line. 4.2.5 is a dependency-only release, so upgrading is low-risk. If your app is on Laravel 13, bump the constraint and update with composer update anayarojo/shoppingcart -W; if you're on Laravel 10/11/12, there's nothing to gain and a widened illuminate/support range can pull in transitive versions your app hasn't locked — stay where you are until you plan the framework upgrade.

Gotchas and Tips

  • The cart is session state, not database state. SESSION_DRIVER=array (the default in phpunit.xml) means the cart vanishes between test requests. Either use file/database in tests, or assert within a single request after $this->withSession([...]).
  • Floats. add() accepts a float price and totals are float math; 19.99 * 3 will bite you eventually. For money-critical apps pass integer cents (or strings) and format only at render. If the fork has no minor-unit support, round explicitly with round($x, 2) before persisting an order total.
  • Formatted vs raw returns. subtotal(), tax(), total() return formatted strings by default, driven by config (format, decimals, thousandsSeparator). total(2, '.', '') gives you a raw numeric string. Never feed Cart::total() straight into a payment gateway — that's why the checkout snippet above strips formatting.
  • Tax in config is global and static. Fine for a single-rate store, wrong for per-region VAT or US sales tax. Compute real tax outside the package and keep tax at 0.
  • Don't put Eloquent models in options. The options array is serialized into the session. A model serializes to an id and rehydrates badly (or explodes on schema changes). Store scalars: ids, slugs, URLs, labels.
  • Cookie session driver + big carts = silent breakage. The default 4KB cookie limit is exceeded around 10–15 line items, and Laravel drops the cookie. Use file, redis, or database in any real deployment.
  • config:cache after publishing. The config is read at runtime, but a stale cached config after you edit tax is a classic "my tax change did nothing" bug. php artisan config:clear.
  • destroy() vs remove(). remove($rowId) kills one line; destroy()/clear() wipes the instance. Easy to fat-finger in a route:list-less codebase — name your routes cart.remove and cart.clear distinctly.
  • Row not found / silent no-ops. update() and remove() with a stale rowId typically fail quietly or throw a CartItemNotFoundException depending on the fork version. Wrap in try/catch and redirect with an error rather than a 500 on a double-submitted remove form.
  • Debugging. dd(Cart::content()) first; then look at the raw session key (session()->get('cart') or config('cart.session_key')) to confirm what's actually persisted. If content() is empty right after add(), you almost always have a session-driver or middleware-ordering problem (StartSession not applied to the API route group).
  • Extension points. Bind your own implementation into the container or extend() the package class if you need custom totals; publish and override the config; the package ships no views, so cart UI is 100% yours. There is no repository/DB layer to hook into, which is the main architectural limitation — if you need a DB-backed cart for multi-device persistence, plan to write it yourself and keep this package only as a session scratchpad.
  • Pin the version, and mind the constraint direction. Still a low-star fork — lock it. Since 4.2.5 only widened illuminate/support for Laravel 13, pin ^4.2.5 on L13 apps and check composer why-not anayarojo/shoppingcart 4.2.5 on older frameworks before assuming a conflict is the package's fault. When the next Laravel major lands, watch for the same laravel-shift dependency-bump PR — a package that only ever gets dependency bumps has an untested API surface against new framework versions, so run your own cart tests after any framework upgrade rather than trusting the release note.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo