A full professional POS (like Square / Shopify POS) requires multiple modules. Here we’ll show you a production-ready architecture + working core code so you can scale it easily using your preferred stack:
- Frontend: HTML + TailwindCSS + jQuery
- Backend: PHP (REST style)
- Database: MySQL
- PWA: Offline POS support
- Hardware support: Barcode scanner, receipt printer
- Responsive: tablet / mobile / desktop
Typical real POS modules:
- Authentication (Cashier login)
- Products + Inventory
- Barcode scanner
- Cart
- Orders
- Payment (cash/card/mobile)
- Receipt printing
- Sales reports
- Customers
- Offline sync (PWA)
Below is a complete base structure you can deploy and expand.
1. Project Structure
pos-system/
│
├── index.php
├── login.php
├── dashboard.php
│
├── manifest.json
├── sw.js
│
├── config/
│ └── db.php
│
├── api/
│ ├── login.php
│ ├── products.php
│ ├── order_create.php
│ ├── customers.php
│ └── report.php
│
├── assets/
│ ├── js/
│ │ ├── pos.js
│ │ ├── cart.js
│ │ └── scanner.js
│ │
│ ├── css/
│ │ └── app.css
│ │
│ └── libs/
│ └── jquery.min.js
│
├── components/
│ ├── product_card.php
│ └── cart_row.php
│
└── printer/
└── receipt.php
2. MySQL Database
CREATE DATABASE pos_system;
USE pos_system;
CREATE TABLE users(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
username VARCHAR(100),
password VARCHAR(255),
role VARCHAR(50)
);
INSERT INTO users(name,username,password,role)
VALUES('Admin','admin',MD5('123456'),'admin');
CREATE TABLE products(
id INT AUTO_INCREMENT PRIMARY KEY,
barcode VARCHAR(50),
name VARCHAR(200),
price DECIMAL(10,2),
stock INT,
image VARCHAR(255)
);
CREATE TABLE customers(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
phone VARCHAR(20)
);
CREATE TABLE orders(
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
total DECIMAL(10,2),
payment_type VARCHAR(20),
cashier INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE order_items(
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT,
product_id INT,
qty INT,
price DECIMAL(10,2)
);3. DB Connection
config/db.php
<?php
$host="localhost";
$user="root";
$pass="";
$db="pos_system";
$conn=new mysqli($host,$user,$pass,$db);
if($conn->connect_error){
die("DB Failed");
}
?>4. Login System
login.php
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<script src="assets/libs/jquery.min.js"></script>
<link
href="https://cdn.jsdelivr.net/npm/tailwindcss@2/dist/tailwind.min.css"
rel="stylesheet"
/>
</head>
<body class="bg-gray-100 flex items-center justify-center h-screen">
<div class="bg-white p-8 w-80 shadow">
<h2 class="text-xl mb-4 font-bold">POS Login</h2>
<input
id="username"
class="border p-2 w-full mb-2"
placeholder="Username"
/>
<input
id="password"
type="password"
class="border p-2 w-full mb-4"
placeholder="Password"
/>
<button id="login" class="bg-blue-500 text-white w-full p-2">
Login
</button>
</div>
<script>
$("#login").click(function () {
$.post(
"api/login.php",
{
username: $("#username").val(),
password: $("#password").val(),
},
function (res) {
if (res == "ok") {
location = "dashboard.php";
} else {
alert("Login Failed");
}
}
);
});
</script>
</body>
</html>5. Barcode Scanner Support
Most USB barcode scanners work as keyboard input.
Example JS:
scanner.js
let barcode = "";
$(document).keypress(function (e) {
if (e.which == 13) {
searchBarcode(barcode);
barcode = "";
} else {
barcode += String.fromCharCode(e.which);
}
});
function searchBarcode(code) {
$.getJSON("api/products.php?barcode=" + code, function (p) {
addToCart(p);
});
}6. POS Dashboard Layout
dashboard.php
<div class="grid grid-cols-12 h-screen">
<!-- product list -->
<div class="col-span-8 p-4 overflow-y-scroll">
<input
id="search"
placeholder="Search product"
class="border p-2 w-full mb-3"
/>
<div id="products" class="grid grid-cols-4 gap-3"></div>
</div>
<!-- cart -->
<div class="col-span-4 bg-white border-l p-4">
<h2 class="font-bold text-xl">Cart</h2>
<div id="cart"></div>
<div class="mt-4">
<p class="text-xl">Total: <span id="total">0</span></p>
<button id="pay" class="bg-green-500 text-white p-3 w-full mt-3">
Checkout
</button>
</div>
</div>
</div>7. POS Main JS
pos.js
let cart = [];
function loadProducts() {
$.getJSON("api/products.php", function (data) {
let html = "";
data.forEach((p) => {
html += `
<div class="bg-white shadow p-3 cursor-pointer product"
data-id="${p.id}"
data-name="${p.name}"
data-price="${p.price}">
<h3 class="font-bold">${p.name}</h3>
<p>${p.price}</p>
</div>
`;
});
$("#products").html(html);
});
}
loadProducts();
$(document).on("click", ".product", function () {
let id = $(this).data("id");
let name = $(this).data("name");
let price = $(this).data("price");
let item = cart.find((i) => i.id == id);
if (item) {
item.qty++;
} else {
cart.push({ id, name, price, qty: 1 });
}
renderCart();
});
function renderCart() {
let html = "";
let total = 0;
cart.forEach((c) => {
total += c.qty * c.price;
html += `<div class="flex justify-between border-b py-2">
<span>${c.name} x${c.qty}</span>
<span>${c.qty * c.price}</span>
</div>`;
});
$("#cart").html(html);
$("#total").text(total);
}8. Checkout API
api/order_create.php
<?php
include "../config/db.php";
$data=json_decode(file_get_contents("php://input"),true);
$total=$data['total'];
$items=$data['items'];
$cashier=1;
$conn->query("INSERT INTO orders(total,cashier) VALUES('$total','$cashier')");
$order_id=$conn->insert_id;
foreach($items as $item){
$id=$item['id'];
$qty=$item['qty'];
$price=$item['price'];
$conn->query("INSERT INTO order_items(order_id,product_id,qty,price)
VALUES('$order_id','$id','$qty','$price')");
$conn->query("UPDATE products SET stock=stock-$qty WHERE id=$id");
}
echo json_encode(["status"=>"ok"]);9. Receipt Printer
Thermal printers normally support ESC/POS.
Basic HTML receipt:
printer/receipt.php
<?php
include "../config/db.php";
$id=$_GET['id'];
$order=$conn->query("SELECT * FROM orders WHERE id=$id")->fetch_assoc();
$items=$conn->query("SELECT * FROM order_items WHERE order_id=$id");
?>
<h2>STORE NAME</h2>
Order #<?= $id ?>
<hr>
<?php while($i=$items->fetch_assoc()): ?>
<?= $i['qty'] ?> x <?= $i['price'] ?>
<br>
<?php endif; ?>
<hr>
Total: <?= $order['total'] ?>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
window.print();
});
</script>10. PWA Manifest
{
"name": "POS System",
"short_name": "POS",
"start_url": "/dashboard.php",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000"
}11. Service Worker
self.addEventListener("install", (e) => {
e.waitUntil(
caches.open("pos").then((cache) => {
return cache.addAll(["/", "/dashboard.php"]);
})
);
});Professional Features You Should Add
To make it real commercial POS, add:
Inventory
- stock alerts
- supplier management
Sales
- refunds
- discounts
- tax
- coupon
Hardware
- barcode scanner
- receipt printer
- cash drawer
Reports
- daily sales
- cashier sales
- product sales
Performance
- product search index
- offline IndexedDB
- background sync
To make it more ADVANCED ENTERPRISE POS architecture, you can includes-
- offline-first PWA
- WebSocket live inventory
- 10k products instant search
- touchscreen UI
- barcode camera scanning
- real-time dashboard
- multi-store POS
If you want, We can build POS for your woocommerce store. Contact for build POS Today