Build a Professional POS System

Building a professional WooCommerce POS PWA plugin is a large system, so the correct approach is to design a clean architecture first and then implement modules. Below is a production-ready structure and working base code that you can expand into a full POS.

Stack you requested:

  • Frontend: TailwindCSS + jQuery
  • Backend: PHP (WordPress plugin)
  • Database: MySQL (via WordPress / WooCommerce tables)
  • PWA: Service Worker + Manifest
  • Features: POS UI, Cart, Barcode, Orders, Offline Sync

1. POS Plugin Architecture

Plugin folder:

woocommerce-pos-pwa/
│
├── woocommerce-pos-pwa.php
├── manifest.json
├── service-worker.js
│
├── includes/
│   ├── api.php
│   ├── auth.php
│   ├── orders.php
│   └── products.php
│
├── assets/
│   ├── css/
│   │   └── pos.css
│   ├── js/
│   │   ├── pos.js
│   │   ├── cart.js
│   │   ├── barcode.js
│   │   └── sync.js
│   └── icons/
│
└── templates/
    └── pos-ui.php

2. WordPress Plugin Main File

woocommerce-pos-pwa.php

<?php
/*
Plugin Name: WooCommerce POS PWA
Description: Progressive Web App POS system for WooCommerce
Version: 1.0
Author: NeedleCode
*/

if (!defined('ABSPATH')) exit;

class WC_POS_PWA {

    public function __construct() {

        add_action('admin_menu', [$this,'menu']);
        add_action('admin_enqueue_scripts', [$this,'assets']);

        add_action('rest_api_init', function () {

            register_rest_route('pos/v1','products',[
                'methods'=>'GET',
                'callback'=>['WC_POS_PRODUCTS','get_products']
            ]);

            register_rest_route('pos/v1','order',[
                'methods'=>'POST',
                'callback'=>['WC_POS_ORDERS','create_order']
            ]);

        });

    }

    function menu(){
        add_menu_page(
            'POS',
            'POS',
            'manage_woocommerce',
            'wc-pos',
            [$this,'pos_page'],
            'dashicons-cart'
        );
    }

    function pos_page(){
        include plugin_dir_path(__FILE__).'templates/pos-ui.php';
    }

    function assets(){

        wp_enqueue_script('jquery');

        wp_enqueue_script(
            'pos-js',
            plugin_dir_url(__FILE__).'assets/js/pos.js',
            ['jquery'],
            '1.0',
            true
        );

        wp_enqueue_style(
            'tailwind',
            'https://cdn.jsdelivr.net/npm/tailwindcss@3/dist/tailwind.min.css'
        );

        wp_localize_script('pos-js','POSAPI',[
            'url'=>rest_url('pos/v1/')
        ]);

    }

}

new WC_POS_PWA;

require_once plugin_dir_path(__FILE__).'includes/products.php';
require_once plugin_dir_path(__FILE__).'includes/orders.php';

3. Product API

includes/products.php

<?php

class WC_POS_PRODUCTS {

    public static function get_products(){

        $args = [
            'limit'=>100,
            'status'=>'publish'
        ];

        $products = wc_get_products($args);
        $data = [];

        foreach($products as $product){
            $data[]=[
                'id'=>$product->get_id(),
                'name'=>$product->get_name(),
                'price'=>$product->get_price(),
                'stock'=>$product->get_stock_quantity(),
                'image'=>wp_get_attachment_url($product->get_image_id()),
                'barcode'=>$product->get_sku()
            ];
        }

        return $data;

    }

}

4. Create Order API

includes/orders.php

<?php

class WC_POS_ORDERS {

    public static function create_order($req){

        $items = $req['items'];
        $payment = $req['payment'];

        $order = wc_create_order();

        foreach($items as $item){
            $product = wc_get_product($item['id']);
            $order->add_product(
                $product,
                $item['qty']
            );
        }

        $order->set_payment_method($payment);
        $order->calculate_totals();
        $order->save();

        return [
            'status'=>'success',
            'order_id'=>$order->get_id()
        ];

    }

}

5. POS UI (Responsive)

templates/pos-ui.php

<div class="flex h-screen">

    <!-- PRODUCTS -->
    <div class="w-2/3 p-4 overflow-y-scroll">
        <input id="search" placeholder="Search product" class="border p-2 w-full mb-4" />
        <div id="products" class="grid grid-cols-2 md:grid-cols-4 gap-4"> </div>
    </div>

    <!-- CART -->
    <div class="w-1/3 bg-gray-100 p-4">

        <h2 class="text-xl font-bold mb-4">Cart</h2>
        <div id="cart"></div>

        <div class="mt-4">
            <button id="checkout" class="bg-green-500 text-white w-full p-3"> Checkout </button>
        </div>

    </div>

