Appearance
Tutorial: Shopping Cart
Build a complete shopping cart application with product catalog, category navigation, add-to-cart functionality, a cart page, and an order placement flow. This is an advanced tutorial that covers multiple tables, nested repeaters, Lookup enrichment, and multi-step action pipelines.
What You Will Build
- 5 database tables (Categories, Products, Cart Items, Orders, Order Items).
- Admin product management.
- Public catalog with category and product browsing.
- Add-to-cart button on each product.
- Cart page showing items with product details and totals.
- Place order pipeline that creates an order from cart items.
- Admin orders view.
Step 1: Create the App
- Go to WP-Nexus > Apps and click Create App.
- Name it
Shop. - Save it.
Step 2: Create the Database Tables
Go to the Tables tab and create these 5 tables:
Table 1: categories
| Column | Type | Description |
|---|---|---|
name | varchar(255) | Category name |
slug | varchar(255) | URL-friendly slug |
description | text | Category description |
image_url | varchar(500) | Category image |
Table 2: products
| Column | Type | Description |
|---|---|---|
name | varchar(255) | Product name |
description | text | Product description |
price | decimal(10,2) | Product price |
image_url | varchar(500) | Product image |
category_id | int | Foreign key to categories |
stock | int | Available quantity |
status | varchar(50) | active / inactive |
Add an index on category_id and status.
Table 3: cart_items
| Column | Type | Description |
|---|---|---|
user_id | int | WordPress user ID |
product_id | int | Foreign key to products |
quantity | int | Number of items |
created_at | datetime | When added to cart |
Add an index on user_id.
Table 4: orders
| Column | Type | Description |
|---|---|---|
user_id | int | Customer's user ID |
total | decimal(10,2) | Order total |
status | varchar(50) | pending / processing / shipped / delivered |
created_at | datetime | Order date |
Add an index on user_id and status.
Table 5: order_items
| Column | Type | Description |
|---|---|---|
order_id | int | Foreign key to orders |
product_id | int | Foreign key to products |
product_name | varchar(255) | Product name snapshot |
price | decimal(10,2) | Price at time of order |
quantity | int | Quantity ordered |
Add an index on order_id.
Step 3: Build the Product Catalog
Create a component called Product Catalog.
UI Layout
Container
├── Heading ("Our Products")
├── Repeater (categories data source) ← Category sections
│ └── Container
│ ├── Heading ("`{ {row.name}}`", h2)
│ ├── Text ("`{ {row.description}}`")
│ └── Repeater (products-by-category) ← Products in category
│ └── Card
│ ├── Image (src: "`{ {row.image_url}}`")
│ ├── Heading ("`{ {row.name}}`", h3)
│ ├── Text ("$`{ {number_format(row.price, 2)}}`")
│ ├── Text ("`{ {row.stock > 0 ? 'In Stock' : 'Sold Out'}}`")
│ └── Button ("Add to Cart", visibility: "row.stock > 0")Categories Data Source
Table Query (categories, orderBy: "name", order: "ASC") → ResultProducts by Category (Inner Repeater)
The inner repeater uses the parent category's id to fetch related products:
Parent Data
→ Table Query (products,
whereField: "category_id",
whereOp: "=",
whereValue: "`{ {input.id}}`",
orderBy: "name",
order: "ASC")
→ Filter (field: "status", operator: "eq", value: "active")
→ ResultTIP
The inner repeater automatically has access to the parent category's row data via the Parent Data node. The { {input.id}} in the Table Query config refers to the parent category's id.
Step 4: Build the Add to Cart Action
Select the Add to Cart button in the inner product repeater. Configure its onClick action:
Pipeline
Context
→ Require Auth (logged_in)
→ Current User
→ Set Field (user_id = input.id)
→ Set Field (quantity = 1)
→ Set Field (created_at)
→ Save Row (tableId: cart_items)
→ OutputSet Field (user_id):
Insert a Current User source node before this Set Field node to access the user's ID:
fieldName: "user_id"
expression: "input.id"Set Field (product_id):
The Context already contains the product's id from the repeater row. However, we need to map it to product_id:
fieldName: "product_id"
expression: "row.id"INFO
Add this Set Field node before Save Row to ensure the product_id field is set correctly from the row context.
Set Field (quantity):
fieldName: "quantity"
expression: "1"Set Field (created_at):
fieldName: "created_at"
expression: "now()"Step 5: Build the Cart Page
Create a component called Cart Page.
UI Layout
Container (visibility: "logged_in")
├── Heading ("Shopping Cart")
├── Text ("Your cart is empty", visibility: "count(row) == 0")
├── Repeater (cart data source)
│ └── Container (flex, row, alignItems: center, gap: 16px)
│ ├── Image (src: "`{ {row.product.image_url}}`", width: 80px)
│ ├── Container (flex: 1)
│ │ ├── Text ("`{ {row.product.name}}`", fontWeight: bold)
│ │ └── Text ("$`{ {number_format(row.product.price, 2)}}` x `{ {row.quantity}}`")
│ ├── Text ("$`{ {number_format(row.product.price * row.quantity, 2)}}`", fontWeight: bold)
│ └── Button ("Remove", variant: danger)
├── Divider
├── Container (flex, row, justifyContent: space-between)
│ ├── Heading ("Total", h3)
│ └── Heading ("$`{ {number_format(row.cart_total, 2)}}`", h3)
└── Button ("Place Order", variant: primary, size: large)Cart Data Source Pipeline
Current User
→ Table Query (cart_items,
whereField: "user_id",
whereOp: "=",
whereValue: "`{ {input.id}}`",
orderBy: "created_at",
order: "DESC")
→ Lookup (tableId: products,
localField: "product_id",
foreignField: "id",
outputField: "product")
→ ResultThis pipeline:
- Gets the current user.
- Fetches their cart items.
- Lookup enriches each cart item with full product data (name, price, image).
Remove Item Action
Configure the Remove button's onClick:
Context → Require Auth → Delete Row (tableId: cart_items) → OutputStep 6: Build the Place Order Pipeline
Select the Place Order button and configure its onClick action. This is the most complex pipeline:
Pipeline
Context
→ Require Auth (logged_in)
→ Current User
→ Table Query (cart_items,
whereField: "user_id",
whereOp: "=",
whereValue: "`{ {input.id}}`")
→ Lookup (products, localField: "product_id", foreignField: "id", outputField: "product")
→ Set Field (cart_total = computed)
→ Save Row (orders: create order)
→ For Each (save order items action)
→ OutputThis is complex, so here is a simpler approach using two actions:
Action 1: Create Order (main pipeline)
Context
→ Require Auth
→ Current User ← Get user ID
→ Table Query (cart_items, ← Fetch cart items
whereField: "user_id",
whereValue: "`{ {input.id}}`")
→ Lookup (products) ← Get product prices
→ Combine (var1: "items", var2: "user")
→ Set Field (calculate total)
→ (continue to create order record)Due to pipeline complexity, consider splitting this into a custom REST endpoint or multiple Run Action nodes.
Simplified approach
A practical approach is to build the Place Order as a saved action:
Step A: Calculate cart total
Use a Math node on the enriched cart items to sum up product.price * quantity for each item.
Step B: Create the order record
Save a new row in the orders table with user_id, total, status: 'pending', and created_at: now().
Step C: Create order items
For each cart item, save a row in order_items with the order_id, product_id, product_name, price, and quantity.
Step D: Clear the cart
Delete all cart items for the current user.
Step 7: Admin Orders View
Create a component called Admin Orders with visibility user_can('manage_options').
UI Layout
Container
├── Heading ("Orders")
└── Repeater (orders data source)
└── Card
├── Text ("Order #`{ {row.id}}`")
├── Text ("$`{ {number_format(row.total, 2)}}`")
├── Badge ("`{ {row.status}}`")
├── Text ("`{ {date_format(row.created_at, 'M j, Y g:i A')}}`")
└── Repeater (order items)
└── Text ("`{ {row.product_name}}` x`{ {row.quantity}}` - $`{ {number_format(row.price * row.quantity, 2)}}`")Orders Data Source
Table Query (orders, orderBy: "created_at", order: "DESC") → ResultOrder Items (Inner Repeater)
Parent Data
→ Related Items (tableId: order_items, foreignKeyField: "order_id", localField: "id")
→ ResultStep 8: Create Shortcodes and Test
- Create shortcodes for: Product Catalog, Cart Page, Admin Orders.
- Add them to WordPress pages.
- Test the full flow:
- Browse the catalog.
- Add items to the cart.
- View the cart page.
- Place an order.
- Check the admin orders view.
What You Learned
- Designing a multi-table database schema with foreign key relationships.
- Building nested repeaters (categories containing products).
- Using the Lookup node to enrich rows with data from another table.
- Using Parent Data and Related Items for parent-child data flow.
- Building complex multi-step action pipelines.
- User-scoped data filtering with Current User.
- Admin-only views with capability-based visibility.
Next Steps
- Add product quantity selectors (update cart item quantity).
- Add order status management (admin can change status).
- Add stock decrement on order placement.
- Add email notifications for new orders.
- Add a search/filter bar to the catalog (see Search & Filter recipe).
- Add product images via File Uploads.