Skip to content

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

  1. Go to WP-Nexus > Apps and click Create App.
  2. Name it Shop.
  3. Save it.

Step 2: Create the Database Tables

Go to the Tables tab and create these 5 tables:

Table 1: categories

ColumnTypeDescription
namevarchar(255)Category name
slugvarchar(255)URL-friendly slug
descriptiontextCategory description
image_urlvarchar(500)Category image

Table 2: products

ColumnTypeDescription
namevarchar(255)Product name
descriptiontextProduct description
pricedecimal(10,2)Product price
image_urlvarchar(500)Product image
category_idintForeign key to categories
stockintAvailable quantity
statusvarchar(50)active / inactive

Add an index on category_id and status.

Table 3: cart_items

ColumnTypeDescription
user_idintWordPress user ID
product_idintForeign key to products
quantityintNumber of items
created_atdatetimeWhen added to cart

Add an index on user_id.

Table 4: orders

ColumnTypeDescription
user_idintCustomer's user ID
totaldecimal(10,2)Order total
statusvarchar(50)pending / processing / shipped / delivered
created_atdatetimeOrder date

Add an index on user_id and status.

Table 5: order_items

ColumnTypeDescription
order_idintForeign key to orders
product_idintForeign key to products
product_namevarchar(255)Product name snapshot
pricedecimal(10,2)Price at time of order
quantityintQuantity 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") → Result

Products 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")
      → Result

TIP

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)
              → Output

Set 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")
      → Result

This pipeline:

  1. Gets the current user.
  2. Fetches their cart items.
  3. 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) → Output

Step 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)
                → Output

This 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") → Result

Order Items (Inner Repeater)

Parent Data
  → Related Items (tableId: order_items, foreignKeyField: "order_id", localField: "id")
    → Result

Step 8: Create Shortcodes and Test

  1. Create shortcodes for: Product Catalog, Cart Page, Admin Orders.
  2. Add them to WordPress pages.
  3. 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.