Chapter 11: Google Merchant Center Integration
Google Merchant Center (GMC) is the bridge between AdPriority’s scoring engine and Google Ads campaigns. AdPriority writes custom labels to GMC products, which Google Ads then uses for product group segmentation and bid optimization. This chapter covers the custom label specification, the supplemental feed pipeline, the Content API for future scaling, and all verified data from the Nexus Clothing GMC account.
9.1 Custom Label Specifications
GMC provides five custom labels (custom_label_0 through custom_label_4) that merchants can populate with arbitrary values. These labels are invisible to shoppers but available for campaign segmentation in Google Ads.
Specification Reference
| Property | Value |
|---|---|
| Number of labels | 5 (custom_label_0 through custom_label_4) |
| Maximum characters per label | 100 |
| Maximum unique values per label | 1,000 |
| Total unique values (all labels) | 5,000 |
| Case sensitivity | Not case-sensitive (Winter = winter = WINTER) |
| Visible to shoppers | No (internal only) |
| Available in Google Ads | Yes (product group filters, reports) |
| Update frequency | Processed within 24 hours of feed fetch |
Important constraint: If a label exceeds 1,000 unique values, additional values are silently ignored for reporting and bidding. AdPriority’s label schema is designed to stay well within this limit.
Unique Value Budget
Label Capacity Planning
========================
custom_label_0 (Priority): 6 unique values [ 0.6% of 1,000 limit ]
custom_label_1 (Season): 4 unique values [ 0.4% of 1,000 limit ]
custom_label_2 (Category): ~20 unique values [ 2.0% of 1,000 limit ]
custom_label_3 (Status): 5 unique values [ 0.5% of 1,000 limit ]
custom_label_4 (Brand Tier): 3 unique values [ 0.3% of 1,000 limit ]
----
Total: ~38 unique values [ 0.8% of 5,000 limit ]
AdPriority uses less than 1% of the available unique value budget, leaving substantial headroom for future expansion.
9.2 AdPriority Label Schema
Each of the five custom labels serves a distinct purpose in the campaign segmentation strategy:
Label Allocation
+------------------+--------------------------------------------------+
| Label | Purpose and Values |
+------------------+--------------------------------------------------+
| | |
| custom_label_0 | PRIORITY SCORE |
| | Values: priority-0, priority-1, priority-2, |
| | priority-3, priority-4, priority-5 |
| | Used for: Primary PMAX campaign segmentation |
| | |
+------------------+--------------------------------------------------+
| | |
| custom_label_1 | SEASON |
| | Values: winter, spring, summer, fall |
| | Used for: Seasonal performance analysis, |
| | seasonal campaign adjustments |
| | |
+------------------+--------------------------------------------------+
| | |
| custom_label_2 | PRODUCT CATEGORY |
| | Values: t-shirts, jeans-pants, shorts, |
| | hoodies-sweatshirts, outerwear-heavy, |
| | outerwear-medium, outerwear-light, |
| | headwear-caps, headwear-cold-weather, |
| | headwear-summer, joggers, underwear- |
| | socks, footwear-sandals, footwear-shoes,|
| | accessories, women-apparel, long-sleeve,|
| | sweatpants, swim-shorts, other |
| | Used for: Category-level ROAS reporting |
| | |
+------------------+--------------------------------------------------+
| | |
| custom_label_3 | INVENTORY STATUS |
| | Values: in-stock, low-inventory, new-arrival, |
| | clearance, dead-stock |
| | Used for: Exclude dead-stock, boost new arrivals|
| | |
+------------------+--------------------------------------------------+
| | |
| custom_label_4 | BRAND TIER |
| | Values: name-brand, store-brand, off-brand |
| | Used for: Brand-level bidding strategy, |
| | ROAS analysis by brand tier |
| | |
+------------------+--------------------------------------------------+
Label Value Details
custom_label_0 – Priority Score:
| Value | Meaning | Google Ads Treatment |
|---|---|---|
priority-5 | Maximum push – seasonal peaks, new arrivals, high margin | Highest budget, aggressive tROAS |
priority-4 | Strong performers – name brands in season | Above-average budget |
priority-3 | Moderate – standard year-round products | Standard budget |
priority-2 | Light – low-margin or low-demand items | Below-average budget |
priority-1 | Minimal – near end-of-life or off-season | Minimal spend |
priority-0 | Excluded – dead stock, archived, out of stock | Paused or excluded |
custom_label_1 – Season:
| Value | Active Period | Example Categories Boosted |
|---|---|---|
winter | Dec 1 – Feb 28 | Puffer jackets, hoodies, beanies, balaclavas |
spring | Mar 1 – May 31 | Windbreakers, t-shirts, light jackets |
summer | Jun 1 – Aug 31 | Shorts, tank tops, swim shorts, sandals |
fall | Sep 1 – Nov 30 | Jeans, hoodies, denim jackets, varsity jackets |
custom_label_2 – Product Category:
Normalized from the 90 Shopify product types into approximately 20 groups (see Chapter 15 for the full category mapping table). Examples:
| Shopify Product Type | Label Value |
|---|---|
Men-Tops-T-Shirts | t-shirts |
Men-Bottoms-Pants-Jeans | jeans-pants |
Men-Tops-Outerwear-Jackets-Puffer Jackets | outerwear-heavy |
Headwear-Baseball-Fitted | headwear-caps |
Men-Footwear-Sandals & Slides | footwear-sandals |
Bath & Body | other |
custom_label_3 – Inventory Status:
| Value | Determination Logic |
|---|---|
new-arrival | Product created_at within last 14 days |
in-stock | Default for active products with inventory |
low-inventory | Shopify tag warning_inv_1 or warning_inv present |
clearance | Shopify tag Sale present |
dead-stock | Shopify tag DEAD50 or archived present, or status = archived |
custom_label_4 – Brand Tier:
| Value | Determination Logic | Nexus Examples |
|---|---|---|
name-brand | Tag NAME BRAND or known vendor list | New Era, Jordan Craig, Psycho Bunny, Lacoste |
store-brand | Vendor = “Nexus Clothing” | Nexus Clothing (126 products) |
off-brand | Tag OFF BRAND or unrecognized vendor | Rebel Minds, Black Keys, generic vendors |
9.3 Nexus GMC Account: Verified Data
The following data was verified from a live GMC product export conducted on 2026-02-10 (124,060 products, TSV format).
Account Overview
| Metric | Value |
|---|---|
| Total products in GMC | 124,060 (variant-level) |
| Active products in Shopify | 2,425 (estimated ~15,000-20,000 active variants) |
| Primary feed source | Shopify Google Channel (automatic) |
| Feed data sources | Content API - US, Content API - Local, Local Feed Partnership |
| Product ID format | shopify_US_{productId}_{variantId} |
| Country code | US (all products) |
Current Custom Label Usage
| Label | Current State | Products Using | Safe to Use? |
|---|---|---|---|
custom_label_0 | "Argonaut Nations - Converting" on 7 products | 7 (0.006%) | Yes – overwrite is safe |
custom_label_1 | EMPTY | 0 | Yes |
custom_label_2 | EMPTY | 0 | Yes |
custom_label_3 | EMPTY | 0 | Yes |
custom_label_4 | EMPTY | 0 | Yes |
Conclusion: All five labels are effectively available. The 7 products on custom_label_0 represent a negligible 0.006% of inventory from an inactive campaign and are safe to overwrite.
Verified Product ID Examples
GMC Product IDs (from live export):
shopify_US_8779355160808_46050142748904
shopify_US_9128994570472_47260097118440
shopify_US_9057367064808_47004004712680
shopify_US_9238797418728_47750439567592
shopify_US_7609551716584_42582395650280
shopify_US_8631136518376_45680298983656
shopify_US_9208152621288_47635854262504
shopify_US_8361353412840_44841122955496
Format: shopify_{country}_{shopifyProductId}_{shopifyVariantId}
Country: US (for all Nexus products)
Product ID: 13 digits
Variant ID: 14 digits
Item Group ID
The item_group_id in GMC is the Shopify product ID without the prefix:
Product ID (GMC): shopify_US_8779355160808_46050142748904
Item Group ID (GMC): 8779355160808
Shopify Product ID: 8779355160808
Shopify Variant ID: 46050142748904
This is important because supplemental feeds operate at the variant level (full product ID), while Google Ads reporting can aggregate at the item group level (parent product).
9.4 Supplemental Feeds
A supplemental feed adds or overrides attributes on products already present in the primary feed. The primary feed (from Shopify Google Channel) provides core product data. AdPriority’s supplemental feed adds only the five custom labels.
How Supplemental Feeds Work
Primary Feed Supplemental Feed
(Shopify Google Channel) (AdPriority Google Sheet)
+---------------------------+ +---------------------------+
| id (required) | | id (required - must match)|
| title | | custom_label_0 |
| description | | custom_label_1 |
| price | | custom_label_2 |
| image_link | | custom_label_3 |
| availability | | custom_label_4 |
| brand | +---------------------------+
| gtin |
| product_type | ||
| ... (30+ attributes) | || GMC merges by ID
+---------------------------+ ||
\/
+---------------------------+
| Merged Product in GMC |
+---------------------------+
| id |
| title |
| description |
| price |
| custom_label_0 <-- NEW |
| custom_label_1 <-- NEW |
| custom_label_2 <-- NEW |
| custom_label_3 <-- NEW |
| custom_label_4 <-- NEW |
| ... (all other attributes)|
+---------------------------+
Critical Requirements
| Requirement | Detail |
|---|---|
id column must be first | Must exactly match the primary feed’s product ID |
| IDs are case-sensitive | shopify_US_123_456 is not Shopify_US_123_456 |
| Header row required | Column names must match GMC attribute names exactly |
| Only override columns included | Omit columns you do not want to change |
| Linked to data source | Supplemental feed must be linked to one or more primary data sources |
Supported Feed Formats
| Format | Pros | Cons | AdPriority Usage |
|---|---|---|---|
| Google Sheets | Auto-syncs, easy to debug, free | Daily sync only | MVP |
| CSV/TSV file | Simple to generate | Requires manual upload or scheduled fetch | Not used |
| Content API | Real-time, programmatic | Complex auth, rate limits | Future (Pro tier) |
MVP Approach: Google Sheets
For the MVP and Starter/Growth tiers, AdPriority uses Google Sheets as the supplemental feed format. This approach was validated with 10 Nexus products on 2026-02-10 with zero issues.
| Test Metric | Result |
|---|---|
| Products tested | 10 |
| Products matched | 10 (100%) |
| Attribute names recognized | All 6 columns |
| Issues found | None |
| Processing time | < 1 hour (manual trigger) |
| Feed accepted | Immediately |
9.5 Content API v2.1 (Future: Pro Tier)
For merchants who need faster-than-daily label updates, the Content API provides programmatic access to GMC product data.
Authentication
OAuth 2.0 for Google APIs
Scope: https://www.googleapis.com/auth/content
Flow:
1. Merchant connects Google account in AdPriority settings
2. OAuth consent screen requests Content API scope
3. Google returns authorization code
4. Backend exchanges code for refresh token
5. Refresh token stored encrypted in database
6. Access tokens generated on demand (1-hour lifetime)
Updating Custom Labels (PATCH)
Endpoint: PATCH https://shoppingcontent.googleapis.com/content/v2.1/{merchantId}/products/{productId}
{
"customLabel0": "priority-5",
"customLabel1": "winter",
"customLabel2": "jeans-pants",
"customLabel3": "in-stock",
"customLabel4": "name-brand"
}
Use the updateMask query parameter to specify which fields to update:
?updateMask=customLabel0,customLabel1,customLabel2,customLabel3,customLabel4
Batch Operations (custombatch)
For bulk updates, the products.custombatch endpoint processes multiple products in a single HTTP request:
// backend/src/integrations/google/merchant.ts
import { google } from 'googleapis';
const content = google.content('v2.1');
interface LabelUpdate {
gmcProductId: string; // e.g., "shopify_US_8779355160808_46050142748904"
labels: {
customLabel0: string;
customLabel1: string;
customLabel2: string;
customLabel3: string;
customLabel4: string;
};
}
export async function batchUpdateLabels(
auth: any,
merchantId: string,
updates: LabelUpdate[]
): Promise<void> {
const entries = updates.map((update, index) => ({
batchId: index,
merchantId: merchantId,
method: 'update',
productId: `online:en:US:${update.gmcProductId}`,
product: {
customLabel0: update.labels.customLabel0,
customLabel1: update.labels.customLabel1,
customLabel2: update.labels.customLabel2,
customLabel3: update.labels.customLabel3,
customLabel4: update.labels.customLabel4,
},
updateMask: 'customLabel0,customLabel1,customLabel2,customLabel3,customLabel4',
}));
const response = await content.products.custombatch({
auth,
requestBody: { entries },
});
// Process response for errors
for (const entry of response.data.entries || []) {
if (entry.errors) {
console.error(`Batch ${entry.batchId} failed:`, entry.errors);
}
}
}
Rate Limits and Quotas
| Limit | Value | AdPriority Strategy |
|---|---|---|
| Product updates per day per product | 2 | Sync at most twice daily per product |
| Requests per minute | Dynamic (throttle-based) | Monitor for 429 responses |
custombatch entries per request | ~1,000 (practical limit) | Chunk updates into batches of 500 |
| Daily aggregate limit | Varies by account | Track usage, alert at 80% |
Error Handling
| Error Code | Meaning | Response |
|---|---|---|
quota/request_rate_too_high | Per-minute rate exceeded | Exponential backoff, starting at 2 seconds |
quota/daily_limit_exceeded | Daily quota consumed | Halt updates, retry next day, alert merchant |
not_found | Product not in primary feed | Skip product, log for reconciliation |
invalid_value | Label exceeds 100 chars or invalid | Truncate and retry |
Exponential Backoff Implementation
async function withBackoff<T>(
fn: () => Promise<T>,
maxRetries: number = 5
): Promise<T> {
let delay = 1000; // Start at 1 second
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error: any) {
if (attempt === maxRetries) throw error;
const isRetryable =
error.code === 429 ||
error.code === 503 ||
error.message?.includes('rate');
if (!isRetryable) throw error;
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 2, 60000); // Cap at 60 seconds
}
}
throw new Error('Max retries exceeded');
}
9.6 Feed Processing Timeline
Understanding the end-to-end timeline from priority change to Google Ads effect:
Timeline: Priority Change to Google Ads Effect
================================================
T+0h AdPriority calculates new priority score
|
T+0m Score written to database, flagged for sync
|
T+1m Google Sheet updated via Sheets API (MVP)
| -- OR --
Content API PATCH sent (Pro tier)
|
T+1h GMC begins processing supplemental feed
to (Google Sheets: fetched on schedule, typically daily)
T+24h (Content API: processed within minutes to hours)
|
T+24h Custom labels visible in GMC product data
to Labels available in Google Ads for
T+48h product group filtering and bid optimization
|
Google Ads campaigns use new labels
for next ad serving cycle
MVP (Google Sheets): ~24-48 hours end-to-end
Pro (Content API): ~1-6 hours end-to-end
For priority scoring, this latency is acceptable. Priority changes are strategic decisions (seasonal shifts, new arrivals) rather than real-time price adjustments.
9.7 Feed Size Planning
Nexus Store Estimates
| Metric | Count | Feed Rows |
|---|---|---|
| Active Shopify products | 2,425 | – |
| Estimated active variants | ~15,000-20,000 | ~15,000-20,000 |
| Total GMC variants (incl. archived) | 124,060 | 124,060 (if syncing all) |
| Columns per row | 6 (id + 5 labels) | – |
| Cells needed (active only) | – | ~120,000 |
| Cells needed (all variants) | – | ~744,360 |
| Google Sheets cell limit | – | 10,000,000 |
| Usage percentage (active only) | – | 1.2% |
| Usage percentage (all variants) | – | 7.4% |
Multi-Tenant Scaling
For the SaaS product, each tenant gets their own Google Sheet:
| Scenario | Variants per Tenant | Sheets per Tenant | Total Cells |
|---|---|---|---|
| Small store | 500 | 1 | 3,000 |
| Medium store | 5,000 | 1 | 30,000 |
| Large store (Nexus-sized) | 20,000 | 1 | 120,000 |
| Enterprise | 100,000 | 1 | 600,000 |
All scenarios are comfortably within Google Sheets limits. Even the largest stores use less than 10% of the 10-million-cell capacity.
9.8 Supplemental Feed Test Results (Verified 2026-02-10)
A live test was conducted on the Nexus GMC account to validate the supplemental feed pipeline end-to-end.
Test Configuration
| Parameter | Value |
|---|---|
| Feed type | Google Sheets |
| Format | 6 columns (id + 5 custom labels) |
| Sample size | 10 active Nexus products |
| Sheet sharing | “Anyone with the link” (Viewer) |
| Data sources linked | Content API - US, Content API - Local, Local Feed Partnership |
Sample Products Tested
| Product | Priority | Season | Category | Status | Brand Tier |
|---|---|---|---|---|---|
| New Era Colts Knit 2015 | priority-4 | winter | headwear-cold-weather | low-inventory | name-brand |
| New Era Yankees 59FIFTY | priority-4 | winter | headwear-caps | in-stock | name-brand |
| G3 Patriots Hoodie | priority-0 | winter | hoodies-sweatshirts | dead-stock | off-brand |
| Rebel Minds Puffer Jacket | priority-5 | winter | outerwear-heavy | low-inventory | off-brand |
Results
| Metric | Result |
|---|---|
| Products matched | 10/10 (100%) |
| Attribute names recognized | All recognized |
| Issues found | Zero |
| Processing time | < 1 hour |
| Feed accepted | Immediately |
Verification Steps Completed
- Created Google Sheet with 10 sample products and 6 columns
- Shared sheet publicly (Viewer access)
- Added as supplemental feed in GMC
- Linked to all 3 primary data sources
- Triggered manual update
- All 10 products matched within 1 hour
- All attribute names recognized with zero issues
Conclusion: The Google Sheets supplemental feed pipeline is confirmed working. The shopify_US_{productId}_{variantId} ID format matches correctly. This validates the MVP approach.
9.9 GMC ID Construction
AdPriority constructs GMC product IDs from Shopify product and variant IDs. The construction is deterministic and requires no GMC API lookup.
// backend/src/services/sync/gmc.ts
/**
* Construct the Google Merchant Center product ID from Shopify IDs.
*
* Format: shopify_{country}_{productId}_{variantId}
*
* Verified against 124,060 products in Nexus GMC export (2026-02-10).
* All products follow this format with country code "US".
*/
export function buildGmcProductId(
shopifyProductId: number | string,
shopifyVariantId: number | string,
countryCode: string = 'US'
): string {
return `shopify_${countryCode}_${shopifyProductId}_${shopifyVariantId}`;
}
// Examples:
// buildGmcProductId(8779355160808, 46050142748904)
// => "shopify_US_8779355160808_46050142748904"
//
// buildGmcProductId(9128994570472, 47260097118440)
// => "shopify_US_9128994570472_47260097118440"
Multi-Country Considerations
For SaaS merchants selling in multiple countries, the country code in the GMC ID varies:
| Country | Code | ID Example |
|---|---|---|
| United States | US | shopify_US_123_456 |
| Canada | CA | shopify_CA_123_456 |
| United Kingdom | GB | shopify_GB_123_456 |
| Australia | AU | shopify_AU_123_456 |
AdPriority stores the country code per tenant and uses it in ID construction. For the Nexus MVP, this is hardcoded to US.
9.10 Reconciliation Strategy
Data drift can occur when products are added or removed from GMC’s primary feed. AdPriority runs periodic reconciliation to ensure labels remain accurate.
Daily Reconciliation (Automated)
Daily Reconciliation Job (runs at 3:00 AM)
============================================
1. Fetch all active products from Shopify API
2. Compare with product_mappings database table:
a. New products (in Shopify, not in DB)
-> Create mapping, calculate priority, add to Sheet
b. Removed products (in DB, not in Shopify)
-> Mark deleted, remove from Sheet on next sync
c. Changed products (type, tags, or vendor changed)
-> Recalculate priority, update Sheet if changed
3. Regenerate complete Google Sheet
4. Log reconciliation report:
- Products added: N
- Products removed: N
- Priorities changed: N
- Errors: N
5. Alert if > 5% of products changed (unusual, may indicate
bulk import or data issue)
Weekly GMC Verification (Pro Tier)
For Pro tier merchants with Content API access:
Weekly GMC Verification (runs Sunday 2:00 AM)
===============================================
1. Fetch product list from Content API
2. Compare custom labels with expected values:
a. Missing labels -> Product may not be in Sheet
b. Wrong labels -> Stale data, queue update
c. Unknown products -> New in GMC, investigate
3. Generate verification report
4. Alert on > 1% label mismatch rate
9.11 Chapter Summary
Google Merchant Center integration is the critical output layer of AdPriority. The app writes five custom labels to GMC products using a supplemental feed (Google Sheets for MVP, Content API for Pro tier). These labels encode priority score, season, category, inventory status, and brand tier – enabling Google Ads campaigns to segment products by business-relevant attributes.
Key verified facts for Nexus Clothing:
- 124,060 variant-level products in GMC
- All 5 custom labels effectively available (only 7 products had
label_0values) - Product ID format confirmed:
shopify_US_{productId}_{variantId} - Supplemental feed test: 10/10 matched, zero issues
- Google Sheets capacity: 1.2% of cell limit for active variants
- Content API rate limit: 2 updates per day per product
- End-to-end label propagation: 24-48 hours (Sheets), 1-6 hours (API)