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

PropertyValue
Number of labels5 (custom_label_0 through custom_label_4)
Maximum characters per label100
Maximum unique values per label1,000
Total unique values (all labels)5,000
Case sensitivityNot case-sensitive (Winter = winter = WINTER)
Visible to shoppersNo (internal only)
Available in Google AdsYes (product group filters, reports)
Update frequencyProcessed 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:

ValueMeaningGoogle Ads Treatment
priority-5Maximum push – seasonal peaks, new arrivals, high marginHighest budget, aggressive tROAS
priority-4Strong performers – name brands in seasonAbove-average budget
priority-3Moderate – standard year-round productsStandard budget
priority-2Light – low-margin or low-demand itemsBelow-average budget
priority-1Minimal – near end-of-life or off-seasonMinimal spend
priority-0Excluded – dead stock, archived, out of stockPaused or excluded

custom_label_1 – Season:

ValueActive PeriodExample Categories Boosted
winterDec 1 – Feb 28Puffer jackets, hoodies, beanies, balaclavas
springMar 1 – May 31Windbreakers, t-shirts, light jackets
summerJun 1 – Aug 31Shorts, tank tops, swim shorts, sandals
fallSep 1 – Nov 30Jeans, 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 TypeLabel Value
Men-Tops-T-Shirtst-shirts
Men-Bottoms-Pants-Jeansjeans-pants
Men-Tops-Outerwear-Jackets-Puffer Jacketsouterwear-heavy
Headwear-Baseball-Fittedheadwear-caps
Men-Footwear-Sandals & Slidesfootwear-sandals
Bath & Bodyother

custom_label_3 – Inventory Status:

ValueDetermination Logic
new-arrivalProduct created_at within last 14 days
in-stockDefault for active products with inventory
low-inventoryShopify tag warning_inv_1 or warning_inv present
clearanceShopify tag Sale present
dead-stockShopify tag DEAD50 or archived present, or status = archived

custom_label_4 – Brand Tier:

ValueDetermination LogicNexus Examples
name-brandTag NAME BRAND or known vendor listNew Era, Jordan Craig, Psycho Bunny, Lacoste
store-brandVendor = “Nexus Clothing”Nexus Clothing (126 products)
off-brandTag OFF BRAND or unrecognized vendorRebel 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

MetricValue
Total products in GMC124,060 (variant-level)
Active products in Shopify2,425 (estimated ~15,000-20,000 active variants)
Primary feed sourceShopify Google Channel (automatic)
Feed data sourcesContent API - US, Content API - Local, Local Feed Partnership
Product ID formatshopify_US_{productId}_{variantId}
Country codeUS (all products)

Current Custom Label Usage

LabelCurrent StateProducts UsingSafe to Use?
custom_label_0"Argonaut Nations - Converting" on 7 products7 (0.006%)Yes – overwrite is safe
custom_label_1EMPTY0Yes
custom_label_2EMPTY0Yes
custom_label_3EMPTY0Yes
custom_label_4EMPTY0Yes

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

RequirementDetail
id column must be firstMust exactly match the primary feed’s product ID
IDs are case-sensitiveshopify_US_123_456 is not Shopify_US_123_456
Header row requiredColumn names must match GMC attribute names exactly
Only override columns includedOmit columns you do not want to change
Linked to data sourceSupplemental feed must be linked to one or more primary data sources

Supported Feed Formats

FormatProsConsAdPriority Usage
Google SheetsAuto-syncs, easy to debug, freeDaily sync onlyMVP
CSV/TSV fileSimple to generateRequires manual upload or scheduled fetchNot used
Content APIReal-time, programmaticComplex auth, rate limitsFuture (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 MetricResult
Products tested10
Products matched10 (100%)
Attribute names recognizedAll 6 columns
Issues foundNone
Processing time< 1 hour (manual trigger)
Feed acceptedImmediately

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

LimitValueAdPriority Strategy
Product updates per day per product2Sync at most twice daily per product
Requests per minuteDynamic (throttle-based)Monitor for 429 responses
custombatch entries per request~1,000 (practical limit)Chunk updates into batches of 500
Daily aggregate limitVaries by accountTrack usage, alert at 80%

Error Handling

Error CodeMeaningResponse
quota/request_rate_too_highPer-minute rate exceededExponential backoff, starting at 2 seconds
quota/daily_limit_exceededDaily quota consumedHalt updates, retry next day, alert merchant
not_foundProduct not in primary feedSkip product, log for reconciliation
invalid_valueLabel exceeds 100 chars or invalidTruncate 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

MetricCountFeed Rows
Active Shopify products2,425
Estimated active variants~15,000-20,000~15,000-20,000
Total GMC variants (incl. archived)124,060124,060 (if syncing all)
Columns per row6 (id + 5 labels)
Cells needed (active only)~120,000
Cells needed (all variants)~744,360
Google Sheets cell limit10,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:

ScenarioVariants per TenantSheets per TenantTotal Cells
Small store50013,000
Medium store5,000130,000
Large store (Nexus-sized)20,0001120,000
Enterprise100,0001600,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

ParameterValue
Feed typeGoogle Sheets
Format6 columns (id + 5 custom labels)
Sample size10 active Nexus products
Sheet sharing“Anyone with the link” (Viewer)
Data sources linkedContent API - US, Content API - Local, Local Feed Partnership

Sample Products Tested

ProductPrioritySeasonCategoryStatusBrand Tier
New Era Colts Knit 2015priority-4winterheadwear-cold-weatherlow-inventoryname-brand
New Era Yankees 59FIFTYpriority-4winterheadwear-capsin-stockname-brand
G3 Patriots Hoodiepriority-0winterhoodies-sweatshirtsdead-stockoff-brand
Rebel Minds Puffer Jacketpriority-5winterouterwear-heavylow-inventoryoff-brand

Results

MetricResult
Products matched10/10 (100%)
Attribute names recognizedAll recognized
Issues foundZero
Processing time< 1 hour
Feed acceptedImmediately

Verification Steps Completed

  1. Created Google Sheet with 10 sample products and 6 columns
  2. Shared sheet publicly (Viewer access)
  3. Added as supplemental feed in GMC
  4. Linked to all 3 primary data sources
  5. Triggered manual update
  6. All 10 products matched within 1 hour
  7. 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:

CountryCodeID Example
United StatesUSshopify_US_123_456
CanadaCAshopify_CA_123_456
United KingdomGBshopify_GB_123_456
AustraliaAUshopify_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_0 values)
  • 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)