Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

Laravel 13 Add to Cart Tutorial: A Session-Based Shopping Cart from Scratch

๐Ÿ› ๏ธ Laravel Tutorials5 min read

Laravel 13 Add to Cart Tutorial: A Session-Based Shopping Cart from Scratch

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 12, 2026
Laravel 13 Add to Cart Tutorial: A Session-Based Shopping Cart from Scratch

The simplest shopping cart in Laravel 13 stores a small array in the session: product ID mapped to quantity. On every page you load the products from the database by those IDs, so prices always come from your own data and never from the browser. This tutorial builds the cart class, the routes, the controller and the Blade views for add, update, remove and total.

Targets: Laravel 13 (PHP 8.3 to 8.5), Blade and the session. This cart is for guests and logged-in users alike. A database cart or a package is better when you need carts that persist across devices.

Step 1: Create a Product model

Code
php artisan make:model Product -mf
Shell
Schema::create('products', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->unsignedInteger('price_cents');
    $table->timestamps();
});
PHP
// app/Models/Product.php
protected $fillable = ['name', 'price_cents'];

Prices are stored as integer cents (for example 1999 for 19.99) to avoid floating point rounding errors in totals. Run php artisan migrate and create a few products with the factory.

Step 2: Write a Cart class

Keep the cart logic in one class so controllers stay small. Put it in app/Services/Cart.php:

JavaScript
<?php

namespace App\Services;

use App\Models\Product;
use Illuminate\Support\Collection;

class Cart
{
    private const KEY = 'cart';

    /** @return array<int, int> product id => quantity */
    public function raw(): array
    {
        return session(self::KEY, []);
    }

    public function add(int $productId, int $quantity = 1): void
    {
        $cart = $this->raw();
        $cart[$productId] = min(($cart[$productId] ?? 0) + $quantity, 99);

        session([self::KEY => $cart]);
    }

    public function set(int $productId, int $quantity): void
    {
        $cart = $this->raw();

        if ($quantity <= 0) {
            unset($cart[$productId]);
        } else {
            $cart[$productId] = min($quantity, 99);
        }

        session([self::KEY => $cart]);
    }

    public function remove(int $productId): void
    {
        $this->set($productId, 0);
    }

    public function clear(): void
    {
        session()->forget(self::KEY);
    }

    /** @return Collection<int, array{product: Product, quantity: int, subtotal: int}> */
    public function items(): Collection
    {
        $cart = $this->raw();

        return Product::whereIn('id', array_keys($cart))->get()->map(fn (Product $product) => [
            'product' => $product,
            'quantity' => $cart[$product->id],
            'subtotal' => $product->price_cents * $cart[$product->id],
        ]);
    }

    public function totalCents(): int
    {
        return $this->items()->sum('subtotal');
    }
}

The session stores only IDs and quantities. Names and prices are read from the database each time, so a user cannot change a price, and price changes in your shop appear in existing carts automatically. The quantity is limited to 99 to stop absurd values.

Step 3: Add the controller and routes

JavaScript
<?php

namespace App\Http\Controllers;

use App\Models\Product;
use App\Services\Cart;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class CartController extends Controller
{
    public function index(Cart $cart): View
    {
        return view('cart', ['items' => $cart->items(), 'total' => $cart->totalCents()]);
    }

    public function add(Request $request, Cart $cart, Product $product): RedirectResponse
    {
        $cart->add($product->id, $request->integer('quantity', 1));

        return redirect()->route('cart.index')->with('success', $product->name.' added to cart.');
    }

    public function update(Request $request, Cart $cart, Product $product): RedirectResponse
    {
        $data = $request->validate(['quantity' => ['required', 'integer', 'min:0', 'max:99']]);

        $cart->set($product->id, $data['quantity']);

        return back();
    }

    public function destroy(Cart $cart, Product $product): RedirectResponse
    {
        $cart->remove($product->id);

        return back();
    }
}
CSS
use App\Http\Controllers\CartController;
use Illuminate\Support\Facades\Route;

Route::get('/cart', [CartController::class, 'index'])->name('cart.index');
Route::post('/cart/{product}', [CartController::class, 'add'])->name('cart.add');
Route::patch('/cart/{product}', [CartController::class, 'update'])->name('cart.update');
Route::delete('/cart/{product}', [CartController::class, 'destroy'])->name('cart.destroy');

Laravel resolves the Cart class from the container automatically, because it has no constructor arguments.

Step 4: Build the cart view

Shell
<h1>Your cart</h1>

@forelse ($items as $item)
    <div>
        {{ $item['product']->name }} -
        {{ number_format($item['product']->price_cents / 100, 2) }}

        <form method="POST" action="{{ route('cart.update', $item['product']) }}" style="display:inline">
            @csrf
            @method('PATCH')
            <input type="number" name="quantity" value="{{ $item['quantity'] }}" min="0" max="99">
            <button type="submit">Update</button>
        </form>

        <form method="POST" action="{{ route('cart.destroy', $item['product']) }}" style="display:inline">
            @csrf
            @method('DELETE')
            <button type="submit">Remove</button>
        </form>
    </div>
@empty
    <p>Your cart is empty.</p>
@endforelse

<p>Total: {{ number_format($total / 100, 2) }}</p>

On the product page add a button that posts to cart.add: a form with @csrf and route('cart.add', $product).

How do I show the item count in the header?

Share it with all views in a service provider's boot() method:

PHP
use Illuminate\Support\Facades\View;

View::composer('*', function ($view) {
    $view->with('cartCount', array_sum(session('cart', [])));
});

How do I check that it works?

  1. Add two different products and one of them twice. The cart page should show both with the right quantities.
  2. Change a quantity to 0 and confirm the product is removed.
  3. Change a product price in the database and reload the cart. The total should update.

Common mistakes

  • Storing prices in the session: users or old data can create wrong totals. Store only IDs and quantities.
  • Using floats for money: use integer cents or a decimal type.
  • Not validating the quantity: negative or huge numbers cause errors.
  • Using GET links for add or remove: use POST, PATCH and DELETE forms with CSRF.
  • Forgetting to clear the cart after checkout: call $cart->clear() when the order is saved.

FAQ

When should I use a database cart instead?

When carts must survive logout, work across devices, or you want abandoned cart emails.

Is there a package?

Yes, several cart packages exist. Check that one supports your Laravel version before using it. The class above is small enough to maintain yourself.

How do I handle stock?

Add a stock column to products and compare the quantity with it in add() and set(), then check again when the order is created.

References

Article Index & Topic Tags
LaravelSessionShopping-CartEcommerce

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.