How to Build a Simple Add to Cart System in PHP Using Sessions (Step-by-Step)
Building an e-commerce shopping cart is one of the most rewarding milestones when learning backend web development. Many beginners mistakenly assume they need a database table for every cart interaction, complicated AJAX scripts, or an entire framework like Laravel just to store two items and calculate a subtotal.
You don't.
With plain PHP, you can build a complete, reliable shopping cart using native $_SESSION in fewer than 60 lines of code.
Here is the exact beginner-friendly architecture:
products.php: Has your product array at the top, loops through items to render product cards with "+ Add to Cart" buttons, and submits via POST directly tocart.php.cart.php: Catches the POST request on arrival, saves/inserts the product into$_SESSION['cart'], runs a session loop withforeach, and displays every item with real-time subtotal and grand total.
Let's build it step by step.
How the Session Flow Works
+-----------------------------+ POST Request +---------------------------------+
| products.php | ----------------------------------------> | cart.php |
| | (product_id, name, price, quantity) | |
| 1. Top: $products array | | 1. Catch POST data |
| 2. Loop product cards | | 2. Save into $_SESSION['cart'] |
| 3. Click "+ Add to Cart" | | 3. Loop through $_SESSION items |
+-----------------------------+ | 4. Display subtotal & total |
+---------------------------------+
HTTP is stateless. When a visitor browses your store, PHP forgets who they are between page loads. Calling session_start() at the beginning of your scripts gives each visitor a unique cookie ID and a temporary storage box on the server:
$_SESSION['cart'][1] = [
'name' => 'Mechanical Keyboard',
'price' => 2499,
'quantity' => 1
];
Step 1: The Product Page (products.php)
Create a file named products.php.
This file has three distinct sections:
- Top: A PHP array of products (
$products). - Next: A
foreachloop that displays each product with its price and an "+ Add to Cart" button. - Form Action: When the user clicks the button, the form sends a POST method directly to
cart.php.
<?php
session_start();
// ==========================================
// 1. TOP: Product Array in PHP
// ==========================================
$products = [
1 => [
'name' => 'Mechanical Keyboard',
'price' => 2499,
'icon' => '⌨️'
],
2 => [
'name' => 'Wireless Gaming Mouse',
'price' => 1299,
'icon' => '🖱️'
],
3 => [
'name' => 'Noise-Cancelling Headset',
'price' => 3199,
'icon' => '🎧'
],
4 => [
'name' => 'Ergonomic Desk Mat',
'price' => 699,
'icon' => '🖥️'
]
];
// Count current items in cart for the navbar badge
$totalCartCount = 0;
if (!empty($_SESSION['cart'])) {
foreach ($_SESSION['cart'] as $item) {
$totalCartCount += $item['quantity'];
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Our Tech Store - Products</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
background: #f8fafc;
color: #0f172a;
max-width: 800px;
margin: 40px auto;
padding: 0 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #e2e8f0;
padding-bottom: 15px;
margin-bottom: 30px;
}
.btn-cart {
background: #6d28d9;
color: #ffffff;
padding: 10px 18px;
border-radius: 8px;
text-decoration: none;
font-weight: bold;
font-size: 14px;
}
.btn-cart:hover {
background: #5b21b6;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px;
}
.product-card {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 20px;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.03);
display: flex;
flex-direction: column;
justify-content: space-between;
}
.product-icon {
font-size: 48px;
margin-bottom: 10px;
}
.product-title {
font-size: 16px;
font-weight: 700;
margin: 10px 0 5px 0;
}
.product-price {
font-size: 18px;
font-weight: 800;
color: #059669;
margin-bottom: 15px;
}
.btn-add {
background: #6d28d9;
color: white;
border: none;
width: 100%;
padding: 10px;
border-radius: 8px;
font-weight: bold;
font-size: 14px;
cursor: pointer;
transition: background 0.2s;
}
.btn-add:hover {
background: #5b21b6;
}
</style>
</head>
<body>
<div class="header">
<div>
<h1 style="margin: 0; font-size: 24px;">🛒 WeBotApp Academy Store</h1>
<p style="margin: 4px 0 0 0; color: #64748b; font-size: 14px;">PHP Session Cart Demo</p>
</div>
<a href="cart.php" class="btn-cart">
View Cart (<?php echo $totalCartCount; ?>)
</a>
</div>
<!-- ======================================================== -->
<!-- 2. NEXT: Loop Products & Add to Cart via POST to cart.php -->
<!-- ======================================================== -->
<h2>Available Products</h2>
<div class="grid">
<?php foreach ($products as $id => $product): ?>
<div class="product-card">
<div>
<div class="product-icon"><?php echo $product['icon']; ?></div>
<div class="product-title"><?php echo htmlspecialchars($product['name']); ?></div>
<div class="product-price">Rs. <?php echo number_format($product['price'], 2); ?></div>
</div>
<!-- 3. Form Posts Product Data Directly to cart.php -->
<form method="POST" action="cart.php">
<input type="hidden" name="product_id" value="<?php echo $id; ?>">
<input type="hidden" name="product_name" value="<?php echo htmlspecialchars($product['name']); ?>">
<input type="hidden" name="product_price" value="<?php echo $product['price']; ?>">
<input type="hidden" name="quantity" value="1">
<button type="submit" name="add_to_cart" class="btn-add">
+ Add to Cart
</button>
</form>
</div>
<?php endforeach; ?>
</div>
</body>
</html>
Step 2: The Cart Page with Session Loop (cart.php)
Now create cart.php.
When the user clicks "+ Add to Cart" on products.php, the browser submits directly to this page. Here is what happens:
- Top:
session_start()initializes the visitor's session. - Catch POST: We check
isset($_POST['add_to_cart']). If sent, we insert the item into$_SESSION['cart'](or increment its quantity if it was already added). - The Session Loop: We run
foreach ($_SESSION['cart'] as $productId => $item)to render each row into an HTML table with its subtotal. - Totals & Actions: We calculate the grand total and provide buttons to remove single items, empty the whole basket, or continue shopping.
<?php
session_start();
// Initialize the cart array in session if not set yet
if (!isset($_SESSION['cart'])) {
$_SESSION['cart'] = [];
}
// ================================================================
// 1. SAVE TO SESSION: Catch POST data from products.php
// ================================================================
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_to_cart'])) {
$id = (int)$_POST['product_id'];
$name = trim($_POST['product_name']);
$price = (float)$_POST['product_price'];
$quantity = isset($_POST['quantity']) ? (int)$_POST['quantity'] : 1;
// If item already exists in session, increase quantity; else add new
if (isset($_SESSION['cart'][$id])) {
$_SESSION['cart'][$id]['quantity'] += $quantity;
} else {
$_SESSION['cart'][$id] = [
'name' => $name,
'price' => $price,
'quantity' => $quantity
];
}
// Redirect to cart.php via GET to prevent form resubmission on F5 refresh
header("Location: cart.php");
exit;
}
// ================================================================
// 2. HANDLE REMOVE ITEM
// ================================================================
if (isset($_GET['remove'])) {
$removeId = (int)$_GET['remove'];
if (isset($_SESSION['cart'][$removeId])) {
unset($_SESSION['cart'][$removeId]);
}
header("Location: cart.php");
exit;
}
// ================================================================
// 3. HANDLE CLEAR ENTIRE CART
// ================================================================
if (isset($_GET['clear'])) {
unset($_SESSION['cart']);
header("Location: cart.php");
exit;
}
// ================================================================
// 4. HANDLE QUANTITY UPDATE
// ================================================================
if (isset($_POST['update_cart']) && isset($_POST['quantities'])) {
foreach ($_POST['quantities'] as $id => $qty) {
$qty = (int)$qty;
if ($qty <= 0) {
unset($_SESSION['cart'][$id]);
} else {
$_SESSION['cart'][$id]['quantity'] = $qty;
}
}
header("Location: cart.php");
exit;
}
$grandTotal = 0;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your Shopping Cart</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
background: #f8fafc;
color: #0f172a;
max-width: 800px;
margin: 40px auto;
padding: 0 20px;
line-height: 1.5;
}
.cart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.btn-link {
color: #6d28d9;
text-decoration: none;
font-weight: 700;
font-size: 14px;
}
table {
width: 100%;
border-collapse: collapse;
background: #ffffff;
border-radius: 12px;
overflow: hidden;
border: 1px solid #e2e8f0;
box-shadow: 0 2px 4px rgba(0,0,0,0.03);
}
th, td {
text-align: left;
padding: 14px 18px;
border-bottom: 1px solid #e2e8f0;
}
th {
background: #f1f5f9;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #475569;
}
.qty-input {
width: 55px;
padding: 6px;
text-align: center;
border: 1px solid #cbd5e1;
border-radius: 6px;
font-size: 14px;
}
.btn {
display: inline-block;
background: #6d28d9;
color: white;
border: none;
padding: 8px 14px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-size: 13px;
text-decoration: none;
}
.btn:hover { background: #5b21b6; }
.btn-danger { background: #ef4444; }
.btn-danger:hover { background: #dc2626; }
.btn-secondary { background: #64748b; }
.btn-secondary:hover { background: #475569; }
.total-box {
margin-top: 25px;
text-align: right;
font-size: 1.3rem;
font-weight: 800;
}
.actions {
margin-top: 25px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 15px;
}
.empty-cart-card {
padding: 50px 20px;
text-align: center;
background: #ffffff;
border-radius: 12px;
border: 2px dashed #cbd5e1;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="cart-header">
<h1 style="margin: 0; font-size: 26px;">🛍️ Your Shopping Cart</h1>
<a href="products.php" class="btn-link">← Back to Products</a>
</div>
<?php if (empty($_SESSION['cart'])): ?>
<div class="empty-cart-card">
<div style="font-size: 48px; margin-bottom: 10px;">🧺</div>
<h3 style="margin: 0 0 6px 0;">Your basket is completely empty!</h3>
<p style="color: #64748b; margin: 0 0 16px 0;">Pick some gear from our store to get started.</p>
<a href="products.php" class="btn">Browse Store</a>
</div>
<?php else: ?>
<form method="POST" action="cart.php">
<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th style="width: 100px;">Quantity</th>
<th>Subtotal</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<!-- ======================================================== -->
<!-- 3. THE SESSION LOOP: Show Products in Session -->
<!-- ======================================================== -->
<?php foreach ($_SESSION['cart'] as $productId => $item): ?>
<?php
$subtotal = $item['price'] * $item['quantity'];
$grandTotal += $subtotal;
?>
<tr>
<td>
<strong><?php echo htmlspecialchars($item['name']); ?></strong>
</td>
<td>Rs. <?php echo number_format($item['price'], 2); ?></td>
<td>
<input
type="number"
name="quantities[<?php echo $productId; ?>]"
value="<?php echo (int)$item['quantity']; ?>"
min="0"
class="qty-input"
>
</td>
<td style="font-weight: 700; color: #059669;">
Rs. <?php echo number_format($subtotal, 2); ?>
</td>
<td>
<a
href="cart.php?remove=<?php echo $productId; ?>"
class="btn btn-danger"
onclick="return confirm('Remove this product from cart?');"
>
Remove
</a>
</td>
</tr>
<?php endforeach; ?>
<!-- ======================================================== -->
<!-- END OF SESSION LOOP -->
<!-- ======================================================== -->
</tbody>
</table>
<div class="total-box">
Grand Total: <span style="color: #059669;">Rs. <?php echo number_format($grandTotal, 2); ?></span>
</div>
<div class="actions">
<div>
<a href="cart.php?clear=1" class="btn btn-secondary" onclick="return confirm('Empty your entire basket?');">
Empty Cart
</a>
<button type="submit" name="update_cart" class="btn">
Update Quantities
</button>
</div>
<div>
<a href="products.php" class="btn btn-secondary" style="margin-right: 8px;">
← Add More Items
</a>
<button
type="button"
class="btn"
style="background: #059669; font-size: 15px; padding: 10px 22px;"
onclick="alert('Order placed successfully! In a real application, you redirect to Razorpay/Stripe checkout.');"
>
Checkout →
</button>
</div>
</div>
</form>
<?php endif; ?>
</body>
</html>
Breakdown of Key Lines
1. Why header("Location: cart.php") After POST?
Notice what we do right after appending the item into $_SESSION['cart']:
header("Location: cart.php");
exit;
This implements the Post/Redirect/Get (PRG) design pattern. Without this redirect, if a customer presses F5 or refreshes their browser, the browser will ask "Confirm form resubmission?" and accidentally re-add the item again and again. Redirecting back to cart.php via GET ensures the cart displays cleanly.
2. How the Session Loop Computes Totals
Inside the loop:
$subtotal = $item['price'] * $item['quantity'];
$grandTotal += $subtotal;
Before the loop runs, we initialize $grandTotal = 0;. On each iteration:
- It computes the line item total (
price * quantity). - It accumulates into
$grandTotal. - When the
foreachloop terminates,$grandTotalholds the exact sum of all products in the session.
3. Removing Single Items with unset()
unset($_SESSION['cart'][$removeId]);
PHP's native unset() removes only the matching product key from the array. The rest of the cart remains intact.
4. Emptying the Cart
unset($_SESSION['cart']);
This resets only the cart bucket while preserving any other session variables (such as login credentials or CSRF tokens).
3 Security Best Practices for Production
- Always Sanitize Output with
htmlspecialchars(): Whenever rendering product names back into HTML, usehtmlspecialchars($item['name'])to prevent Cross-Site Scripting (XSS). - Never Trust Prices From Hidden Form Inputs in Production:
In a production application connected to MySQL, only submit the
product_id. Incart.php, query the database or your master catalog array to get the verified price before storing it in$_SESSION['cart']. - Line 1
session_start(): Always keep<?php session_start(); ?>at the very top of your files without any preceding spaces, line breaks, or HTML to prevent "Headers already sent" errors.
Summary
With just two simple PHP files:
products.php: Declares the product array at the top and loops through "+ Add to Cart" buttons.cart.php: Catches the POST, inserts into$_SESSION['cart'], runs the session loop, and shows all cart items with live subtotals.
You now have a clean, working e-commerce cart engine in native PHP!
Inquire for Course Fee & Discounts
Get full program details, syllabus breakdown, and claim your exclusive course fee discount for the Best Digital Marketing Course in Guwahati.