Overview
Icon for Google Merchant Center Feed

Google Merchant Center Feed

Google Merchant Center product feed

Medusa Google Merchant Center Feed

Google Merchant Center XML product feed for Medusa v2. Serves a token-protected feed URL with your catalog: region prices, sales channel stock, admin settings, and a Copy to clipboardbeforeItem hook to customize every item.

Compatibility

Medusa >= 2.11.0.

Setup

  1. Register the plugin in Copy to clipboardmedusa-config.ts:
plugins: [
{
resolve: "@weareseeed/medusa-google-merchant-center-plugin",
options: {
logging: true, // optional: false (default) | true | "debug"
// productFields: ["variants.weight"] (see "Extra product fields")
// hooks: { beforeItem } (see "Customizing items")
},
},
],
  1. Run the migrations (they ship with the plugin):
npx medusa db:migrate
  1. In the admin, open Google Merchant and set: Save. The page shows the feed URL, built from the public backend URL: Copy to clipboardhttps://<public-backend-url>/google-merchant/feed?token=<token>. The endpoint is public; the token is what protects it.
    • Frontend URL: your storefront, e.g. Copy to clipboardhttps://shop.example.com. Product links are Copy to clipboard{frontend URL}/products/{handle}. If your storefront prefixes a country code (like the Medusa Next.js starter), include it: Copy to clipboardhttps://shop.example.com/us.
    • Public backend URL: where this Medusa backend is reachable from the internet, e.g. Copy to clipboardhttps://api.example.com. Google fetches the feed from there, so don't use Copy to clipboardlocalhost or an internal address. Use current fills in the address the admin is using.
    • Pricing region: prices are Medusa's calculated price for this region, in its currency.
    • Sales channel (optional): only its products are included, and stock counts only its stock locations. "All sales channels" includes every published product and counts all locations.
    • Feed token: type one or click Generate (UUID). It is required in the URL.
  2. In Merchant Center: Products → Data sources → Add product source → Add products from a file. Enter the feed URL and choose a daily schedule. Then use Fetch now to test.

What goes in the feed

One Copy to clipboard<item> per variant of every published product (in the selected sales channel, if any):

Attribute Value Copy to clipboardid Variant SKU, or the variant id when it has no SKU Copy to clipboarditem_group_id Product id, when the product has more than one variant Copy to clipboardtitle Product title plus the variant title, which holds its option values, e.g.Copy to clipboardHoodie - Navy / M. A variant title that already contains the product name is used as-is. Single-variant products use the product title. Cut to 150 characters at a word boundary Copy to clipboarddescription Product description (falls back to the product title), cut to 5000 characters at a word boundary Copy to clipboardlink Copy to clipboard{frontend URL}/products/{handle} Copy to clipboardimage_link Variant thumbnail, else the product thumbnail, else the first product image (by gallery order) Copy to clipboardadditional_image_link Up to 10 other product images, in gallery order Copy to clipboardprice Calculated price in the pricing region, e.g.Copy to clipboard49.99 USD (price lists included) Copy to clipboardavailability Copy to clipboardin_stock if inventory isn't managed or stock > 0; otherwise Copy to clipboardbackorder if allowed, else Copy to clipboardout_of_stock Copy to clipboardcondition Copy to clipboardnew Copy to clipboardcolor / Copy to clipboardsize / Copy to clipboardmaterial From variant options with those names (Copy to clipboardvariant.material wins)

A variant is left out when it has no image or no price in the region, or when its final item (after Copy to clipboardbeforeItem) has an Copy to clipboardid longer than 50 characters or a price of zero or less. Google rejects those items. With Copy to clipboardlogging: "debug", the reason is logged. Other limits are left for Merchant Center to report. The feed ends with a comment Copy to clipboard<!-- items: N, skipped: M -->.

Tax: the price is sent exactly as Medusa calculates it. Whether it includes tax follows the region's price settings. Google expects tax-exclusive prices for the US and Canada (configure tax in Merchant Center) and tax-inclusive prices for VAT countries (EU, UK, AU…).

Customizing items

