freshbitsweb/laravel-cart-manager
A Laravel package for adding shopping cart functionality to your app. It supports multiple carts, cart items, quantities, and totals, with a simple API and database migrations to persist carts and items. Easily integrate e-commerce cart features into any Laravel project.
Pull the package in and publish its scaffolding:
composer require freshbitsweb/laravel-cart-manager
php artisan vendor:publish --provider="Freshbitsweb\LaravelCartManager\CartServiceProvider"
php artisan migrate
Publishing gives you a config/cart.php (tax rate, currency, formatting) plus migrations for the carts and cart_items tables. Look here first — the config file is where currency/tax behavior is decided, and the migrations tell you exactly what columns you get on CartItem (typically cart_id, cartable_id/cartable_type, quantity, price, tax, total, and a JSON attributes column).
Next, attach the trait to whatever model owns a cart — usually User:
use Freshbitsweb\LaravelCartManager\Traits\Cartable;
class User extends Authenticatable
{
use Cartable;
}
Your first end-to-end use case is the classic "add a product, read the cart back":
$cart = auth()->user()->cart; // default cart
$cart->add($product, 2, ['size' => 'M']);
$cart->add($accessory, 1);
$cart->items; // CartItem collection
$cart->total(); // cart total
$cart->totalQuantity(); // item count
That is the whole loop for a minimal storefront: add, render items, show total, and delete. Everything else in this package is variations on that.
Cart as a thin wrapper around Eloquent. The value of this package is that Cart/CartItem are real models you can query and eager-load. Lean into that instead of hiding them:
$cart = auth()->user()->cart()->with('items.cartable')->first();
foreach ($cart->items as $item) {
$item->cartable; // the Product (polymorphic)
$item->quantity;
$item->price;
$item->attributes; // cast to array
}
Item attributes for options. Pass variant data at add time and treat it as a snapshot:
$cart->add($product, $qty, [
'size' => 'M',
'color' => 'blue',
'engraving' => 'A. Smith',
]);
The price and tax are captured when the line is added — good for price integrity, but it means the cart does not auto-follow catalog price changes.
Guest → user cart merge on login. The most common real-world requirement with a database cart. Keep a guest cart keyed by session, then on Login/Registered events merge it into $user->cart:
// In a listener
$guest = Cart::where('session_id', session()->getId())->first();
$userCart = $user->cart;
foreach ($guest->items as $item) {
// De-duplicate on cartable + attributes if you want merged lines
$userCart->add($item->cartable, $item->quantity, $item->attributes);
}
$guest->empty();
Multiple carts as named collections. Since the trait exposes cart() (default) and carts() (all), use them for wishlists and saved-for-later without a second schema:
$wishlist = $user->carts()->create(['name' => 'wishlist']); // verify columns shipped
$wishlist->add($product, 1);
// "move to cart"
$item = $wishlist->items()->findOrFail($id);
$user->cart->add($item->cartable, $item->quantity, $item->attributes);
$item->delete();
Wrap mutations in a service. Cart operations touch multiple rows and totals; putting them behind a small service (or CartManager-style class) keeps controllers thin and gives you one place to add locking, analytics, and coupon logic:
class CartService
{
public function addToCart(Cart $cart, Model $product, int $qty, array $attrs = []): CartItem
{
return DB::transaction(function () use ($cart, $product, $qty, $attrs) {
// look for existing line with same cartable + attributes first
return $cart->add($product, $qty, $attrs);
});
}
}
Checkout = snapshot, not reuse. When an order is placed, copy cart_items into your own order_items table with price/tax/attributes frozen. Do not let the cart row become the order — the cart is mutable, the order must not be.
Presentation is a Blade partial. Render the cart once, reuse it on cart page, mini-cart, and checkout summary:
@foreach ($cart->items as $item)
<tr>
<td>{{ $item->cartable->name }}</td>
<td>{{ $item->quantity }}</td>
<td>{{ number_format($item->price, 2) }}</td>
<td>{{ number_format($item->total, 2) }}</td>
</tr>
@endforeach
<tr><td colspan="3">Total</td><td>{{ number_format($cart->total(), 2) }}</td></tr>
config/cart.php tax rate later does not retroactively fix existing cart items. If tax rules change, you need a recalculation pass over open carts. Plan for this before launch.total() vs. formatted output. Check whether the installed version returns a numeric value or a formatted string — mixing the two into your own arithmetic is a classic source of "$19.99" + 5 bugs. Store and compute in raw decimals; format only at the view layer.cartable has no FK. Deleting a product leaves orphaned cart_items rows with a dangling cartable_id. Add a cleanup job or observer on product deletion, and guard $item->cartable being null in the cart view.add() may always create a new line rather than incrementing an existing matching line. If you want one row per product+variant, look up the existing CartItem (matching cartable and attributes) and updateQuantity instead.DB::transaction and consider lockForUpdate() on the cart item for anything money-touching.cartable. Always ->with('items.cartable') when rendering. A cart page with 20 lines and lazy loading is 21+ queries.user_id are two different lookup keys. Pick one resolution order (user cart if logged in, else session cart), put it in a single currentCart() helper, and never inline it in controllers.php artisan config:clear after changing .env in dev; stale config is the usual "my tax rate didn't change" report.empty() before deleting a cart. Clearing items through the package keeps totals and relations consistent; hard-deleting the Cart row may leave cart_items behind depending on the migration's cascade setup — check the published migration.add, updateQuantity, remove, merge, empty) and the Cartable trait's relation names (cart, carts) are worth a five-minute read of vendor/freshbitsweb/laravel-cart-manager/src before you build services on top of them.CartItemAdded, CartMerged) around service methods rather than editing vendor code.How can I help you explore Laravel packages today?