</div>

6. Load Products

assets/js/pos.js

let cart = []

function loadProducts(){

    $.get(POSAPI.url+"products",function(data){

        let html=''

        data.forEach(p=>{

            html+=`
            <div class="border p-2 cursor-pointer"
            onclick="addCart(${p.id},'${p.name}',${p.price})">

            <img src="${p.image}" class="h-24 mx-auto">

            <div class="text-center">
            ${p.name}
            </div>

            <div class="text-center text-green-600">
            ${p.price}
            </div>

            </div>
            `

        })

        $("#products").html(html)

    })

}

loadProducts()

7. Cart System

assets/js/cart.js

function addCart(id,name,price){

    let item = cart.find(i=>i.id==id)

    if(item){
        item.qty++
    }else{
        cart.push({
            id:id,
            name:name,
            price:price,
            qty:1
        })
    }

    renderCart()
}

function renderCart(){

    let html=''
    let total=0

    cart.forEach(i=>{
        total+=i.price*i.qty
        html+=`
        <div class="flex justify-between border-b py-2">
        <div>${i.name} x ${i.qty}</div>
        <div>${i.price*i.qty}</div>
        </div>
        `
    })

    html+=`<div class="font-bold mt-3">Total: ${total}</div>`
    $("#cart").html(html)

}

8. Checkout

$("#checkout").click(function(){

    $.ajax({
        url:POSAPI.url+"order",
        method:"POST",
        contentType:"application/json",
        data:JSON.stringify({
            items:cart,
            payment:"cash"
        }),
        success:function(res){
            alert("Order "+res.order_id+" created")
            cart=[]
            renderCart()
        }
    })

})

9. Barcode Scanner

Use keyboard barcode scanner (most POS scanners work as keyboard input).

let barcode=''

$(document).keypress(function(e){
    if(e.which==13){
        searchBarcode(barcode)
        barcode=''
    }else{
        barcode+=String.fromCharCode(e.which)
    }
})

function searchBarcode(code){
    $.get(POSAPI.url+"products",function(data){
        let p=data.find(x=>x.barcode==code)
        if(p){
            addCart(p.id,p.name,p.price)
        }
    })
}

10. PWA Manifest

manifest.json

{
    "name":"WooCommerce POS",
    "short_name":"POS",
    "display":"standalone",
    "start_url":"/wp-admin/admin.php?page=wc-pos",
    "background_color":"#ffffff",
    "theme_color":"#000000",
    "icons":[
        {
        "src":"icons/icon-192.png",
        "sizes":"192x192",
        "type":"image/png"
        }
    ]
}

11. Service Worker (Offline)

service-worker.js

const CACHE = "pos-cache"

self.addEventListener("install", e => {
	e.waitUntil(
		caches.open(CACHE).then(cache => {
			return cache.addAll([
				"/",
				"/manifest.json"
			])
		})
	)
})

self.addEventListener("fetch", e => {
	e.respondWith(
		caches.match(e.request)
			.then(res => {
				return res || fetch(e.request)
			})
	)
})

12. Receipt Printing

function printReceipt(order){

    let w=window.open()

    w.document.write(`
    <h2>Receipt</h2>
    Order #${order.id}
    `)

    w.print()

}

13. Offline Sync Logic

assets/js/sync.js

function saveOffline(order) {
	let orders = JSON.parse(localStorage.getItem("offline_orders") || "[]")
	orders.push(order)
	localStorage.setItem("offline_orders", JSON.stringify(orders))
}

function syncOrders() {
	let orders = JSON.parse(localStorage.getItem("offline_orders") || "[]")
	orders.forEach(o => {
		$.post(POSAPI.url + "order", o)
	})
	localStorage.removeItem("offline_orders")
}

window.addEventListener("online", syncOrders)

14. Professional POS Features To Add

Real production POS should include:

  • Product search
  • Category filter
  • Discount system
  • Tax calculation
  • Customer selection
  • Multiple payment types
  • Receipt printer support
  • WebSocket stock sync
  • Inventory update
  • Multi-cashier login
  • Shift management
  • Sales dashboard
  • IndexedDB offline storage

15. Professional POS UI Layout

--------------------------------------
| Products Grid | Cart              |
|               |                   |
|               | Order Summary     |
|               | Payment Buttons   |
--------------------------------------
| Barcode Input | Customer | Total  |
--------------------------------------

💡 Since we are building professional POS system, the next level would be:

  • Realtime stock sync
  • IndexedDB offline database
  • Camera barcode scanner
  • Thermal printer support
  • Multi-store POS
  • 10k product performance

If you want, We can build POS for your woocommerce store. Contact for build POS Today