A Shopify POS app providing automotive parts lookup and customer purchase history recommendations for Tunexia retail stores.
This app consists of two POS UI extensions that enhance the point-of-sale experience:
- Part Lookup - Search for compatible automotive parts by vehicle (make, model, year)
- Purchase History Recommendations - Show product recommendations based on customer purchase history
Both extensions appear as tiles on the POS home screen and open modals for interaction.
- React Router v7.9.3 - Server-side rendering framework
- React 18.3.1 - UI library (used by Purchase History extension)
- Preact - Used in Part Lookup extension for smaller bundle size
- Vite 6.3.6 - Build tool and dev server
- Shopify App React Router v1.0.0 - App authentication and routing
- Shopify Admin API - GraphQL API for product data
- POS UI Extensions API - POS extension framework
- @shopify/ui-extensions-react/point-of-sale - React components for POS (Purchase History)
- Polaris Web Components - UI components for POS (Part Lookup)
- Prisma 6.16.3 - ORM for session storage
- SQLite - Default database (suitable for single-instance deployments)
- Part Lookup Extension:
2025-10 - Purchase History Extension:
unstable - Webhook API Version:
2026-01
Allows POS staff to search for automotive parts compatible with specific vehicles using car metaobject data. Staff enter a vehicle's make, model, and year, and the system returns all products tagged with that exact vehicle variant.
Location: extensions/part-lookup/
- Search by vehicle make, model, and year
- Display matching products with images, pricing, and stock levels
- Add products directly to cart
- View full product details in Shopify Admin
- Stock badges (green for in stock, red for out of stock)
- Fixed "Back to Search" button that remains visible while scrolling
- Card-based product presentation
Represents a specific vehicle variant with make, model, and year.
Fields:
handle(String) - Unique identifier (e.g., "honda-civic-2007")displayName(String) - CRITICAL - Formatted as "Make Model Year" with exact spacing (e.g., "Honda Civic 2007", "Lotus Emira 2024")type(String) - Must be "car_variant"
Example:
{
"id": "gid://shopify/Metaobject/426858021239",
"handle": "honda-civic-2007",
"displayName": "Honda Civic 2007",
"type": "car_variant"
}Links products to compatible vehicles via metaobject references.
Configuration:
- Namespace:
custom - Key:
compatible_car - Type: List of Metaobject References
- References:
car_variantmetaobjects
Setup Steps:
- Go to Shopify Admin > Settings > Custom data > Metaobjects
- Create
car_variantmetaobject definition - Create metaobject entries for each vehicle (Make Model Year format)
- Add
compatible_carmetafield definition to Products - Edit products and link them to compatible vehicle metaobjects
Searches all products and retrieves compatible car variants:
query SearchProductsByCarVariant {
products(first: 250) {
nodes {
id
title
description
featuredImage {
url
}
variants(first: 1) {
nodes {
id
title
price
image {
url
}
inventoryItem {
id
inventoryLevel(locationId: "gid://shopify/Location/LOCATION_ID") {
quantities(names: "available") {
quantity
}
}
}
}
}
compatibleCars: metafield(namespace: "custom", key: "compatible_car") {
... on Metafield {
id
references(first: 100) {
nodes {
... on Metaobject {
id
handle
displayName
type
}
}
}
}
}
}
}
}File: extensions/part-lookup/src/Modal.jsx
- User Input: Staff enters vehicle make, model, and year in separate text fields
- Validation: Ensures all three fields are filled
- Search String Construction: Concatenates input as "Make Model Year" (e.g., "Honda Civic 2007")
- GraphQL Query:
- Fetches all products (up to 250) with their
compatible_carmetafield - Includes inventory levels for the current POS location
- Fetches all products (up to 250) with their
- Client-Side Filtering:
const matchingProducts = allProducts.filter(product => { const metafield = product.compatibleCars; if (!metafield?.references?.nodes) return false; return metafield.references.nodes.some(node => { return node.displayName?.toLowerCase() === searchString.toLowerCase(); }); });
- Results Display: Shows matching products in card layout with:
- Product title (heading)
- Price and stock badge
- Product image (200px max width)
- "Add to Cart" and "View Details" buttons
Built with Shopify Polaris web components:
<s-page>- Page container with heading<s-scroll-box>- Scrollable content area<s-stack>- Layout with flexbox (inline/block direction, configurable gap)<s-section>- Content sections with visual separation<s-heading>- Semantic headings<s-text>- Text content<s-image>- Product images<s-button>- Action buttons<s-badge>- Status indicators (stock levels with tone)<s-text-field>- Form inputs<s-banner>- Error and empty state messages<s-box>- Generic container with padding control
Uses Preact hooks:
useStatefor: currentPage, make, model, year, searchResults, isSearching, errorMessage, currentLocationId, searchStringuseEffectto fetch current POS location ID on component mount- Conditional rendering for search vs results pages
searchProducts()- Executes GraphQL query, filters results, navigates to results pageaddToCart(product)- Adds product variant to POS cart usingshopify.cart.addLineItemviewProductDetails(product)- Opens product admin page usingshopify.action.navigate
Displays product recommendations for the current customer based on their previous purchases. The extension checks the customer's last 5 orders for products that have recommendation metafields, then shows related products that can be quickly added to the current cart.
Location: extensions/purchase-history-recommendations/
- Auto-detects current cart customer
- Fetches customer's last 5 orders
- Shows previously purchased products that have recommendations
- Displays recommended products with inventory levels
- Toggle products in cart (add/remove)
- Smart tile indicator showing if recommendations are available
- Purchase history context (order number, date)
- Stock status badges
Contains a JSON array of recommended product variant IDs.
Configuration:
- Namespace:
custom - Key:
recommendations - Type: JSON (single line text field storing JSON array)
- Applied to: Product Variants
- Value Format: JSON array of global product variant IDs
Example Value:
[
"gid://shopify/ProductVariant/123456789",
"gid://shopify/ProductVariant/987654321",
"gid://shopify/ProductVariant/555666777"
]Setup Steps:
- Go to Shopify Admin > Settings > Custom data > Variants
- Create a new metafield definition:
- Name: "Recommendations"
- Namespace and key:
custom.recommendations - Type: Single line text
- Edit product variants and add JSON array of recommended variant IDs
query GetCustomerOrderHistory($customerId: ID!) {
customer(id: $customerId) {
id
firstName
lastName
orders(first: 5, sortKey: CREATED_AT, reverse: true) {
edges {
node {
id
name
createdAt
totalPrice
lineItems(first: 250) {
edges {
node {
id
title
quantity
variant {
id
title
price
image {
url
}
product {
id
title
featuredImage {
url
}
}
metafield(namespace: "custom", key: "recommendations") {
value
}
}
}
}
}
}
}
}
}
}query GetRecommendedProductsWithInventory($variantIds: [ID!]!, $locationId: ID!) {
nodes(ids: $variantIds) {
... on ProductVariant {
id
title
price
image {
url
}
product {
id
title
featuredImage {
url
}
}
inventoryItem {
id
inventoryLevel(locationId: $locationId) {
quantities(names: "available") {
quantity
}
}
}
}
}
}Files:
extensions/purchase-history-recommendations/src/Tile.jsx- Tile componentextensions/purchase-history-recommendations/src/Modal.jsx- Main modal interface
- Customer Detection: Monitors cart for customer ID changes
- Purchase History Check: Queries customer's last 5 orders
- Recommendation Count: Counts products with
recommendationsmetafield - Tile State:
- Shows count if recommendations found
- Disables tile if no customer or no recommendations
- Displays appropriate subtitle
- On Open:
- Fetches customer's last 5 orders
- Extracts variants with
recommendationsmetafield
- Product Selection:
- User taps on a previously purchased product
- Extension parses the JSON array of recommended variant IDs
- Fetch Recommendations:
- Queries Shopify for recommended product details
- Retrieves inventory levels for current POS location
- Display Recommendations:
- Shows products in a list with images
- Displays price, inventory status
- Shows "Added to Cart" or "Recently Purchased" badges
- Cart Actions:
- Tap to add product to cart
- Tap again to remove from cart
- Toast notifications for success/error
Built with @shopify/ui-extensions-react/point-of-sale:
<Navigator>- Multi-screen navigation container<Screen>- Individual screen/page<ScrollView>- Scrollable content container<Text>- Text elements<Section>- Content sections with titles<List>- List component with image, label, subtitle, badges<Tile>- Home screen tile
Uses React hooks:
useStatefor: cartItems, enhancedCartItems, recommendedItems, selectedVariantId, selectedVariantUUID, queriedRecommendations, inventoryLevelsuseEffectto:- Fetch purchase history on customer change
- Enhance cart items with product details
- Fetch recommended products when variant selected
useCartSubscription- Real-time cart stateuseApi- Access to POS APIs (cart, toast, navigation)
fetchCustomerPurchaseHistory(customerId)- Fetches last 5 orders with recommendation metafieldsfetchRecommendedProductsWithInventory(variantIds, locationId)- Fetches recommended products and inventory in one queryproductSearchlineItemsToListComponent(items, api)- Formats purchased products for list displayrecommendedItemsToListComponent(items, api)- Formats recommended products with cart toggle functionalitygetDisplayTitle(item)- Helper to get product display title with fallback logic
The app requires the following Shopify API scopes in shopify.app.toml:
scopes = "write_products,read_customers,read_orders,read_inventory,read_metaobjects"write_products- Add products to cart via POS APIsread_customers- Access customer data for purchase historyread_orders- Read customer order historyread_inventory- Display stock levels for current locationread_metaobjects- CRITICAL for Part Lookup - Read car_variant metaobjects and their references
read_metaobjects scope, the Part Lookup extension will return null for metafield references, causing no results to appear.
- Node.js >= 20.10
- Shopify Partner Account
- Development or Plus Sandbox store
- Shopify CLI installed globally:
npm install -g @shopify/cli@latest
# Install dependencies
npm install
# Generate Prisma client and run migrations
npm run setup
# Start development server
npm run devThe dev server will:
- Start a Cloudflare tunnel
- Open POS in your browser
- Hot-reload on file changes
- Rebuild extensions automatically
shopify app devPress P to open the app URL and install it on your test store.
# Build the app
npm run build
# Deploy to Shopify
npm run deploypos-ui/
├── app/ # React Router app (web admin)
│ ├── routes/ # Route handlers
│ ├── shopify.server.js # Shopify authentication
│ └── db.server.js # Database connection
├── extensions/
│ ├── part-lookup/
│ │ ├── src/
│ │ │ ├── Modal.jsx # Main search interface (Preact + Polaris)
│ │ │ └── Tile.jsx # POS home tile
│ │ ├── shopify.extension.toml # Config (API version: 2025-10)
│ │ └── package.json
│ └── purchase-history-recommendations/
│ ├── src/
│ │ ├── Modal.jsx # Recommendations display (React)
│ │ └── Tile.jsx # POS home tile with customer detection
│ ├── shopify.extension.toml # Config (API version: unstable)
│ └── package.json
├── prisma/
│ └── schema.prisma # Database schema
├── shopify.app.toml # App configuration with access scopes
└── package.json # Root package with workspaces
-
Create Car Variant Metaobject:
- Admin > Settings > Custom data > Metaobjects
- Create definition:
car_variantwith fields: handle, displayName, type - Create entry:
displayName= "Honda Civic 2007" (exact format)
-
Add to Product:
- Edit a product
- Add/populate
compatible_carmetafield - Select the Honda Civic 2007 car_variant metaobject
- Save
-
Test in POS:
- Open POS
- Click "Part Finder" tile
- Search: Make="Honda", Model="Civic", Year="2007"
- Product should appear in results
-
Create Recommendations Metafield:
- Admin > Settings > Custom data > Variants
- Create metafield:
custom.recommendations(Single line text)
-
Add Recommendations to Variant:
- Edit a product variant (Product A)
- Add recommendation metafield value:
["gid://shopify/ProductVariant/123", "gid://shopify/ProductVariant/456"]
- Replace with actual variant IDs from your store
-
Create Test Order:
- Create a test customer
- Create an order for that customer containing Product A
- Mark order as paid/fulfilled
-
Test in POS:
- Open POS
- Add test customer to cart
- "Purchase History" tile should show count
- Open modal to see previously purchased products
- Tap Product A to see recommendations
- Add recommended products to cart
1. "No compatible parts found" when parts should exist
Possible causes:
- ❌ Missing
read_metaobjectsscope inshopify.app.toml- Fix: Add scope, redeploy, reinstall app
- ❌ Metaobject
displayNameformat doesn't match exactly- Fix: Ensure format is "Make Model Year" with exact spacing (e.g., "Honda Civic 2007")
- ❌ Products don't have
compatible_carmetafield populated- Fix: Edit products and link to car_variant metaobjects
- ❌ Search input has extra spaces or different casing
- Fix: Extension already handles case-insensitive matching, but check for typos
2. Metafield references returning null
- ❌ Missing
read_metaobjectsscope (most common) - ❌ API version incompatibility (use
unstableor2025-10for metaobject references) - ❌ Metafield not properly configured as List of Metaobject References
3. Console Logging
The Part Lookup extension includes extensive logging. Check browser console (Inspector > Console):
console.log('GraphQL Response - Total Products:', result.data?.products?.nodes?.length);
console.log('Products WITH compatible_car metafield:', productsWithMetafield);
console.log('Products WITHOUT compatible_car metafield:', productsWithoutMetafield);
console.log('Matching Products:', matchingProducts.length);1. Tile shows "No Customer"
- ❌ No customer added to current cart
- Fix: Add a customer to the cart in POS
2. Tile shows "No recommendation data found"
- ❌ Customer has no orders
- Fix: Create test orders for the customer
- ❌ Purchased products don't have
recommendationsmetafield- Fix: Add metafield to product variants with JSON array of IDs
3. Recommendations not showing inventory
- ❌ Products not tracked at current location
- Fix: Enable inventory tracking for products at the POS location
4. JSON parsing errors
- ❌ Malformed JSON in
recommendationsmetafield- Fix: Validate JSON format:
["gid://shopify/ProductVariant/123"]
- Fix: Validate JSON format:
Before reporting issues:
- ✅ Verify all required scopes are granted (especially
read_metaobjects) - ✅ Check browser console for errors and API responses
- ✅ Test GraphQL queries directly in Shopify Admin GraphiQL
- ✅ Verify metafield configurations and values
- ✅ Restart dev server after scope/config changes
- ✅ Reinstall app after scope changes
- POS UI Extensions Documentation
- Polaris Web Components
- POS UI Extensions React Components
- Shopify Admin API
- Metaobjects Guide
- Metafields Guide
- Shopify CLI
- React Router Shopify App
This app is developed for Tunexia.
Peter Walker