BuyWhere Price Widget Integration
Embed real-time price comparison widgets into any website. Display product prices from multiple merchants with just a few lines of JavaScript.
Base URL: https://api.buywhere.ai
Widget Version: 1.0
Quick Start
1. Load the Widget
Option A: Script Tag (Recommended for static HTML)
<script src="https://cdn.buywhere.ai/widgets/price-widget@1.0.js" defer></script>
Option B: ES Module (For React/Vue/Next.js)
import PriceWidget from 'https://cdn.buywhere.ai/widgets/price-widget@1.0.mjs';
2. Add a Container
<div id="price-badge"></div>
3. Render the Widget
<script>
PriceWidget.render('#price-badge', {
query: 'Sony WH-1000XM5 Wireless Headphones',
type: 'badge',
country: 'SG'
});
</script>
Result: Inline price badges showing the lowest prices across Shopee, Lazada, and other merchants.
Widget Types
Badge (type: 'badge')
Compact inline badges showing merchant name + price. Best for product listings, comparison tables, and inline placements.
PriceWidget.render('#my-badge', {
query: 'iphone 15 pro 256gb',
type: 'badge',
maxPlatforms: 4,
showPriceDiff: true
});
Visual:
![Badge Example]
- Shows up to
maxPlatformsmerchant badges - Green/red dot indicates in-stock status
+N moreappears when additional merchants existshowPriceDiff: truedisplays price range
Card (type: 'card')
Full product card with lowest price, savings percentage, and CTA button. Best for product detail pages and featured deals.
PriceWidget.render('#my-card', {
query: 'samsung galaxy s24 ultra',
type: 'card',
country: 'US'
});
Visual:
![Card Example]
- Shows lowest price prominently
- Stock status badge
- Crossed-out highest price with savings %
- "View Deal" CTA button
Comparison (type: 'comparison')
Full table of all merchants with price, stock, and rating. Best for dedicated comparison pages and detailed research views.
PriceWidget.render('#my-comparison', {
query: 'macbook air m3',
type: 'comparison',
maxMerchants: 6,
showRating: true
});
Visual:
![Comparison Example]
- Sortable merchant table
- Price, stock status, and rating columns
- "View" links to each merchant
Configuration
Global Configuration
Create a custom loader to share settings across widgets:
const loader = new PriceWidget.Loader({
apiKey: 'bw_live_your_key_here',
baseUrl: '/api', // Use your proxy or '/api' for BuyWhere proxy
defaultCurrency: 'S$',
defaultCountry: 'SG',
cacheTimeout: 5 * 60 * 1000 // 5 minutes
});
// Render multiple widgets with the same loader
PriceWidget.render('#badge-1', { query: 'product a', type: 'badge', loader });
PriceWidget.render('#badge-2', { query: 'product b', type: 'badge', loader });
Render Options
| Option | Type | Default | Description |
|---|---|---|---|
query | string | required | Product search query |
productId | number | — | Direct product ID lookup (faster, exact match) |
type | string | 'badge' | Widget type: 'badge', 'card', 'comparison' |
country | string | 'SG' | ISO country code: 'SG', 'MY', 'US', etc. |
region | string | — | Region override: 'sea', 'us', 'eu' |
currency | string | — | Override display currency |
loader | Loader | defaultLoader | Custom PriceWidgetLoader instance |
showLoading | boolean | true | Show skeleton loader while fetching |
maxPlatforms | number | 4 | Max badges to show (badge type) |
maxMerchants | number | 6 | Max rows in table (comparison type) |
showPriceDiff | boolean | false | Show price range (badge type) |
showRating | boolean | true | Show merchant ratings (comparison type) |
showHistory | boolean | false | Show price history (card type) |
Installation
For React / Next.js
import { useEffect, useRef } from 'react';
import PriceWidget from '@/lib/price-widget';
export function PriceBadge({ query, country = 'SG' }) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (containerRef.current) {
PriceWidget.render(containerRef.current, {
query,
type: 'badge',
country,
});
}
}, [query, country]);
return <div ref={containerRef} />;
}
For Vue 3
<template>
<div ref="container"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import PriceWidget from '@/lib/price-widget';
const props = defineProps({
query: { type: String, required: true },
country: { type: String, default: 'SG' }
});
const container = ref(null);
onMounted(() => {
PriceWidget.render(container.value, {
query: props.query,
type: 'badge',
country: props.country
});
});
</script>
For Vanilla JS (HTML pages)
<!DOCTYPE html>
<html>
<head>
<title>Product Page</title>
<script src="https://cdn.buywhere.ai/widgets/price-widget@1.0.js" defer></script>
</head>
<body>
<h1>Sony WH-1000XM5</h1>
<div id="price-widget"></div>
<script>
document.addEventListener('DOMContentLoaded', () => {
PriceWidget.render('#price-widget', {
query: 'Sony WH-1000XM5 Wireless Headphones',
type: 'badge',
country: 'SG'
});
});
</script>
</body>
</html>
Styling
CSS Classes
The widget uses scoped CSS classes to avoid conflicts:
| Element | CSS Class |
|---|---|
| Root container | .bw-widget |
| Badge type | .bw-widget-badge |
| Card type | .bw-widget-card |
| Comparison type | .bw-widget-comparison |
| Skeleton loader | .bw-widget-skeleton |
Custom Styling
Override styles with higher specificity:
/* Custom badge colors */
.bw-widget-badge a {
border-radius: 4px;
font-family: 'Inter', sans-serif;
}
/* Custom card styling */
.bw-widget-card {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
max-width: 320px;
}
/* Custom table styling */
.bw-widget-comparison table {
font-size: 14px;
}
Platform Colors
The widget automatically applies platform-specific colors:
| Platform | Background | Text |
|---|---|---|
| Shopee | bg-red-50 | text-red-600 |
| Lazada | bg-blue-50 | text-blue-600 |
| Amazon.sg | bg-orange-50 | text-orange-600 |
| Amazon.com | bg-orange-50 | text-orange-600 |
| Carousell | bg-green-50 | text-green-600 |
| Qoo10 | bg-purple-50 | text-purple-600 |
| Walmart | bg-blue-50 | text-blue-600 |
| Best Buy | bg-blue-50 | text-blue-600 |
| Target | bg-red-50 | text-red-600 |
| eBay | bg-indigo-50 | text-indigo-600 |
API Proxy (Recommended)
For production, proxy through your backend to protect your API key:
const loader = new PriceWidget.Loader({
apiKey: 'bw_live_your_key_here',
baseUrl: 'https://your-proxy.com/buywhere-api'
});
Proxy Requirements
Your proxy should:
- Forward
Authorizationheader - Pass through query parameters:
query,country,region,productId - Return the price data in the format below
Expected Response Format
{
"productName": "Sony WH-1000XM5 Wireless Headphones",
"prices": [
{
"merchant": "Shopee",
"price": "349.00",
"currency": "S$",
"url": "https://shopee.sg/sony-wh1000xm5",
"in_stock": true,
"rating": 4.8,
"last_updated": "2026-04-18T10:30:00Z"
}
],
"lowestPrice": {
"merchant": "Shopee",
"price": "349.00",
"currency": "S$",
"url": "https://shopee.sg/sony-wh1000xm5",
"in_stock": true
},
"highestPrice": {
"merchant": "Lazada",
"price": "399.00",
"currency": "S$",
"url": "https://lazada.sg/sony-wh1000xm5",
"in_stock": true
},
"priceDiff": "50.00"
}
Caching
The widget includes built-in caching to reduce API calls:
const loader = new PriceWidget.Loader({
cacheTimeout: 5 * 60 * 1000 // 5 minutes (default)
});
// Clear cache manually
loader.clearCache();
// Per-render cache busting
PriceWidget.render('#widget', {
query: 'product',
// Cache is keyed by query + options
});
Cache key format: price-{query}-{stringifiedOptions}
Error Handling
The widget gracefully handles errors:
PriceWidget.render('#widget', {
query: 'some product',
type: 'badge'
}).catch(error => {
console.error('Widget failed:', error);
});
Error States
| Scenario | Behavior |
|---|---|
| Network error | Falls back to mock data in development; shows error message in production |
| No results | Shows "No products found" message |
| API error | Logs error, shows error state in widget |
Mock Data (Development)
In development or when the API is unreachable, the widget generates realistic mock data so you can build UI without a live connection.
Accessibility
The widget follows accessibility best practices:
- All interactive elements are keyboard navigable
- Links have proper
rel="noopener noreferrer"for external URLs - Stock status uses color + icon (not color alone)
- Skeleton loaders use
aria-busy="true"
Supported Countries
| Country | Code | Currency | Merchants |
|---|---|---|---|
| Singapore | SG | S$ | Shopee, Lazada, Amazon.sg, Carousell, Qoo10 |
| Malaysia | MY | RM | Shopee, Lazada, Amazon.my |
| United States | US | $ | Amazon.com, Walmart, Target, Best Buy, eBay |
| Thailand | TH | ฿ | Shopee, Lazada |
| Philippines | PH | ₱ | Shopee, Lazada |
| Vietnam | VN | ₫ | Shopee, Lazada |
US Price Comparison API
The widget uses BuyWhere's price comparison API to fetch real-time pricing from US merchants.
Endpoint
GET /api/prices
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Product search query |
country | string | Yes | ISO country code (US for United States) |
region | string | No | Region override (us for US region) |
productId | number | No | Direct product ID lookup (exact match) |
Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer <your_api_key> |
Content-Type | Yes | application/json |
US-Specific Features
Target.com Integration The widget supports Target.com as a US merchant. Target products include:
- Fashion & Apparel
- Home Goods & Decor
- Beauty & Personal Care
- Electronics
US Merchant Response Format
{
"productName": "Apple AirPods Pro (2nd Generation)",
"prices": [
{
"merchant": "Amazon.com",
"price": "249.00",
"currency": "$",
"url": "https://amazon.com/dp/XXXXX",
"in_stock": true,
"rating": 4.7,
"last_updated": "2026-04-18T10:30:00Z"
},
{
"merchant": "Walmart",
"price": "229.00",
"currency": "$",
"url": "https://walmart.com/ip/XXXXX",
"in_stock": true,
"rating": 4.5,
"last_updated": "2026-04-18T10:30:00Z"
},
{
"merchant": "Target",
"price": "249.99",
"currency": "$",
"url": "https://target.com/p/XXXXX",
"in_stock": true,
"rating": 4.6,
"last_updated": "2026-04-18T10:30:00Z"
},
{
"merchant": "Best Buy",
"price": "249.99",
"currency": "$",
"url": "https://bestbuy.com/site/XXXXX",
"in_stock": true,
"rating": 4.4,
"last_updated": "2026-04-18T10:30:00Z"
},
{
"merchant": "eBay",
"price": "199.00",
"currency": "$",
"url": "https://ebay.com/itm/XXXXX",
"in_stock": true,
"rating": 4.2,
"last_updated": "2026-04-18T10:30:00Z"
}
],
"lowestPrice": {
"merchant": "eBay",
"price": "199.00",
"currency": "$",
"url": "https://ebay.com/itm/XXXXX",
"in_stock": true
},
"highestPrice": {
"merchant": "Target",
"price": "249.99",
"currency": "$",
"url": "https://target.com/p/XXXXX",
"in_stock": true
},
"priceDiff": "50.99"
}
API Key Setup
- Sign up at buywhere.ai for an API key
- Configure the loader with your key:
const loader = new PriceWidget.Loader({
apiKey: 'bw_live_your_key_here',
baseUrl: 'https://api.buywhere.ai'
});
// US-specific rendering
PriceWidget.render('#widget', {
query: 'macbook air m3',
type: 'comparison',
country: 'US',
loader
});
Rate Limits
| Plan | Requests/minute | Daily Limit |
|---|---|---|
| Free | 10 | 1,000 |
| Pro | 60 | 50,000 |
| Enterprise | 600 | Unlimited |
Platform Integrations
WordPress
Method 1: Custom HTML Block (Gutenberg)
Add a custom HTML block and paste:
<div class="price-widget-container">
<h3>Compare Prices</h3>
<div id="bw-price-badge"></div>
</div>
<script src="https://cdn.buywhere.ai/widgets/price-widget@1.0.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
PriceWidget.render('#bw-price-badge', {
query: 'YOUR_PRODUCT_NAME',
type: 'badge',
country: 'US'
});
});
</script>
Method 2: Theme Template File
Add to your theme's functions.php:
function buywhere_price_widget_shortcode($atts) {
$atts = shortcode_atts(array(
'query' => '',
'type' => 'badge',
'country' => 'US'
), $atts, 'buywhere_price');
$uniqid = 'bw-' . uniqid();
ob_start();
?>
<div id="<?php echo $uniqid; ?>"></div>
<script src="https://cdn.buywhere.ai/widgets/price-widget@1.0.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
PriceWidget.render('#<?php echo $uniqid; ?>', {
query: <?php echo json_encode($atts['query']); ?>,
type: <?php echo json_encode($atts['type']); ?>,
country: <?php echo json_encode($atts['country']); ?>
});
});
</script>
<?php
return ob_get_clean();
}
add_shortcode('buywhere_price', 'buywhere_price_widget_shortcode');
Usage in WordPress editor: [buywhere_price query="iphone 15 pro" type="badge" country="US"]
Shopify
Method 1: Custom Liquid Section
Create a new section file sections/buywhere-price.liquid:
{%- if product -%}
<div class="buywhere-price-widget" data-product="{{ product.title | escape }}">
<div id="bw-{{ section.id }}"></div>
</div>
<script src="https://cdn.buywhere.ai/widgets/price-widget@1.0.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
PriceWidget.render('#bw-{{ section.id }}', {
query: {{ product.title | json }},
type: '{{ section.settings.widget_type }}',
country: '{{ section.settings.country }}'
});
});
</script>
{%- endif -%}
{% schema %}
{
"name": "BuyWhere Price",
"settings": [
{
"type": "select",
"id": "widget_type",
"label": "Widget Type",
"options": [
{ "value": "badge", "label": "Badge" },
{ "value": "card", "label": "Card" },
{ "value": "comparison", "label": "Comparison" }
]
},
{
"type": "select",
"id": "country",
"label": "Country",
"options": [
{ "value": "SG", "label": "Singapore" },
{ "value": "US", "label": "United States" },
{ "value": "MY", "label": "Malaysia" }
]
}
],
"presets": [
{
"name": "BuyWhere Price"
}
]
}
{% endschema %}
Method 2: Product Page Snippet
Add to snippets/product-price-comparison.liquid:
{%- if product -%}
<div class="buywhere-price-section">
<p class="buywhere-label">Compare prices:</p>
<div id="buywhere-price-{{ product.id }}" data-product="{{ product.title | escape }}"></div>
</div>
<style>
.buywhere-price-section {
margin: 1.5rem 0;
padding: 1rem;
background: #f9f9f9;
border-radius: 8px;
}
.buywhere-label {
font-weight: 600;
margin-bottom: 0.5rem;
}
</style>
<script src="https://cdn.buywhere.ai/widgets/price-widget@1.0.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
PriceWidget.render('#buywhere-price-{{ product.id }}', {
query: {{ product.title | json }},
type: 'badge',
country: 'US'
});
});
</script>
{%- endif -%}
Include in your product template: {% render 'product-price-comparison' %}
Squarespace
Method 1: Code Block (Any Page)
Add a Code Block to your page and paste:
<div id="buywhere-widget"></div>
<script src="https://cdn.buywhere.ai/widgets/price-widget@1.0.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
PriceWidget.render('#buywhere-widget', {
query: 'YOUR_PRODUCT_NAME',
type: 'badge',
country: 'US'
});
});
</script>
Method 2: Squarespace Developer Platform (Developer Mode)
Add to your page.conf files collection:
// In your page's JSON template
{
"title": "Product Page",
"widgets": [
{
"type": "custom",
"html": "<div id='buywhere-price-widget'></div>"
}
]
}
Then add the script to Settings > Advanced > Code Injection > Footer:
<script src="https://cdn.buywhere.ai/widgets/price-widget@1.0.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Auto-detect product name from page title
var productName = document.querySelector('.ProductItem-details h1')?.textContent || '';
if (productName && document.getElementById('buywhere-price-widget')) {
PriceWidget.render('#buywhere-price-widget', {
query: productName.trim(),
type: 'badge',
country: 'US'
});
}
});
</script>
Wix
Using Wix Editor:
- Add an Embed element to your page
- Set to "Code" mode
- Paste:
<div id="buywhere-price-widget"></div>
<script src="https://cdn.buywhere.ai/widgets/price-widget@1.0.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
PriceWidget.render('#buywhere-price-widget', {
query: 'YOUR_PRODUCT_NAME',
type: 'badge',
country: 'US'
});
});
</script>
BigCommerce
Method 1: Storefront Global Footer
Go to Storefront > Footer Scripts and add:
<script src="https://cdn.buywhere.ai/widgets/price-widget@1.0.js" defer></script>
<script>
// Auto-initialize on product pages
document.addEventListener('DOMContentLoaded', function() {
if (document.querySelector('.product-title')) {
var productName = document.querySelector('.product-title').textContent;
var widgetContainer = document.createElement('div');
widgetContainer.id = 'buywhere-price-widget';
document.querySelector('.product-title').after(widgetContainer);
PriceWidget.render('#buywhere-price-widget', {
query: productName.trim(),
type: 'card',
country: 'US'
});
}
});
</script>
Method 2: Custom Template
Add to your Stencil theme's templates/pages/product.html:
<div class="buywhere-price-comparison">
<h3>Compare Prices</h3>
<div id="bw-price-widget"></div>
</div>
<script>
var productName = {{ product.title | json }};
PriceWidget.render('#bw-price-widget', {
query: productName,
type: 'comparison',
country: 'US'
});
</script>
Example Implementations
E-commerce Product Page
<div class="product-container">
<h1>Sony WH-1000XM5</h1>
<p>Premium wireless headphones with noise cancellation</p>
<div id="price-comparison"></div>
<script>
PriceWidget.render('#price-comparison', {
query: 'Sony WH-1000XM5 Wireless Headphones',
type: 'comparison',
country: 'SG',
maxMerchants: 5
});
</script>
</div>
Blog Post Price Alert
<aside class="price-sidebar">
<h3>Check Prices</h3>
<div id="price-card"></div>
</aside>
<script>
PriceWidget.render('#price-card', {
query: 'iphone 15 pro',
type: 'card',
country: 'US'
});
</script>
Comparison Table Row
<table>
<tr>
<td>Sony WH-1000XM5</td>
<td><div id="price-1"></div></td>
</tr>
<tr>
<td>Apple AirPods Max</td>
<td><div id="price-2"></div></td>
</tr>
</table>
<script>
['Sony WH-1000XM5', 'Apple AirPods Max'].forEach((query, i) => {
PriceWidget.render(`#price-${i + 1}`, {
query,
type: 'badge',
country: 'SG',
maxPlatforms: 3
});
});
</script>
Troubleshooting
Widget not appearing
- Check the container element exists in the DOM
- Verify the script loaded successfully
- Check browser console for errors
- Ensure
queryis not empty
"Failed to load price data" error
- Verify your API key is valid
- Check network tab for failed requests
- Ensure CORS is configured if using a proxy
Wrong products shown
- Use
productIdinstead ofqueryfor exact matches - Try more specific search terms
- Check country/region settings match your target market
Styling conflicts
- Use more specific CSS selectors
- Wrap widget in a container with unique class
- Use
!importantsparingly for overrides
Custom Widgets
The Price Widget supports custom widget registration for specialized display requirements.
Registering a Custom Widget
PriceWidget.registerWidgetType('myWidget', {
render: (container, priceData, options) => {
// container: DOM element to render into
// priceData: { productName, prices[], lowestPrice, highestPrice, priceDiff }
// options: render options passed to PriceWidget.render()
const el = document.createElement('div');
el.className = 'bw-widget bw-widget-custom';
el.innerHTML = `
<h4>${priceData.productName}</h4>
<p>Lowest: ${priceData.lowestPrice.price} at ${priceData.lowestPrice.merchant}</p>
`;
container.appendChild(el);
}
});
// Use your custom widget
PriceWidget.render('#container', {
query: 'Sony WH-1000XM5',
type: 'myWidget'
});
Listing Registered Widgets
const widgets = PriceWidget.getRegisteredWidgets();
// Returns: [['myWidget', { render: fn }], ...]
Utility Functions
The widget exports helper functions for advanced integrations:
formatPrice
const { formatPrice } = PriceWidget;
formatPrice(349.00, 'S$'); // "S$ 349.00"
formatPrice(99.99, '$'); // "$ 99.99"
Element Creation Functions
Create widget elements directly without rendering:
const { createPriceBadgeElement, createPriceCardElement, createPriceComparisonElement, createSkeletonLoader } = PriceWidget;
const badge = createPriceBadgeElement(priceData, { maxPlatforms: 3 });
const card = createPriceCardElement(priceData);
const comparison = createPriceComparisonElement(priceData, { maxMerchants: 8, showRating: true });
const skeleton = createSkeletonLoader('card');
document.getElementById('container').appendChild(badge);
Loader Utilities
Access mock data and merchant lists directly:
const loader = new PriceWidget.Loader();
// Generate mock data for development
const mockData = loader.getMockPriceData('Sony WH-1000XM5', { country: 'SG' });
// Get merchant list for a region
const seaMerchants = loader.getMerchantList('sea'); // ['Shopee', 'Lazada', ...]
const usMerchants = loader.getMerchantList('us'); // ['Amazon.com', 'Walmart', ...]
TypeScript Support
Type Definitions
interface PriceData {
productName: string;
prices: PriceItem[];
lowestPrice: PriceItem;
highestPrice: PriceItem;
priceDiff: string;
}
interface PriceItem {
merchant: string;
price: string;
currency: string;
url: string;
in_stock: boolean;
rating?: number;
last_updated: string;
}
interface WidgetOptions {
query: string;
productId?: number;
type?: 'badge' | 'card' | 'comparison' | string;
country?: string;
region?: string;
currency?: string;
loader?: PriceWidgetLoader;
showLoading?: boolean;
maxPlatforms?: number;
maxMerchants?: number;
showPriceDiff?: boolean;
showRating?: boolean;
showHistory?: boolean;
}
React TypeScript Example
import { useEffect, useRef } from 'react';
import PriceWidget, { PriceData, WidgetOptions } from '@/lib/price-widget';
interface PriceBadgeProps {
query: string;
country?: string;
className?: string;
}
export function PriceBadge({ query, country = 'SG', className }: PriceBadgeProps) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
let mounted = true;
const container = containerRef.current;
PriceWidget.render(container, {
query,
type: 'badge',
country,
className,
}).then(() => {
if (!mounted) {
container.innerHTML = '';
}
});
return () => {
mounted = false;
};
}, [query, country, className]);
return <div ref={containerRef} />;
}
Support
- Email: api@buywhere.ai
- Documentation: https://docs.buywhere.ai
- Widget CDN:
https://cdn.buywhere.ai/widgets/