๐๏ธ What Are Products?
In Phygrid, Products represent the core entities for showcasing and managing inventory. Each product serves as a detailed, structured data object that includes attributes such as multilingual names and descriptions, product types, pricing, availability across physical locations (spaces), and configuration variants like color or size.
Products are centrally maintained through the Phygrid Console and are designed to integrate seamlessly into Screen Apps and Edge Apps, enabling rich, dynamic, and data-driven customer experiences.
Each product includes:
- Localization: Support for multiple languages in names, descriptions, and statuses
- Space-specific status: Products can be active or inactive across different physical spaces
- Variants: Differentiations such as size, color, or style under the same product group
- Relationships: Links to recommended or related product groups
- Metadata: Information including sort order, availability, introduction or discontinuation dates, and shelf life
- Presentation logic: Including sort-friendly names and prices to control how products appear in UI layers
๐ง Common Use Cases and Real-Life Scenarios
Products integration is essential for various business scenarios:
- Retail Displays: Showcase products with real-time pricing and availability on digital signage
- Interactive Kiosks: Allow customers to browse products, view details, and check inventory
- Inventory Management: Track stock levels and update product information across multiple locations
- Dynamic Pricing: Display promotional pricing, discounts, and special offers
- Product Catalogs: Create comprehensive product listings with images, descriptions, and specifications
- Multi-location Retail: Manage products across different stores with location-specific pricing and inventory
๐ Integration Options
There are several methods to integrate products into your Phygrid ecosystem, each suited for different use cases and technical requirements.
API-Based Integration
The most flexible approach for custom integrations and automated workflows.
Best for:
- Custom applications with specific requirements
- Automated product updates from external systems
- Real-time synchronization with existing inventory systems
Key benefits:
- Full control over product data structure
- Real-time updates and synchronization
- Integration with existing business systems
// TODO: Add a link for the products SDK API Reference How to integrate?
Google Product Feed (GPF)
A streamlined approach for retailers using Google's product feed format.
Best for:
- Retailers already using Google Shopping
- Standardized product data management
- Quick setup with existing Google feeds
Key benefits:
- Leverages existing Google Shopping feeds
- Automatic synchronization with Google's product format
Console UI
The most user-friendly approach for manual product management.
Best for:
- Small to medium product catalogs
- Manual product entry and management
- Teams without technical expertise
Key benefits:
- No technical knowledge required
- Visual interface for product management
๐ฏ Getting Started with Products in Screen Apps
Let's walk through how to integrate products into your Screen Apps, starting with the basics and moving to more advanced implementations.
The goal is to integrate the Products SDK into a screen app to create a signage application that allows you to show promotions by using a product picker and displaying the selected product on the screen.
As a first step, create a screen app.
How to Initialize Products SDK
The Products SDK provides a simple interface for accessing product data in your applications. Here's how to get started:
After creating a new screen app, you need to install the Products SDK. Inside your app directory, run the following command:
cd <app-name>
yarn add @phygrid/products-react
Create an Example Product in Console
Before diving into code, let's create a sample product to work with:
- Navigate to Content โ Products in the Phygrid Console
- Click New Product in the Products tab
- Fill in the basic information:
- Title: "Sample Product"
- Description: "A sample product for testing integration"
- Set the product details in the sidebar:
- Environment: Production
- Product Language: English
- Space: Default
- Category: Electronics
- Tags: sample, test
- Add product media (images, videos)
- Configure pricing in the Pricing section
- Set inventory quantities
- Click Save Product
Create an Example Product Using Products Push API
- Navigate to Apps โ Developer โ Access tokens in the Phygrid Console
- Create a token that can be used to authorize the API
- Using Postman:
- API URL:
https://api.phygrid.com/regions/<Data-Residency>/products/v1/tenants/<tenant-Id>/<environment>/products-push
- HTTP Method: POST
- Headers:
{"x-api-key": <token>}
- Payload:
- API URL:
{
"data": [
{
"productGroupId": "PARENT_SKU_TEST",
"productName": [
{
"isoLanguageId": "en-US",
"productName": "OtterBox USB C PD GaN Wall Charger 30W Shimmer"
}
],
"productDescription": [
{
"isoLanguageId": "en-US",
"productDescription": "<p>OtterBox Chargers are engineered rugged and built to outlast.
Featuring a smart and compact design, OtterBox Chargers are drop tested and wrapped in a tough exterior.
</p><ul><li>Rigorous testing to ensure devices charge safely and efficiently</li><li>Smart,
compact chargers for at home or on the go</li><li>Designed to work flawlessly with OtterBox cables</li></ul>"
}
],
"variants": [
{
"productGroupId": "PARENT_SKU_TEST",
"productId": "PARENT_SKU_TEST_01",
"color": "Black",
"size": "",
"style": "",
"europeanArticleNumber": ["BARCODE_VALUE"],
"globalTradeItemNumber": [],
"universalProductCode": [],
"productName": [
{
"isoLanguageId": "en-US",
"productName": "OtterBox USB C PD GaN Wall Charger 30W Black Shimmer"
}
],
"originalEuropeanArticleNumber": [],
"originalGlobalTradeItemNumber": [],
"originalUniversalProductCode": [],
"colorImageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSsfL6qHffOMYlYqWccvHq7u7-fDOicHBxhHQ&s"
},
{
"productGroupId": "PARENT_SKU_TEST",
"productId": "PARENT_SKU_TEST_02",
"color": "White",
"size": "",
"style": "",
"europeanArticleNumber": ["BARCODE_VALUE"],
"globalTradeItemNumber": [],
"universalProductCode": [],
"productName": [
{
"isoLanguageId": "en-US",
"productName": "OtterBox USB C PD GaN Wall Charger 30W White Shimmer"
}
],
"originalEuropeanArticleNumber": [],
"originalGlobalTradeItemNumber": [],
"originalUniversalProductCode": [],
"colorImageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR-IJZzny3CE-8vUkrgIqpqB9GkZuKfyoA8Lg&s"
}
],
"brand": [
{
"isoLanguageId": "en-US",
"brandName": "OtterBox"
}
],
"productFeature": [
{
"productId": "PARENT_SKU_TEST_01",
"isoLanguageId": "en-US",
"productFeatureType": "Color",
"productFeatureValue": "Black"
},
{
"productId": "PARENT_SKU_TEST_01",
"isoLanguageId": "en-US",
"productFeatureType": "Brand",
"productFeatureValue": "OtterBox"
},
{
"productId": "PARENT_SKU_TEST_01",
"isoLanguageId": "en-US",
"productFeatureType": "Tag",
"productFeatureValue": "Charging Solutions"
},
{
"productId": "PARENT_SKU_TEST_01",
"productFeatureType": "Make",
"productFeatureValue": "Universal",
"isoLanguageId": "en-US"
},
{
"productId": "PARENT_SKU_TEST_02",
"isoLanguageId": "en-US",
"productFeatureType": "Color",
"productFeatureValue": "White"
},
{
"productId": "PARENT_SKU_TEST_02",
"isoLanguageId": "en-US",
"productFeatureType": "Brand",
"productFeatureValue": "OtterBox"
},
{
"productId": "PARENT_SKU_TEST_02",
"isoLanguageId": "en-US",
"productFeatureType": "Tag",
"productFeatureValue": "Charging Solutions"
},
{
"productId": "PARENT_SKU_TEST_02",
"productFeatureType": "Make",
"productFeatureValue": "Universal",
"isoLanguageId": "en-US"
}
],
"catalogPageLocationProduct": [
{
"productGroupId": "PARENT_SKU_TEST",
"productId": "PARENT_SKU_TEST_01",
"catalogType": "image/jpg",
"catalogPageLocationProduct": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/2f8c9716-ffa4-5d02-bfec-8687e7dc4cb3",
"catalogPageLocation": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/2f8c9716-ffa4-5d02-bfec-8687e7dc4cb3"
},
{
"productGroupId": "PARENT_SKU_TEST",
"productId": "PARENT_SKU_TEST_02",
"catalogType": "image/jpg",
"catalogPageLocationProduct": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/d076ae2d-827e-5746-8953-8b915a0e3d00",
"catalogPageLocation": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/d076ae2d-827e-5746-8953-8b915a0e3d00"
},
{
"productGroupId": "PARENT_SKU_TEST",
"productId": "PARENT_SKU_TEST_01",
"catalogType": "image/jpg",
"catalogPageLocationProduct": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/3b549046-3dfc-5ce3-bf91-0e729d154bea",
"catalogPageLocation": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/3b549046-3dfc-5ce3-bf91-0e729d154bea"
},
{
"productGroupId": "PARENT_SKU_TEST",
"productId": "PARENT_SKU_TEST_01",
"catalogType": "image/jpg",
"catalogPageLocationProduct": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/e3943be1-e46d-59aa-b4a6-61b5bd360151",
"catalogPageLocation": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/e3943be1-e46d-59aa-b4a6-61b5bd360151"
},
{
"productGroupId": "PARENT_SKU_TEST",
"productId": "PARENT_SKU_TEST_02",
"catalogType": "image/jpg",
"catalogPageLocationProduct": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTY7iemlA2bw-w33-GB5OLbzlcJjDV6TR0ipQ&s",
"catalogPageLocation": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTY7iemlA2bw-w33-GB5OLbzlcJjDV6TR0ipQ&s"
},
{
"productGroupId": "PARENT_SKU_TEST",
"productId": "PARENT_SKU_TEST_02",
"catalogType": "image/jpg",
"catalogPageLocationProduct": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/d076ae2d-827e-5746-8953-8b915a0e3d00",
"catalogPageLocation": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/d076ae2d-827e-5746-8953-8b915a0e3d00"
},
{
"productGroupId": "PARENT_SKU_TEST",
"productId": "PARENT_SKU_TEST_02",
"catalogType": "image/jpg",
"catalogPageLocationProduct": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/3b549046-3dfc-5ce3-bf91-0e729d154bea",
"catalogPageLocation": "https://api.omborigrid.com/regions/us/products/v1/assets/658076ecdb0cff0008f07007-prod/3b549046-3dfc-5ce3-bf91-0e729d154bea"
}
],
"productType": ["ALL_PRODUCTS"],
"productItemQuantity": [
{
"productId": "PARENT_SKU_TEST_01",
"spaceId": "<Space-ID>",
"productItemQuantity": 20
},
{
"productId": "PARENT_SKU_TEST_02",
"spaceId": "<Space-ID>",
"productItemQuantity": 20
}
],
"productPriceList": [
{
"productId": "PARENT_SKU_TEST_01",
"priceListType": "Standard",
"isoLanguageId": "en-US",
"isoCurrencyCode": "USD",
"spaceId": "<Space-ID>",
"listPrice": 2900
},
{
"productId": "PARENT_SKU_TEST_02",
"priceListType": "Standard",
"isoLanguageId": "en-US",
"isoCurrencyCode": "USD",
"spaceId": "<Space-ID>",
"listPrice": 2900
}
]
}
]
}
After pushing the product, you should see it appear in your console:
Use Settings Schema for Product Configuration
Update the settings schema by adding the Product Picker:
// schema.ts
interface Product {
ref: "grid-product"
productGroupId: string
productId: string
defaultImage: string
productSourceUrl: string
env: string
}
export default interface Settings {
/**
* @title Product
* @description Select a product from your Grid catalog.
* @ui productPicker
*/
product: Product
}
After updating the schema, update the app version in the package.json
file,
then run the following command:
yarn run build && yarn run pub
After installing the app or upgrading to the latest version, select the product from the product picker and save the settings. Finally, download these settings to work on the app locally:
Run the following command to download the app settings:
phy app settings <installation-name>
After downloading the settings, you'll find them in src/settings/index.json
:
{
"general": {
"appId": "68625a103d17e50008ff041a",
"appName": "signage-app-test",
"appDisplayName": "signage-app-test",
"provider": "app-container",
"type": "gdm",
"release": "GridApp v4.1.20",
"defaultLanguage": "",
"supportedLanguages": [],
"organizationId": "64c785b9b8e89400080e6f7e",
"organizationName": "ahmed-workspace",
"dataResidency": "UAE",
"country": "AE",
"installationType": "screen"
},
"notifications": {
"org_id": "ahmed-workspace",
"secret": "",
"endpoint": "https://push.ombori.com"
},
"spacesSettings": {},
"devicesSettings": {},
"app": {
"gridApp": {
"valid": true,
"url": "686259af69b2a600075a2814/1f9c3e16-8d7a-4e62-bc21-5de8bf454b1a",
"name": "ahmed-workspace.signage-app-test-0.1.2.gridapp",
"id": "686259b869b2a600075a324c",
"type": "application/x-gridapp",
"ref": "media",
"iconUrl": null,
"gridapp": {
"id": "686259af69b2a600075a2814",
"buildId": "686259b869b2a600075a324c"
},
"settings": {
"product": {
"ref": "grid-product",
"productGroupId": "99465",
"productId": "N9C3MS-GOOGPL9-BLK",
"defaultImage": "https://api.omborigrid.com/regions/uae/products/v1/assets/64c785b9b8e89400080e6f7e-prod/321133d6-4132-595e-a632-eda797edeb09",
"productSourceUrl": "regions/UAE/products/v1/tenants/64c785b9b8e89400080e6f7e/prod/products",
"env": "prod"
}
},
"filename": "686259b869b2a600075a324c.tar.gz",
"path": "/grid-settings/686259b869b2a600075a324c"
}
}
}
Integrate Product Information and Run the App Locally
After installing the @phygrid/products-react
package,
we'll fetch the product details and display the product in the app.
Now let's create a React component that displays products using the SDK:
We'll create a custom hook that can be used for fetching product details and update the app to show the product details. Our goal is to create a screen app like this:
First, create a custom hook useProduct.ts
:
import { useState, useEffect } from 'react';
import Settings from '../schema';
interface ProductData {
id: string;
name: string;
price: number;
currency: string;
image: string;
description?: string;
}
export const useProduct = (settings: Settings | null) => {
const [productData, setProductData] = useState<ProductData | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!settings?.product) {
setError('No product settings available');
return;
}
const fetchProduct = async () => {
setLoading(true);
setError(null);
try {
const { productGroupId, productId, productSourceUrl } = settings.product;
const baseUrl = 'https://api.omborigrid.com/';
const fullUrl = `${baseUrl}${productSourceUrl}/${productGroupId}`;
const response = await fetch(fullUrl);
if (!response.ok) {
throw new Error(`Failed to fetch product: ${response.status}`);
}
const data = await response.json();
const productData = data.data;
const productName = productData.productName?.[0]?.productName || 'Product Name';
const priceEntry = productData.productPriceList?.find((price: any) => price.productId === productId);
const price = priceEntry?.listPrice || 0;
const currency = priceEntry?.isoCurrencyCode || 'USD';
const image = settings.product.defaultImage;
setProductData({
id: settings.product.productId,
name: productName,
price: price,
currency: currency,
image: image,
description: productData.productDescription?.[0]?.productDescription
});
} catch (err) {
console.error('Error fetching product:', err);
setError(err instanceof Error ? err.message : 'Failed to fetch product');
} finally {
setLoading(false);
}
};
fetchProduct();
}, [settings]);
return { productData, loading, error };
};
Update app.tsx
:
import React, { useCallback, useEffect, useState } from 'react';
import styled, { keyframes } from 'styled-components';
import { connectPhyClient, PhyHubClient } from '@phygrid/hub-client';
import { getDevSettings, isDevMode } from './utils/dev-mode';
import Settings from './schema';
import { useProduct } from './hooks/useProduct';
interface AppState {
client: PhyHubClient | null;
settings: Settings | null;
signals: PhyHubClient['signals'] | null;
}
const initialState: AppState = {
client: null,
settings: null,
signals: null,
};
const fadeInUp = keyframes`
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
const pulse = keyframes`
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
`;
function App() {
const [state, setState] = useState<AppState>(initialState);
const { productData, loading, error } = useProduct(state.settings);
useEffect(() => {
if (productData?.image) {
const img = new Image();
img.onload = () => console.log('Background image loaded successfully');
img.onerror = () => console.error('Background image failed to load');
img.src = productData.image;
}
}, [productData?.image]);
const initializeDevMode = useCallback(() => {
setState(prev => ({
...prev,
settings: getDevSettings()
}));
}, []);
const initializeClient = useCallback(async () => {
try {
const client = await connectPhyClient();
const signals = await client.initializeSignals();
const settings = await client.getSettings() as Settings;
setState({ client, settings, signals });
} catch (err) {
console.error('Error initializing client:', err);
}
}, []);
useEffect(() => {
const initialize = async () => {
if (isDevMode()) {
initializeDevMode();
return;
}
await initializeClient();
};
initialize();
}, [initializeDevMode, initializeClient]);
if (!state.settings) {
return <Container>Loading gridapp settings...</Container>;
}
if (loading) {
return <Container>Loading product data...</Container>;
}
if (error) {
return <Container>Error: {error}</Container>;
}
return (
<Container
style={{
backgroundImage: productData?.image ? `url(${productData.image})` : undefined,
backgroundColor: productData?.image ? 'transparent' : '#000000'
}}
>
<ProductPrice>
{productData?.price ? `${productData.currency} ${productData.price.toFixed(2)}` : 'Price not available'}
</ProductPrice>
<ProductName>
{productData?.name || 'Product Name'}
</ProductName>
</Container>
);
}
const Container = styled.div`
text-align: center;
background-color: #000000;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
min-height: 100vh;
width: 100%;
color: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 1.5vmin);
position: relative;
overflow: hidden;
`;
const ProductPrice = styled.div`
position: absolute;
top: 40px;
right: 40px;
background: rgba(0, 0, 0, 0.7);
padding: 20px 30px;
border-radius: 15px;
font-size: 24px;
font-weight: bold;
animation: ${fadeInUp} 1s ease-out, ${pulse} 2s ease-in-out infinite;
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.2);
`;
const ProductName = styled.div`
position: absolute;
bottom: 40px;
left: 40px;
right: 40px;
background: rgba(0, 0, 0, 0.7);
padding: 20px 30px;
border-radius: 15px;
font-size: 20px;
font-weight: 600;
animation: ${fadeInUp} 1s ease-out 0.5s both;
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.2);
text-align: center;
`;
export default App;
Now you can deploy a new version and connect either a PhyOS browser or PhyOS Physical/VM device.
Update the version in package.json
to 1.0.0
, then run the following command:
yarn run build && yarn run pub
Now you can change the app settings, select a different product, and save the settings. The changes will be deployed to the device, and you can easily see the updates.
๐ Next Steps
Now that you understand the basics of Products integration, you can:
- Create settings schemas to configure your product displays
- Use Signals to track product interactions and user behavior
- Integrate with peripherals to create interactive product experiences
- Set up checkout integration to enable purchases