npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@spindlecommerce/plugin-google-shopping

v1.0.0

Published

Google Shopping for SpindleCommerce: a product feed for Google Merchant Center, with an item for each variant, priced and worded as the channel's storefront shows it.

Readme

Google Shopping, a SpindleCommerce plugin

A product feed for Google Merchant Center, so your products can appear in Shopping ads and free listings on Google. The feed is RSS 2.0 in Google's format, served by the plugin at /plugins/google-shopping/feed.xml. Merchant Center fetches it on a schedule.

  • One item per variant that has a price, with its own SKU, price, availability, images, color and size. The variants of a product are grouped with item_group_id.
  • As the storefront shows it. Names, descriptions and prices come from the catalog as a guest sees it in the channel's storefront, in the feed's locale and currency. Products kept out of listings and search are left out.
  • Streamed. The feed is written a page of 100 products at a time, so a large catalog is never held in memory.

Setup

  1. Add the plugin to your SpindleCommerce installation and deploy. It needs plugin SDK 1.1 or later, which has the catalog service.

    pnpm add @spindlecommerce/plugin-google-shopping
    // ecom.config.ts
    import { googleShopping } from "@spindlecommerce/plugin-google-shopping";
    
    export default defineConfig({ plugins: [email(), stripe(), googleShopping()] });
  2. Check the links. Products link to the channel's storefront, from Settings › Storefronts.

    • If the storefront puts the language in the path (/de/products/…), fill in Locale prefixes.
    • If its product pages aren't at /products/<slug>, change Product URL.
  3. Settings › Plugins › Google Shopping. The plugin works without settings. To keep the feed private, set a Feed token: the feed then needs ?token= in its URL. Without one, anyone with the URL can read the feed, which shows what the storefront shows.

  4. Open the feed in a browser to check it, for example https://shop-api.example.com/plugins/google-shopping/feed.xml?channel=eu&locale=en-GB&currency=GBP.

  5. Add it to Merchant Center by scheduled fetch.

    • Open Products › Data sources. Google's help reaches it through Settings › Data sources.
    • Select Add product source, then Add products from a file, then Enter a link to your file.
    • Paste the feed URL, with its token if it has one.
    • Choose when Google fetches it: daily, by default.
    • Choose the language and countries of the feed's locale and currency. For example, a feed with locale=en-GB&currency=GBP is for the United Kingdom, in English.

    Add a source for each channel, locale and currency you advertise in.

  6. Set up shipping in Merchant Center. The feed has no shipping costs (see Limitations).

Settings

  • Product URL (default {storefront}/products/{slug}): how the feed links to products. It must make absolute http or https links. Placeholders:

    • {storefront}: the channel's storefront URL, followed by the locale's prefix;
    • {slug}: the product's slug, in the feed's locale;
    • {locale}: the locale, such as de-DE;
    • {language}: its language, such as de.

    Examples are {storefront}/{language}/p/{slug}, or https://shop.example.com/products/{slug} for a storefront that isn't under Settings › Storefronts.

  • Locale prefixes (for example de-DE=/de, fr-FR=/fr): paths the storefront puts after its URL for some locales. The prefix goes after {storefront} for that locale.

    • A prefix for a language, such as fr=/fr, serves each of its locales.
    • Locales not listed have no prefix.
  • Brand attribute (brand), Color attribute (color) and Size attribute (size): the codes of the attributes that hold them.

    • The brand is a product attribute.
    • Color and size are variant attributes. A product attribute with the same code serves products whose variants don't differ by it.
  • Feed token (secret, optional): when set, the feed answers only requests with ?token= and this value, and others with 401.

Feed URL

The feed is at /plugins/google-shopping/feed.xml on your SpindleCommerce API: the address of the admin, without /admin.

| Parameter | Value | |---|---| | channel | A channel's code. Default: the default channel. | | locale | A locale, such as en-GB. Default: the channel's default locale. A locale the channel lacks falls back to another of the same language, then to the default. | | currency | A currency code, such as GBP. Default: the channel's default currency, also used for a currency the channel doesn't sell in. | | token | The feed token, when one is set. |

Examples:

# The default channel, in its default locale and currency
https://shop-api.example.com/plugins/google-shopping/feed.xml

# The eu channel in German, priced in euros
https://shop-api.example.com/plugins/google-shopping/feed.xml?channel=eu&locale=de-DE&currency=EUR

# The eu channel in English, priced in pounds, with a feed token
https://shop-api.example.com/plugins/google-shopping/feed.xml?channel=eu&locale=en-GB&currency=GBP&token=…

