Products Integration ยท Build Phygrid

Products Integration

๐Ÿ›๏ธ 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

How to integrate?

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

How to integrate?

๐ŸŽฏ 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:

  1. Navigate to Content โ†’ Products in the Phygrid Console
  2. Click New Product in the Products tab
  3. Fill in the basic information:
    • Title: "Sample Product"
    • Description: "A sample product for testing integration"
  4. Set the product details in the sidebar:
    • Environment: Production
    • Product Language: English
    • Space: Default
    • Category: Electronics
    • Tags: sample, test
  5. Add product media (images, videos)
  6. Configure pricing in the Pricing section
  7. Set inventory quantities
  8. Click Save Product

Create an Example Product Using Products Push API

  1. Navigate to Apps โ†’ Developer โ†’ Access tokens in the Phygrid Console
  2. Create a token that can be used to authorize the API
  3. 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:
{
  "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:

Product example

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:

After selecting the product

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:

Signage Screen App

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
PhyOS browsers

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:

ยฉ 2025 ยท Phygrid. An Ombori company