Copy to clipboardbeforeItem runs for every item. It receives a copy of the default item and returns the item to include, modified or not. Returning Copy to clipboardundefined, Copy to clipboardnull or Copy to clipboard"skip" leaves it out. Items use Google's attribute names: arrays become repeated elements, objects become nested elements (e.g. Copy to clipboardshipping), and Copy to clipboardtrue/Copy to clipboardfalse are written as Copy to clipboardyes/Copy to clipboardno.

options: {
hooks: {
beforeItem: (item, ctx) => {
if (ctx.product.metadata?.hide_from_google) {
return "skip"
}
return {
...item,
brand: (ctx.product.metadata?.brand as string) ?? "Acme",
gtin: ctx.variant.ean ?? undefined,
identifier_exists: Boolean(ctx.variant.ean),
product_type: ctx.product.type?.value,
shipping: { country: "US", service: "Standard", price: "5.99 USD" },
}
},
},
}

Copy to clipboardctx contains Copy to clipboardproduct (with Copy to clipboardmetadata, Copy to clipboardtype, Copy to clipboardcollection, Copy to clipboardtags, Copy to clipboardimages, Copy to clipboardvariants), Copy to clipboardvariant (with Copy to clipboardsku, Copy to clipboardean, Copy to clipboardupc, Copy to clipboardbarcode, Copy to clipboardmetadata, Copy to clipboardoptions, Copy to clipboardcalculated_price, Copy to clipboardavailability), Copy to clipboardsettings (without the feed token), Copy to clipboardregion, Copy to clipboardsalesChannel, Copy to clipboardcontainer and Copy to clipboardlogger. The hook may be Copy to clipboardasync.

The returned item is final; text you return is not truncated. It is only left out (and logged) if it lacks Copy to clipboardid, Copy to clipboardtitle, Copy to clipboardlink, Copy to clipboardimage_link or Copy to clipboardprice, if its Copy to clipboardid is longer than 50 characters, if its price is zero or less, or if the hook throws.

Extra product fields

Copy to clipboardctx.product and Copy to clipboardctx.variant only contain the fields the feed needs. To read more in Copy to clipboardbeforeItem, list them in Copy to clipboardproductFields. They are added to the default fields (the defaults can't be removed) and fetched in the same batched query, so there's no per-item lookup. Use Query field syntax; fields from linked modules (e.g. Copy to clipboardbrand.name) need the link to exist in your app.

options: {
productFields: ["variants.weight", "categories.name", "brand.name"],
hooks: {
beforeItem: (item, { product, variant }) => ({
...item,
brand: (product.brand as { name: string } | null)?.name,
shipping_weight: variant.weight ? `${variant.weight} g` : undefined,
product_type: (product.categories as { name: string }[] | undefined)
?.map((category) => category.name)
.join(" > "),
}),
},
}

Extra fields are typed as Copy to clipboardunknown, so cast them when you read them. A field that doesn't exist makes the query fail, which leaves the feed incomplete (see below) and logs the error. With Copy to clipboardlogging: "debug" the added fields are logged on every feed request.

Endpoint behaviour

Copy to clipboardGET /google-merchant/feed?token=… is public (outside Copy to clipboard/store and Copy to clipboard/admin, so Google can fetch it without headers):

  • 401 if the token is missing or wrong.
  • 503 if the feed isn't configured (no frontend URL or region, or the selected region/channel was deleted), with the reason in the body.
  • 200 otherwise: the feed is generated live from the database and streamed.

If an error happens while streaming, the document is left incomplete on purpose. Merchant Center then reports a fetch error and keeps your products, instead of deleting everything missing from a truncated file.

The token is part of the URL, so it can appear in proxy and access logs. If the backend sits behind nginx, disable response buffering for Copy to clipboard/google-merchant/feed. The endpoint sends Copy to clipboardX-Accel-Buffering: no.

You may also like

Browse all integrations

Build your own

Develop your own custom integration

Build your own integration with our API to speed up your processes. Make your integration available via npm for it to be shared in our Library with the broader Medusa community.

gift card interface

Ready to build your custom commerce setup?