The answer is the feed, as application/xml; charset=utf-8, unless:

  • the token is missing or wrong: 401;
  • the channel doesn't exist or isn't active: 404;
  • the plugin is disabled, or products can't be linked to: 503. Links fail when the Product URL uses {storefront} and the channel has no storefront URL, or when it doesn't make http or https links. The JSON answer says which.

If reading the catalog fails part-way, the response is broken off rather than ended early. A feed that looked complete would make Google remove the products it lacks.

Field mapping

| Google attribute | Value | |---|---| | g:id | The variant's SKU. Google allows 50 characters. | | g:item_group_id | The product's ID, when it has more than one variant. | | title | The variant's name, such as "Oxford Shirt – Blue / M", or the product's name when it has one variant. At most 150 characters. | | description | The product's description as plain text: tags dropped, entities decoded, paragraphs and list items on lines of their own. Else its SEO description, else the title. At most 5,000 characters. | | link | The product's page, from Product URL. | | g:image_link | The variant's own image, else the product's first image. | | g:additional_image_link | Up to 10 of the product's other images, leaving out the images of other variants. Videos aren't sent. | | g:availability | in_stock when the variant can be bought, else out_of_stock. | | g:price | The compare-at price when there is one above the price, else the price, such as 59.00 GBP. | | g:sale_price | The price, when there is a compare-at price above it. | | g:brand | The display value of the brand attribute. At most 70 characters. | | g:gtin | The barcode, when it is a valid GTIN (below). | | g:identifier_exists | no when there is neither a brand nor a GTIN. | | g:condition | new. | | g:color, g:size | The variant's option for the color or size attribute, by its label, such as Blue or M. | | g:product_type | The path of the product's first category, such as Men > Shirts. | | g:shipping_weight | The variant's weight, such as 250 g. |

  • Prices are what a guest pays on the storefront, including tax when the channel's prices do. They're written with the currency's decimals: 59.00 GBP, 1200 JPY, 5.250 KWD.
  • GTINs. A barcode is sent when, once spaces and dashes are dropped, it has 8, 12, 13 or 14 digits with a correct check digit. GS1's ranges for restricted circulation (02, 04 and 2) and for coupons (05, 98 and 99) aren't sent, as Google refuses them.

Limitations

  • Vercel's 4.5 MB response cap. Vercel limits a function's response to 4.5 MB, which is about 5,000 items. An item takes 0.7 to 1 KB, more with a long description. The feed is streamed, and Vercel's guide to the cap says streamed responses are exempt, but its limits page makes no such exception. Check the first fetches of a large feed in Merchant Center.
  • One feed per channel, locale and currency. Each feed URL has one channel's products in one language and one currency, so Merchant Center needs a data source for each.
  • No shipping or tax attributes.
    • Set shipping costs, and tax where it applies, in Merchant Center's settings. Google requires shipping costs in many countries, including the US, the UK, Germany and France.
    • Google wants prices without tax in the US and Canada, and with VAT or GST everywhere else. The feed's prices include tax when the channel's do, so check the channel's setting.
  • Fewer identifiers than Google can use. The feed sends no MPN, and doesn't know Google's product categories. Outside clothing, Google asks for identifier_exists no when a product has no GTIN and no MPN. A product with a brand but no GTIN gets Google's default instead, yes.
  • Other attributes aren't sent, such as gender, age_group, material and google_product_category. Google requires gender and age group for clothing and accessories in several countries, including the US, the UK, Germany and France.
  • One link per product. Every variant links to its product's page, with no variant chosen.
  • Updates wait for the fetch. Changes to prices and stock reach Google when it next fetches the feed, daily by default.

Develop

pnpm install
pnpm test
pnpm build

The tests run the feed route on a Hono app, against a fake catalog. The plugin API it builds on is @spindlecommerce/plugin-sdk.

Release

Each version is staged on npm and goes live when a maintainer approves it.

  1. Raise version in package.json and in src/index.ts. A test checks they match.
  2. Commit, then tag and push: for example git tag v1.0.1 && git push origin main v1.0.1.
  3. The Release workflow tests, builds and stages the version, and creates the GitHub release.
  4. Approve it on npmjs.com, under Staged Packages, with 2FA.

The workflow publishes with npm's trusted publishing, so it needs no token. Before its first release, a maintainer links the package to the workflow:

npm trust github @spindlecommerce/plugin-google-shopping --repo spindlecommerce/plugin-google-shopping --file release.yml --allow-stage-publish

Run it just before tagging: npm drops a link that isn't used within 48 hours.

License

MIT